iw-3.15代码阅读

9 篇文章 1 订阅

iw

        iw主要用于显示和操作无线设备及其配置,其通过netlink实现用户空间和内核空间的交互。
        下面来开始分析一下iw的代码,目前的代码版本选择得失iw-3.15。

目录结构

        首先看看iw-3.15中所包含的文件:
[scott@SDW iw-3.15]$ tree
.
├── Android.mk
├── android-nl.c
├── bitrate.c
├── coalesce.c
├── connect.c
├── CONTRIBUTING
├── COPYING
├── cqm.c
├── event.c
├── genl.c
├── hwsim.c
├── ibss.c
├── ieee80211.h
├── info.c
├── interface.c
├── iw.8
├── iw.c
├── iw.h
├── link.c
├── Makefile
├── mesh.c
├── mpath.c
├── nl80211.h
├── offch.c
├── p2p.c
├── phy.c
├── ps.c
├── README
├── reason.c
├── reg.c
├── roc.c
├── scan.c
├── sections.c
├── station.c
├── status.c
├── survey.c
├── util.c
├── version.sh
└── wowlan.c


0 directories, 39 files
[scott@SDW iw-3.15]$

主框架

        下面开始进行代码分析。
[iw.c +495]
494 
495 int main(int argc, char **argv)
496 {
497         struct nl80211_state nlstate;
498         int err;
499         const struct cmd *cmd = NULL;
500 
501         /* calculate command size including padding */
502         cmd_size = abs((long)&__section_set - (long)&__section_get);
503         /* strip off self */
504         argc--;
505         argv0 = *argv++;
506 
507         if (argc > 0 && strcmp(*argv, "--debug") == 0) {
508                 iw_debug = 1;
509                 argc--;
510                 argv++;
511         }
512 
513         if (argc > 0 && strcmp(*argv, "--version") == 0) {
514                 version();
515                 return 0;
516         }
517 
518         /* need to treat "help" command specially so it works w/o nl80211 */
519         if (argc == 0 || strcmp(*argv, "help") == 0) {
520                 usage(argc - 1, argv + 1);
521                 return 0;
522         }
523
        在这里,先不管497行的nl80211_state结构体。在第502行出现了一个奇怪的几个“变量”:__section_set和__section_get,而这两个变量在当前的代码中并没有搜索到对应的定义,不过将这两个变量的形式进行拆分,会发现有意思的事情了,拆分成“__section”和另一半部。此时,可以找到在iw.h中进行了定义:
[iw.h +103]
103 #define SECTION(_name)                                                  \
104         struct cmd __section ## _ ## _name                              \
105         __attribute__((used)) __attribute__((section("__cmd"))) = {     \
106                 .name = (#_name),                                       \
107                 .hidden = 1,                                            \
108         }
109 
110 #define DECLARE_SECTION(_name)                                          \
111         extern struct cmd __section ## _ ## _name;
112
        从104行可以知道,__section和_name进行连接,形成了一个新的符号。105行的作用是,将这新的变量(或符号)存放到自定义名称为“__cmd”的段中,用SECTION宏声明的变量,其中的.name域和.hidden域被赋值了,其他的域没有被赋有效值。而什么地方使用了SECTION宏呢?通过搜索代码发现,使用这个宏的地方还不少,但是回归到main函数中,在iw.c的第502行,与我们有关的只是“get”和“set”两个字符串。所以,界定main函数的502行使用的是section.c中的内容:
[section.c]
  1 #include "iw.h"
  2 
  3 SECTION(get);
  4 SECTION(set);
        注意,在这里,先定义的是__section_get,后定义的是__section_set。其内容可以看一下下面命令的输出:
[scott@SDW iw-3.15]$ objdump --section=__cmd -d iw| grep "<__"
000000000061ca20 <__section_help>:
000000000061ca60 <__section_event>:
000000000061caa0 <__section_list>:
……
000000000061dc60 <__cmd_bitrates_handle_bitrates_NL80211_CMD_SET_TX_BITRATE_MASK_CIB_NETDEV_0>:
000000000061dca0 <__section_get>:
000000000061dce0 <__section_set>:
[scott@SDW iw-3.15]$
        从这里可以看出,在最终生成的目标文件中,__section_set的位置是大于__section_get的位置的,而它们两个均是cmd类型的结构体变量,所以这两个连续变量的差值,应该就是cmd结构体成员在__cmd段中所占空间的大小了。
        为何要将cmd放置在__cmd段中,这个目前还不清楚,不过可以通过对比一般的实现方法,获得一些信息:
将所有的cmd放置到内存中,最简单的一个实现是用数组来保存这些命令,但是,可能就需要比较大的空间了,后续的扩展性就不是很好了。当然了,也可以动态的申请内存,此时,考虑到分层设计,就需要提供存储管理(最简单的也得给一个恰好的空间)、函数注册、调用等一堆方法。若通过新的数据段来实现,对于程序来说,就不需要额外申请内存了,还得承担内存泄漏的风险,另外,也避免了初始化,而直接将__cmd这个段当成保存cmd的静态数组来使用,由于省掉了内存管理的开销,程序的内存占用会更少一些。
        当然了,在section.c中定义了__section_get和__section_set两个全局变量,其外部声明却在iw.h文件中:
[iw.h]
174 
175 DECLARE_SECTION(set);
176 DECLARE_SECTION(get);
177
        DECLARE_SECTION()宏的定义,是在SECTION()定义后完成的,见上文iw.h中的110行。这样,__section_set和__setction_get两个全局变量就可以正常使用了。

        回归到main函数中。
        从507行到522行,可以看出来,iw首先处理了三个参数:--debug、--version和help,这三个参数的处理比较简单,没什么可以说的,只是要注意一下504和505行对argc和argv的处理。

        下面是iw的对netlink的使用部分了。额,netlink是一个比较复杂的用于内核空间和用户空间的通信方法,这个可以在后续进行补充,目前暂时先回归到对iw代码逻辑的理解这条主线上来。

[iw.c +528]
528         if (strcmp(*argv, "dev") == 0 && argc > 1) {
529                 argc--;
530                 argv++;
531                 err = __handle_cmd(&nlstate, II_NETDEV, argc, argv, &cmd);
532         } else if (strncmp(*argv, "phy", 3) == 0 && argc > 1) {
533                 if (strlen(*argv) == 3) {
534                         argc--;
535                         argv++;
536                         err = __handle_cmd(&nlstate, II_PHY_NAME, argc, argv, &cmd);
537                 } else if (*(*argv + 3) == '#')
538                         err = __handle_cmd(&nlstate, II_PHY_IDX, argc, argv, &cmd);
539                 else
540                         goto detect;
541         } else if (strcmp(*argv, "wdev") == 0 && argc > 1) {
542                 argc--;
543                 argv++;
544                 err = __handle_cmd(&nlstate, II_WDEV, argc, argv, &cmd);
545         } else {
546                 int idx;
547                 enum id_input idby = II_NONE;
548  detect:
549                 if ((idx = if_nametoindex(argv[0])) != 0)
550                         idby = II_NETDEV;
551                 else if ((idx = phy_lookup(argv[0])) >= 0)
552                         idby = II_PHY_NAME;
553                 err = __handle_cmd(&nlstate, idby, argc, argv, &cmd);
554         }
555
        528~554行,就是iw进行处理的最上层逻辑的封装。从代码结构上来看,其主要区分成四部分:dev、phy、wdev和其他。这里的“其他”,先不用管,dev一般指的是在802.11无线网卡上创建出来一般的网络设备,这个网络设备能够被ifconfig一类的命令查看到;phy设备么,就是与802.11 Radio紧密相关的设备,一般是一块802.11无线网卡,驱动层就创建出一个与之对应的phy设备;wdev设备,这个还不清楚,但是通过搜索代码发现,这个设备是与p2p有关的,这是一个WiFi Direct规范,但是iw-3.15并没有提供对应的实现,这个功能也不用关心。一般,用到最多的也就是dev和phy两个命令参数。

        下面就用个例子来跟踪一下iw的执行过程,选用的这个命令是:
iw dev mon0 set channel 11
        当然啦,mon0设备此前通过iw命令给创建出来了。
        分析命令发现,这个命令的第一个参数是dev,所以,在main函数中,应该进入531行,作为II_NETDEV方式调用__handle_cmd()。
        __handle_cmd()函数比较长,有一些部分在理解上并没有难度,现在就其中比较困难的部分进行说明。
[iw.c]
295 
296 static int __handle_cmd(struct nl80211_state *state, enum id_input idby,
297                         int argc, char **argv, const struct cmd **cmdout)
298 {
299         const struct cmd *cmd, *match = NULL, *sectcmd;
300         struct nl_cb *cb;
301         struct nl_cb *s_cb;
302         struct nl_msg *msg;
303         signed long long devidx = 0;
304         int err, o_argc; 
305         const char *command, *section;
306         char *tmp, **o_argv;
307         enum command_identify_by command_idby = CIB_NONE;
308 
309         if (argc <= 1 && idby != II_NONE)
310                 return 1;
311 
312         o_argc = argc;
313         o_argv = argv;
314 
315         switch (idby) {
316         case II_PHY_IDX:
317                 command_idby = CIB_PHY;
318                 devidx = strtoul(*argv + 4, &tmp, 0);
319                 if (*tmp != '\0')
320                         return 1;
321                 argc--;
322                 argv++;
323                 break;
……
351 
352         section = *argv;
353         argc--;
354         argv++;
355 
356         for_each_cmd(sectcmd) {
357                 if (sectcmd->parent)
358                         continue;
359                 /* ok ... bit of a hack for the dupe 'info' section */
360                 if (match && sectcmd->idby != command_idby)
361                         continue;
362                 if (strcmp(sectcmd->name, section) == 0)
363                         match = sectcmd;
364         }
365 
366         sectcmd = match;
367         match = NULL;
368         if (!sectcmd)
369                 return 1;
        这段代码,一直到351行的理解都不是很难,唯一需要注意的是,在switch语句中,对argc和argv进行了操作。
        执行命令 iw dev mon0 set channel 11,当到达352行的时候,section的值就已经指向了set字符串。
        下面就重点看看这个for_each_cmd()宏。这个宏同样定义在iw.c中:
[iw.c] 
 88 
 89 #define for_each_cmd(_cmd)                                      \
 90         for (_cmd = &__start___cmd; _cmd < &__stop___cmd;               \
 91              _cmd = (const struct cmd *)((char *)_cmd + cmd_size))
 92 
 93
        这里的cmd_size,已经在上面介绍过了。__start___cmd和__stop___cmd是如何来的,在前奏中也能够非常清楚的知道。这个宏,就可以非常方便地遍历在__cmd段中保存的所有_cmd信息,并在代码中得到比较恰当的应用。总之,对于这个宏,清楚了__start___cmd和cmd_size,其他的,都是比较一般的内容了。对于保存在段__cmd中的命令,后面将会给出具体的介绍。
        __handle_cmd()的357行说明,sectcmd的parent必须为非空,也就是,sectcmd必须存在一个parent。其第360行说明,其idby必须与command_idby相同,也就是说,对于测试命令,idby必须是II_NETDEV了。第362行的功能,是根据操作名称set来查找匹配的命令。从代码的结构和严谨程度上来看,在执行363行的时候,代码就可以跳出for循环了,因为若后续还有命令匹配362行,但是在下一次循环的第360行就已经跳过了本次循环,即,356~364行的功能是在__cmd段找到第一个满足对于测试命令来说的“set”操作的命令。
        第366行将查找到的match进行保存。
[iw.c]
370 
371         if (argc > 0) {
372                 command = *argv;
373 
374                 for_each_cmd(cmd) {
375                         if (!cmd->handler)
376                                 continue;
377                         if (cmd->parent != sectcmd)
378                                 continue;
379                         /*
380                          * ignore mismatch id by, but allow WDEV
381                          * in place of NETDEV
382                          */
383                         if (cmd->idby != command_idby &&
384                             !(cmd->idby == CIB_NETDEV &&
385                               command_idby == CIB_WDEV))
386                                 continue;
387                         if (strcmp(cmd->name, command))
388                                 continue;
389                         if (argc > 1 && !cmd->args)
390                                 continue;
391                         match = cmd;
392                         break;
393                 }
394 
395                 if (match) {
396                         argc--;
397                         argv++;
398                 }
399         }
400
        这一段代码的查找结果是父命令是上一次查找的sectcmd,命令标识符与期望相同,并且命令名称(对于当前测试来说,这个名称是channel)是期望的cmd指针。从这个for循环来看,这里的代码就没有上一次循环中的浪费问题了,在找到匹配结果之后,代码就乖乖地break出来了。
[iw.c]
400 
401         if (match)
402                 cmd = match;
403         else {
404                 /* Use the section itself, if possible. */
405                 cmd = sectcmd;
406                 if (argc && !cmd->args)
407                         return 1;
408                 if (cmd->idby != command_idby &&
409                     !(cmd->idby == CIB_NETDEV && command_idby == CIB_WDEV))
410                         return 1;
411                 if (!cmd->handler)
412                         return 1;
413         }
414 
415         if (cmd->selector) {
416                 cmd = cmd->selector(argc, argv);
417                 if (!cmd)
418                         return 1;
419         }
420 
421         if (cmdout)
422                 *cmdout = cmd;
423 
424         if (!cmd->cmd) {
425                 argc = o_argc;
426                 argv = o_argv;
427                 return cmd->handler(state, NULL, NULL, argc, argv, idby);
428         }
429
        这部分代码比较简单,不多说明了。
[iw.c]
429 
430         msg = nlmsg_alloc();
431         if (!msg) {
432                 fprintf(stderr, "failed to allocate netlink message\n");
433                 return 2;
434         }
435 
436         cb = nl_cb_alloc(iw_debug ? NL_CB_DEBUG : NL_CB_DEFAULT);
437         s_cb = nl_cb_alloc(iw_debug ? NL_CB_DEBUG : NL_CB_DEFAULT);
438         if (!cb || !s_cb) {
439                 fprintf(stderr, "failed to allocate netlink callbacks\n");
440                 err = 2;
441                 goto out_free_msg;
442         }
443 
444         genlmsg_put(msg, 0, 0, state->nl80211_id, 0,
445                     cmd->nl_msg_flags, cmd->cmd, 0);
446 
447         switch (command_idby) {
448         case CIB_PHY:
449                 NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, devidx);
450                 break;
451         case CIB_NETDEV:
452                 NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, devidx);
453                 break;
454         case CIB_WDEV:
455                 NLA_PUT_U64(msg, NL80211_ATTR_WDEV, devidx);
456                 break;
457         default:
458                 break;
459         }
460 
461         err = cmd->handler(state, cb, msg, argc, argv, idby);
462         if (err)
463                 goto out;
464 
465         nl_socket_set_cb(state->nl_sock, s_cb);
466 
467         err = nl_send_auto_complete(state->nl_sock, msg);
468         if (err < 0)
469                 goto out;
470 
471         err = 1;
472 
473         nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
474         nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);
475         nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err);
476 
477         while (err > 0)
478                 nl_recvmsgs(state->nl_sock, cb);
479  out:
480         nl_cb_put(cb);
481  out_free_msg:
482         nlmsg_free(msg);
483         return err;
484  nla_put_failure:
485         fprintf(stderr, "building message failed\n");
486         return 2;
487 }

        这段代码主体上是在为执行netlink做准备,忽略与netlink有关的部分,重点就剩下461行的handler的执行了。

        到目前为止,iw的主体执行框架应该比较清楚了,下面来分析一下handler指针来源和针对示例(iw dev mon0 set channel 11命令)的后续执行部分。

cmd结构体及其相关的操作

        cmd结构体,应该来说,是iw的核心部分了,它实现了框架与具体功能的衔接,借助于一堆宏和其实现的技巧,其完美的完成了上面的目标。
[iw.h]
 40 
 41 struct cmd {
 42         const char *name;
 43         const char *args;
 44         const char *help;
 45         const enum nl80211_commands cmd;
 46         int nl_msg_flags;
 47         int hidden;
 48         const enum command_identify_by idby;
 49         /*
 50          * The handler should return a negative error code,
 51          * zero on success, 1 if the arguments were wrong
 52          * and the usage message should and 2 otherwise.
 53          */
 54         int (*handler)(struct nl80211_state *state,
 55                        struct nl_cb *cb,
 56                        struct nl_msg *msg,
 57                        int argc, char **argv,
 58                        enum id_input id);
 59         const struct cmd *(*selector)(int argc, char **argv);
 60         const struct cmd *parent;
 61 };
 62 
 63 #define ARRAY_SIZE(ar) (sizeof(ar)/sizeof(ar[0]))
 64 #define DIV_ROUND_UP(x, y) (((x) + (y - 1)) / (y))
 65 
 66 #define __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel)\
 67         static struct cmd                                               \
 68         __cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden\
 69         __attribute__((used)) __attribute__((section("__cmd"))) = {     \
 70                 .name = (_name),                                        \
 71                 .args = (_args),                                        \
 72                 .cmd = (_nlcmd),                                        \
 73                 .nl_msg_flags = (_flags),                               \
 74                 .hidden = (_hidden),                                    \
 75                 .idby = (_idby),                                        \
 76                 .handler = (_handler),                                  \
 77                 .help = (_help),                                        \
 78                 .parent = _section,                                     \
 79                 .selector = (_sel),                                     \
 80         }
 81 #define __ACMD(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel, _alias)\
 82         __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel);\
 83         static const struct cmd *_alias = &__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden
 84 #define COMMAND(section, name, args, cmd, flags, idby, handler, help)   \
 85         __COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, NULL)
 86 #define COMMAND_ALIAS(section, name, args, cmd, flags, idby, handler, help, selector, alias)\
 87         __ACMD(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, selector, alias)
 88 #define HIDDEN(section, name, args, cmd, flags, idby, handler)          \
 89         __COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 1, idby, handler, NULL, NULL)
 90 
 91 #define TOPLEVEL(_name, _args, _nlcmd, _flags, _idby, _handler, _help)  \
 92         struct cmd                                                      \
 93         __section ## _ ## _name                                         \
 94         __attribute__((used)) __attribute__((section("__cmd"))) = {     \
 95                 .name = (#_name),                                       \
 96                 .args = (_args),                                        \
 97                 .cmd = (_nlcmd),                                        \
 98                 .nl_msg_flags = (_flags),                               \
 99                 .idby = (_idby),                                        \
100                 .handler = (_handler),                                  \
101                 .help = (_help),                                        \
102          }
103 #define SECTION(_name)                                                  \
104         struct cmd __section ## _ ## _name                              \
105         __attribute__((used)) __attribute__((section("__cmd"))) = {     \
106                 .name = (#_name),                                       \
107                 .hidden = 1,                                            \
108         }
109 
110 #define DECLARE_SECTION(_name)                                          \
111         extern struct cmd __section ## _ ## _name;
        在这里,不对cmd结构体的成员进行说明,这里重点阐述一下这里面的几个宏。
        1. __COMMAND宏
        这个宏是在几个宏里面,算是比较基础的一个宏了,它接收并重置了cmd结构体的所有成员。创造出这个宏的目的,不在于对成员进行初始化,请看68行和69行。第68行,它使用##创建一个“比较”唯一的变量。第69行是iw中使用的一个重点编程技巧,used属性没什么可说的,就是让编译器不再提供对应的警告罢了,section属性比较有意思,这一属性的作用是将这个属性修饰的变量存放在指定的段中,当然啦,section属性只对全局变量有意义,对于局部变量,当然没用的啦。
        2. __ACMD宏
        这个宏没有什么实际含义,调用__COMMAND宏,并创建一个指向对应空间的新的指针。
        3. COMMAND宏
        这个宏在代码中使用的最多。这个宏没什么特别之处,只是对cmd结构体中的parent进行了一次封装。

        对于COMMAND_ALIAS、HIDDEN、TOPLEVEL的理解,也没有什么难点,这里就不再一一说明了。

        测试用的命令在phy.c中进行了定义,
[iw.c->phy.c]
 90
 91 static int handle_freqchan(struct nl_msg *msg, bool chan,
 92                            int argc, char **argv)
 93 {
 94         char *end;
 95         static const struct {
 96                 const char *name;
 97                 unsigned int val;
 98         } htmap[] = {
 99                 { .name = "HT20", .val = NL80211_CHAN_HT20, },
100                 { .name = "HT40+", .val = NL80211_CHAN_HT40PLUS, },
101                 { .name = "HT40-", .val = NL80211_CHAN_HT40MINUS, },
102         };
103         unsigned int htval = NL80211_CHAN_NO_HT;
104         unsigned int freq;
105         int i;
106 
107         if (!argc || argc > 4)
108                 return 1;
109 
110         if (!*argv[0])
111                 return 1;
112         freq = strtoul(argv[0], &end, 10);
113         if (*end)
114                 return 1;
115 
116         if (chan) {
117                 enum nl80211_band band;
118                 band = freq <= 14 ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ;
119                 freq = ieee80211_channel_to_frequency(freq, band);
120         }
121 
122         NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq);
123 
124         if (argc > 2) {
125                 return handle_freqs(msg, argc - 1, argv + 1);
126         } else if (argc == 2) {
127                 for (i = 0; i < ARRAY_SIZE(htmap); i++) {
128                         if (strcasecmp(htmap[i].name, argv[1]) == 0) {
129                                 htval = htmap[i].val;
130                                 break;
131                         }
132                 }
133                 if (htval == NL80211_CHAN_NO_HT)
134                         return handle_freqs(msg, argc - 1, argv + 1);
135         }
136 
137         NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE, htval);
138 
139         return 0;
140  nla_put_failure:
141         return -ENOBUFS;
142 }
143 
144 static int handle_freq(struct nl80211_state *state,
145                        struct nl_cb *cb, struct nl_msg *msg,
146                        int argc, char **argv,
147                        enum id_input id)
148 {
149         return handle_freqchan(msg, false, argc, argv);
150 }
151 COMMAND(set, freq, "<freq> [HT20|HT40+|HT40-]",
152         NL80211_CMD_SET_WIPHY, 0, CIB_PHY, handle_freq,
153         "Set frequency/channel the hardware is using, including HT\n"
154         "configuration.");
155 COMMAND(set, freq, "<freq> [HT20|HT40+|HT40-]\n"
156         "<control freq> [20|40|80|80+80|160] [<center freq 1>] [<center freq 2>]",
157         NL80211_CMD_SET_WIPHY, 0, CIB_NETDEV, handle_freq, NULL);
158 
159 static int handle_chan(struct nl80211_state *state,
160                        struct nl_cb *cb, struct nl_msg *msg,
161                        int argc, char **argv,
162                        enum id_input id)
163 {
164         return handle_freqchan(msg, true, argc, argv);                                                                                                
165 }
166 COMMAND(set, channel, "<channel> [HT20|HT40+|HT40-]",
167         NL80211_CMD_SET_WIPHY, 0, CIB_PHY, handle_chan, NULL);
168 COMMAND(set, channel, "<channel> [HT20|HT40+|HT40-]",
169         NL80211_CMD_SET_WIPHY, 0, CIB_NETDEV, handle_chan, NULL);
        上面的代码是处理修改channel部分的代码,从中可以知道,修改channel也可以通过freq参数实现。在166行和168行使用COMMAND宏定义的这个命令中,这个命令的parent是__section_set,命令的名称叫做channel,后续的字符串是其需要的参数,回调函数是handle_chan,这个回调函数,也就是在__handle_cmd()函数中第461行进行调用使用的。
        通过阅读handle_freqchan()函数,不难发现,这个函数只是对nl_msg进行了必要的设置处理,并没有与其他模块存在实质性的沟通,亦即,cmd结构体的hander成员完成的是想nl_msg填充必要的信息,接着由__handle_cmd()完成后续的工作。

        至此,iw的执行过程应该比较清楚了。具体内核部分与之匹配的响应,将会在驱动代码的解读中进行详细介绍。
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值