zoukankan      html  css  js  c++  java
  • Socket与系统调用深度分析

    本次实验要求:

    请将Socket API编程接口、系统调用机制及内核中系统调用相关源代码、 socket相关系统调用的内核处理函数结合起来分析,并在X86 64环境下Linux5.0以上的内核中进一步跟踪验证。

    Socket API编程接口:

     C语言中的Socket API就是一种涉及系统调用的API,常用的函数如下:

    int socket(int domain, int type, int protocol)
    //创建一个新的套接字,返回套接字描述符
    
    
    int connect(int sockfd, struct sockaddr *server_addr, int sockaddr_len)
    //同远程服务器主动连接,成功时返回0,失败时返回1
    
    
    int bind(int sockfd, struct sockaddr* my_addr, int addrlen)
    //为套接字指明一个本地端点地址,TCP/IP协议使用sockaddr_in结构,包含IP地址和端口号,服务器使用它来指明熟悉的端口号,然后等待连接
    
    
    int listen(int sockfd, int input_queue_size)
    //面向连接的服务器指明某个套接字,将其置为被动模式,并准备接收传入连接
    
    
    int accept(int sockfd, void* addr, int* addrlen)
    //获取传入连接请求,返回新的连接套接字描述符,为每个新连接请求创建一个新的套接字,服务器只对新的连接使用该套接字,原来的监听套接字接受其他的连接请求。新的连接上传输数据使用新的套接字
    
    
    int sendto(int sockfd, const void* data, int data_len, unsigned int flags, struct sockaddr* remaddr,int remaddr_len)
    //基于UDP发送数据报,返回实际发送的数据长度,出错时返回1
    
    
    int send(int sockfd, const void* data, int data_len, unsigned int flags)
    //在TCP连接上发送数据,返回成功传送数据的长度,出错时返回-1,将外发数据复制到OS内核中
    
    
    int recvfrom(int sockfd, void *buf, int buf_len,unsigned int flags,struct sockaddr *from,int *fromlen);
    //从UDP接收数据,返回实际接收的字节数,失败时返回-1
    
    
    int recv(int sockfd, void* buf, int buf_len,unsigned int flags) 
    //从TCP接收数据,返回实际接收的数据长度,出错时返回-1。服务器使用其接收客户请求,客户使用它接受服务器的应答。如果没有数据,将阻塞,如果收到的数据大于缓存的大小,多余的数据将丢弃
    
    close(int sockfd)
    //撤销套接字,如果只有一个进程使用,立即终止连接并撤销该套接字,如果多个进程共享该套接字,将引用数减一,如果引用数降到零,则撤销它
    

      网络程序调用基本流程如下图所示:

    socket api和系统调用关系

    系统调用:

    在计算机系统中,通常运行着两类程序:系统程序和应用程序,为了保证系统程序不被应用程序有意或无意地破坏,为计算机设置了两种状态:

    系统态(也称为管态或核心态),操作系统在系统态运行
    用户态(也称为目态),应用程序只能在用户态运行。
    在实际运行过程中,处理机会在系统态和用户态间切换。相应地,现代多数操作系统将 CPU 的指令集分为特权指令和非特权指令两类。

    执行态切换过程:

    应用程序在用户态准备好调用参数,执行 int 指令触发软中断 ,中断号为 0x80 ;

    CPU 被软中断打断后,执行对应的中断处理函数 ,这时便已进入内核态 ;

    系统调用处理函数准备内核执行栈 ,并保存所有寄存器 (一般用汇编语言实现);

    系统调用处理函数根据系统调用号调用对应的 C 函数—— 系统调用服务例程 ;

    系统调用处理函数准备返回值并从内核栈中恢复 寄存器 ;

    系统调用处理函数执行 ret 指令切换回用户态 ;

    socket系统调用发生流程

    当用户进程使用socket API 的时候,会产生向量为0x80的编程异常,系统执行系统调用。

    进程传递系统调用号到寄存器eax,指明需要哪个系统调用,同时会将系统调用需要的参数存入相关寄存器。

    系统调用处理函数system_call是Linux中所有系统调用的入口点,通过进程存在eax寄存器中的系统调用号决定调用哪个系统调用。

    其中,socket api有两种系统调用方式:(1)所有的socket系统调用的总入口是sys_socketcall(系统调用号102)  (2)每一个独立的socket api都对应一个单独的系统调用。

    初始化 MenuOS 系统的网络功能,跟踪分析 TCP 协议:

    在本次实验中,我们创建的是一个利用socket的基于TCP的连接,接下来我们结合源码,接口来进行整个hello/hi的实现过程的调用分析与追踪。

    上次的课程实验实现了menuos的调试环境的配置主要是在menuos中增加了replyhi和hello两条命令,其结果大致如下:

    我们首先以最常用的listen()函数为例进行分析:

    进入上次的MenuOS目录,更改makefile,将-S参数去除。(否则将会挂起CPU)

    之后在终端编译:

    make rootfs
    

      结果如下所示:

    在 gdb中给__sys_linsten打上断点:

    gdb
    file ./vmlinux
    target remote:1234
    break __sys_listen 

    结果如下所示: 

     

    说明gdb已找到这两条系统调用的函数定义所在。

    我们接下来看一下接下来要分析的replyhi和hello的源码

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

    从上面代码中看到replyhi和hello命令需要分别调用 StartReplyhi 和 Hello函数。

    下面是StartReplyhi的源码:

    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!
    ");
        }
        else
        {
            /*     parent process     */
            printf("Please input hello...
    ");
        }
    }
    

      从上面看出StartReplyhi又调用了

    {
        char szBuf[MAX_BUF_LEN] = "";
        char szReplyMsg[MAX_BUF_LEN] = "hi";
        InitializeService();
        while (1)
        {
            ServiceStart();
            RecvMsg(szBuf);
            SendMsg(szReplyMsg);
            ServiceStop();
        }
        ShutdownService();
        return 0;
    }
    #InitializeService(),ServiceStart()这些函数均定义在头文件syswrapper.h中
    /* public macro */ 
    #define InitializeService()                             
            PrepareSocket(IP_ADDR,PORT);                    
            InitServer();
    #InitializeService()中分别调用了PrepareSocket和InitServer()
    /* private macro */
    #define PrepareSocket(addr,port)                        
            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(addr);   
            memset(&serveraddr.sin_zero, 0, 8);             
            sockfd = socket(PF_INET,SOCK_STREAM,0);
            
    #define InitServer()                                    
            int ret = bind( sockfd,                         
                            (struct sockaddr *)&serveraddr, 
                            sizeof(struct sockaddr));       
            if(ret == -1)                                   
            {                                               
                fprintf(stderr,"Bind Error,%s:%d
    ",        
                                __FILE__,__LINE__);         
                close(sockfd);                              
                return -1;                                  
            }                                               
            listen(sockfd,MAX_CONNECT_QUEUE);
    #显然我们可以发现到这一步,InitializeService这个宏已经完成了socket的创建,绑定和listen
    #我们继续看ServiceStart(),RecvMsg(szBuf),SendMsg(szReplyMsg),ServiceStop(),ServiceStop()
    #define ServiceStart() 
     int newfd = accept( sockfd, 
                        (struct sockaddr *)&clientaddr,     
                        &addr_len);                         
            if(newfd == -1)                                 
            {                                               
                fprintf(stderr,"Accept Error,%s:%d
    ",      
                                __FILE__,__LINE__);         
            }
    #这一步完成了accept
     
    #define RecvMsg(buf) 
           ret = recv(newfd,buf,MAX_BUF_LEN,0);             
           if(ret > 0)                                      
           {                                                
                printf("recv "%s" from %s:%d
    ",          
                buf,                                        
                (char*)inet_ntoa(clientaddr.sin_addr),      
                ntohs(clientaddr.sin_port));                
           }
           #这一步完成了recv
    #define SendMsg(buf)                                    
            ret = send(newfd,buf,strlen(buf),0);            
            if(ret > 0)                                     
            {                                               
                printf("rely "hi" to %s:%d
    ",            
                (char*)inet_ntoa(clientaddr.sin_addr),      
                ntohs(clientaddr.sin_port));                
            }
    #这一步完成了send
    #define ServiceStop() 
     close(newfd);
    #这一步完成了close
    

      

    hello的过程与其类似,通过源码我们可以看出replayhi函数涉及到的系统调用按照调用顺序为

    socket,bind,listen,accept,recv,send

     于是我们在一个终端打开qemu启动MenuOS(指令中去掉 -S,在另一个终端用gdb读入linux-5.0.1的vmlinux,

    通过端口1234与qemu建立连接,在相关的系统调用内核处理函数处设置断点,结果如下图所示:

    接着在gdb中按c运行Menu OS

     

    上图中可以看到,首先就等待了。然后我们在Menu OS中打开服务器,即输入replyhi:

    不停地按回车,发现捕获到如下断点,一直到sys_accept4函数停止。说明此时服务器处于阻塞状态,一直在等待客户端连接。

     

    打开qemu发现指令已经完整运行:

    通过实验可知,在replyhi中,分别调用了socket、bind、listen、accept。

    结果与预想一致,至此完成对replyhi/hello的追踪。

  • 相关阅读:
    使用Mutex实现程序单实例运行(c#)
    KMP(转载来自Matrix67原创)
    【转载】搞ACM的你伤不起(转载,不过这个神作实在是太经典了)
    POJ 3125 Printer Queue【打印队列】
    弱校ACM奋斗史
    POJ 2063 Investment
    程序员的艺术:排序算法舞蹈【视频】
    POJ 2063 Investment【经典完全背包】
    快速幂模板
    搞ACM的你伤不起(转载,不过这个神作实在是太经典了)
  • 原文地址:https://www.cnblogs.com/zzydexiaowu/p/12067150.html
Copyright © 2011-2022 走看看