本文主要介绍linux内核中和WiFi密切相关的80211栈。80211栈包含nl80211,cfg80211和mac80211,主要用于WiFi配置和管理,本文将从功能和代码实现角度对各个模块进行解读。
首先给出Linux无线设备的软件架构图:
下文将分别介绍80211栈中的nl80211,cfg80211和mac80211模块,并基于上述问题对各个模块的功能和代码实现进行解读。
nl80211是介于用户空间与内核空间之间的 API ,可以算是 cfg80211 的前端,也会生成 “事件” (events) 信息。该模块依赖 netlink 协议来在两个空间进行信息交互,通过socket接收上层命令,执行对应函数进行配置管理网络接口。Netlink 是一个 Linux 中的 socket 类型,用于在内核与用户空间之间传递事件。
代码参见linux-5.4.196/net/wireless/nl80211.c
。nl80211的main
函数主要做了以下几件事:
genl_register_family
函数,注册nl80211_fam
结构。struct genl_family nl80211_fam
结构。主要字段有op字段,值为nl80211_ops
。struct genl_ops nl80211_ops[]
,数组定义了命令和对应的钩子函数。上层通过Generic netlink套接字通信发送命令,nl80211中执行对应的函数。linux-5.4.196/net/netlink/genetlink.c
中的函数genl_family_rcv_msg
会接收上层消息,进行pre_doit,doit,post_doit
等处理。
代码如下:
static int genl_family_rcv_msg(const struct genl_family *family,
struct sk_buff *skb,
struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
...
if (family->pre_doit) {
err = family->pre_doit(ops, skb, &info);
if (err)
goto out;
}
err = ops->doit(skb, &info);
if (family->post_doit)
family->post_doit(ops, skb, &info);
...
}
nl80211通过genl_family_rcv_msg
接收来自用户态的netlink信息,通过回调函数nl80211_pre_doit
, 查找cfg80211注册时的设备信息dev。 通过调用函数cfg80211函数接口或者dev钩子函数实现信息的获取或参数的设置。
其中支持的消息在nl80211_commands
中给出:
enum nl80211_commands {
/* don't change the order or add anything between, this is ABI! */
NL80211_CMD_UNSPEC,
NL80211_CMD_GET_WIPHY, /* can dump */
NL80211_CMD_SET_WIPHY,
NL80211_CMD_NEW_WIPHY,
NL80211_CMD_DEL_WIPHY,
...
}
消息和对应的处理函数在nl80211_ops[]中注册:
static const struct genl_ops nl80211_ops[] = {
{
.cmd = NL80211_CMD_GET_WIPHY,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = nl80211_get_wiphy,
.dumpit = nl80211_dump_wiphy,
.done = nl80211_dump_wiphy_done,
/* can be retrieved by unprivileged users */
.internal_flags = NL80211_FLAG_NEED_WIPHY,
},
#if LINUX_VERSION_IS_GEQ(5,10,0)
};
static const struct genl_small_ops nl80211_small_ops[] = {
#endif
{
.cmd = NL80211_CMD_SET_WIPHY,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = nl80211_set_wiphy,
.flags = GENL_UNS_ADMIN_PERM,
},
{
.cmd = NL80211_CMD_GET_INTERFACE,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = nl80211_get_interface,
.dumpit = nl80211_dump_interface,
/* can be retrieved by unprivileged users */
.internal_flags = NL80211_FLAG_NEED_WDEV,
},
...
}
需要注意的是doit
和dumpit
的区别:
doit
:标准命令回调函数,在当前族中收到数据时触发调用,函数的第一个入参skb中保存了用户下发的消息内容;
dumpit
:转储回调函数,当genl_ops
的flag标志被添加了NLM_F_DUMP
以后,每次收到genl消息即会回触发这个函数。
dumpit
与doit
的区别是:dumpit
的第一个参数skb不会携带从客户端发来的数据。相反地,开发者应该在skb中填入需要传给客户端的数据, 然后,将skb的数据长度(可以用skb->len)return。skb中携带的数据会被自动送到客户端。只要dumpit
的返回值大于 0,dumpit
函数就会再次被调用,并被要求在skb中填入数据。当服务端没有数据要传给客户端时,dumpit
要返回0。如果函数中出错,要求返回一 个负值。
cfg80211实现了wlan设备的注册,上可接收用户态配置管理命令,下可通过mac80211进行和硬件交互。因为实现了设备注册,虚拟接口的区分也是在此定义实现的(struct wiphy
,一个 wiphy 设备可以有一个对应 MAC)。
代码位置在linux-5.4.196/net/wireless/core.c
, 文件主要提供一些相关接口函数,实现设备在cfg80211中设备的注册,并在链表cfg80211_rdev_list
中添加设备信息。
cfg80211在函数cfg80211_init(void)
中初始化,主要做了一下几件事:
这是最底层的模块,与 hardware offloading 关联最多。如果某些功能无法由设备硬件实现,那么就可以以纯软的方式实现在这里。另外,以软件形式实现也可以赋予开发者对逻辑有更大的控制权。其也被称为 “Soft MAC” 模块,与 “Hard MAC” (由设备固件完成所有工作)相对。实际场景中,通常是这两种方案混合使用。802.11 协议状态机就在这里,需要处理所有类型的帧。
ieee80211_init
rc80211_minstrel_init //注册速率控制ops
ieee80211_iface_init //注册设备接口回调函数,用于更改相应结构体设备名称
backports-5.15.81-1/net/mac80211/main.c
创建station由用户空间通过nl80211发起
分配sta_info对象空间
初始化sta_info对象(包括侦听间隔,支持速率集等等)
初始化sta_info对象的速率控制对象
把sta_info对象加入local->sta_pending_list
调用local->ops->sta_add通知驱动创建station
把sta_info对象加入local->sta_list
http://blog.chinaunix.net/uid-22510743-id-5780801.html
https://www.cnblogs.com/ink-white/p/16822559.html
https://zhuanlan.zhihu.com/p/650693108
https://blog.csdn.net/zwl1584671413/article/details/114902310
https://blog.csdn.net/zxygww/article/details/24874155