Socket与系统调用深度分析

  • 实验要求
  • Socket API编程接口之上可以编写基于不同网络协议的应用程序;
  • Socket接口在用户态通过系统调用机制进入内核;
  • 内核中将系统调用作为一个特殊的中断来处理,以socket相关系统调用为例进行分析;
  • socket相关系统调用的内核处理函数内部通过“多态机制”对不同的网络协议进行的封装方法;

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

  • Socket API编程接口
  •     创建socket      

    UNIX/Linux的一个哲学:所有的东西都是文件,socket也不例外,可读。可写,可控制,可关闭的文件描述符。socket基础API在sys/socket.h下面的socket系统调用可以创建一个socket。

    

1 #include <sys/types.h>
2 #include <sys/socket.h>
3 int socket(int domain,int type, int protocol);  

    

  •     命名socket

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd,const sockaddr* my_addr,socklen_t addrlen);

 

     客户端通常不需要命名socket采用的是匿名方式,操作系统自动分配的socket地址(客户端使用的就是这个分配的一个随机的非知名端口号)。

    

  •     监听socket

 

 

     socket被命名之后还不能马上接受客户的连接,需要创建一个监听队列存放待处理的客户连接。
服务器通过listen调用被动的接连接。

#include <sys/socket.h>

int listen(int sockfd, int backlog);

 

 

  • 接受连接

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr, socklen *addrlen);

 

 accept调用对于客户端的网络断开毫不知情。只是从监听队列当中取出连接,不论连接处于何种状态。

  • 发起连接

客户端通过connect调用主动的与服务器建立连接。

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
  • 关闭连接

#include <unistd.h>
int close(int fd);

fd是待关闭的socket的描述符,将fd的引用计数减一只有当引用计数为0的时候才真正的关闭连接。一次fock将是的父进程当中打开的socket引用计数加一。因此必须都关闭才会真正的将连接关闭。

  • 系统调用机制

    

 

 

如上图,系统调用执行的流程如下:

  1. 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
  2. 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;
  3. CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);
  4. 系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;
  • socket相关系统调用分析

  基于上次实验构建的Menu OS系统来进行本次的socket系统调用内核处理函数的跟踪分析

  即通过在Menu OS系统上运行TCP客户端/服务器程序,然后用gdb设置断点来跟踪分析socket内核处理函数。

  使用跟踪分析 ~/Linux 内核的启动过程的 -s 和 -S 选项启动 MenuOS 系统。

$ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

 

  •  打开一个新的终端执行 gdb
# 打开 GDB 调试器
$ gdb

# 在 GDB 中输入以下命令:

# 在 gdb 界面中 targe remote 之前加载符号表
(gdb)file linux-3.18.6/vmlinux 

# 建立 gdb 和 gdbserver 之间的连接
(gdb)target remote:1234
  • 设置断点
# 断点的设置可以在target remote之前,也可以在之后
(gdb)break #start_kernel# 

# 按 c 让qemu上的Linux继续运行
(gdb)c      

只需在断点处输入list即可查看相应的内核处理函数,,比如__sys_listen和__sys_bind源码如下:

int __sys_listen(int fd, int backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

 sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned int)backlog > somaxconn)
            backlog = somaxconn;

 err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

 fput_light(sock->file, fput_needed);
    }
    return err;
}
int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, &address);
        if (!err) {
            err = security_socket_bind(sock,
                           (struct sockaddr *)&address,
                           addrlen);
            if (!err)
                err = sock->ops->bind(sock,
                              (struct sockaddr *)
                              &address, addrlen);
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}
posted @ 2019-12-19 21:03  Sunmengjie  阅读(212)  评论(0编辑  收藏  举报