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引用计数加一。因此必须都关闭才会真正的将连接关闭。
- 系统调用机制
如上图,系统调用执行的流程如下:
- 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
- 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;
- CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);
- 系统调用处理函数 调用 系统调用服务例程 ( 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步