Socket与系统调用深度分析

1、 什么是系统调用

  操作系统通过系统调用为运行于其上的进程提供服务。当用户态进程发起一个系统调用,CPU将切换到内核态并开始执行一个内核函数。内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。在Linux中系统调用是有Linux内核提供的各种功能服务,为了便于调用Linux提供了一个底层C语言库libc(glibc是GUN版本的libc,其他类似库还有uclibc、klibc),目前glibc是linux标准函数库,这些都对系统系统接口打包成了标准C函数,这些函数一般就成为系统调用。系统调用可以通过syscall()函数发起,或者调用每个对应的一个C函数,这些函数定义在 或者 头文件中。Linux系统中通过软中断0x80调用实现控制权转移给内核,内容执行完成后返回结果。所有系统调用在linux内核的源文件目录" arch/x86/kernel"中的各种文件中定义。

  内核实现了很多的系统调用函数, 这些函数会有自己的名字, 以及编号. 用户要调用系统调用, 首先需要使用  int 0x80 触发软中断. 这个指令会在0x80代表十进制的128, 所以这个指令会找终端向量表的128项, 找到以后, 跳转到相应的函数, 这个处理函数就是system_call. 这个中断向量表的设置, 是在操作系统初始化的时候, 通过trap_init()函数设置的. 在进入中断处理函数system_call以后, 首先要进行一般的中断处理流程, 即保护现场. 这个体现在指令SAVE_ALL(494行)上. 然后有一个重要的函数调用 call *sys_call_table(,%eax,4). 这个表示查找系统调用函数表(), 然后调用相应的系统调用函数. 对于32位的系统, 函数位置存了4个Bytes, eax中是我们传入的系统调用号, 所以4*eax,就可以找到对应的系统调用函数, 执行函数. 之后还需要进行返回值的保存等工作.

2、 gdb跟踪相关系统调用

1) 因为上一次实验是在shiyanlou环境下完成,所以此次实验需要重新下载下载linux-5.0.1的内核并编译内核,并制作根文件系统。

 Socket与系统调用深度分析_第1张图片

 Socket与系统调用深度分析_第2张图片

2)此处先不进行理论上的分析,不妨首先通过gdb跟踪其在内核中的系统调用,在Menuos中输入replyhi命令,观察其系统调用

 Socket与系统调用深度分析_第3张图片

 Socket与系统调用深度分析_第4张图片

   Socket与系统调用深度分析_第5张图片

 接下来输入hello命令,观察系统系统调用

Socket与系统调用深度分析_第6张图片

 Socket与系统调用深度分析_第7张图片

3、socket相关系统调用的内核处理函数深入分析

  1)通过socket和hello/hi的跟踪分析,可以看到一共进行了14次系统调用,其中初始化占了3次,replyhi的socket创建,bind,listen,accept;hello的socket创建等共10次,完成这些操作后,replyhi重新调用accept。

  2)通过以上结果并结合内核函数源码进一步分析,在menu目录下找到test.c源代码,如下位置

      Socket与系统调用深度分析_第8张图片

int Replyhi()
{
        char szBuf[MAX_BUF_LEN] = "\0";
        char szReplyMsg[MAX_BUF_LEN] = "hi\0";
        int sockfd = -1;
        struct sockaddr_in serveraddr;
        struct sockaddr_in clientaddr;
        socklen_t addr_len = sizeof(struct sockaddr);
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(PORT);
        serveraddr.sin_addr.s_addr = inet_addr(IP_ADDR);
        memset(&serveraddr.sin_zero, 0, 8);
        sockfd = socket(PF_INET,SOCK_STREAM,0);
        int ret = bind( sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)); 
        if(ret == -1)
        {
            fprintf(stderr,"Bind Error,%s:%d\n", __FILE__,__LINE__);
            close(sockfd);
            return -1; 
        }
        listen(sockfd,MAX_CONNECT_QUEUE); 
    while(1)
    {
        int newfd = accept( sockfd, (struct sockaddr *)&clientaddr, &addr_len);
        if(newfd == -1) 
        { 
            fprintf(stderr,"Accept Error,%s:%d\n", __FILE__,__LINE__); 
        } 
        ret = recv(newfd,szBuf,MAX_BUF_LEN,0); 
        if(ret > 0)
        {
            printf("recv \"%s\" from %s:%d\n", szBuf, (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));                \
        }
        ret = send(newfd,szReplyMsg,strlen(szReplyMsg),0);
        if(ret > 0) 
        { 
            printf("rely \"hi\" to %s:%d\n", (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));                \
        }
        close(newfd);
    }
    close(sockfd);
    return 0;
}

int StartReplyhi(int argc, char *argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if (pid < 0)
 {
        /* error occurred */
        fprintf(stderr, "Fork Failed!");
        exit(-1);
    }
    else if (pid == 0)
    {
        /*     child process     */
        Replyhi();
        printf("Reply hi TCP Service Started!\n");

    }
    else
    {
        /*     parent process     */
        printf("Please input hello...\n");
    }
}

int Hello(int argc, char *argv[])
{
        char szBuf[MAX_BUF_LEN] = "\0";
        char szMsg[MAX_BUF_LEN] = "hello\0";
        int sockfd = -1;
        struct sockaddr_in serveraddr;
        struct sockaddr_in clientaddr;
        socklen_t addr_len = sizeof(struct sockaddr);
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(PORT);
        serveraddr.sin_addr.s_addr = inet_addr(IP_ADDR);
        memset(&serveraddr.sin_zero, 0, 8);
        sockfd = socket(PF_INET,SOCK_STREAM,0);
        int ret = connect(sockfd,  (struct sockaddr *)&serveraddr,  sizeof(struct sockaddr));
        if(ret == -1) 
        {  
            fprintf(stderr,"Connect Error,%s:%d\n",  __FILE__,__LINE__);
            return -1; 
        }
        ret = send(sockfd,szMsg,strlen(szMsg),0);
        if(ret > 0) 
        { 
            printf("send \"hello\" to %s:%d\n", (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));                \
        }
        ret = recv(sockfd,szBuf,MAX_BUF_LEN,0); 
        if(ret > 0)
        {
            printf("recv \"%s\" from %s:%d\n", szBuf, (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));                \
        }
    close(sockfd); 
    return 0;

}

int main()
{
    PrintMenuOS();
    SetPrompt("MenuOS>>");
    MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
    MenuConfig("quit","Quit from MenuOS",Quit);
    MenuConfig("time","Show System Time",Time);
    MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
    MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi);
    MenuConfig("hello", "Hello TCP Client", Hello);
    ExecuteMenu();
}

  先看main()函数,replyhi和hello命令分别调用StartReplyhi()和hello()函数。接着分析StartReplyhi()函数,当pid=0时,调用Replyhi(),进入Replyhi中,可以看到其中用到了socket(),bind(),listen(),accept(),recv(),send()接口。同理进入Hello()函数,可以看到调用了socket(),connect(),send(),recv()等接口。这个分析结果与也与之前的实验结果相吻合。

你可能感兴趣的:(Socket与系统调用深度分析)