Linux 内核是如何实现无线网络接口呢?数据包是经过怎样的方式被发送和接收呢?今天跟着 LinuxStory 小编一块儿来探索一番吧!
刚开始工做接触 Linux 无线网络时,我曾迷失在浩瀚的基础代码中,寻找具备介绍性的材料来回答如上面提到的那些高层次的问题。跟踪探索了一段时间的源代码后,我写下了这篇总结,但愿在 Linux 无线网络的工做原理上,读者能从这篇文章得到一个具备帮助性的概览。网络
在开始探索 Linux 无线具体细节以前,让咱们先来把握一下 Linux 无线子系统总体结构。如图1,展现了 Linux 无线子系统各个模块之间的抽象关系。数据结构
图一 Linux 无线网络结构示意图app
图示中的虚线内展现的是内核空间的状况。用户空间的程序运行在最上层,而硬件相关的设备则在最下面。图示中左边为以太网设备,右边为WiFi 设备。
正如图中看到的同样,存在着两种 WiFi 设备,具体是哪一类要看 IEEE802.11 标准的 MLME 如何实现。
若是直接经过硬件实现,那么设备就是硬 MAC (full MAC)设备;若是经过软件的方式实现,那么设备就是软 MAC (soft MAC)设备。现阶段大部分无线设备都是软件实现的软 MAC 设备。ide
一般咱们把 Linux 内核无线子系统当作两大块: cfg80211 和mac80211 ,它们连通内核其余模块和用户空间的应用程序。
特别指出, cfg80211 在内核空间提供配置管理服务,内核与应用层经过 nl80211 实现配置管理接口。须要记住的是,
硬 MAC 设备和软 MAC 设备都须要 cfg80211 才能工做。而mac80211 只是一个驱动 API ,它只支持软件实现的软 MAC 设备。
接下来,咱们主要关注软 MAC 设备。函数
Linux 内核无线子系通通一各类 WiFi 设备,并处理 OSI 模型中最底层的 MAC 、 PHY 两层。
若进一步划分, MAC 层能够分为 MAC 高层和 MAC 底层。前者负责管理 MAC 层无线网络的探测发现、身份认证、关联等;
后者实现 MAC 层如 ACK 等紧急操做。大部分状况下,硬件(如无线适配器)处理大部分的 PHY 层以及 MAC 底层操做。Linux 子系统实现大部分的 MAC 高层回调函数。工具
从图一中咱们能够看出,各个模块之间分界线很清晰,而且模块间相互透明不可见。模块之间通常不会相互影响。
举个例子,咱们在 WiFi 设备驱动作修改(如,打补丁、添加新的 WiFi 驱动等),这些变动并不会影响到 mac80211 模块,
因此咱们根本不用改动 mac80211 的代码。再如,添加一个新的网络协议理论上是不用修改套接字层以及设备无关层代码。通常状况下,内核经过一系列的函数指针实现各层之间相互透明。
以下代码展现 rtl73usb 无线网卡驱动与 mac80211 的联系。post
左侧是 mac80211 为 WiFi 驱动模块实现的 ieee80211_ops 结构体形式的回调接口,回调函数的具体内容由驱动层实现。
显然,不一样设备相应驱动的实现不一样。结构体 ieee80211_ops 负责将不一样设备驱动实现的回调函数与 mac80211 提供的 API 映射绑定起来。
当驱动模块插入注册时,这些回调函数就被注册到 mac80211 里面(经过 ieee80211_alloc_hw 实现),接着 mac80211 就绑定了相应的回调函数,根本不用知道具体的名字,以及实现细节等。
完整定义的 ieee80211_ops 结构包含不少成员,但不是全部都必需要驱动层实现。通常而言,实现的前七个成员函数就足够了。可是,要想正确实现其余功能,某些相关的成员函数就须要被实现,就像上面的例子同样。spa
图一所示中,存在两条主要路径:数据路径和管理路径。数据路径对应 IEEE802.11 数据帧,而管理路径对应着控制帧。
在 IEEE802.11 的控制帧中,大部分用于如 ACK 这类时间紧急的操做,而且通常直接由硬件实现。一个例外可能就是 PS-Poll 帧(用于 Power Save 控制),它也能够由 mac80211 实现。
数据和管理路径在 mac80211 里面是分开实现的。3d
接下来,咱们集中探讨下数据的发送过程。
首先,数据包起源于用户空间的应用程序,应用程序首先建立一个套接字,而后绑定一个接口(如,以太网接口、 WiFi 接口)。
接下来将数据写入到套接字缓冲区,最后再将缓冲区的数据发送出去。在套接字建立时,咱们须要指明将要使用的协议族,这将在内核中起做用。
刚才这些发生在图一中的 Data Application 模块中,最终应用程序陷入系统调用,随后在内核空间进行接下来的工做。指针
数据的传输首先通过套接字层,这个过程当中一个最重要的数据结构就是 sk_buff ,通常称为 skb 。一个 skb 结构中的成员包含着缓冲区的地址以及数据长度。
它还为内核中不一样层对数据的操纵提供了良好的支持;实现了众多的接口,如,不一样网络层首部的插入与去除等。整个数据的发送/接收过程均会用到这个结构。
咱们跳过网络协议模块,对于网络协议我没有太多想说的,由于一旦涉及网络协议,简直说不尽道不完。在这里协议并非咱们主要关心的。
不过咱们须要知道的是,数据传输使用的协议在套接字建立的时候就与指定的协议绑定了,而后相关的协议便会负责相关层的数据传输。
接下来,数据由网络层落到了设备无关层。这一层透明的链接着各类各样的硬件设备(如以太网设备、 WiFi 设备等)。
设备无关层一个重要的结构是: net_device 。咱们回去看图一,再看接下来的代码就能解释内核是如何与以太网设备驱动通讯的。
具体接口经过 net_device_ops 结构实现,该结构对应了 net_device 的不少操做。
以下是 net_device_ops 结构的部分红员:
发包的时候, skb 在调用 dev_queue_xmit 时被传入。在跟踪具体调用关系后,最终是这样调用的: ops->ndo_start_xmit(skb, dev) 。
注意,刚才的这个函数须要注册才能生效。
对于 WiFi 设备而言,一般咱们使用 mac80211 (代替了相应的设备驱动),那是由于 mac80211 已经帮咱们注册了。
从 net/mac80211/iface.c 能够看到:
所以 mac80211 也就能够看做是一个 net_device ,当一个数据包经过 WiFi 传输时,相关的传输函数 ieee80211_subif_start_xmit 将被调用。
咱们进入 mac80211 内部 ieee80211_subif_start_xmit 实现能够看到这样一个调用子序列:ieee80211_xmit => ieee80211_tx => ieee80211_tx_frags => drv_tx
目前咱们处在 mac80211 和 WiFi 驱动的边界, drv_tx 仅仅调用了一个在 WiFi 驱动层实现的并已注册的回调函数。
到这里, mac80211 就结束了,而且设备驱动相关也暂时告一段落了。
正如以前提到的同样,经过 mac80211 中的 local->ops->tx ,注册到设备驱动中的回调函数将会被调用。尽管每一个驱动对相应回调函数的实现不尽相同。
下面利用以前模块间接口的例子。结构体成员 tx 对应的函数 rt2x00max_tx 首先须要填充准备发送描述符(通常包含帧长度、ACK 策略、 RTS/CTS、重传时限、分片标志以及 MCS 等)
部分信息由 mac80211 传下来(结构体 ieee80211_tx_info 中就有一些信息将会被使用到),而后驱动程序还要将数据转换成底层硬件可识别的形式。
一旦发送描述符就位,驱动程序还会调整帧数据(如,调整字节对齐等),而后将数据帧放入发送队列,最后将要发送的帧的描述符发给硬件。
因为咱们以一个基于 rt73usb 的 USB WiFi 适配器为例,因此数据帧最后是经过 USB 接口发送给无线设备。
而后数据将被插入 PHY 首部以及其余信息,最后数据包被发送到了空中。驱动同时也须要反馈发送状态给 mac80211 , 一般状态信息存放在 struct ieee80211_tx_info 中。
通过 ieee80211_tx_status 一系列的调用,或者某些变种函数反馈给上层。
说到这里,关于数据包的发送也暂时告一段落了。
理论上,咱们能够像数据路径同样在用户空间下经过套接字发送控制帧。可是目前有不少开发得十分完善的用户层管理工具能完成这样的工做。
特别是 wpa_supplicant 和 host_apd 。wpa_supplicant 控制客户端 STA 模式下无线网络的链接,如扫描发现网络、身份认证、关联等。
而 host_apd 能够作 AP 。说白了前者就是用来链接热点,后者用来发射热点。这些用户层工具经过 netlink 套接字与内核通讯。
内核中相关的回调接口是 cfg80211 中的 nl80211 。用户层的工具经过 netlink 提供的库(如, NL80211_CMD_TRIGGER_SCAN )将命令发送到内核。
在内核中,由 nl80211 接收应用层发出的命令。以下代码展现了对应绑定状况。
以 triggering scan 为例,扫描请求从 cfg80211 到 mac80211 是经过 mac80211 在 cfg80211 中注册的回调函数来实现的。
在 mac80211 中, ieee80211_scan 将会具体去实现扫描发现网络的具体细节。
咱们接下来反过来看看数据接收的过程,如今咱们再也不比较数据路径与管理路径的不一样了。相信读者一样能明白。
当一个数据包在空中被无线设备捕捉到后,硬件将会向内核发出一个中断(大部分 PCI 接口的设备这样作),或则经过轮询机制判断是否有数据到来(如,使用了 USB 接口)。
前者,中断将会引起中断处理程序的执行,后者促使特定的接收函数将被调用。
通常设备驱动层的回调函数不会作太多关于接收数据包的操做,仅仅作数据校验,为 mac80211 填充接收描述符,而后把数据包推给 mac80211 , 由 mac80211 来作以后的工做(直接或间接将数据包放入接收队列)。
数据进入 mac80211 后,将会调用 ieee80211_rx 或者其余变种接收函数。在这里数据路径和管理路径也将分开进行。
若是收到的帧是数据,它将被转换成 802.3 数据帧(经过 __ieee80211_data_to8023 实现),而后该数据帧将经过 netif_receive_skb 交付到网络协议栈。在协议栈中,各层网络协议将会对数据进行解析,识别协议首部。
若是接收到的是控制帧,数据将会由 ieee80211_sta_rx_queued_mgmt 处理。部分控制帧在 mac80211 层就终止,另一些将会经过 cfg80211 发送到用户空间下的管理程序。
例如,身份认证控制帧被 cfg80211_rx_mlme_mgmt 处理,而后经过 nl80211_send_rx_auth 发送到用户空间下的 wpa_supplicant ; 相应的关联响应控制帧被 cfg80211_rx_assoc_resp 处理,并由 nl80211_send_rx_assoc 发送到用户空间。
通常 WiFi 驱动包含以下三个部分:配置、发送回调、接收回调。再以 USB WiFi 适配器为例,当内核探测到设备被插入时,会调用 probe 函数。这可能发生在注册配置好的 ieee80211_ops 时。
首先, ieee80211_alloc_hw 分配一个 ieee80211_hw 结构体,表明着相应 WiFi 设备。另外,以下的数据结构也会被分配:
wiphy 结构:主要用来描述 WiFi 硬件参数(如, MAC 地址、接口模式与组合、支持的波特率以及其余一些硬件功能)。
ieee80211_local 结构:这是一个设备驱动层可见的结构,而且被 mac80211 大量使用。ieee80211_ops 的映射绑定将连接到 ieee80211_local 中。 前者做为后者的一个成员。在 ieee80211_hw 中能够经过 container_of 或者 hw_to_local 这个专用 API 获得 ieee80211_local 。
设备驱动使用到的在 ieee80211_hw 中的私有结构 void *priv 。