一、系统调用
什么是系统调用呢?
来了解这个问题之前我们先来分析一下什么是用户态,什么是内核态。用户态是非特权状态,简单来说,就是不允许执行某些可能存在危险的操作的状态,在这种状态下,进程只能在各自的用户空间当中运行。内核态也就是特权状态,也就是具有可以具有权限来执行某些可能存在危险的操作。当一个进程执行系统调用,由于这个进程在用户态下没有足够的权限,那么它就会陷入到内核态,在调用内核代码来执行这些系统调用。系统调用由操作系统核心提供,运行于内核态。
系统调用的过程是怎样的呢?
step1:应用程序代码调用系统调用x(),而这个函数是包装成系统调用的库函数
step2:库函数负责向内核传递参数,触发软中断切换到内核
step3:CPU被中断后,按照中断向量找到中断处理函数并执行中断处理函数,也就是系统调用处理函数
step4:系统调用处理函数调用系统调用服务程序,真正开始处理该系统调用
Socket与系统调用
Socket API编程接口之上可以编写基于不同网络协议的应用程序,Socket接口在用户态通过系统调用机制进入内核。start_kernel-->trap_init-->idt_setup_traps-->0x80-->entry_INT80。
在这里调用中断指令 int 0x80实现由用户态转向内核态,,当运行到int 0x80这条中断指令时,跳转到entry_INT80_32,这是liunx系统中所有系统调用的入口点。entry_INT80_32不是一段普通的函数,它是一段汇编代码,同时,我们将系统调用号用eax寄存器进行传递,entry_INT80_32通过系统调用号来查询对应的内核处理函数并跳转到相应的内核处理函数执行,完毕后再按顺序逐步返回到用户态。
利用GDB跟踪系统调用过程,我们来用上次创建的MenuOS,运行hello/hi程序,跟踪系统调用的过程。首先在sys_socketcall处打上断点,接下来按c继续运行,看看都调用了哪些函数。
socket()调用sys_socketcall()系统调用。bind,connect等等函数都需要sys_socketcall()作为入口。
sys_socketcall()里面都有些什么呢?
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) { unsigned long a[AUDITSC_ARGS]; unsigned long a0, a1; int err; unsigned int len; if (call < 1 || call > SYS_SENDMMSG) return -EINVAL; call = array_index_nospec(call, SYS_SENDMMSG + 1); len = nargs[call]; if (len > sizeof(a)) return -EINVAL; /* copy_from_user should be SMP safe. */ if (copy_from_user(a, args, len)) return -EFAULT; err = audit_socketcall(nargs[call] / sizeof(unsigned long), a); if (err) return err; a0 = a[0]; a1 = a[1]; switch (call) { case SYS_SOCKET: err = __sys_socket(a0, a1, a[2]); break; case SYS_BIND: err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]); break; case SYS_CONNECT: err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]); break; case SYS_LISTEN: err = __sys_listen(a0, a1); break; case SYS_ACCEPT: err = __sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], 0); break; ... ... default: err = -EINVAL; break; } return err; }