Linux 网络设备驱动开发(一) —— linux内核网络分层结构
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
Linux内核对网络驱动程序使用统一的接口并且对于网络设备采用面向对象的思想设计。
Linux内核采用分层结构处理网络数据包。分层结构与网络协议的结构匹配既能简化数据包处理流程又便于扩展和维护。
一、内核网络结构
在Linux内核中对网络部分按照网络协议层、网络设备层、设备驱动功能层和网络媒介层的分层体系设计。
网络驱动功能层主要通过网络驱动程序实现。
在Linux内核所有的网络设备都被抽象为一个接口处理该接口提供了所有的网络操作。
net_device结构表示网络设备在内核中的情况也就是网络设备接口。网络设备接口既包括软件虚拟的网络设备接口如环路设备也包括了网络硬件设备如以太网卡。
Linux内核有一个dev_base的全局指针指向一个设备链表包括了系统内的所有网络设备。该设备链表每个节点是一个网络设备。
在net_device结构中提供了许多供系统访问和协议层调用的设备方法包括初始化、打开关闭设备、数据包发送和接收等。
二、与网络有关的数据结构
内核对网络数据包的处理都是基于sk_buff结构的该结构是内核网络部分最重要的数据结构。
网络协议栈中各层协议都可以通过对该结构的操作实现本层协议数据的添加或者删除。使用sk_buff结构避免了网络协议栈各层来回复制数据导致的效率低下。
sk_buff结构可以分为两个部分一部分是存储数据包缓存在图中表示为PackertData另一部分是由一组用于内核管理的指针组成。
sk_buff管理的指针最主要的是下面4个
head指向数据缓冲(PackertData)的内核首地址
data指向当前数据包的首地址
tail指向当前数据包的尾地址
end 指向数据缓冲的内核尾地址。
数据包的大小在内核网络协议栈的处理过程中会发生改变因此data和tail指针也会不断变化而head和tail指针是不会发生改变的。
对于一个TCP数据包为例sk_buff还提供了几个指针直接指向各层协议头。mac指针指向数据的mac头nh指针指向网络协议头一般是IP协议头h指向传输层协议头在本例中是TCP协议头。
对各层设置指针的是方便了协议栈对数据包的处理。
三、net_device结构
Linux内核中网络设备最重要的数据结构就是net_device结构了它是网络驱动程序最重要的部分。
net_device结构保存在include/linux/netdevices.h头文件理解该结构对理解网络设备驱动有很大帮助。
内核中所有网络设备的信息和操作都在net_device设备中无论是注册网络设备还是设置网络设备参数都用到该结构。
下面是主要数据成员。
设备名称
总线参数
协议参数
链接层变量
接口标志
四、数据包接收流程
在Linux内核中一个网络数据包从网卡接收到用户空间需要经过链路层、传输层和socket的处理最终到达用户空间。
以DM9000网卡为例当网卡收到数据包以后调用中断处理函数 dm9000_interrupt()该函数检查中断处理类型如果是接收数据包中断则调用 dm9000_rx()函数接收数据包到内核空间。
dm9000_rx()函数收到数据包完成后内核会继续调用 netif_rx()函数函数的作用是把网卡接收到数据提交给协议栈处理。
协议栈使用 net_rx_action()函数处理接收数据包队列该函数处理数据包后如果是 IP数据包则提交给ip_recv()函数处理。ip_recv()函数主要是检查一个数据包IP头的合法性检查通过后交给 ip_local_deliver()和 ip_local_deliver_finish()函数处理之所以分开处理是因为内核中有防火墙相关的代码需要动态加载到此处。
IP头处理完毕后以UDP数据包为例将交由 udp_recv()函数处理与 ip_recv()函数类亿该函数检查 UDP头的合法性然后交给 udp_queue_recv()函数处理最后提交给 sock_queue_recv()函数处理。
数据包进入 socket部分的第一个函数是 skb_recv_datagram()该函数从内核的 socket队列取出数据包交给 socket部分的 udp_recvmsg()函数该函数负责处理UDP的数据sock_recvmsg()处理提交给 sock_read()函数。
sock_read()函数读取接收到的数据缓冲把数据返回给 sys_read()系统调用。sys_read()函数调用最终把数据复制到用户空间供用户使得。
五、数据包发送流程
以UDP数据包发送流程为例在DM9000网卡上如何发送一个数据包。
当用户空间的应用程序通过 socket函数 sento()发送一个UDP数据后会调用内核空间的 sock_writev()函数然后通过 sock_sendmsg()函数处理。
sock_sendmsg()函数调用 inet_sendmsg()函数处理inet_sendmsg()函数会把要发送的数据交给传输层的 udp_sendmsg()函数处理。
udp_sendmsg()函数在数据前加入UDP头然后把数据交给 ip_build_xmit()函数处理该函数根据 socket提供的目的 IP和端口信息构造IP头然后调用 output_maybe_reroute()函数处理。out_maybe_reroute()函数检查数据包是否需要经过路由最后交给 ip_output()函数写入到发送队列写入完成后由 ip_finish_output()函数处理后续工作。
链路层的 dev_queue_xmit()函数处理发送队列调用 DM9000网卡的发送数据包函数 dm9000_xmit()发送数据包发送完毕后调用 dm9000_xmit_done函数处理发送结果。