文章目录
前言
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
版权归原作者 Liuwe1Ye 所有, 如有侵权,请联系我们删除。