[网络]网卡是如何接受数据包的

# 网卡是如何将数据帧发送到网络层

> 本文章内容参考:深入理解Linux网络。

看计算机底层的前提基础是我们有某个方面的知识不是很理解,从而根据这个点在去理解该技术的基础底层,这样的话学习起来不会很枯燥。

我们平常可能都接触过`TCP`网络编程等,但是对于`socket.read`函数读取`client`传送过来的数据其底层是怎么传过来的,我们可能不是很理解,**我们看下面的代码**,如果不特意的去查看看这方面的资料,很可能我们都处于一种这种状态:我调用`socket.read`可以接受对方的数据,但是怎么接受的,就不是很理解了,换句话说就是:我们知道TCP是面向链接的传输层通信协议,但是在一个链接中,`server`端每次具体要读取多少个字节的数据,我们可能并不知道其底层原理,通常我们只需要知道调用`socket.read`就可以获取到`client`发送的一次数据包。所以该系列文章将会以该问题为维度去探究网络底层原理。

```go

func main() {

// server

go func() {

ls, err := net.Listen("tcp", "127.0.0.1:8899")

if err != nil {

log.Fatal(err)

}

for {

accept, err := ls.Accept()

if err != nil {

continue

}

go func(conn net.Conn) {

req := make([]byte, 1024)

size, err := conn.Read(req)

if err != nil {

log.Fatal(err)

}

log.Printf("result: %v", string(req[:size]))

}(accept)

}

}()

time.Sleep(3 * time.Second)

// client.

go func() {

conn, err := net.Dial("tcp", "127.0.0.1:8899")

if err != nil {

log.Fatalln(err)

}

_, err = conn.Write([]byte("hello word"))

if err != nil {

log.Fatalln(err)

}

fmt.Println("client send suc")

}()

time.Sleep(10 * time.Second)

}

```

本文首先将会介绍数据包是怎么从网卡到达网络层的。

## 一、从宏观看网卡是如何接受数据的.

![image-20220828163643181](https://typoraimg-1303903194.cos.ap-guangzhou.myqcloud.com/image-20220828163643181.png)

这里先总结一下大概流程:

当网线有数据送达到网卡时,网卡会直接将帧直接`DMA`到内存中(`RingBuffer`)该结构体的具体结构后文会介绍,这个时候数据已经从网卡转移到内存中,此时网卡向`CPU`发送一个硬中断(**对于多队列网卡来说,每个队列可以实现将该队列的硬中断和单个CPU进行绑定从而实现该队列只能由该CPU进行处理**),硬中断只做简单的处理,如果做的处理多了,会导致`CPU`占用时间过程造成`IO`输入输出设备卡顿等,所以在硬中断的回调函数中,只是简简单单的记录了下该类型硬中断的发生频率,并立马调用该类型的软中断,从而尽快释放`CPU`的资源。在硬中断函数内会调用已经注册过的软中断回调函数,在该函数内部将会调用网卡注册过的`poll`函数来进行收包。软中断函数是由`ksoftirqd`内核线程来执行的,每个`Linux`实例中,内核线程`ksoftirqd`的数量和该`CPU`的核数一致,网卡注册的`poll`函数会从`RingBuffer`中将数据帧以`skb_buffer`的形式取下来,然后调用网络层注册的`IP`协议回调函数(回调函数存在每个`CPU`数据结构下`ptypes_base`)将当前帧转送到网络层,到了这里数据已经到达了我们耳熟能详的网络层了,只不过在网络层之前会经过`iptables`过滤,这个后面会稍微介绍以下。

> 我觉得有必要介绍上面出现过的名词的含义.

- DMA:网卡直接将数据放到内存上,而不经过CPU,从而减少使用CPU的资源。

- 硬中断:网卡向CPU的某个引脚发送一个电压变化用来告知该CPU发生了硬中断。硬中断和某个具体的`RingBuffer`相关联。

- 软中断:CPU通过或运算来修改某个内存变量用来通知内核线程`ksoftirqd`发生了软中断,软中断的类型有很多,并不局限与网络软中断。

- `Poll`函数:在子系统初始化的时候,初始化CPU的时候会为每个CPU初始化一个`softnet_data`数据结构,该数据结构中包含了一个`poll_list`双向链表用来保存所有驱动注册的回调函数,而`List`的每个节点中都包含该节点的输入设备的帧等着被软中断回调函数进行处理。而软中断就是调用`poll`函数中的节点来从硬件读取数据。

- `RingBuffer`:网卡收发数据的中转站。

## 二、、网卡初始化和启动.

### 2.1 初始化网络子系统.

`Linux`初始化网络子系统的时候会先为每个CPU初始化一个`softnet_data`数据结构,并且将每个`CPU`的网络读写软中断赋值到该CPU静态变量中。 而硬中断注册是在分配`RX`和`TX`发送接受队列是进行和`CPU`绑定操作的。当内核线程`ksoftirqd`监听到有软中断发生时,将会去执行相应软中断注册的回调函数。

当为每个CPU都注册软中断之后,将会去初始化`协议栈`,自下而上进行初始化,`Linux内核`会将网络层的回调函数注册到静态变量`ptyps_base`中,该静态变量为`hash`类型,当网络软中断从`poll`函数中读取出来数据时,会从`ptyps_base`变量中获取网络层的回调函数,并将将数据包转到网络层。

而`Linux内核`注册传输层协议是分别将`udp`和`tcp`协议的回调函数指针地址存入到`inet_protos`数组中。之后由网络层将数据包转到传输层时只需要根据`network`获取对应的`action`并调用执行即可将数据包转到对应的传输层。

当上面协议栈初始化完毕之后,将会去执行网卡驱动初始化了。对于`linux`操作系统来说,会让每一个驱动程序去向内核注册一个初始化函数,用来对该驱动进行初始化,这个初始化其实就是将该驱动的详细信息报告给`linux内核`。下面给出`linux内核`宏观初始化网卡的流程图。

![image-20220828202937073](https://typoraimg-1303903194.cos.ap-guangzhou.myqcloud.com/image-20220828202937073.png)

当`linux`内核识别到该网卡的相信信息之后,就会调用网卡的`probe`函数,而该`probe`函数首先会去实现`ethtool`相关函数,并且去注册网卡打开函数`open`等,最后将网卡的`poll`函数注册到内核中。注:硬中断在启动网卡过程中注册,而软中断在初始化网络子系统的时候为每个CPU分配`softnet_data`数据结构时进行注册到CPU软中断结构中。

我们平常可能会偶尔用到`ethtool`去查看网络包的一些相关信息,但是确不知道其为什么要这样去实现,那么到了这里你就一定明白了,其实每个网卡都默认去实现了`ethtool`相关函数,并且注册到了内核相关信息,当`linux`识别用户调用了`ethtool`函数的时候,内核将会去调用网卡驱动的相应方法。到了这里网卡驱动的相关打开函数以及`ethtool`工具已经完成注册,那么剩下的就是启动网卡,并且打开硬中断等待数据包的到来。

网卡启动过程如下:

![image-20220828204545450](https://typoraimg-1303903194.cos.ap-guangzhou.myqcloud.com/image-20220828204545450.png)

网卡初始化完之后(网卡驱动函数相关指针以及注册到内核中),就开始启动网卡了,内核会直接调用open函数去启动网卡,并且根据网卡的相关信息去初始化`RX`和`TX`队列内存,并且会为每个队列初始化并绑定硬中断函数(小知识点:如果`CPU1`发生了硬中断,那么执行软中断也将会在`CPU1`中进行执行),这个队列的数量是可以控制的,对于多队列网卡情况来说,队列的数量并不一定得是CPU的核数。举个情况:当`RingBuffer`出现丢包时,不仅仅可以扩大单个`RingBuffer`队列的内存,还可以多设置几个`RIngBuffer`并绑定多个不同的CPU进而来更加高效的处理更多的网络包数据。当网卡所需要的队列内存分配好之后就可以接受网络传过来的数据(打开硬中断来接受数据)。

**下面我们来看看`RingBuffer`的数据结构**

> skb是数据包.

![image-20220828211628601](https://typoraimg-1303903194.cos.ap-guangzhou.myqcloud.com/image-20220828211628601.png)

`RingBuffer`其实并不是单纯是一个环形队列,每一个RingBuffer中包含两个指针数组,指针指向的就是本次网卡接受的数据包`skb`.其中一个数组供内核使用,一个数组供网卡使用。

**如果本文有哪些地方理解错了,欢迎指出,谢谢!**

**欢迎关注公众号(如果本文对您有帮助):考拉小同学。**

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容