请将Socket API编程接口、系统调用机制及内核中系统调用相关源代码、 socket相关系统调用的内核处理函数结合起来分析,并在X86 64环境下Linux5.0以上的内核中进一步跟踪验证。
实验环境:vmware 15.5下的ubuntu16.04虚拟机
基于内核:linux 5.0.1
内核编译方式:x86-64
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。实质上socket就是一种为了完成两个应用程序之间的数据传输独立于协议的网络编程接口。
套接字Socket=(IP地址:端口号),套接字的表示方法是点分十进制的IP地址后面写上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。
套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。
套接字调用流程如下图所示:
linux内核中设置了一组用于实现系统功能的子程序,称为系统调用。系统调用和普通库函数调用非常相似,只是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态。
一般进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数,CPU硬件决定了这些。为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)。实际上提供这组接口主要是为了保证系统稳定可靠,避免应用程序滥用。并且提供了简单一致的机制供应用程序使用。
操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用:
系统调用是用户态进入内核态的唯一入口。
内核态:一般现代CPU有几种指令执行级别。在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别对应着内核态。
用户态:在相应的低级别执行状态下,代码的掌控范围有限,只能在对应级别允许的范围内活动。如intel x86 CPU有四种不同的执行级别0-3,Linux只使用0级表示内核态,3级表示用户态。权限级别的划分使系统更稳定。
区分用户态与内核态主要通过代码段选择寄存器cs和偏移量寄存器eip,cs寄存器的最低两位表明了当前代码特权级,CPU每条指令的读取都是通过cs:eip这两个寄存器一般在Linux中,(逻辑)地址空间是显著标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都能访问。
中断处理是从用户态进入内核态的主要方式。系统调用只是一种特殊的中断。从用户态切换到内核态时:必须保存用户态的寄存器上下文,同时将内核态的寄存器相应的值放入当前CPU。中断/int指令会在堆栈上保存一些寄存器的值:如用户态栈顶地址、当前的状态字、当时cs:eip的值(当前中断程序的入口)Linux内核代码中定义了两个宏指令来进行保护和恢复。
系统调用处理程序也其他异常处理程序的结构类似,执行下列操作
之前我们已经将 TCP 网络程序的服务端 replyhi 集成到 MenuOS 中了,而且可以正常的启动 TCP 服务,方便我们跟踪 socket、bind、listen、accept 几个 API 接口到内核处理函数,但是我们启动的 TCP 服务并不能正常对外提供服务,因为 MenuOS 没有初始化网络设备(包括本地回环 loopback 设备),因此它无法接收到任何网络请求。接下来我们将激活 Linux 网络设备,并将 MenuOS 系统的网络设备用简便的方式配置好,使我们将 TCP 客户端也集成进去后可以完整的运行 TCP 网络程序的服务端和客户端程序。并将 C/S 方式的网络通信程序的客户端也集成到 MenuOS 系统中,成为 MenuOS 系统的命令 hello,我们 git clone 克隆一个 linuxnet.git;进入 lab3 目录执行 make rootfs,脚本就可以帮助我们自动编译、自动生成根文件系统,还会帮我们运行起来 MenuOS 系统。运行起来的 MenuOS 中执行 help 命令可以看到其中不止有 replyhi,也有了 hello 命令,我们可以先执行 replyhi,然后执行 hello。
因为篇幅有限,我们在这里对Socket两个非常常用的系统调用sock_create函数和sock_map_fd函数进行跟踪,具体步骤如下:
打开Ubuntu虚拟机后,进入上次实验的装有MenuOS内核的文件夹,在实验之前,首先要修改上次的~/MenuOS/menu/Makefile文件:
1 ls 2 cd menu 3 sudo su // 切换至root用户以修改Makefile文件 4 gedit Makefile // 去掉-S
然后,和上次一样,打开MenuOS:
make rootfs
再打开另一个终端,输入如下命令( 此时切不可关闭终端和QEMU)
gdb file ./vmlinux target remote:1234 break sock_create break sock_map_fd
上图已说明gdb已找到这两条系统调用的函数定义所在。然后开始对MenuOS的运行,在gdb终端输入c,接下来两条命令replyhi和hello在QEMU中输入,结果如下:
gdb已经成功跟踪到系统调用。
在~/linux-5.0.1/net/socket.c中找到了相应的函数定义,如下图(自己在里面加了一些注释):
这里比较重要的是sock_alloc()和pf->create()这两个函数。其中sock_alloc()里体现了linux一切皆文件理念,即使用文件系统来管理socket。
在~/linux-5.0.1/net/socket.c中找到了相应的函数定义,如下图(自己在里面加了一些注释):
这个函数主要有两个部分,一个是创建file文件结构,fd文件描述符,另一部分是将file文件结构和fd文件描述符关联,同时将上一步返回的socket也一起绑定,形成一个完整的逻辑。
参考链接:
https://www.shiyanlou.com/courses/1198/learning/?id=9010
https://blog.csdn.net/u010039418/article/details/79347844
原文:https://www.cnblogs.com/ai469/p/12061645.html