Socket 与系统调用深度分析

本次实验我们主要来研究SocketAPI,相应系统调用以及内核处理函数之间的关系。

实验环境

linux-5.0.1以x86_46_defconfig配置编译的内核

gdb-7.12

qemu-2.5.0

 

前言

SocketAPI:Socket网络编程相关的应用程序接口。所谓的应用程序接口我们可以将其视为库函数,封装底层具体实现逻辑,并提供一个用户友好的具体功能函数。

系统调用(system call):系统调用为内核提供给外界,一个用来将程序状态由用户态陷入内核态的入口。

内核处理函数:在内核态运行的函数,可以执行高权限的内核态指令。

 

Replyhi Socket API

我们以Replyhi函数为整个Socket调用的起始,先来看下Replyhi的代码。

 1 int Replyhi()
 2 {
 3     char szBuf[MAX_BUF_LEN] = "\0";
 4     char szReplyMsg[MAX_BUF_LEN] = "hi\0";
 5     printf("Start InitializeService!\n");
 6     InitializeService();
 7     while (1)
 8     {
 9         printf("Start ServiceStart!\n");
10         ServiceStart();
11         printf("Start RecvMsg!\n");
12         RecvMsg(szBuf);
13         printf("Start SendMsg!\n");
14         SendMsg(szReplyMsg);
15         printf("Start ServicesStop!\n");
16         ServiceStop();
17     }
18     printf("Start ShutdownService!\n");
19     ShutdownService();
20     return 0;
21 }

可以看到在Replyhi中主要调用了六个方法来进行网络通信,这六个方法均以宏的形式定义在syswrapper.h中。

如SendMsg的宏定义如下所示,在宏定义中我们可以清楚地看到,该函数调用了send socket api。其他方法也分别调用了不同的socket api。

1 #define SendMsg(buf)                                    \
2         ret = send(newfd,buf,strlen(buf),0);            \
3         if(ret > 0)                                     \
4         {                                               \
5             printf("rely \"hi\" to %s:%d\n",            \
6             (char*)inet_ntoa(clientaddr.sin_addr),      \
7             ntohs(clientaddr.sin_port));                \
8         }

 

Qemu调试 

为了探明这些api背后的故事,我们利用gdb来进行内核调试。首先对跟socket相关的系统调用均打上断点。

 

在qemu中输入replyhi指令,可以看到分别停止于__sys_socket,__sys_bind,__sys_listen和__sys_accept4断点处,并且每次遇到__sys_xxx断点前均会与到__ia32_compat_sys_socketcall断点。

 

并且此时MenuOS输出了Start InitializeService!和Start ServiceStart!,表明Replyhi执行了前两个方法,而调用的系统函数也正好与syswrapper中的socket api相对应。

接下来我们来看下__ia32_compat_sys_socketcall断点出的代码,可以看到该代码位于net/compat.c的838行。

 

从上方的注释就可以看出这个函数调用是一个系统调用向量。下方代码也验证了该点,该函数会根据call的值来调用合适的socket相关的系统调用。

 

然后输入hello,断点情况如下所示,分别在__sys_socket,__sys_connect,__sys_sendto,__sys_recvfrom,...,__sys_accept4处停止,同样与replyhi后续方法调用的socket api一一对应。

 

内核处理函数

当程序运行都某一预设的断点处时,我们可以使用l来查看当前系统调用的内核处理函数。下图显示的是在gdb中查看__sys_sendto的内核处理函数的部分代码。

完整的处理函数代码位于net/socket.c中,其内容如下所示

/*
 *    Send a datagram to a given address. We move the address into kernel
 *    space and check the user space data area is readable before invoking
 *    the protocol.
 */
int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
         struct sockaddr __user *addr,  int addr_len)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err;
    struct msghdr msg;
    struct iovec iov;
    int fput_needed;

    err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);
    if (unlikely(err))
        return err;
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    msg.msg_name = NULL;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_namelen = 0;
    if (addr) {
        err = move_addr_to_kernel(addr, addr_len, &address);
        if (err < 0)
            goto out_put;
        msg.msg_name = (struct sockaddr *)&address;
        msg.msg_namelen = addr_len;
    }
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    msg.msg_flags = flags;
    err = sock_sendmsg(sock, &msg);

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

 

posted @ 2019-12-19 18:45  fiveFish  阅读(284)  评论(0编辑  收藏  举报