不多BB，为了简单起见，我们用 udp 来举例，如下：

```c
int main(){
    int serverSocketFd = socket(AF_INET, SOCK_DGRAM, 0);
    bind(serverSocketFd, ...);

    char buff[BUFFSIZE];
    int readCount = recvfrom(serverSocketFd, buff, BUFFSIZE, 0, ...);
    buff[readCount] = '\0';
    printf("Receive from client:%s\n", buff);
}
```

上面代码是一段 udp server 接收收据的逻辑。 当在开发视角看的时候，只要客户端有对应的数据发送过来，服务器端执行`recv_from`后就能收到它，并把它打印出来。

我们现在想知道的是，当网络包达到网卡，直到我们的`recvfrom`收到数据，这中间，究竟都发生过什么？

看完本文，你将会学习到以下知识：

- Linux 视角的网络协议栈
- 什么是硬中断和软中断？分别有什么用？到底如何区分？

## 1、Linux 网络收包总概况

在 TCP/IP 网络分层模型里， 我们可以根据使用范围来分：

- 用户进程：应用层，客户端。
- Linux内核：
- - 协议栈：传输层；网络层
  - 驱动：网络设备驱动

内核和网络设备驱动是通过中断的方式来处理的。当设备上有数据到达的时候，会给CPU的相关引脚上触发一个电压变化。以通知CPU来处理数据。对于网络模块来说，由于处理过程比较复杂和耗时，如果在中断函数中完成所有的处理，将会导致中断处理函数（优先级过高）将过度占据CPU，将导致CPU无法响应其它社保，例如鼠标或者键盘的消息。

因为 Linux 中断处理函数是分上下两部分。上半部分只是进行最简单的工作，快速处理然后释放CPU，接着CPU就可以允许其它中断进来。剩下的绝大部分的工作可以放到下半部分，可以慢慢从容处理。

2.4 版以后的内核版本采用的是下半部分实现方式是软中断，会使用由 ksoftirqd 内核线程全权处理。

硬中断和软中断的区别是：**硬中断是通过给 CPU 物理引脚施加电压变化，而软中断是通过内存中的一个变量的二进制值以通知软中断处理程序。**

当网卡上收到数据之后，Linux 中第一个工作的模块是网络驱动。网络驱动会以 DMA 的方式把网卡上的收到的帧写到内存里。再去向CPU发起一个中断，以通知CPU有数据到达。第二：当CPU收到中断请求后，会去调用网络驱动注册的中断处理函数。网卡的中断处理函数并不做过多的工作，发出软中断请求，然后尽快释放CPU。ksoftirqd检测到有软中断请求到达，调用poll开始轮询收包，收到后交由各级协议栈处理。对于UDP包来说，会被放到用户socket的接收队列中。