[网络]网卡是如何接受数据包的
网卡是如何将数据帧发送到网络层
本文章内容参考:深入理解Linux网络。
看计算机底层的前提基础是我们有某个方面的知识不是很理解,从而根据这个点在去理解该技术的基础底层,这样的话学习起来不会很枯燥。
我们平常可能都接触过TCP
网络编程等,但是对于socket.read
函数读取client
传送过来的数据其底层是怎么传过来的,我们可能不是很理解,我们看下面的代码,如果不特意的去查看看这方面的资料,很可能我们都处于一种这种状态:我调用socket.read
可以接受对方的数据,但是怎么接受的,就不是很理解了,换句话说就是:我们知道TCP是面向链接的传输层通信协议,但是在一个链接中,server
端每次具体要读取多少个字节的数据,我们可能并不知道其底层原理,通常我们只需要知道调用socket.read
就可以获取到client
发送的一次数据包。所以该系列文章将会以该问题为维度去探究网络底层原理。
func main() {// servergo 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)
}
本文首先将会介绍数据包是怎么从网卡到达网络层的。
一、从宏观看网卡是如何接受数据的.
这里先总结一下大概流程:
当网线有数据送达到网卡时,网卡会直接将帧直接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内核
宏观初始化网卡的流程图。
当linux
内核识别到该网卡的相信信息之后,就会调用网卡的probe
函数,而该probe
函数首先会去实现ethtool
相关函数,并且去注册网卡打开函数open
等,最后将网卡的poll
函数注册到内核中。注:硬中断在启动网卡过程中注册,而软中断在初始化网络子系统的时候为每个CPU分配softnet_data
数据结构时进行注册到CPU软中断结构中。
我们平常可能会偶尔用到ethtool
去查看网络包的一些相关信息,但是确不知道其为什么要这样去实现,那么到了这里你就一定明白了,其实每个网卡都默认去实现了ethtool
相关函数,并且注册到了内核相关信息,当linux
识别用户调用了ethtool
函数的时候,内核将会去调用网卡驱动的相应方法。到了这里网卡驱动的相关打开函数以及ethtool
工具已经完成注册,那么剩下的就是启动网卡,并且打开硬中断等待数据包的到来。
网卡启动过程如下:
网卡初始化完之后(网卡驱动函数相关指针以及注册到内核中),就开始启动网卡了,内核会直接调用open函数去启动网卡,并且根据网卡的相关信息去初始化RX
和TX
队列内存,并且会为每个队列初始化并绑定硬中断函数(小知识点:如果CPU1
发生了硬中断,那么执行软中断也将会在CPU1
中进行执行),这个队列的数量是可以控制的,对于多队列网卡情况来说,队列的数量并不一定得是CPU的核数。举个情况:当RingBuffer
出现丢包时,不仅仅可以扩大单个RingBuffer
队列的内存,还可以多设置几个RIngBuffer
并绑定多个不同的CPU进而来更加高效的处理更多的网络包数据。当网卡所需要的队列内存分配好之后就可以接受网络传过来的数据(打开硬中断来接受数据)。
下面我们来看看RingBuffer
的数据结构
skb是数据包.
RingBuffer
其实并不是单纯是一个环形队列,每一个RingBuffer中包含两个指针数组,指针指向的就是本次网卡接受的数据包skb
.其中一个数组供内核使用,一个数组供网卡使用。
如果本文有哪些地方理解错了,欢迎指出,谢谢!
欢迎关注公众号(如果本文对您有帮助):考拉小同学。
[网络]网卡是如何接受数据包的相关推荐
- python 抓网卡数据包_Python选择网卡发包及接收数据包
当一台计算机上有多个网卡时,需要选择对应IP地址的网卡进行发送数据包或者接受数据包. 1.选择网卡发包(应用scapy):plface=conf.route.route("××.××.××. ...
- python通过指定网卡发包_Python选择网卡发包及接收数据包
当一台计算机上有多个网卡时,需要选择对应IP地址的网卡进行发送数据包或者接受数据包. 1.选择网卡发包(应用scapy): plface=conf.route.route("××.××.×× ...
- python scapy发包_Python选择网卡发包及接收数据包
当一台计算机上有多个网卡时,需要选择对应IP地址的网卡进行发送数据包或者接受数据包. 1.选择网卡发包(应用scapy):plface=conf.route.route("××.××.××. ...
- 网络编程,捕获IP数据包
任务三 网络编程 1. IP数据报的格式说明: IP数据包格式包含了标头固定部分,标头可变部分和数据区三部分.IP数据报标头部分固定为20个字节,其中包含了12个参数域,各参数域隐含着网间协议的传输机 ...
- JAVA网络编程:TCP/IP数据包结构
2019独角兽企业重金招聘Python工程师标准>>> 一般来说,网络编程我们仅仅须要调用一些封装好的函数或者组件就能完毕大部分的工作,可是一些特殊的情况下,就须要深入的理解网络数据 ...
- 解决windows10 wireshark无法抓取发出去的包只能抓取接受数据包
我的电脑是windows10系统,安装了wireshark2.2.6版本,,发现抓包的时候只能抓取到接受的包,无法抓取到电脑发出去的包 解决办法: 卸载wireshark默认安装的WinpCap工具包 ...
- linux 监听数据包,linux下网络监听与发送数据包的方法(即libpcap、libnet两种类库的使用方法)...
linux下可以用libpcap函数库实现监听数据包,使用libnet 函数库发送数据包 安装: 在命令行下apt-get install 就可以了 libpcap的使用: /*author hjj ...
- linux下网络监听与发送数据包的方法(即libpcap、libnet两种类库的使用方法)
linux下可以用libpcap函数库实现监听数据包,使用libnet 函数库发送数据包 安装: 在命令行下apt-get install 就可以了 libpcap的使用: /*author hjjd ...
- Linux 系统应用编程——网络编程(TCP/IP 数据包格式解析)
图中括号中的数字代表的是当前域所占的空间大小,单位是bit位. 黄色的是数据链路层的头部,一共14字节 绿色的部分是IP头部,一般是20字节 紫色部分是TCP头部,一般是20字节 最内部的是数据包内容 ...
最新文章
- 矩阵变换应用-求演化矩阵
- spring InitializingBean接口分析
- centos输入正确的账号和密码登陆不进去
- 如何Exchange移动数据库文件(一)
- ABAP help document F1
- listview频繁刷新报错
- gcc编译缺少数学库
- mac新建react脚手架
- Python入门--字典的创建
- java关于替换文本输出的讲解_java替换文件中某一行文本的内容
- python无法定位到table_selenium3 + python - table定位
- Windows10系统JDK下载和安装
- 虚拟空间 搬迁 云服务器,服务器空间搬迁到虚拟主机
- 水平面:篡命铜钱の1
- Java实现对png图片文件电子签名操作
- Android百度地图(地位和POI附近搜索)-仿微信、QQ地理位置的分享
- winform窗口的切换
- MySql使用MyCat分库分表(四)分片规则
- OneNote 找回误删除笔记
- NOKIA PC套恢复通讯录时
热门文章
- 高职高专代码 本科专业代码
- 仿百度壁纸客户端(三)——首页单向,双向事件冲突处理,壁纸列表的实现
- 吸顶音响怎么安装,看完这几个步骤就懂了~
- 【前端】Echarts的scatter3D各个属性具体含义
- [Uva12260]Free Goodies(dp+贪心)
- NOI OJ 1.5 16:买房子 C语言
- 西门子1200连接安川伺服的心得
- CentOS7内网使用rpm方式安装MySQL5.6数据库
- linux .. 权限详解,Linux用户及权限详解(示例代码)
- ABAQUS几何非线性问题:薄板大变形(如何定义材料方向)