*/

Socket与系统调用深度分析

一.socket函数接口工作流程图

 

上一篇博文中我们简单分析了这个模型,本节里面我们将在此基础上深入内核里分析。 

二.系统调用与中断相关概念

这里我们会涉及到一些概念,先让我们熟悉一下它们!

用户空间:指的就是用户可以操作和访问的空间,这个空间通常存放我们用户自己写的数据等等;而内核空间则是系统内核来操作的一块空间,这块空间里面存放系统内核的函数、接口等。

不管对于Linux还是Windows, 他们都具有自己用户空间内核空间。当一个程序运行时,如果它是在用户空间下执行,我们把此时运行得程序的这种状态成为用户态,而当这段程序执行在内核的空间执行时,这种状态称为内核态

linux用户态下的我们是不能直接接触到kernel的,那我们该如何从用户态转到内核态呢?

用户空间的程序无法直接执行内核代码。所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。

软件中断Linux操作系统一般是通过软件中断从用户态切换到内核态。软件中断和我们常说的中断(硬件中断)不同之处在于,它是通过软件指令触发而并非外设引发的中断,也就是说,又是编程人员开发出的一种异常(该异常为正常的异常)。

那么在Linux下,这个异常具体就是调用int $0x80的汇编指令,这条汇编指令将产生向量为0x80的编程异常。

之所以系统调用需要借助这个中断异常来实现,是因为这个异常实际上就是通过系统门陷入内核(除了int 0x80外用户空间还可以通过int3——向量3into——向量4 bound——向量5等异常指令进入内核,而其他异常无法被用户空间程序利用,都是由系统使用的)。

好了,现在我们知道是先通过软件中断调用了0x80的这个编程异常,这个编程异常对应的是中断描述符表IDT中的第128项——也就是对应的系统门描述符。门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序:system_call()(别和系统调用服务程序混淆,这个程序在entry.S文件中用汇编语言编写)。

 现在我们就知道通过系统描述符中的内核空间来找到系统调用程序,从而进入到内核态。可很显然,所有的系统调用都会统一地转到这个地址,但Linux一共有三百多个系统调用都从这里进入内核后,又该如何派发到它们到各自的服务程序去呢?

首先Linux为每个系统调用都进行了编号 0NR_syscall),同时在内核中保存了一张系统调用表sys_call_table ,该表中保存了系统调用编号和其对应的服务例程,因此在系统调入通过系统门陷入内核前,需要把系统调用号一并传入内核。在x86上,这个传递动作是通过在执行int0x80前把调用号装入eax寄存器实现的。这样系统调用处理程序一旦运行,就可以从eax中得到数据,然后再去系统调用表中寻找相应服务例程了。

 下面我们Socket API编程接口系统调用机制内核中系统调用相关源代码 socket相关系统调用的内核处理函数结合起来分析,并在上一节构件的linux5.0.1内核下进一步跟踪验证。

三.跟踪并分析socket api直至内核代码

我们在上一节构件的MenuOS环境下进行本节的实验

 

1.原理解析

首先我们查看内核源码内系统调用表与系统调用编号

gedit ~/MenuOS/linux-5.0.1/arch/sh/include/uapi/asm/unistd_64.h

 

可以看到该头文件下一共定义了共394个编号

我们本节感兴趣的是和socket相关的编号。如下图(编号220-236)

 

获取到了系统调用号有什么用呢?系统又是如何利用这些系统调用号的呢?

以系统调用号NR_syscall作为下标,可找出系统调用表sys_call_table中对应表项的内容,它正好是该系统调用的响应函数sys_name的入口地址。有了sys_call_table这张表,就很容易根据特定系统调用在表中的偏移量,找到对应的系统调用响应函数的入口地址

用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。而这一过程不需要指明系统调用的名称。

由于所有的系统调用都会使用户态陷入内核态与此同时把系统调用号一并传给内核

2.系统调用命令转换为syscall中断

2.1系统调用模块初始化

start_kernel --> trap_init --> cpu_init --> syscall_init

 

2.2系统调用表初始化

由/linux-5.0.1/arch/x86/entry/entry_64.S 完成

 

2.3执行系统调用
SYM_CODE_START(entry_SYSCALL_64)
...

    /* IRQs are off. */

    movq    %rax, %rdi

    movq    %rsp, %rsi

    call    do_syscall_64        /* returns with IRQs disabled */

 * [do_syscall_64](https://github.com/torvalds/linux/blob/ab851d49f6bfc781edd8bd44c72ec1e49211670b/arch/x86/entry/common.c#L282)

 

 

#ifdef CONFIG_X86_64

__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)

{

...

    if (likely(nr < NR_syscalls)) {

        nr = array_index_nospec(nr, NR_syscalls);

        regs->ax = sys_call_table[nr](regs);

...

}

#endif

 

2.4内核开始服务

系统调用的参数由各通用寄存器传递,然后执行syscall,以内核态进入入口地址system_call

3.socket内核处理函数跟踪分析

基于已经集成在MenuOS中的replyhi程序来追踪socket调用过程

首先打开MenuOS系统

qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -append nokaslr -s

接着我们在MenuOS目录下另外新开一个终端。输入如下命令gdb连接Menu OS服务器,端口1234,开始调试Menu OS系统:

gdb

file linux-5.0.1/vmlinux

target remote:1234

接着我们打上10个断点 输入

b  __sys_socket

b  __sys_bind

b  __sys_listen

b  __sys_connect

b  __sys_accept4

b  __sys_recvmsg

b  __sys_sendmsg

b  __sys_recvfrom

b  __sys_sendto

b  __sys_shutdown

查看我们打的断点信息

Info breakpoints

 

接着gdb中按c运行Menu OS

 

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

 

此时我们就在终端中看到运行到第一个断点处停下。

输入list可以看到此处的函数源码

 

接着我们继续输入c继续运行,想要查看源码的位置输入list即可看到

 

 

 

可以看到依次运行了sys_bind  listen accept4,执行到accept时,中断,然后Menu OS中输入hello打开客户端,如下图:

 

之后就可以依次跟踪到后续的函数。在需要追踪的函数出,输入list可以观察到相应的源码。

总结

通过gdb跟踪的断点可以看到完整的这个hello/hi的内核执行过程。

replyhi调用了服务端的socket()bind()listen()accept()recv()send()close()

MenuOS中输入hello之后调用了客户端的socket()connect()send()recv()close()

参考资料:

https://github.com/mengning/net

posted @ 2019-12-19 17:28  maizeDu  阅读(295)  评论(0编辑  收藏  举报