Socket与系统调用深度分析
用户态、内核态、中断和系统调用的关系
所谓的中断就是在计算机执行程序的过程中,由于出现了某些特殊事情,使得CPU暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。中断一般分为三类:
- 由计算机硬件异常或故障引起的中断,称为内部异常中断
- 由程序中执行了引起中断的指令而造成的中断,称为软中断(系统调用就属于软中断)
- 由外部设备请求引起的中断,称为外部中断
进程的执行在系统上有两个级别:用户态和核心态。程序的执行一般是在用户态下执行的,但在此状态下,不能执行特权指令(访问IO设备、访问特殊寄存器、置时钟... ...)。当程序需要使用这些特权指令时,就需要向操作系统发出调用服务的请求,这就是系统调用。 Linux系统有专门的函数库来提供这些请求操作系统服务的入口,这个函数库中包含了操作系统所提供的对外服务的接口。
系统调用也是函数调用,系统函数也是函数代码。系统函数与普通函数唯一的不同在于,系统函数可以使用cpu体系结构指令集中的特权指令,如启动I/O设备指令、修改某些个特殊寄存器的指令,如程序状态寄存器PSW。既然系统调用也是函数调用,那么就要遵守函数调用的基本法。1.传参 2.调用 3.返回。在汇编代码中,在正式call一个函数之前,需要将相关寄存器的值设置好,然后再修改pc寄存器值指向函数。上图中的eax = 2,就是在准备参数,为中断服务程序提供参数。
用户态、内核态、系统调用和中断的关系就在于,当用户态进程发出系统调用申请的时候,会产生一个软中断。产生这个软中断以后,系统会去对这个软中断进行处理,这个时候进程就处于核心态了。
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; case SYS_GETSOCKNAME: err = __sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_GETPEERNAME:
INT 80进入到sys_call这个函数,这个函数实现上是switch,如果在进入sys_call这个函数时,传入102,那么就会跳转到sys_sokcetcall这个函数中去,然后我们在sys_sokcetcall函数中又看到了switch函数。根据你进入到sys_sokcetcall函数所传入的参数,将调用不同的系统函数。
实验过程
1.先进入menuos
2.打开另一个终端,输入以下命令:
gdb file ~/LinuxKernel/linux-5.0.1/vmlinux target remote:1234 break sys_socketcall //设置断点 c //查看断点信息
如下图所示:
3.在menuos中输入终端命令:
replyhi hello
此时已经成功捕捉到了sys_socketcall,对应的内核处理函数为SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
如下图所示:
4.根据这些系统调用返回的系统调用号,可以查看这些系统调用实现了哪些功能,我们找到socket.c文件,其源码如下:
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; case SYS_GETSOCKNAME: err = __sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_GETPEERNAME: err = __sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_SOCKETPAIR: err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]); break; case SYS_SEND: err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], NULL, 0); break; case SYS_SENDTO: err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], (struct sockaddr __user *)a[4], a[5]); break; case SYS_RECV: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], NULL, NULL); break; case SYS_RECVFROM: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], (struct sockaddr __user *)a[4], (int __user *)a[5]); break; case SYS_SHUTDOWN: err = __sys_shutdown(a0, a1); break; case SYS_SETSOCKOPT: err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]); break; case SYS_GETSOCKOPT: err = __sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]); break; case SYS_SENDMSG: err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1, a[2], true); break; case SYS_SENDMMSG: err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], true); break; case SYS_RECVMSG: err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1, a[2], true); break; case SYS_RECVMMSG: if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME)) err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], (struct __kernel_timespec __user *)a[4], NULL); else err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], NULL, (struct old_timespec32 __user *)a[4]); break; case SYS_ACCEPT4: err = __sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], a[3]); break; default: err = -EINVAL; break; } return err; }
#此函数根据不同的call来进入不同的分支,从而调用不同的内核处理函数。
#在replyhi/hello的执行过程中,涉及到了socket的建立、recv、send等,这些不同的系统调用传给SYSCALL_DEFINE2()的参数call是不同的。