亲宝软件园·资讯

展开

Socket与系统调用深度分析

iyuanyuan 人气:1

学习一下对Socket与系统调用的分析分析

一、介绍

我们都知道高级语言的网络编程最终的实现都是调用了系统的Socket API编程接口,在操作系统提供的socket系统接口之上可以建立不同端口之间的网络连接,从而使我们可以编写各基于不同网络协议的应用程序。而用户程序一般都是运行在用户态,依靠的Socket接口也是在在用户态,我们都知道socket接口是通过系统调用机制进入内核,从而从内核的层面提高服务。本次实验主要需要分析出socketAPI函数是如何进行系统调用的。

首先我们再明确一下系统调用和socketAPI之间的关系:也就是说系统调用是使得API能获得内核支持的途径。

 

 

 

 二、实验步骤

首先从参考了大佬的博客之后获悉,在linux系统中,与socket API有关的系统调用包括所有的与socketapi都使用的sys_socketcall系统调用和每一个socketapi都对应特定的系统调用,因此我主要通过这两类来进行分析。

1、首先使用gdb连接menu系统,这个系统是上次实验制作的简易系统

注意:这里需要先用下面的命令启动tcp server,否则会显示连接超时

 

执行 qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s -S

 

然后新开一个终端,打开gdb,并且使用target连接上menu系统

gdb
file ~/net/linux-5.0.1/vmlinux   
target remote:1234

连接以后就可以打断点进行调试了。

 

2、对特殊的系统调用进行测试:给sys_bind和sys_listen分析

 

 

 接着按c即继续运行程序,此时menu系统继续启动。

 

 

 

根据提示,输入replyhi以后,发现gdb的终端如下所示:

说明并没有停在断点,也就是使用replyhi的时候并没有调用sys_listen和sys_bind这两个内核函数。

 

 

 3、接下来对所有socketapi都使用的sys_socketcall系统调用进行测试,使用 b sys_socketcall打断点,接着按c继续执行

 

 

 然后在mune系统进行测试,输入replyhi以后,发现gdb终端有反馈了,如下所示:

说明停在了SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args),也就是此时使用了这个内核函数

 

查看源码如下:

 

 下面我们简单分析一下这个函数:发现这段c语言很大一部分都是switch语句,说明这个函数的主要作用是根据不同的输入来选择不同的操作,调用不同的内核处理函数,传入是SYS_BIND则调用__sys_bind,传入SYS_LISTEN则调用 __sys_listen等内核函数进行相应的处理。

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

此时我们查看一下replyhi的源码,看看究竟是如何调用的。

从下面的程序可以看到,replyhi调用其他函数的顺序InitializeService,ServiceStart,RecvMsg,SendMsg、ServiceStop。

 

 

 接下来我们需要做的是找出InitializeService,ServiceStart,RecvMsg,SendMsg和ServiceStop,看看这些函数是如何使用内核处理函数的。

(1)首先是InitializeService,可以看到这个函数的主要就是调用了 PrepareSocket(IP_ADDR,PORT)和 InitServer()

 

 

 所以只能继续追踪,首先看 PrepareSocket(IP_ADDR,PORT),可以看到这个函数的主要作用是分配内存空间,并调用socket函数,创建套接字

 

 

 接着是InitServer()函数,从程序中看到这个函数的主要作用调用bind函数,服务器使用bind来指明熟知的端口号,然后等待连接
并进行监听

 

 所以InitializeService函数主要作用在于建立socket,绑定socket并进行端口的监听。

(2)ServiceStart函数,显然,这个函数的作用只是调用accept,获取传入连接请求。

 

 (3) SendMsg,顾名思义,这个函数主要就是进行信息的发送,依靠的内核函数是send函数

 

 (4)RecvMsg 这个函数主要就是进行信息的接受,依靠的内核函数是recv函数

 

 

 

(5)ServiceStop,这个函数很简单,就是调用close函数进行撤销套接字,结束当前的连接。

 

 至此,我们终于分析完了replyhi这个函数是如何依靠socketAPI以及内核服务程序进行网络通信的。

回顾总结一下:

reply函数通过调用InitializeService,ServiceStart,RecvMsg,SendMsg、ServiceStop子函数,这些函数进行的系统调用分别是socket、bind、accept、recv、send、close。然后在内核的sockct函数中,通过传入的系统调用,使用switch语句调用不同的内核处理函数,完成网络通信。

三、总结

这次的实验确实挺难的,做了很久,也只是做出了点皮毛,但是通过这个实现,进一步了解了linux内核网络通信部分的机制,对写内核的人更加钦佩,只能说,大佬太强了!!!

实验过程参考同学博客:https://www.cnblogs.com/hhssqq9999/p/12048964.html

 

 

 

加载全部内容

相关教程
猜你喜欢
用户评论