一、内核接收数据流程
内核接收数据.png
1.网卡发现 MAC 地址符合,就将包收进来;发现 IP 地址符合,根据 IP 头中协议项,知道上一层是 TCP 协议;
2.DMA把TCP数据包copy到内核缓冲区;
3.触发CPU中断,中断程序摘除TCP头通过socket五要素(源IP/PORT、目的IP/PORT、协议)找到对应的socket文件,并把原始二进制数据报copy到socket接收缓冲区;
4.中断程序唤醒被阻塞的内核线程;
5.内核线程切换到用户线程把数据从socket接口缓冲区copy到应用内存;
二、中断处理流程
中断处理.png
I/O发出的信号的异常代码,拿到异常代码之后,CPU就会触发异常处理的流程。计算机在内存里会保存中断向量表,用来存放不同的异常代码对应的异常处理程序所在的地址。
CPU在拿到了异常码之后,会先把当前的程序执行的现场,保存到程序栈里面,然后根据异常码查询,找到对应的异常处理程序,最后把后续指令执行的指挥权,交给这个异常处理程序。
异常处理程序结束之后返回到原来指令执行的位置继续执行;
三、阻塞不占用 cpu
网卡何时接收到数据是依赖发送方和传输路径的,这个延迟通常都很高,是毫秒(ms)级别的。而应用程序处理数据是纳秒(ns)级别的。也就是说整个过程中,内核态等待数据,处理协议栈是个相对很慢的过程。这么长的时间里,用户态的进程是无事可做的,因此用到了“阻塞(挂起)”。
阻塞是进程调度的关键一环,指的是进程在等待某事件发生之前的等待状态。请看下表,在 Linux 中,进程状态大致有 7 种(在 include/linux/sched.h 中有更多状态):
从说明中可以发现,“可运行状态”会占用 CPU 资源,另外创建和销毁进程也需要占用 CPU 资源(内核)。重点是,当进程被"阻塞/挂起"时,是不会占用 CPU 资源的。
为了支持多任务,Linux 实现了进程调度的功能(CPU 时间片的调度)。而这个时间片的切换,只会在“可运行状态”的进程间进行。因此“阻塞/挂起”的进程是不占用 CPU 资源的。
四、工作队列和等待队列
工作队列和等待队列.png
工作队列:为了方便时间片的调度,所有“可运行状态”状态的进程组成的队列;
fd文件列表:内核打开的文件句柄,Linux一切皆文件,用户线程执行创建Socket时内核就会创建一个由文件系统管理的sock对象;
sock:socket内核中的数据结构,主要包含发送缓冲区、接收缓冲区、等待队列;
structsock{__u32 daddr;/* 外部IP地址 */__u32 rcv_saddr;/* 绑定的本地IP地址 */__u16 dport;/* 目标端口 */__u16