浅谈Linux内核无线子系统

Linux 内核是如何实现无线网络接口呢?数据包是通过怎样的方式被发送和接收呢?今天跟着 LinuxStory 小编一起来探索一番吧!

刚开始工作接触 Linux 无线网络时,我曾迷失在浩瀚的基础代码中,寻找具有介绍性的材料来回答如上面提到的那些高层次的问题。跟踪探索了一段时间的源代码后,我写下了这篇总结,希望在 Linux 无线网络的工作原理上,读者能从这篇文章获得一个具有帮助性的概览。

1 全局概览

在开始探索 Linux 无线具体细节之前,让我们先来把握一下 Linux 无线子系统整体结构。如图1,展示了 Linux 无线子系统各个模块之间的抽象关系。

图一 Linux 无线网络结构示意图

图示中的虚线内展示的是内核空间的情况。用户空间的程序运行在最上层,而硬件相关的设备则在最下面。图示中左边为以太网设备,右边为 WiFi 设备。

正如图中看到的一样,存在着两种 WiFi 设备,具体是哪一类要看 IEEE802.11 标准的 MLME 如何实现。

如果直接通过硬件实现,那么设备就是硬 MAC (full MAC)设备;如果通过软件的方式实现,那么设备就是软 MAC (soft MAC)设备。现阶段大部分无线设备都是软件实现的软 MAC 设备。

通常我们把 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 高层回调函数。

2 模块间接口

从图一中我们可以看出,各个模块之间分界线很清晰,并且模块间相互透明不可见。模块之间一般不会相互影响。

举个例子,我们在 WiFi 设备驱动做修改(如,打补丁、添加新的 WiFi 驱动等),这些变更并不会影响到 mac80211 模块,

所以我们根本不用改动 mac80211 的代码。再如,添加一个新的网络协议理论上是不用修改套接字层以及设备无关层代码。一般情况下,内核通过一系列的函数指针实现各层之间相互透明。

如下代码展示 rtl73usb 无线网卡驱动与 mac80211 的联系。

左侧是 mac80211 为 WiFi 驱动模块实现的 ieee80211_ops 结构体形式的回调接口,回调函数的具体内容由驱动层实现。

显然,不同设备相应驱动的实现不同。结构体 ieee80211_ops 负责将不同设备驱动实现的回调函数与 mac80211 提供的 API 映射绑定起来。

当驱动模块插入注册时,这些回调函数就被注册到 mac80211 里面(通过 ieee80211_alloc_hw 实现),接着 mac80211 就绑定了相应的回调函数,根本不用知道具体的名字,以及实现细节等。

完整定义的 ieee80211_ops 结构包含很多成员,但不是所有都必须要驱动层实现。一般而言,实现的前七个成员函数就足够了。但是,要想正确实现其他功能,某些相关的成员函数就需要被实现,就像上面的例子一样。

3 数据路径与管理路径

图一所示中,存在两条主要路径:数据路径和管理路径。数据路径对应 IEEE802.11 数据帧,而管理路径对应着控制帧。

在 IEEE802.11 的控制帧中,大部分用于如 ACK 这类时间紧急的操作,并且一般直接由硬件实现。一个例外可能就是 PS-Poll 帧(用于 Power Save 控制),它也可以由 mac80211 实现。

数据和管理路径在 mac80211 里面是分开实现的。

4 数据包是如何被发送?

接下来,我们集中探讨下数据的发送过程。

首先,数据包起源于用户空间的应用程序,应用程序首先创建一个套接字,然后绑定一个接口(如,以太网接口、 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 一系列的调用,或者某些变种函数反馈给上层。

说到这里,关于数据包的发送也暂时告一段落了。

5 谈谈管理路径

理论上,我们可以像数据路径一样在用户空间下通过套接字发送控制帧。但是目前有很多开发得十分完善的用户层管理工具能完成这样的工作。

特别是 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 将会具体去实现扫描发现网络的具体细节。

6 数据包又是如何被接收?

我们接下来反过来看看数据接收的过程,现在我们不再比较数据路径与管理路径的不同了。相信读者同样能明白。

当一个数据包在空中被无线设备捕捉到后,硬件将会向内核发出一个中断(大部分 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 发送到用户空间。

7 总结一下

一般 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 。

注意:硬件设备的注册由 ieee80211_register_hw 完成,前提是事先已经插入注册了 mac80211 模块,好比在 STA 模式中,要先用 wpa_supplicant 控制设备连接上了某个热点才能进行通信一样。

最后希望这篇总结能让相关人员在探索源代码时具有整体把握。

作者:RAIN

来源:51CTO

时间: 2024-08-02 10:38:51

浅谈Linux内核无线子系统的相关文章

浅谈Linux内核创建新进程的全过程_Linux

进程描述 进程描述符(task_struct) 用来描述进程的数据结构,可以理解为进程的属性.比如进程的状态.进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct 进程控制块(PCB) 是操作系统核心中一种数据结构,主要表示进程状态. 进程状态 fork() fork()在父.子进程各返回一次.在父进程中返回子进程的 pid,在子进程中返回0. fork一个子进程的代码   #include <stdio.h> #include <std

浅谈linux下的一些常用函数的总结(必看篇)_Linux

1.exit()函数 exit(int n)  其实就是直接退出程序, 因为默认的标准程序入口为int main(int argc, char** argv),返回值是int型的. 一般在shell下面,运行一个程序,然后使用命令echo $?就能得到该程序的返回值,也就是退出值,在main()里面,你可以用return n,也能够直接用exit(n)来做.unix默认的习惯正确退出是返回0,错误返回非0. 重点:单独的进程是返回给操作系统的.如果是多进程,是返回给父进程的. 在父进程里面调用w

浅谈 Linux 高负载的系统化分析

讲解 Linux Load 高如何排查的话题属于老生常谈了,但多数文章只是聚焦了几个点,缺少整体排查思路的介绍.所谓 "授人以鱼不如授人以渔".本文试图建立一个方法和套路,来帮助读者对 Load 高问题排查有一个更全面的认识. 从消除误解开始 没有基线的 Load,是不靠谱的 Load 从接触 Unix/Linux 系统管理的第一天起,很多人就开始接触 System Load Average 这个监控指标了,然而,并非所有人都知道这个指标的真正含义.一般说来,经常能听到以下误解: Lo

浅谈Linux配置定时,使用crontab -e与直接编辑/etc/crontab的区别_Linux

Linux配置定时任务,大家都知道使用crontab这个系统功能,但有时候我们需要区分用户执行,下面就直接说一下2种方法的区别: 方法1: 使用命令 crontab -e 然后直接编辑定时脚本. 这样执行以后,属于用户自定义的,会被写到 /var/spool/cron 目录下,生成一个和用户名一致的文件,文件内容就是我们编辑的定时脚本. 如: [root@localhost cron.d]# cd /var/spool/cron [root@localhost cron]# ll 总用量 4 -

浅谈Linux下通过find命令进行rm文件删除的小技巧_Linux

我们经常会通过find命令进行批量操作,如:批量删除旧文件.批量修改.基于时间的文件统计.基于文件大小的文件统计等,在这些操作当中,由于rm删除操作会导致目录结构变化,如果要通过find结合rm的操作写成脚本,就会遇到一些麻烦,本文通过一个例子为大家进行介绍. 系统环境: SUSE Linux Enterprise Server 11 或 Red Hat Enterprise Linux 问题症状: 客户现场有一个自动化的脚本,有以下的find语句,每天运行以删除某个目录下7天以前的文件或目录,

浅谈linux几种定时函数的使用_Linux

在程序开发过程中,我们时不时要用到一些定时器,通常如果时间精度要求不高,可以使用sleep,uslepp函数让进程睡眠一段时间来实现定时, 前者单位为秒(s),后者为微妙(us):但有时候我们又不想让进程睡眠阻塞在哪儿,我们需要进程正常执行,当到达规定的时间时再去执行相应的操作, 在linux下面我们一般使用alarm函数跟setitimer函数来实现定时功能: 下面对这两个函数进行详细分析: (1)alarm函数 alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,

浅谈Linux下tar,jar压缩,解压的常用命令_Linux

如下所示: tar cvf /data/d2/apps.tar apps cd /data01/applsrm/SRM tar xvf apps.tar jar cvf /data01/xxx.jar * cd wq jar xvf xxxx.jar 以上这篇浅谈Linux下tar,jar压缩,解压的常用命令就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持. 以上是小编为您精心准备的的内容,在的博客.问答.公众号.人物.课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮

浅谈linux定时器模型

用户态的定时器设计 记得某段时间的工作中,经常会用到定时器.发现有些同学为了图方便,会这样实现定时器: while(1) { sleep_awhile(); while((timer = get_expired_timer())) do_timer_handler(timer); } 用一个线程,周期性地睡眠一段时间,然后起来看看有没有需要触发的定时任务. 这种定时器写起来确实很简单,但是也让人感觉很拙.一方面,周期性的睡眠与唤醒,占用了一定的调度开销,并且定时线程被唤醒之后,经常是无事可做的.

浅谈Linux的文件系统

如果您是一位新手,也许您还不知道如何把文件从Windows拷贝到Linux上吧?下面,我们将说明Unix文件系统以及mount的工作过程,然后再比较详细地讨论 mount的使用和有关选项.如果您已经了解Unix文件系统是如何工作的,那么可以跳过下面一节.否则,您最好继续接着学习"mount"的含义. 什么是mount? 在一些操作系统(如Windows)中,计算机通过设备名来识别设备,例如,大多数PC机系统都包括"A:drive"(软盘)."C:drive