0


【WiFi软件开发】IOCTL和Netlink----用户空间和内核空间交互的两种方式

文章目录


前言

linux开发中通常会涉及用户空间和内核模块的交互。以WiFi软件开发为例,

hostapd系列、iw、cfg80211tool、iwpriv、iwconfig、ifconfig

等一系列WiFi相关的应用均会和内核模块产生通信和交互,而从通信方式上划分,通常可分为

IOCTL

Netlink

两种方式,如下所示:

  • IOCTL:hostapd系列(可以用ioctl进行开发),iwpriv,iwconfig,ifconfig
  • Netlink:hostapd系列(默认方式是netlink),iw,cfg80211tool等

一、IOCTL

ioctl

本质上为一种系统调用,是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、网口的配置等等。

read/write

函数相比,他们都可以往内核中写数据,但是

read

函数只能完成读的功能,

write

函数只能完成写的功能,而

ioctl

既可以读也可以写,当然在读取大数据时,

ioctl

的效率不及

read/write

函数。下文给出

ioctl

的实现和使用流程:

1.定义IOCTL命令

在内核模块中,需要使用宏定义你的

IOCTL

命令。通常情况下,

IOCTL

命令包括了一个命令编号、请求类型的方向(读/写/两者)以及数据大小:

**linux-5.4.196/include/uapi/linux/sockios.h**/* Socket configuration controls. */#defineSIOCGIFNAME0x8910/* get iface name        */#defineSIOCSIFLINK0x8911/* set iface channel        */#defineSIOCGIFCONF0x8912/* get iface list        */#defineSIOCGIFFLAGS0x8913/* get flags            */#defineSIOCSIFFLAGS0x8914/* set flags            */#defineSIOCGIFADDR0x8915/* get PA address        */#defineSIOCSIFADDR0x8916/* set PA address        */#defineSIOCGIFDSTADDR0x8917/* get remote PA address    */#defineSIOCSIFDSTADDR0x8918/* set remote PA address    */#defineSIOCGIFBRDADDR0x8919/* get broadcast PA address    */#defineSIOCSIFBRDADDR0x891a/* set broadcast PA address    */#defineSIOCGIFNETMASK0x891b/* get network PA mask        */#defineSIOCSIFNETMASK0x891c/* set network PA mask        */#defineSIOCGIFMETRIC0x891d/* get metric            */#defineSIOCSIFMETRIC0x891e/* set metric            */#defineSIOCGIFMEM0x891f/* get memory address (BSD)    */#defineSIOCSIFMEM0x8920/* set memory address (BSD)    */#defineSIOCGIFMTU0x8921/* get MTU size            */#defineSIOCSIFMTU0x8922/* set MTU size            */#defineSIOCSIFNAME0x8923/* set interface name */#defineSIOCSIFHWADDR0x8924/* set hardware address     */
**linux-5.4.196/arch/mips/include/uapi/asm/sockios.h**#defineFIOGETOWN_IOR('f',123,int)#defineFIOSETOWN_IOW('f',124,int)#defineSIOCATMARK_IOR('s',7,int)#defineSIOCSPGRP_IOW('s',8,pid_t)#defineSIOCGPGRP_IOR('s',9,pid_t)#defineSIOCGSTAMP_OLD0x8906/* Get stamp (timeval) */#defineSIOCGSTAMPNS_OLD0x8907/* Get stamp (timespec) */

2.用户空间调用IOCTL

wld_linuxIfUtils_setMac(wld_rad_getSocket(pRad), intfName, macAddress);//sock最终调用socket(AF_INET, SOCK_DGRAM, 0);intwld_linuxIfUtils_setMac(int sock,char* intfName,swl_macBin_t* macInfo){...int ret =ioctl(sock, SIOCSIFHWADDR,&ifr);//调用ioctl   ...}

3.内核实现IOCTL的系统调用

在用户空间使用

ioctl

函数时,会使得系统从用户态trap到内核态,即调用到内核态的

sys_ioctl

函数。调用流程如下:

**linux-5.4.196/fs/ioctl.c**SYSCALL_DEFINE3(ioctl,unsignedint, fd,unsignedint, cmd,unsignedlong, arg)ksys_ioctl(fd, cmd, arg);structfd f =fdget(fd);//获取fddo_vfs_ioctl(f.file, fd, cmd, arg);vfs_ioctl(filp, cmd, arg);
                filp->f_op->unlocked_ioctl(filp, cmd, arg);// .unlocked_ioctl = dev_ioctl,intdev_ioctl(structnet*net,unsignedint cmd,structifreq*ifr, bool *need_copyout)//  linux-5.4.196/net/core/dev_ioctl.cdev_ifsioc(net, ifr, cmd);dev_set_mac_address_user(dev,&ifr->ifr_hwaddr,NULL);//对于SIOCSIFHWADDR命令dev_set_mac_address(dev, sa, extack);conststructnet_device_ops*ops = dev->netdev_ops;
                                    ops->ndo_set_mac_address(dev, sa);//调用网卡驱动的net_device_ops结构体下的成员函数进行操作

上面讲的

dev

变量是

struct net_device

类型,而

struct net_device

在内核中表示我们的一个网卡驱动设备,注册该变量的文件都处于内核

drivers/net

目录下,通过

register_netdev()

内核函数来注册。

本质上来说,

ioctl

最终是调用

file_operations->unlocked_ioctl

,进而调用网卡驱动中注册的函数实现的。
(注:

read/write

函数本质上也是调用

file_operations->write/read

二、Netlink

Netlink

本质上是

socket

。它是一种

IPC(Inter Process Commumicate)

机制,用于内核与用户空间通信的机制,同时它也以用于进程间通信(

Netlink

更多用于内核通信,进程之间通信更多使用Unix域套接字)。

在一般情况下,用户态和内核态通信会使用传统的

Ioctl

sysfs

属性文件或者

procfs

属性文件,这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。

Netlink

是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为

Netlink

通信提供了一组特殊的API接口,用户态则基于

socket API

,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。

Netlink

具有以下优点:

  • 双向全双工异步传输,支持由内核主动发起传输通信,而不需要用户空间出发(例如使用ioctl这类的单工方式)。如此用户空间在等待内核某种触发条件满足时就无需不断轮询,而异步接收内核消息即可。
  • 支持组播传输,即内核态可以将消息发送给多个接收进程,这样就不用每个进程单独来查询了。

下文以

hostapd

和内核的通信为例,对

Netlink

的通信流程进行分析:

1.内核中初始化Netlink

创建

netlink socket

和接收函数:

**linux-5.4.196/net/netlink/genetlink.c**staticint __net_init genl_pernet_init(structnet*net){structnetlink_kernel_cfg cfg ={.input        = genl_rcv,//定义接收函数.flags        = NL_CFG_F_NONROOT_RECV,};/* we'll bump the group number right afterwards */
    net->genl_sock =netlink_kernel_create(net, NETLINK_GENERIC,&cfg);//创建netlink socketif(!net->genl_sock &&net_eq(net,&init_net))panic("GENL: Cannot initialize generic netlink\n");if(!net->genl_sock)return-ENOMEM;return0;}
netlink

命令注册:

nl80211

main

函数注册和初始化了

nl80211_fam

结构,其中就包含了

op

字段,值为

nl80211_ops

。之后又初始化了

struct genl_ops nl80211_ops[]

,数组定义了命令和对应的钩子函数。上层通过

netlink socket

通信发送命令,

nl80211

中执行对应的函数。

**linux-5.4.196/net/wireless/nl80211.c**staticconststructgenl_ops nl80211_ops[]={...{.cmd = NL80211_CMD_SET_CHANNEL,.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,.doit = nl80211_set_channel,.flags = GENL_UNS_ADMIN_PERM,.internal_flags = NL80211_FLAG_NEED_NETDEV,},...}

2.内核中接收消息

如上,内核使用

socket

建立时声明的接收函数

.input = genl_rcv,

进行消息接收,最终调用nl80211_ops表中注册的函数,流程如下:

**linux-5.4.196/net/netlink/genetlink.c**staticvoidgenl_rcv(structsk_buff*skb)netlink_rcv_skb(skb,&genl_rcv_msg);genl_family_rcv_msg(family, skb, nlh, extack);
            err = ops->doit(skb,&info);//执行nl80211_ops表中对应的doit函数

3.hostapd中Netlink的初始化

hostapd

中想要通过

netlink

发送消息,同样需要先对

netlink

进行初始化:

**hostapd-2022-07-29-b704dc72/src/drivers/driver_nl80211.c**

nl80211_global_init
    global->netlink =netlink_init(cfg);//  struct netlink_data *netlink;
        netlink->sock =socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);bind(netlink->sock,(structsockaddr*)&local,sizeof(local))eloop_register_read_sock(netlink->sock, netlink_receive, netlink,NULL);wpa_driver_nl80211_init_nl_global(global)
        global->nl =nl_create_handle(global->nl_cb,"nl");//创建新的 struct nl_sock *nl;
            handle =nl_socket_alloc_cb(cb);//创建新的 struct nl_sock *nl;genl_connect(handle)//连接内核nl_connect(sk, NETLINK_GENERIC);//调用libnl库中函数,连接内核

4.hostapd中消息的发送

基于新建的

netlink socket

hostapd

可以和驱动进行双向通信:

**hostapd-2022-07-29-b704dc72/src/drivers/driver_nl80211.c**

nl80211_set_channel
    msg =nl80211_drv_msg(drv,0, set_chan ? NL80211_CMD_SET_CHANNEL : NL80211_CMD_SET_WIPHY);
    ret =send_and_recv_msgs(drv, msg,NULL,NULL,NULL,NULL);/*通过建立的socket drv->global->nl发送消息*/send_and_recv(drv->global, drv->global->nl, msg, valid_handler, valid_data, ack_handler_custom, ack_data);            
            setsockopt
            setsockopt
            err =nl_send_auto_complete(nl_handle, msg);//调用libnl库中函数,发送msg
            res =nl_recvmsgs(nl_handle, cb);//调用libnl库中函数,接收

总结

以上就是对

IOCTL

Netlink

实现和使用的介绍,在linux和WiFi开发中我们会经常接触到这两种方式用于用户空间和内核的通信。

学习链接:

构建自己的ioctl:
https://blog.csdn.net/eidolon_foot/article/details/135575367
https://cloud.tencent.com/developer/article/1431907
https://blog.csdn.net/qq_32276547/article/details/130181646
https://blog.csdn.net/u010571709/article/details/117632568

构建自己的netlink:
https://blog.csdn.net/eidolon_foot/article/details/135575367
https://blog.csdn.net/cleanfield/article/details/135952862

系统调用:
https://blog.csdn.net/weixin_45264425/article/details/136820917
https://blog.csdn.net/qq_43142509/article/details/124600228

file_operations文件操作结构体:
https://zhuanlan.zhihu.com/p/666583468
https://blog.csdn.net/yusiguyuan/article/details/11352155

其他:
https://zhuanlan.zhihu.com/p/703570419
https://blog.csdn.net/shujuliu818/article/details/122491924
https://blog.csdn.net/zhoucl123/article/details/131528131

标签: c语言 linux 学习

本文转载自: https://blog.csdn.net/liuwe1ye/article/details/139698174
版权归原作者 Liuwe1Ye 所有, 如有侵权,请联系我们删除。

“【WiFi软件开发】IOCTL和Netlink----用户空间和内核空间交互的两种方式”的评论:

还没有评论