上一篇文章说明了hostapd代码的初始化,程序信息处理框架以及hostapd的回调处理机制,以此大致了解hostapd的程序执行流程。
这一篇我们对hostapd怎么将配置信息传递至kernel环境下,使无线驱动能运用这些user space下的配置信息进行组帧这个问题进行解答。
接下来三篇为一个系列,主要针对以下3点进行一个顺序的描述:
截取起始处链接的一副程序框架图。它是说明mac80211在整个无线体系结构中的位置:
既然提到了mac80211,那么就先对mac80211进行说明,引出相关的无线名词,方便后续文章的阅读和了解。
9、struct ieee80211_hw:表示硬件信息和状态。
!!!其中的netlink机制在下一个模块说明。
了解了无线程序相关框架之后,接下来依据具体代码从上至下进行一个一个模块的具体了解和说明。
首先是user space模块,这里主要针对hostapd进行说明,同时也会有其他的相关程序参与了无线的信息传递,比如wpa_supplicant等。
从上一篇文章中知道了hostapd的几个主要知识点,分别为物理网卡初始化,信息回调处理机制和具体的管理帧处理。这其中网卡初始化为后续的信息回调处理和传递做了充分的准备工作。它决定了接收到什么信息去调用哪个回调,网卡初始化时调用哪些接口去设置信息等,然后接受命令,最后传至kernel space的nl80211中。
所以要说明该篇内容,网卡初始化是hostapd在这一篇需要进行主要说明的。进行主要网卡初始化的是hostapd的main函数中的hostapd_interface_init接口,下面我们主要以此接口观察初始化的回调接口是怎么引导信息向底层传递的。
static int wpa_driver_nl80211_set_ap(void *priv,
struct wpa_driver_ap_params *params)
{
...
if (beacon_set)
cmd = NL80211_CMD_SET_BEACON; //设置beacon帧的命令序号
nl80211_cmd(drv, msg, 0, cmd);
wpa_hexdump(MSG_DEBUG, "nl80211: Beacon head",
params->head, params->head_len);
NLA_PUT(msg, NL80211_ATTR_BEACON_HEAD, params->head_len, params->head); //组成beacon的head部分
wpa_hexdump(MSG_DEBUG, "nl80211: Beacon tail",
params->tail, params->tail_len);
NLA_PUT(msg, NL80211_ATTR_BEACON_TAIL, params->tail_len, params->tail); //组成beacon的tail部分
...
ret = send_and_recv_msgs(drv, msg, NULL, NULL); //调用send_and_recv_msgs向kenel space发送信息
if (ret) {
wpa_printf(MSG_DEBUG, "nl80211: Beacon set failed: %d (%s)",
ret, strerror(-ret));
} else {
bss->beacon_set = 1;
nl80211_set_bss(bss, params->cts_protect, params->preamble,
params->short_slot_time, params->ht_opmode,
params->isolate, params->basic_rates);
}
}
熟悉了user space层的信息向底层传递方式,接下来了解底层接收到信息后怎么获取信息和执行相应的回调操作。
!!!至于是通过什么将user space数据传递给kernel space的,涉及到netlink信息传递机制,为了更具体地说明,将其放在下一个大模块中。
nl80211中的结构体struct nl80211的内容充分体现了其在user space和kernel space的过渡作用,如下结构体所示:
/*nl80211.c*/
static struct genl_ops nl80211_ops[] = {
...
{
.cmd = NL80211_CMD_SET_BEACON, //命令序号
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.doit = nl80211_set_beacon, //接收到序号后执行的操作
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL,
},
{
.cmd = NL80211_CMD_START_AP,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.doit = nl80211_start_ap,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL,
},
...
};
从上面的结构体中可以发现,nl80211_ops是一个结构体数组,一个结构体内容包括了命令序号(cmd)以及回调执行的函数指针(doit)等。
细心的读者就会发现,这里的命令序号在hostapd中也存在,并且命名也相同。比如上面代码中的命令序号NL80211_CMD_SET_BEACON,也同样存在于前面”user space(hostapd)“模块中举例说明的wpa_driver_nl80211_set_ap接口中,并且名字相同。同样地,该结构体数组中的其他的命令序号与hostapd的传递信号也是一致。
由此可以推断nl80211_ops结构体数组在user space和kernel space间的通信扮演重要的角色。
确定了nl80211_ops结构体数组,那么kernel中是哪里让这个结构体数组发挥作用的呢?
搜索nl80211_ops结构体数组,发现在genl_register_family_with_ops()接口中将该结构体数组作为了第一个参数。
/*nl80211.c-----> nl80211_init*/
/*
* 在nl80211的初始化中,引导相关的命令回调函数进行注册
*/
int nl80211_init(void)
{
int err;
err = genl_register_family_with_ops(&nl80211_fam, //genl_register_family_with_ops接口将nl80211_ops作为第一个参数注册
nl80211_ops, ARRAY_SIZE(nl80211_ops));
if (err)
return err;
err = genl_register_mc_group(&nl80211_fam, &nl80211_config_mcgrp);
if (err)
goto err_out;
...
}
//文件:genetlink.c ----> genl_register_family_with_ops
/**
* genl_register_family_with_ops - register a generic netlink family 注册netlink族,用于后续回调操作
* @family: generic netlink family
* @ops: operations to be registered
* @n_ops: number of elements to register
*
* Registers the specified family and operations from the specified table.
* Only one family may be registered with the same family name or identifier.
*
* The family id may equal GENL_ID_GENERATE causing an unique id to
* be automatically generated and assigned.
*
* Either a doit or dumpit callback must be specified for every registered
* operation or the function will fail. Only one operation structure per
* command identifier may be registered.
*
* See include/net/genetlink.h for more documenation on the operations
* structure.
*
* This is equivalent to calling genl_register_family() followed by
* genl_register_ops() for every operation entry in the table taking
* care to unregister the family on error path.
*
* Return 0 on success or a negative error code.
*/
int genl_register_family_with_ops(struct genl_family *family,
struct genl_ops *ops, size_t n_ops)
{
int err, i;
err = genl_register_family(family);
if (err)
return err;
for (i = 0; i < n_ops; ++i, ++ops) {
err = genl_register_ops(family, ops);
if (err)
goto err_out;
}
return 0;
err_out:
genl_unregister_family(family);
return err;
}
额外地,在genl_register_family_with_ops函数中又再一次出现了netlink,说明netlink在user space和kernel space中的关键程度。
知道了nl80211初始化会引导回调函数赋值。那么nl80211又是怎么与程序框架中的cfg80211联系起来的呢?
继续搜索nl80211_init接口,发现其被cfg80211_init接口调用,如下代码段所示:
//文件:core.c --------> cfg80211_init
static int __init cfg80211_init(void)
{
int err;
err = register_pernet_device(&cfg80211_pernet_ops);
if (err)
goto out_fail_pernet;
err = wiphy_sysfs_init();
if (err)
goto out_fail_sysfs;
err = register_netdevice_notifier(&cfg80211_netdev_notifier);
if (err)
goto out_fail_notifier;
err = nl80211_init(); //cfg80211中调用nl80211_init来关联nl80211
if (err)
goto out_fail_nl80211;
...
}
subsys_initcall(cfg80211_init); //关于subsys_initcall,网络上说是一个入口函数,对于驱动来说相当于module_init。具体还需要后续了解
至此,我们该模块叙述了user space和kernel space间的过渡结构体数组nl80211_ops,和联系该结构体和cfg80211的方式。
跨过了user space和kernel space的“河”,再接下来就是kernel space内部的信息传递和操作了。
cfg80211_ops是一个结构体,结构体中包含各种给底层Linux 802.11配置使用的函数接口指针,在程序中定义了一个以mac80211_config_ops命名的cfg80211_ops的全局变量。
如下是cfg80211_ops结构体部分内容:
//文件:cfg.c -----> cfg80211_ops
struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
.change_virtual_intf = ieee80211_change_iface,
.start_p2p_device = ieee80211_start_p2p_device,
.stop_p2p_device = ieee80211_stop_p2p_device,
.add_key = ieee80211_add_key,
.del_key = ieee80211_del_key,
.get_key = ieee80211_get_key,
.set_default_key = ieee80211_config_default_key,
.set_default_mgmt_key = ieee80211_config_default_mgmt_key,
.start_ap = ieee80211_start_ap,
.change_beacon = ieee80211_change_beacon,
.stop_ap = ieee80211_stop_ap,
.add_station = ieee80211_add_station,
.del_station = ieee80211_del_station,
.change_station = ieee80211_change_station,
.get_station = ieee80211_get_station,
.dump_station = ieee80211_dump_station,
...
};
因为cfg80211_ops结构体中的内容只是供Linux 802.11调用的API,要找cfg80211与cfg80211_ops的联系只需要找cfg80211是哪里去调用cfg80211_ops中的API接口的,参数是怎么传递的?
针对这个问题进行进一步的探索,同时也还是以NL80211_CMD_SET_BEACON这个命令为例来具体地说明讲解。
cfg80211是哪里去调用cfg80211_ops中的API接口的:
顺着cfg80211找下来,发现最终在调用的nl80211_ops中的doit接口中会调用到cfg80211_ops的API。
/*------nl80211_set_beacon-------*/
static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0]; //cfg80211_ops指针是从接口参数中的info中获取得出
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_beacon_data params;
int err;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP && dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
return -EOPNOTSUPP;
if (!rdev->ops->change_beacon) //!!!这里的change_beacon为cfg80211_ops中的API
return -EOPNOTSUPP;
if (!wdev->beacon_interval)
return -EINVAL;
err = nl80211_parse_beacon(info->attrs, ¶ms);
if (err)
return err;
return rdev_change_beacon(rdev, dev, ¶ms);
}
cfg80211_ops中的API指针是什么时候传给nl80211_ops的:
发现在nl80211_ops的doit接口中,会从接口的第二个参数中获取到该cfg80211_ops结构体指针。(如上方的nl80211_set_beacon代码注释中所示)
doit中的参数是什么时候传递的,即nl80211_ops中的doit是什么时候被调用的:
全局搜索doit,发现在genl_rcv_msg中会有调用,其整个调用流程如下图所示。
mac80211在上面的框架图中已经说明过,为cfg80211的回调函数。即用于cfg80211和local的设置调用。
同时在mac80211的main.c中也发现了一处late_initcall(wla_init)的初始化,该初始化会调用wla_init,然后调用ieee80211_alloc_hw,然后再又通过wiphy_new接口进行mac80211_config_ops结构体的空间申请和local的赋值,使local也能使用mac80211的API。
???这里上面这样一套流程暂时还不知道和”cfg80211”模块中说的流程有什么联系。如果有哪位对这方面有了解的麻烦能帮忙解惑。
ieee80211_ops结构体定义了24个方法,以下7个方法必须实现(tx,start,stop,add_interface,remove_interface,config和configure_filter),每个driver实现它的成员函数,且它的成员函数都以struct ieee80211_hw做为第一个参数。
经过搜索,其中的7个方法一般都在driver_ops.h中被重新封装,并被iface,c和util.c中的一些接口调用,进行对更底层的功能选项配置。具体后续研究后再进行进一步的说明。