Merge branch 'master' of github.com:gatieme/LDD-LinuxDeviceDrivers

This commit is contained in:
gatieme
2016-09-27 21:59:54 +08:00
19 changed files with 987 additions and 479 deletions
@@ -0,0 +1,268 @@
进程虚拟地址空间
=======
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
#1 Linux 网络栈剖析
-------
**从socket 到设备驱动程序**
`Linux®`操作系统的最大特性之一就是它的网络栈. 它最初源于 BSD 的网络栈,具有一套非常干净的接口,组织得非常好。其接口范围从协议无关层(例如通用 socket 层接口或设备层)到各种网络协议的具体层。本文将从分层角度对 Linux 网络栈的接口进行探索,并介绍其中的一些主要结构.
#2 协议简介
-------
虽然对于网络的正式介绍一般都参考了 OSI(Open Systems Interconnection)模型, 但是本文对 Linux 中基本网络栈的介绍分为四层的 Internet 模型(如图 1 所示).
![图 1. 网络栈的 Internet 模型](../images/network_stack_layout.gif)
这个栈的最底部是**链路层**, 链路层是指提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备
链路层上面是**网络层**, 它负责将报文定向到目标位置. 再上一层称为传输层,负责端到端的通信(例如,在一台主机内部). 尽管网络层负责管理主机之间的通信,但是传输层需要负责管理主机内部各端之间的通信。最后一层是应用层,它通常是一个语义层,能够理解要传输的数据. 例如,超文本传输协议(HTTP)就负责传输服务器和客户机之间对 Web 内容的请求与响应。
实际来说,网络栈的各个层次有一些更为人所熟知的名字.
* 在**链路层**上,可以找到以太网, 这是最常用的一种高速介质. 更早的链路层协议包括一些串口协议,例如 SLIPSerial Line Internet Protocol)、CSLIPCompressed SLIP)和PPPPoint-to-Point Protocol
* 最常见的**网络层**协议是 IPInternet Protocol),但是网络层中还存在一些满足其他需求的协议,例如 ICMPInternet Control Message Protocol)和ARP Address Resolution Protocol
* 在**传输层**上是 TCPTransmission Control Protocol)和 UDPUser Datagram Protocol
* 最后, **应用层**中包含很多大家都非常熟悉的协议,包括标准的 Web 协议 HTTP 和电子邮件协议 SMTP(Simple Mail Transfer Protocol).
<br>
| TCP/IP层 | 协议 |
|:----------:|:-----:|
| 数据链路层 | ARP,RARP |
| 网络层 | IP,ICMP,IGMP |
| 传输层 | TCP ,UDP,UGP |
| 应用层 | Telnet,FTP,SMTP,SNMP |
#3 核心网络架构
-------
现在继续了解Linux网络栈的架构以及如何实现这种Internet模型.
下图提供了 Linux 网络栈的高级视图
![图 2. Linux 高级网络栈架构](../images/advance_network_stack_layout.gif)
最上面是用户空间层,或称为<font color = 0x00ffff>**应用层(Application layout)**</font>,其中定义了网络栈的用户
底部是<font color = 0x00ffff>**物理设备(Physical device hardware)**</font>, 提供了对网络的连接能力(串口或诸如以太网之类的高速网络).
中间是内核空间, 即<font color = 0x00ffff>**网络子系统**</font>, 也是本文介绍的重点. 流经网络栈内部的是socket缓冲区(sk_buffs), 它负责在源和汇点之间传递报文数据.
| 层次 | 名称 |描述 |
|:-----:|:-----:|:----:|
| Application layout | 应用层 | 网络栈的用户, 即使用网络的各个应用程序 |
| Kernel Space | 内核空间的网络子系统 | 它简单地为用户空间的应用程序提供了一种访问内核网络子系统的方法<br>流经网络栈内部的是socket缓冲区(sk_buffs), 它负责在源和汇点之间传递报文数据 |
| Physical device hardware | 物理设备 | 提供了对网络的连接能力(串口或诸如以太网之类的高速网络). |
<br>
>* 内核在应用程序与网络物理设备之间插入了一个内核空间
>
>* 内核空间为驱动着网络设备的正常运转, 同时又为应用程序提供可供使用的接口
<br>
首先,让我们来快速浏览一下<font color = 0xaaff00>**Linux网络子系统的核心元素**</font>, 后续章节中会更详细进行介绍.
顶部是<font color = 0xaaff00>**系统调用接口(System call interface)**</font>, 它简单地为用户空间的应用程序提供了一种访问内核网络子系统的方法
位于其下面的是一个<font color = 0xaaff00>**协议无关层(Protocol agnostic interface)**</font>, 它提供了一种通用方法来使用底层传输层协议
然后是<font color = 0xaaff00>**实际协议(Network protocols)**</font> , 在 Linux中包括内嵌的协议TCP、UDP, 当然还有IP.
然后是另外一个<font color = 0xaaff00>**设备无关层(Device agnostic interface)**</font>, 提供了与各个设备驱动程序通信的通用接口
最下面是<font color = 0xaaff00>**设备驱动程序(Device driders)**</font>本身.
如下所示
| 内核空间层次 | 名称 |描述 |
|:--------------:|:-----:|:----:|
| System call interface | 系统调用接口 | 它简单地为用户空间的应用程序提供了一种访问内核网络子系统的方法 |
| Protocol agnostic interface | 协议无关层 | 它提供了一种通用方法来使用底层传输层协议 |
| Network protocols | 实际协议 | 在 Linux中包括内嵌的协议TCP、UDP, 当然还有IP |
| Device agnostic interface | 设备无关层 | 提供了与各个设备驱动程序通信的通用接口 |
| Device driders | 设备驱动程序 | 提供了网络硬件设备的基础驱动 |
<br>
>内核并没有简单的为内核网络子系统实现常规的系统调用接口, 协议层和设备驱动层次3个简单的层次, Linux内核必须支持各种不同的协议和不同的设备, 没有什么问题是通过一个虚拟层无法实现的, 因此内核在不同的层次之间插入了一个协议或者设备无关的层次
>
>* 系统调用接口和实际协议层之间插入了一个协议无关层
>
>* 实际协议层与设备驱动程序之间插入了一个设备无关层
<br>
##3.1 系统调用接口
-------
系统调用接口可以从两个角度进行描述. 用户发起网络调用时, 通过系统调用接口进入内核的过程应该是多路的. 最后调用[`net/socket.c`](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2315)中的[`sys_socketcall` ](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2340)结束该过程, 然后进一步将调用分路发送到指定目标.
系统调用接口的另一种描述是使用普通文件操作作为网络I/O. 例如, 典型的读写操作可以在网络`socket`上执行(`socket`使用一个文件描述符表示, 与一个普通文件一样). 因此, 尽管有很多操作是网络专用的(使用`socket`调用创建一个 `socket`, 使用`connect`调用连接一个收信方, 等等), 但是也有一些标准的文件操作可以应用于网络对象, 就像操作普通文件一样.
最后, 系统调用接口提供了在用户空间应用程序和内核之间转移控制的方法.
| 系统调用 | 宏 |描述 |
|:---------:|:------:|
| socketcall | []() | socket系统调用 |
| socket | [SYS_SOCKET](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2341) | 建立socket |
| bind | [SYS_BIND](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2345) | 绑定socket到端口 |
| connect | [SYS_CONNECT](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2347) |连接远程主机 |
| listen | [SYS_LISTEN](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2350) | 监听socket端口 |
| accept | [SYS_ACCEPT](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2353) | 响应socket连接请求 |
| getsockname | [SYS_GETSOCKNAME](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2357) | 取得本地socket名字 |
| getpeername | [SYS_GETPEERNAME](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2362) | 获取通信对方的socket名字 |
| socketpair | [SYS_SOCKETPAIR](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2367) | 创建一对已联接的无名socket |
| send | [SYS_SEND](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2370) | 通过socket发送信息 |
| sendto | [SYS_SENDTO](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2373) | 发送UDP信息 |
| recv | [SYS_RECV](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2377) | 通过socket接收信息 |
| recvfrom | [SYS_RECVFROM](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2380) | 接收UDP信息 |
| recvmsg | 参见recv |
| select | 对多路同步I/O进行轮询 |
| shutdown | [SYS_SHUTDOWN](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2385) | 关闭socket上的连接 |
| getsockopt | [SYS_GETSOCKOPT](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2391) | 取端口设置 |
| setsockopt | [SYS_SETSOCKOPT](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2388) | 设置端口参数 |
| sendmsg | [SYS_SENDMSG](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2396) | 参见send |
| sendmmesg | [SYS_SENDMMSG](http://lxr.free-electrons.com/source/net/socket.c?v=4.7#L2399) | |
| | SYS_RECVMSG | |
| | SYS_RECVMMSG | |
| | SYS_ACCEPT4 | |
| sendfile | 在文件或端口间传输数据 |
##3.2 协议无关接口
-------
`socket`层是一个协议无关接口, 它提供了一组通用函数来支持各种不同协议.
`socket`层不但可以支持典型的TCP和UDP协议, 而且还可以支持IP、裸以太网和其他传输协议, 例如 SCTP(Stream Control Transmission Protocol).
通过网络栈进行的通信都需要对`socket`进行操作.
`Linux`中的`socket`结构是`struct sock``, 这个结构是在`linux/include/net/sock.h`中定义的. 这个巨大的结构中包含了特定`socket`所需要的所有状态信息, 其中包括`socket`所使用的特定协议和在`socket`上可以执行的一些操作.
网络子系统可以通过一个定义了自己功能的特殊结构来了解可用协议. 每个协议都维护了一个名为`proto`的结构(可以在`linux/include/net/sock.h`中找到). 这个结构定义了可以在从`socket`层到传输层中执行特定的`socket`操作(例如, 如何创建一个`socket`, 如何使用`socket`建立一个连接, 如何关闭一个`socket` 等等).
##3.3 网络协议
-------
网络协议这一节对一些可用的特定网络协议作出了定义(例如TCP、UDP等). 它们都是在[`linux/net/ipv4/af_inet.c`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c)文件中一个名为[`inet_init`的函数](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L1754)中进行初始化的(因为TCP和UDP都是inet簇协议的一部分).
[`inet_init`函数](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L1754)使用[`proto_register`](http://lxr.free-electrons.com/source/net/core/sock.c?v=4.7#L2902)函数来注册每个内嵌协议. 这个函数是在[`linux/net/core/sock.c`](http://lxr.free-electrons.com/source/net/core/sock.c?v=4.7)中定义的, 除了可以将这个协议添加到活动协议列表中之外, 如果需要, 该函数还可以选择分配一到多个`slab` 缓存.
通过[`net/ipv4`](http://lxr.free-electrons.com/source/net/ipv4/?v=4.7)目录中[`udp.c`](http://lxr.free-electrons.com/source/net/ipv4/udp.c?v=4.7)和[`raw.c`](http://lxr.free-electrons.com/source/net/ipv4/raw.c?v=4.7)文件中的`proto`接口, 您可以了解各个协议是如何标识自己的. 这些协议接口每个都按照类型和协议映射到[`inetsw_array`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L999), 该数组将内嵌协议与操作映射到一起. [`inetsw_array`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L999)结构及其关系如图3 所示
![图 3. Internet 协议数组结构](../images/data_struct.gif)
最初, 会调用[`inet_init`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L1754)中的[`inet_register_protosw`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L1037)将这个数组中的每个协议都初始化为[`inetsw`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L128), 函数[`inet_init`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L1754)也会对各个`inet`模块进行初始化, 例如ARP、ICMP和IP模块, 以及TCP和UDP模块.
>Socket 协议的相互关系
>
>回想以下在创建 socket 时,需要指定类型和协议,例如my_sock = socket( AF_INET, SOCK_STREAM, 0 )。AF_INET 表示一个 Internet 地址簇,它使用的是一个流 socket,定义为 SOCK_STREAM(如此处的 inetsw_array 所示).
注意在图中, [`proto`](http://lxr.free-electrons.com/source/include/net/sock.h?v=4.7#L970)结构定义了传输特有的方法, 而[`proto_ops`](http://lxr.free-electrons.com/source/include/linux/net.h?v=4.7#L132)结构则定义了通用的`socket`方法. 可以通过调用[`inet_register_protosw`](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.7#L1037)将其他协议加入到 inetsw协议中. 例如, SCTP就是通过调用[`net/sctp/protocol.c`](http://lxr.free-electrons.com/source/net/sctp/protocol.c?v=4.7) 中的 [`sctp_init`](http://lxr.free-electrons.com/source/net/sctp/protocol.c#L1353?v=4.7)加入其中的
有关`SCTP`的更多信息,请参阅[SCTP优化网络](http://www.ibm.com/developerworks/cn/linux/l-sctp/)
`socket`中的数据移动是使用一个所谓的`socket`缓冲区([`sk_buff`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L626))的核心结构实现的.
`sk_buff`中包含了报文数据, 以及涉及协议栈中多个层次的状态数据. 所发送或接收的每个报文都是使用一个`sk_buff `示的. `sk_buff`结构是在 [`linux/include/linux/skbuff.h`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7)中定义的, 如下图所示.
![图 4. Socket 缓冲区及其与其他结构的关系](../images/sk_buff_connection.gif)
多个`sk_buff`可以针对某个给定连接链接在一起. 每个`sk_buff`都在设备结构(`net_device`)中标识报文发送的目的地, 或者接收报文的来源地. 由于每个报文都是使用一个`sk_buff`表示的, 因此报文头都可以通过一组指针(th、iph 和mac[用于Media Access Control或者MAC头])方便地进行定位.
![多个`sk_buff`可以针对某个给定连接链接在一起](../images/dou_link.png)
由于`sk_buff`是`socket`数据管理的中心, 因此创建了很多支持函数来对它们进行管理. 其中有些函数用于创建和销毁`sk_buff`结构, 或对它进行克隆或排队管理.
针对给定的`socket`, `Socket`缓冲区可以链接在一起, 这样可以包含众多信息, 包括到协议头的链接、时间戳(报文是何时发送或接收的), 以及与这个报文相关的设备.
##3.4 设备无关接口
-------
协议层下面是另外一个无关接口层, 它将协议与具有很多各种不同功能的硬件设备连接在一起。这一层提供了一组通用函数供底层网络设备驱动程序使用,让它们可以对高层协议栈进行操作.
* 首先, 设备驱动程序可能会通过调用[`register_netdevice`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7#L6983)或[`unregister_netdevice`](http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=4.7#L2361)在内核中进行注册或注销.
1. 调用者首先填写[`net_device`](http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=4.7#L1607)结构
2. 然后传递这个结构进行注册. 内核调用它的init函数(如果定义了这种函数), 然后执行一组健全性检查, 并创建一个`sysfs`条目, 然后将新设备添加到设备列表中(内核中的活动设备链表). 在[`linux/include/linux/netdevice.h`](http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=4.7)中可以找到这个[`net_device`](http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=4.7#L1607)结构
3. 这些函数都是在[`net/core/dev.c`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7)中实现的
* 要从协议层向设备中发送`sk_buff`, 就需要使用[`dev_queue_xmit`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7#L3432)函数. 这个函数可以对`sk_buff`进行排队, 从而由底层设备驱动程序进行最终传输(使用`sk_buff`中引用的[`net_device`](http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=4.7#L1607)或 [`sk_buff->dev`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L641)所定义的网络设备). dev结构[`net_device`](http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=4.7#L1607)中包含了一个名为hard_start_xmit的方法(位于`net_device->net_device_ops->ndo_start_xmit`), 其中保存有发起`sk_buff`传输所使用的驱动程序函数.
* 报文的接收通常是使用[`netif_rx`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7#L3830)执行的.
1. 当底层设备驱动程序接收一个报文(包含在所分配的`sk_buff`中)时, 就会通过调用[`netif_rx`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7#L3830)将[`sk_buff`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L626)上传至网络层.
2. 然后, 这个函数通过`__napi_schedule`(早期内核通过`netif_rx_schedule`)将 sk_buff 在上层协议队列中进行排队, 供以后进行处理。可以在 linux/net/core/dev.c 中找到[`dev_queue_xmit`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7#L3432)和[`netif_rx`](http://lxr.free-electrons.com/source/net/core/dev.c?v=4.7#L3830)函数.
后来, 内核中引入了一种新的应用程序编程接口(NAPI), 该接口允许驱动程序与设备无关层(dev)进行交互. 有些驱动程序使用的是NAPI, 但是大多数驱动程序仍然在使用老式的帧接收接口(比例大约是6比1). NAPI在高负载的情况下可以产生更好的性能, 它避免了为每个传入的帧都产生中断.
加入了NAPI后, `netif_rx_schedule`这个函数从内核中删除, 取而代之的是`__napi_schedule`, 可以到[`LXR?v=2.6.30;i=netif_rx_schedule`](http://lxr.free-electrons.com/ident?v=2.6.30;i=netif_rx_schedule)查证.
##3.5 设备驱动程序
-------
网络栈底部是负责管理物理网络设备的设备驱动程序. 例如, 包串口使用的 SLIP 驱动程序以及以太网设备使用的以太网驱动程序都是这一层的设备.
在进行初始化时, 设备驱动程序会分配一个`net_device`结构, 然后使用必须的程序对其进行初始化.
这些程序中有一个是`hard_start_xmit`的方法(位于`net_device->net_device_ops->ndo_start_xmit`), 它定义了上层应该如何对 `sk_buff`排队进行传输. 这个程序的参数为`sk_buff`. 这个函数的操作取决于底层硬件, 但是通常`sk_buff`所描述的报文都会被移动到硬件环或队列中. 就像是设备无关层中所描述的一样, 对于NAPI兼容的网络驱动程序来说, 帧的接收使用了`netif_rx`和`netif_receive_skb`接口.
NAPI 驱动程序会对底层硬件的能力进行一些限制. 有关更详细的信息,请参阅[NAPI接口和设计](http://linux-net.osdl.org/index.php/NAPI)
设备驱动程序在`dev`结构中配置好自己的接口之后, 调用`register_netdevice`便可以使用该配置. 在[`drivers/net`](http://lxr.free-electrons.com/source/drivers/net?v=4.7)中可以找出网络设备专用的驱动程序.
#4 展望
-------
Linux 源代码是学习有关大多数设备类型的设备驱动程序设计最佳方法, 包括网络设备驱动程序. 在这里可以找到的是各种设计的变化以及对可用内核API的使用, 但是所学到的每一点都会非常有用, 都可以作为新设备驱动程序的起点. 除非您需要一种新协议, 否则网络栈中的其余代码都是通用的, 都会非常有用. 即使现在, TCP(用于流协议)或 UDP(用于基于消息的协议)的实现都可以作为开始新开发有用模块使用.
+125 -4
View File
@@ -262,17 +262,91 @@ struct sk_buff
![套接字缓冲区在各个协议层之间传递时,对缓冲区的操作](../images/trans_data.png)
在字长32位的系统上,数据类型sk_buff_data_t用来表示各种类型为简单指针的数据, 定义在[`include/linux/skbuff.h?v=4.7, line 626`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L626), 在64位 CPU上, 可使用一点小技巧来节省一些空间。sk_buff_data_t的定义改为整型变量
内核实现了一些宏用来获取缓冲区数据段的结束位置. 这些函数定义在[`include/linux/skbuff.h?v=4.7, line 1165`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L1165)
```cpp
#ifdef NET_SKBUFF_DATA_USES_OFFSET
typedef unsigned int sk_buff_data_t;
static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
{
return skb->head + skb->end;
}
static inline unsigned int skb_end_offset(const struct sk_buff *skb)
{
return skb->end;
}
#else
typedef unsigned char *sk_buff_data_t;
static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
{
return skb->end;
}
static inline unsigned int skb_end_offset(const struct sk_buff *skb)
{
return skb->end - skb->head;
}
#endif
```
套接字缓冲区需要很多指针来表示缓冲区中内容的不同部分. 由于网络子系统必须保证较低的内存占用和较高的处理速度, 因而对struct sk_buff来说,我们需要保持该结构的长度尽可能小.
内核实现了一些宏用来获取协议数据区域的结束的结束位置tail的函数. 这些函数定义在[`include/linux/skbuff.h?v=4.7, line 1849`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L1849)
```cpp
#ifdef NET_SKBUFF_DATA_USES_OFFSET /* 64 bit */
static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)
{
return skb->head + skb->tail;
}
static inline void skb_reset_tail_pointer(struct sk_buff *skb)
{
skb->tail = skb->data - skb->head;
}
static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
skb_reset_tail_pointer(skb);
skb->tail += offset;
}
#else /* NET_SKBUFF_DATA_USES_OFFSET */
static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)
{
return skb->tail;
}
static inline void skb_reset_tail_pointer(struct sk_buff *skb)
{
skb->tail = skb->data;
}
static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
skb->tail = skb->data + offset;
}
#endif /* NET_SKBUFF_DATA_USES_OFFSET */
```
在字长32位的系统上,数据类型sk_buff_data_t用来表示各种类型为简单指针的数据(`char *`), 定义在[`include/linux/skbuff.h?v=4.7, line 487`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L487), 在64位 CPU上, 可使用一点小技巧来节省一些空间. `sk_buff_data_t`的定义改为整型变量
```cpp
#if BITS_PER_LONG > 32 /* 64位的linux系统中long是8个字节 > 4 */
#define NET_SKBUFF_DATA_USES_OFFSET 1
#endif
#ifdef NET_SKBUFF_DATA_USES_OFFSET /* 64位系统 */
typedef unsigned int sk_buff_data_t; /* 4个字节 */
#else /* 32位系统 */
typedef unsigned char *sk_buff_data_t; /* 4个字节 */
#endif
```
套接字缓冲区需要很多指针来表示缓冲区中内容的不同部分. 由于网络子系统必须保证较低的内存占用和较高的处理速度, 因而对`struct sk_buff`来说,我们需要保持该结构的长度尽可能小.
@@ -447,3 +521,50 @@ struct sk_buff_head {
分组通常放置在等待队列中,例如分组等待处理时,或需要重新组合已经分析过的分组时.
![通过双链表管理套接字缓冲区](../images/dou_link.png)
##4.3.3 skb_shared_info结构体
-------
内核`skb_shared_info`结构体该类型用来管理数据包分片信息, 该结构定义在[`include/linux/skbuff.h?v=4.7, line 408`](http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=4.7#L408)
```cpp
/* This data is invariant across clones and lives at
* the end of the header data, ie. at skb->end.
*/
struct skb_shared_info {
unsigned char nr_frags;
__u8 tx_flags;
unsigned short gso_size; /* 尺寸 */
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs; /* 顺序 */
unsigned short gso_type;
struct sk_buff *frag_list;
struct skb_shared_hwtstamps hwtstamps; /* 硬件时间戳 */
u32 tskey;
__be32 ip6_frag_id;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref; /* 使用计数 */
/* Intermediate layers must ensure that destructor_arg
* remains valid until skb destructor */
void * destructor_arg;
/* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];
};
```
通过宏可以表示与skb的关系
```cpp
/* Internal */
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
```
@@ -0,0 +1,30 @@
进程虚拟地址空间
=======
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
#3 发送分组
-------
在网络层中特定于协议的函数通知网络访问层处理由套接字缓冲区定义的一个分组时, 将发送完成的分组.
当信息从计算机发送出去时, 必须注意哪些事项? 除了特定协议需要完成的首部和校验和, 以及由高层协议实例生成的数据之外, 分组的路由是最重要的.(即使计算机只有一个网卡, 内核仍然需要区分发送到外部目标的分组和针对环回接口的分组.)
因为该问题只能由更高层的协议实例决定(特别是, 如果可以选择到预期目标的路由时), 所以设备驱动程序假定高层协议已经做出了决策.
在分组可以发送到下一个正确的计算机之前(通常不同于目标计算机,因为除非存在直接的硬件链路, 否则IP分组通常通过网关发送), 必须确定接收方网卡的硬件地址.
这是一个复杂的过程, 将在12.8.5节详细讲述. 此时, 我们假定已经知道接收方的MAC地址. 网络访问层的所需的另一个首部, 通常由特定于协议的函数产生.
`net/core/dev.c`中的`dev_queue_xmit`用于将分组放置到发出分组的队列上.
这里将忽略这个特定于设备的队列的实现, 因为它并没有揭示什么网络层的运作机制. 只要知道, 在分组放置到等待队列上一定的时间之后, 分组将发出即可.
这是通过特定于适配器的函数`hard_start_xmit`完成的, 在每个net_device结构中都以函数指针的形式出现, 由硬件设备驱动程序实现.
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+138
View File
@@ -0,0 +1,138 @@
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
#1 网络层
-------
网络访问层仍然受到传输介质的性质以及相关适配器的设备驱动程序的很大影响. 网络层(具体地说是IP协议)与网络适配器的硬件性质几乎是完全分离的。为什么说是几乎?
读者稍后会看到, 该层不仅负责发送和接收数据, 还负责在彼此不直接连接的系统之间转发和路由分组. 查找最佳路由并选择适当的网络设备来发送分组,也涉及对底层地址族的处理(如特定于硬件的MAC地址), 这是该层至少要与网卡松散关联的原因. 在网络层地址和网络访问层之间的指派是由这一层完成的, 这也是
互联网络层无法与硬件完全脱离的原因.
如果不考虑底层硬件, 是无法将较大的分组分割为较小单位的(事实上, 硬件的性质是需要分割分组的首要原因). 因为每一种传输技术所支持的分组长度都有一个最大值, IP协议必须方法将较大的分组划分为较小的单位, 由接收方重新组合, 更高层协议不会注意到这一点. 划分后分组的长度取决于特定传输协议的能力.
IP在1981年正式定义(在RFC791中), 现在已经进入暮年
尽管事实上的情况与公司新闻稿的说法截然不同, 例如, 后者可能将电子表格的每个新版本都称赞为人类有史以来最伟大的发明, 但过去的20年确实在当今的技术上留下了印痕. 此前的缺陷和未能预料到的问题, 随着因特网的发展, 现在
变得越来越明显. 这也是开发IPv6标准作为目前IPv4后继者的原因. 遗憾的是, 因为缺乏核心的权威机构, 对这个未来标准的采用比较缓慢. 本章主要关注IPv4算法的实现, 但也会略看一看可用于未来的技术, 及其在Linux内核的实现.
为理解IP协议在内核中的实现, 必须简要介绍其工作方式. 很自然, 这是个非常大的领域, 我们只能略微谈谈相关的主题.
#2 IPv4
-------
IP分组使用的协议首部如下图所示.
![图12-14 IP首部的结构](./images/ip_header.png)
| 字段 | 描述 |
|:-----:|:-----:|
| version(版本) |指定了所用IP协议的版本。当前,该字段的有效值为4或6。 在支持两种协议版本的主机上,所使用的版本由前一章讨论的传输协议标识符确定。对协议的两个版本来说,该标识符中保存的值是不同的 |
| IHL(IP首部长度) | 定义了首部的长度,由于选项数量可变,这个值并不总是相同的 |
| Codepoint(代码点)<br>Type of Service(服务类型) | 用于更复杂的协议选项,我们在这里无须关注 |
| Length(长度) | 指定了分组的总长度,即首部加数据的长度 |
| fragment ID(分片标识) | 标识了一个分片的IP分组的各个部分。分片方法将同一分片ID指定到同一原始分组的各个数据片,使之可标识为同一分组的成员。各个部分的相对位置由fragment offset(分片偏移量)字段定义。偏移量的单位是64 bit |
第三个标志位"保留供未来使用", 但考虑到IPv6的存在, 这是不太可能的.
| 字段 | 描述 |
|:-----:|:-----:|
| TTL意为"Time to Live" | 指定了从发送者到接收者的传输路径上中间站点的最大数目(或跳数) |
| Protocol | 标识了IP分组承载的高层协议(传输层)。例如,TCP和UDP协议都有对应的唯一值 |
| Checksum | 包含了一个校验和,根据首部和数据的内容计算。如果指定的校验和与接收方计算的值不一致,那么可能发生了传输错误,应该丢弃该分组 |
| src和dest | 指定了源和目标的32位IP地址 |
| options | 用于扩展IP选项 |
| data | 保存了分组数据(净荷) |
IP首部中所有的数值都以网络字节序存储(大端序).
在内核源代码中, 该首部由iphdr数据结构实现, 定义在[include/uapi/linux/ip.h?v=4.7, line 85](http://lxr.free-electrons.com/source/include/uapi/linux/ip.h?v=4.7#L85)
```cpp
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
};
```
`ip_rcv`函数是网络层的入口点. 分组向上穿过内核的路线如下图所示
![图12-15 分组穿过互联网络层的路线](./images/route.png)
该函数定义在[`net/ipv4/ip_input.c?v=4.7, line 405`](http://lxr.free-electrons.com/source/net/ipv4/ip_input.c?v=4.7#L405)
| 函数 | 功能 | 定义 |
|:-----:|:-----:|:-----:|
| ip_rcv | 对驱动送上来的数据报文进行ip头的合法性检查, 并调用netfilter过滤器上NF_INET_PRE_ROUTING | [net/ipv4/ip_input.c?v=4.7, line 405](http://lxr.free-electrons.com/source/net/ipv4/ip_input.c?v=4.7#L405) |
| ip_local_deliver | | [net/ipv4/ip_input.c?v=4.7, line 192](http://lxr.free-electrons.com/source/net/ipv4/ip_input.c?v=4.7#L192) |
<br>
| 函数 | 功能 | 定义 |
|:-----:|:-----:|:-----:|
| ip_queue_xmit | | [net/ipv4/ip_output.c?v=4.7, line 376](http://lxr.free-electrons.com/source/net/ipv4/ip_output.c?v=4.7#L376) |
| ip_output | | [net/ipv4/ip_output.c?v=4.7, line 346](http://lxr.free-electrons.com/source/net/ipv4/ip_output.c?v=4.7#L346) |
```cpp
```
发送和接收操作的程序流程并不总是分离的, 如果分组只通过当前计算机转发, 那么发送和接收操作是交织的. 这种分组不会传递到更高的协议层(或应用程序), 而是立即离开计算机, 发往新的目的地.
#3 接收分组
-------
在分组(以及对应的套接字缓冲区, 其中的指针已经设置了适当的值)转发到ip_rcv之后, 必须检查接收到的信息, 确保它是正确的. 主要检查计算的校验和与首部中存储的校验和是否一致.
其他的检查包括分组是否达到了IP首部的最小长度,分组的协议是否确实是IPv4(IPv6的接收例程是另一个).
在进行了这些检查之后, 内核并不立即继续对分组的处理, 而是调用一个`netfilter`挂钩, 使得用户空间可以对分组数据进行操作.
`netfilter`挂钩插入到内核源代码中定义好的各个位置, 使得分组能够被
外部动态操作. 挂钩存在于网络子系统的各个位置, 每种挂钩都有一个特别的标记, 例如NF_IP_POST_ROUTING.
在内核到达一个挂钩位置时, 将在用户空间调用对该标记支持的例程. 接下来, 在另一个内核函数中继续内核端的处理(分组可能被修改过).
12.8.6节讨论了netfilter机制的实现。
在下一步中,接收到的分组到达一个十字路口,此时需要判断该分组的目的地是本地系统还是远
程计算机。根据对分组目的地的判断,需要将分组转发到更高层,或转到互联网络层的输出路径上(这
里不打算讨论第三种选项,即通过多播将分组发送到一组计算机)。
ip_route_input负责选择路由。这个相对复杂的决策过程在12.8.5节详细讨论。判断路由的结果
是,选择一个函数,进行进一步的分组处理。可用的函数是ip_local_deliver和ip_forward。具体
选择哪个函数,取决于分组是交付到本地计算机下一个更高协议层的例程,还是转发到网络中的另一
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

-260
View File
@@ -1,260 +0,0 @@
struct net_device {
char name[IFNAMSIZ];
struct hlist_node name_hlist;
char *ifalias;
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
unsigned long mem_end;
unsigned long mem_start;
unsigned long base_addr;
int irq;
atomic_t carrier_changes;
/*
* Some hardware also needs these fields (state,dev_list,
* napi_list,unreg_list,close_list) but they are not
* part of the usual set specified in Space.c.
*/
unsigned long state;
struct list_head dev_list;
struct list_head napi_list;
struct list_head unreg_list;
struct list_head close_list;
struct list_head ptype_all;
struct list_head ptype_specific;
struct {
struct list_head upper;
struct list_head lower;
} adj_list;
struct {
struct list_head upper;
struct list_head lower;
} all_adj_list;
netdev_features_t features;
netdev_features_t hw_features;
netdev_features_t wanted_features;
netdev_features_t vlan_features;
netdev_features_t hw_enc_features;
netdev_features_t mpls_features;
netdev_features_t gso_partial_features;
int ifindex;
int group;
struct net_device_stats stats;
atomic_long_t rx_dropped;
atomic_long_t tx_dropped;
atomic_long_t rx_nohandler;
#ifdef CONFIG_WIRELESS_EXT
const struct iw_handler_def *wireless_handlers;
struct iw_public_data *wireless_data;
#endif
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
#ifdef CONFIG_NET_SWITCHDEV
const struct switchdev_ops *switchdev_ops;
#endif
#ifdef CONFIG_NET_L3_MASTER_DEV
const struct l3mdev_ops *l3mdev_ops;
#endif
const struct header_ops *header_ops;
unsigned int flags;
unsigned int priv_flags;
unsigned short gflags;
unsigned short padded;
unsigned char operstate;
unsigned char link_mode;
unsigned char if_port;
unsigned char dma;
unsigned int mtu;
unsigned short type;
unsigned short hard_header_len;
unsigned short needed_headroom;
unsigned short needed_tailroom;
/* Interface address info. */
unsigned char perm_addr[MAX_ADDR_LEN];
unsigned char addr_assign_type;
unsigned char addr_len;
unsigned short neigh_priv_len;
unsigned short dev_id;
unsigned short dev_port;
spinlock_t addr_list_lock;
unsigned char name_assign_type;
bool uc_promisc;
struct netdev_hw_addr_list uc;
struct netdev_hw_addr_list mc;
struct netdev_hw_addr_list dev_addrs;
#ifdef CONFIG_SYSFS
struct kset *queues_kset;
#endif
unsigned int promiscuity;
unsigned int allmulti;
/* Protocol-specific pointers */
#if IS_ENABLED(CONFIG_VLAN_8021Q)
struct vlan_info __rcu *vlan_info;
#endif
#if IS_ENABLED(CONFIG_NET_DSA)
struct dsa_switch_tree *dsa_ptr;
#endif
#if IS_ENABLED(CONFIG_TIPC)
struct tipc_bearer __rcu *tipc_ptr;
#endif
void *atalk_ptr;
struct in_device __rcu *ip_ptr;
struct dn_dev __rcu *dn_ptr;
struct inet6_dev __rcu *ip6_ptr;
void *ax25_ptr;
struct wireless_dev *ieee80211_ptr;
struct wpan_dev *ieee802154_ptr;
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
struct mpls_dev __rcu *mpls_ptr;
#endif
/*
* Cache lines mostly used on receive path (including eth_type_trans())
*/
unsigned long last_rx;
/* Interface address info used in eth_type_trans() */
unsigned char *dev_addr;
#ifdef CONFIG_SYSFS
struct netdev_rx_queue *_rx;
unsigned int num_rx_queues;
unsigned int real_num_rx_queues;
#endif
unsigned long gro_flush_timeout;
rx_handler_func_t __rcu *rx_handler;
void __rcu *rx_handler_data;
#ifdef CONFIG_NET_CLS_ACT
struct tcf_proto __rcu *ingress_cl_list;
#endif
struct netdev_queue __rcu *ingress_queue;
#ifdef CONFIG_NETFILTER_INGRESS
struct list_head nf_hooks_ingress;
#endif
unsigned char broadcast[MAX_ADDR_LEN];
#ifdef CONFIG_RFS_ACCEL
struct cpu_rmap *rx_cpu_rmap;
#endif
struct hlist_node index_hlist;
/*
* Cache lines mostly used on transmit path
*/
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
unsigned int num_tx_queues;
unsigned int real_num_tx_queues;
struct Qdisc *qdisc;
unsigned long tx_queue_len;
spinlock_t tx_global_lock;
int watchdog_timeo;
#ifdef CONFIG_XPS
struct xps_dev_maps __rcu *xps_maps;
#endif
#ifdef CONFIG_NET_CLS_ACT
struct tcf_proto __rcu *egress_cl_list;
#endif
#ifdef CONFIG_NET_SWITCHDEV
u32 offload_fwd_mark;
#endif
/* These may be needed for future network-power-down code. */
struct timer_list watchdog_timer;
int __percpu *pcpu_refcnt;
struct list_head todo_list;
struct list_head link_watch_list;
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERED, /* completed register_netdevice */
NETREG_UNREGISTERING, /* called unregister_netdevice */
NETREG_UNREGISTERED, /* completed unregister todo */
NETREG_RELEASED, /* called free_netdev */
NETREG_DUMMY, /* dummy device for NAPI poll */
} reg_state:8;
bool dismantle;
enum {
RTNL_LINK_INITIALIZED,
RTNL_LINK_INITIALIZING,
} rtnl_link_state:16;
void (*destructor)(struct net_device *dev);
#ifdef CONFIG_NETPOLL
struct netpoll_info __rcu *npinfo;
#endif
possible_net_t nd_net;
/* mid-layer private */
union {
void *ml_priv;
struct pcpu_lstats __percpu *lstats;
struct pcpu_sw_netstats __percpu *tstats;
struct pcpu_dstats __percpu *dstats;
struct pcpu_vstats __percpu *vstats;
};
struct garp_port __rcu *garp_port;
struct mrp_port __rcu *mrp_port;
struct device dev;
const struct attribute_group *sysfs_groups[4];
const struct attribute_group *sysfs_rx_queue_group;
const struct rtnl_link_ops *rtnl_link_ops;
/* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE 65536
unsigned int gso_max_size;
#define GSO_MAX_SEGS 65535
u16 gso_max_segs;
#ifdef CONFIG_DCB
const struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
u8 num_tc;
struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE];
u8 prio_tc_map[TC_BITMASK + 1];
#if IS_ENABLED(CONFIG_FCOE)
unsigned int fcoe_ddp_xid;
#endif
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
struct netprio_map __rcu *priomap;
#endif
struct phy_device *phydev;
struct lock_class_key *qdisc_tx_busylock;
bool proto_down;
};
+47
View File
@@ -0,0 +1,47 @@
进程虚拟地址空间
=======
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
http://blog.sina.com.cn/s/blog_4b9eab320102vme0.html
http://blog.csdn.net/geekcome/article/details/7971463
http://blog.chinaunix.net/uid-29478572-id-4119158.html
https://yq.aliyun.com/articles/5862
http://blog.sina.com.cn/s/blog_4b9eab320102v9qn.html
http://blog.csdn.net/shanshanpt/article/details/19918171
http://blog.chinaunix.net/uid-21768364-id-2936798.html
http://www.linuxidc.com/Linux/2012-09/70700.htm
http://www.oschina.net/question/234345_47845
http://xuela-net.iteye.com/blog/1899566
http://blog.chinaunix.net/uid-22359610-id-1626525.html
http://www.360doc.com/content/15/0522/07/18252487_472352496.shtml
http://www.openstack.cn/?p=4756
http://weibo.com/p/1001603837806467238143
大家都知道TCP/IP协议栈现在是世界上最流行的网络协议栈,恐怕它的普及的最重要的原因就是其清晰的层次结构以及清晰定义的原语和接口。不仅使得上层应用开发者可以无需关心下层架构或者内部机制,从而相对透明的操作网络。这个明显的层次结构也可以在Linux内核的网络协议栈中观察到。
主要的参考文献是:Linux网络栈剖析(中文版)/Anatomy of Linux networking stack(英文原版)by Tim Jones.
以及:Linux内核2.4.x的网络接口结构
另外一些参考资料可以从这个页面找到:http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/ (纽约州立大学石溪分校的页面)
Linux内核网络协议栈采用了如下的层次结构:
http://www.ibm.com/developerworks/cn/linux/l-linux-networking-stack/
http://www.ibm.com/developerworks/linux/library/l-linux-networking-stack/
http://gjiwwa.blu.livefilestore.com/y1p99O_6n29_58mKHdfUgbvzgKOsD5_7unXA5noegNcJ9Y2h3QMaQ-m0Rb9JGP1v_X4zI7-n7J6aqbOm1xr-HMy56E3YJZbWajO/Linux%E5%86%85%E6%A0%B82.4.x%E7%9A%84%E7%BD%91%E7%BB%9C%E6%8E%A5%E5%8F%A3%E7%BB%93%E6%9E%84.pdf?download
http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB