linux内核中socket有关的编程接口

内核中socket有关的编程接口及其对应的功能:

socketcallsocket系统调用
socket建立socket
bind绑定socket到端口
connect连接远程主机
accept响应socket连接请求
send通过socket发送信息
sendto发送UDP信息
sendmsg参见send
recv通过socket接收信息
recvfrom接收UDP信息
recvmsg参见recv
listen监听socket端口
select对多路同步I/O进行轮询
shutdown关闭socket上的连接
getsockname取得本地socket名字
getpeername获取通信对方的socket名字
getsockopt取端口设置
setsockopt设置端口参数
sendfile在文件或端口间传输数据
socketpair创建一对已联接的无名socket

对应的接口可以在unistd.h【左图:中断向量表】与syscalls_64.S【右图:系统调用表】文件中找到:

系统调用机制:

系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口,是一种特殊的中断,中断处理是从用户态进入内核态从而使用内核函数的主要方式。从用户态到和心态调用流程主要包括三个部分xyz(API)、system_ call(中断向量)、sys_xyz(中断向量对应的中断服务程序)。

其中在进行系统调用时,从用户态程序到以上述文件中的系统调用号_NR_xxxx作为下标,可找出系统调用表sys_call_table中对应表项的内容,该表中对应的内容就是系统调用的响应函数sys_xxxx的入口地址。也就是说整个系统调用的过程就是从中断向量表 ->系统调用表 JUMP(EAX*4+基地址)根据系统调用号找到对应的系统调用代码并执行。【其中xxxx对应的是具体的调用函数名字】

从用户态程序到内核态程序的过程可描述为下图:

系统调用号怎么从用户函数传递到内核函数呢?

当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,在Linux中是通过执行int $0x80来执行系统调用传参,由于内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,通过在eax寄存器存储系统调用号来准确的锁定本次的系统调用。

replyhi和hello的通信过程中系统调用过程分析:

从本文一开始的图中各个接口的作用可以得到,我们的replyhi/hello通信过程中,一定会涉及到sys_socket【socket系统调用】,sys_bind【绑定端口】,sys_listen【服务器端倾听socket端口】。

我们为这三个接口打上断点,看是否在通信过程中调用了这几个接口,方法是通过逐步执行到下个断点。

根据break打断点的响应可以判断在我们的socket接口中确实存在这几个接口。其次通过c逐步执行断点。可以看出在这个通信过程中确实三个接口都被调用了。且调用顺序如下。

使用list查看可得每次socket系统调用都要经过一次SYSCALL_DEFINE2这个函数,查看该函数:

  1 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
  2 {
  3     unsigned long a[AUDITSC_ARGS];
  4     unsigned long a0, a1;
  5     int err;
  6     unsigned int len;
  7
  8     if (call < 1 || call > SYS_SENDMMSG)
  9         return -EINVAL;
 10     call = array_index_nospec(call, SYS_SENDMMSG + 1);
 11
 12     len = nargs[call];
 13     if (len > sizeof(a))
 14         return -EINVAL;
 15
 16     /* copy_from_user should be SMP safe. */
 17     if (copy_from_user(a, args, len))
 18         return -EFAULT;
 19
 20     err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
 21     if (err)
 22         return err;
 23
 24     a0 = a[0];
 25     a1 = a[1];
 26
 27     switch (call) {
 28     case SYS_SOCKET:
 29         err = __sys_socket(a0, a1, a[2]);
 30         break;
 31     case SYS_BIND:
 32         err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
 33         break;
 34     case SYS_CONNECT:
 35         err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
 36         break;
 37     case SYS_LISTEN:
 38         err = __sys_listen(a0, a1);
 39         break;
 40     case SYS_ACCEPT:
 41         err = __sys_accept4(a0, (struct sockaddr __user *)a1,
 42                     (int __user *)a[2], 0);
 43         break;
 44     case SYS_GETSOCKNAME:
 45         err =
 46             __sys_getsockname(a0, (struct sockaddr __user *)a1,
 47                       (int __user *)a[2]);
 48         break;
 49     case SYS_GETPEERNAME:
 50         err =
 51             __sys_getpeername(a0, (struct sockaddr __user *)a1,
 52                       (int __user *)a[2]);
 53         break;
 54     case SYS_SOCKETPAIR:
 55         err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
 56         break;
 57     case SYS_SEND:
 58         err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],
 59                    NULL, 0);
 60         break;
 61     case SYS_SENDTO:
 62         err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],
 63                    (struct sockaddr __user *)a[4], a[5]);
 64         break;
 65     case SYS_RECV:
 66         err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
 67                      NULL, NULL);
 68         break;
 69     case SYS_RECVFROM:
 70         err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
 71                      (struct sockaddr __user *)a[4],
 72                      (int __user *)a[5]);
 73         break;
 74     case SYS_SHUTDOWN:
 75         err = __sys_shutdown(a0, a1);
 76         break;
 77     case SYS_SETSOCKOPT:
 78         err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3],
 79                        a[4]);
 80         break;
 81     case SYS_GETSOCKOPT:
 82         err =
 83             __sys_getsockopt(a0, a1, a[2], (char __user *)a[3],
 84                      (int __user *)a[4]);
 85         break;
 86     case SYS_SENDMSG:
 87         err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1,
 88                     a[2], true);
 89         break;
 90     case SYS_SENDMMSG:
 91         err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2],
 92                      a[3], true);
 93         break;
 94     case SYS_RECVMSG:
 95         err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1,
 96                     a[2], true);
 97         break;
 98     case SYS_RECVMMSG:
 99         if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME))
100             err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,
101                          a[2], a[3],
102                          (struct __kernel_timespec __user *)a[4],
103                          NULL);
104         else
105             err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,
106                          a[2], a[3], NULL,
107                          (struct old_timespec32 __user *)a[4]);
108         break;
109     case SYS_ACCEPT4:
110         err = __sys_accept4(a0, (struct sockaddr __user *)a1,
111                     (int __user *)a[2], a[3]);
112         break;
113     default:
114         err = -EINVAL;
115         break;
116     }
117     return err;
118 }

 根据该函数可得,确实socket调用都要经过这个函数,通过一个switch语句来实现不同的真正的socket系统调用。

那么我们对该switch语句中的所有调用对应的方法都打上断点,来判断该通信过程到底使用了那些接口。

根据断点可以得到,确实都存在这些系统接口。接着使用c来逐步调到下个断点看使用了哪些端口。可以得到。

首先确实sys socket_call 是每个socket系统调用的入口函数,其次在这个通信过程中不仅使用到了之前预测的sys_bind,sys_listen,而且也用到了sys_sendto,sys_recvfrom,recvfrom等调用。与老师的给的通信代码对应。代码地址:replyhi/hello。

12-28 13:46