Socket与系统调用深度分析

Posted huangmengyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket与系统调用深度分析相关的知识,希望对你有一定的参考价值。

Socket与系统调用深度分析

socket接口在用户态通过系统调用机制进入内核:

操作系统内核进入与退出的三种方式:系统调用、异常、中断

技术图片

内核将系统调用作为一个特殊的中断来处理,即软中断(对应128号中断向量),使用int 0x80指令陷入到内核,128号中断向量对应的中断服务例程是 entry_INT80_32 。

中断向量是怎么初始化的?在内核初始化的时候(start_kernel)初始化了中断向量和对应的中断处理程序。把0x80中断与entry_INT80_32绑定,使得当用户态程序执行到int 0x80指令时程序能够跳转到entry_INT80_32。对于使用x86架构的32位系统,系统调用的初始化过程为:

start_kernel-->trap_init-->idt_setup_traps-->0x80与entry_INT80_32绑定

irq(interrupt request,中断请求)

idt(interrupt descriptor table,中断描述符表)

绑定后(初始化完),每次系统调用的底层汇编都会执行int 0x80,然后使用%eax寄存器传递系统调用号,操作系统根据系统调用号去系统调用表里找对应的服务例程。例如:用户态调用connect()函数,对应的汇编是先将362放到寄存器%eax中,然后执行int 0x80指令,进入内核,内核执行128号中断向量对应的服务例程entry_INT80_32 ,entry_INT80_32根据%eax的值362去系统调用表里找对应的服务例程为sys_connect,执行sys_connect函数,执行完毕后返回到用户态。当然这只是大致流程,具体的函数调用不会这么简单。

技术图片

glibc提供的与socket有关的系统调用函数API、系统调用号及对应的内核处理函数:

系统调用号 系统调用API 对应的内核处理函数
102 socektcall sys_socketcall
359 socket sys_socket
361 bind sys_bind
362 connect sys_connect
363 listen sys_listen
6 close sys_close
364 accept4 sys_accept4
369 sendto sys_sendto
371 recvfrom sys_recvfrom

实验验证:

  1. 系统初始化过程:

断点设在:start_kernel、trap_init、idt_setup_traps,跟踪内核启动过程,

技术图片

内核启动依次停在断点处,结果与分析一致。

  1. 跟踪socket系统调用直至内核处理函数

首先在sys_socketcall处设置断点:

技术图片

在net/socket.c下找到该函数的定义:可以看到socketcall函数使用swith语句,根据call的值执行不同的内核处理函数,从而使不同的socket系统调用有统一的接口

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;
}

在qemu中执行命令replyhi后,服务端程序启动,使用gdb单步调试结果如下:

技术图片

执行顺序为:call=1、call=2、call=4、call=5。在我们的实验环境中,socket接口的调用是通过给socket接口函数编号的方式通过112号系统调用来处理的。这些socket接口函数编号的宏定义见如下:

26#define SYS_SOCKET    1       /* sys_socket(2)        */
27#define SYS_BIND  2       /* sys_bind(2)          */
28#define SYS_CONNECT   3       /* sys_connect(2)       */
29#define SYS_LISTEN    4       /* sys_listen(2)        */
30#define SYS_ACCEPT    5       /* sys_accept(2)        */
31#define SYS_GETSOCKNAME   6       /* sys_getsockname(2)       */
32#define SYS_GETPEERNAME   7       /* sys_getpeername(2)       */
33#define SYS_SOCKETPAIR    8       /* sys_socketpair(2)        */
34#define SYS_SEND  9       /* sys_send(2)          */
35#define SYS_RECV  10      /* sys_recv(2)          */
36#define SYS_SENDTO    11      /* sys_sendto(2)        */
37#define SYS_RECVFROM  12      /* sys_recvfrom(2)      */
38#define SYS_SHUTDOWN  13      /* sys_shutdown(2)      */
39#define SYS_SETSOCKOPT    14      /* sys_setsockopt(2)        */
40#define SYS_GETSOCKOPT    15      /* sys_getsockopt(2)        */
41#define SYS_SENDMSG   16      /* sys_sendmsg(2)       */
42#define SYS_RECVMSG   17      /* sys_recvmsg(2)       */
43#define SYS_ACCEPT4   18      /* sys_accept4(2)       */
44#define SYS_RECVMMSG  19      /* sys_recvmmsg(2)      */
45#define SYS_SENDMMSG  20      /* sys_sendmmsg(2)      */

查表可知内核函数执行顺序为:SYS_SOCKET->SYS_BIND->SYS_LISTEN->SYS_ACCEPT。然后服务端处于阻塞状态,等待客户端的连接。这与理论完全一致:

技术图片

然后在qemu中输入命令hello,启动客户端程序:

技术图片

客户端的执行顺序为:call=1、call=3、call=10、call=9。查表可知内核函数的执行次序为:SYS_SOCKET->SYS_CONNECT->SYS_RECV->SYS_SEND。

然后服务端执行:call=10、call=9、call=5。查表可知内核函数的执行次序为:SYS_RECV->SYS_SEND->SYS_ACCEPT。服务端又阻塞在accept状态,等待客户端的连接。

以上是关于Socket与系统调用深度分析的主要内容,如果未能解决你的问题,请参考以下文章

Socket与系统调用深度分析

Socket与系统调用深度分析

Socket与系统调用深度分析

Socket系统调用Socket与系统调用深度分析

Socket与系统调用深度分析

Socket与系统调用深度分析