Socket与系统调用深度分析

一、系统调用概述

  系统调用由操作系统实现提供的所有系统调用所构成的集合即程序接口应用编程接口(Application Programming Interface,API)。是应用程序系统之间的接口。当用户态进程发起一个系统调用, CPU将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。

  操作系统中的状态分为管态(内核态)和目态(用户态)。
 
  特权指令:一类只能在内核态下运行而不能在用户态下运行的特殊指令。不同的操作系统特权指令会有所差异,但是一般来说主要是和硬件相关的一些指令。访管指令:本身是一条特殊的指令,但不是特权指令。(trap指令)。基本功能:“自愿进管”,能引起访管异常。
 
  用户程序只在用户态下运行,有时需要访问系统核心功能,这时通过系统调用接口使用系统调用。 

 

  调用流程如下图所示:

  1.当应用程序执行到xyz()这个函数时,该函数是一个包装了系统调用的库函数

  2.xyz()负责准备好需要向内核传递的参数,并且触发软中断用户态切换到内核态 

  3.CPU被软中断打断后,执行中断处理函数,即系统调用处理函数(system_call)

  4.系统调用处理函数调用系统调用服务例程(sys_xyz),开始处理系统调用

  5.处理结束后,再将处理结果返回给用户态,继续后面程序的执行

 

二、Socket概述

  套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合

  套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。工作流程如下图所示:

Socket():创建套接字。

 
Bind():指定本地地址。在某个知名端口(Well-known Port)上操作的服务器进程必须要对系统指定本地端口,所以一旦创建了一个套接字,服务器就必须使用bind()系统调用为套接字建立一个本地地址。
 
Connect():将套接字连接到目的地址。客户机可以调用connect()为套接字绑定一个永久的目的地址,将它置于已连接状态。对数据流方式的套接字,必须在传输数据前,调用connect()构造一个与目的地的TCP连接,并在不能构造连接时返回一个差错代码。
 
Listen():设置等待连接状态。对于一个服务器的程序,当申请到套接字,并调用bind()与本地地址绑定后,就应该等待某个客户机的程序来要求连接。listen()就是把一个套接字设置为这种状态的函数。
 
Accept():接受连接请求。服务器进程使用系统调用socket,bind和listen创建一个套接字,将它绑定到知名的端口,并指定连接请求的队列长度。然后,服务器调用Accept进入等待状态,直到到达一个连接请求。
 
Send()/Receive():发送和接收数据 。在数据流方式中,一个连接建立以后,或者在数据报方式下,调用了Connect()进行了套接字与目的地址的绑定后,就可以调用Send()和Receive()函数进行数据传输。
 
Close():关闭套接字。
 
三、Socket API编程接口、 socket相关系统调用的内核处理函数跟踪分析
 
  上次实验,我们已经在MenuOS上能够完成TCP客户端和服务器发送和接收hello/hi,也就是MenuOS的网络可以正常工作。这次我们基于该程序对Socket API编程接口、 socket相关系统调用的内核处理函数进行跟踪分析。
 
首先再lab3目录下执行以下命令,在qemu中启动gdb server
qemu -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s -S

连接gdb server,如下图所示:

执行完后QEMU虚拟机会启动

重开一个终端依次执行下面的命令

gdb
file ~/LinuxKernel/linux-5.0.1/vmlinux
break sys_socketcall
target remote:
1234 c

执行完成后如下图所示:

 在QEMU窗口中输入以下命令:

replyhi

gdb调试终端界面显示如下:

   观察上图倒数第二行,gdb跟踪到了 sys_socketcall函数,对应的系统调用处理函数为SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args),倒数第三行指出了该函数所在文件及位置,再net目录下socket.c的第2527行。

打开socket.c文件,可以看到SYSCALL_DEFINE2函数的定义,如下面所示:

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,在函数内部会根据call的不同值来确定返回不同的处理方式,在socket中有不同的函数如Socket():创建套接字,Bind():指定本地地址,Connect():将套接字连接到目的地址等,根据传入参数的不同进行相应的处理。

 

增加断点继续执行,如下图所示:

不断输入c命令,结果如下:

Breakpoint 1, __se_sys_socketcall (call=1, args=-1079671440)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 2, __sys_socket (family=2, type=1, protocol=0) at net/socket.c:1327
1327    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=2, args=-1079671452)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 3, __sys_bind (fd=4, umyaddr=0xbfa585bc, addrlen=16)
    at net/socket.c:1469
1469    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=1, args=-1079671424)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 2, __sys_socket (family=2, type=1, protocol=0) at net/socket.c:1327
1327    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=3, args=-1079671424)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 5, __sys_connect (fd=4, uservaddr=0xbfa585dc, addrlen=16)
    at net/socket.c:1646
1646    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=10, args=-1079671444)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 8, __sys_recvfrom (fd=5, ubuf=0xbfa585dc, size=1024, flags=0, 
    addr=0x0, addr_len=0x0) at net/socket.c:1819
1819    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=9, args=-1079671428)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 10, __sys_sendto (fd=4, buff=0xbfa589fc, len=5, flags=0, addr=0x0, 
    addr_len=0) at net/socket.c:1758
1758    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=9, args=-1079671444)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 10, __sys_sendto (fd=5, buff=0xbfa589dc, len=2, flags=0, addr=0x0, 
    addr_len=0) at net/socket.c:1758
1758    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=10, args=-1079671428)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 8, __sys_recvfrom (fd=4, ubuf=0xbfa585fc, size=1024, flags=0, 
    addr=0x0, addr_len=0x0) at net/socket.c:1819
1819    {
(gdb) c
Continuing.

Breakpoint 1, __se_sys_socketcall (call=5, args=-1079671440)
    at net/socket.c:2527
2527    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
(gdb) c
Continuing.

Breakpoint 6, __sys_accept4 (fd=4, upeer_sockaddr=0xbfa585cc, 
    upeer_addrlen=0xbfa585ac, flags=0) at net/socket.c:1542
1542    {
(gdb) c
Continuing.

call的值依次为1,2,1,3,10,9,9,10,5,分别对应不同的网络系统调用,由此可以总结出下表:

call值 对应的系统调用
1 __sys_socket
2 __sys_bind
3 __sys_connect
5 __sys_accept4
9 __sys_sendto
10 __sys_recvfrom

实验结束 

posted @ 2019-12-19 16:57  zzh大帅  阅读(262)  评论(0编辑  收藏  举报