1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > netlink实现驱动和应用层通信

netlink实现驱动和应用层通信

时间:2018-12-03 03:43:12

相关推荐

netlink实现驱动和应用层通信

1、netlink及相关接口说明

说到驱动和应用层进行数据通信,很多人就会想到使用传统的ioctl,诚然,很多时候应用层调用驱动进行参数配置和参数获取使用的也正是ioctl的方式,但这种方式有一种局限性,那就是只适合于数据量较小且操作不频繁的情况。否则,频繁操作io将大大影响设备的性能,甚至造成设备宕机。

最近在做一个关于wifi诊断需求时,需要记录wifi连接过程中的控制帧和管理帧,以及STA断开及断开原因。由于wifi驱动不断在进行数据帧和控制帧的交互,如果直接在驱动中操作io写文件,wifi性能将收到很大影响。后来相到了使用netlink进行数据传输,将数据传输到应用层进行log的记录。

下面简单介绍下linux下netlink相关的接口,由于我使用的内核版本是linux-lsk-v4.1.25,接口主要介绍该版本接口,代码实例中会进行版本兼容,需要连接其他版本的请自行跟读相关版本源码。

1.1、netlink_kernel_create接口

netlink_kernel_create接口的定义在lnclude/linux/netlink.h中,可以看出他是真身实际上是__netlink_kernel_create,具体我们不需要过多关心,我们先看看他的参数和返回值情况。

static inline struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg){return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);}

第一个参数(struct net)是一个网络的namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等,默认情况下都是使用 init_net这个全局变量。

第二个参数(int unit)实际上是当前使用的实例号,由于Netlink本身是linux的一个部分,linux内核中的很多和应用层的交互也使用过netlink(比如防火墙iptables)。因此部分协议号实际已经被占用,我们只能从之后的协议号中挑选能使用的协议号,否则将造成协议冲突。协议号的定义在include/uapi/linux/netlink.h中,如下。

#define NETLINK_ROUTE0/* Routing/device hook*/#define NETLINK_UNUSED1/* Unused number*/#define NETLINK_USERSOCK2/* Reserved for user mode socket protocols */#define NETLINK_FIREWALL3/* Unused number, formerly ip_queue*/#define NETLINK_SOCK_DIAG4/* socket monitoring*/#define NETLINK_NFLOG5/* netfilter/iptables ULOG */#define NETLINK_XFRM6/* ipsec */#define NETLINK_SELINUX7/* SELinux event notifications */#define NETLINK_ISCSI8/* Open-iSCSI */#define NETLINK_AUDIT9/* auditing */#define NETLINK_FIB_LOOKUP10#define NETLINK_CONNECTOR11#define NETLINK_NETFILTER12/* netfilter subsystem */#define NETLINK_IP6_FW13#define NETLINK_DNRTMSG14/* DECnet routing messages */#define NETLINK_KOBJECT_UEVENT15/* Kernel messages to userspace */#define NETLINK_GENERIC16/* leave room for NETLINK_DM (DM Events) */#define NETLINK_SCSITRANSPORT18/* SCSI Transports */#define NETLINK_ECRYPTFS19#define NETLINK_RDMA20#define NETLINK_CRYPTO21/* Crypto layer */#define NETLINK_INET_DIAGNETLINK_SOCK_DIAG#define MAX_LINKS 32

从上述代码中可以看出,协议号最大支持到32,目前0~21已经被占用,所以实际我们能选择的只有22-31之间的某一个数。

第三个参数(struct netlink_kernel_cfg *cfg)是关于netlink的相关参数的封装,之前的版本中该结构体实际上是分成好几个参数实现的,具体见之后的实例。该结构体如下,我们主要关注其中注释部分。

struct netlink_kernel_cfg {unsigned intgroups; //多播组,一般多播的时候设置成1,单播设置成0unsigned intflags;void(*input)(struct sk_buff *skb); //回调函数,内核收到应用层发过来的msg时会调用此函数。struct mutex*cb_mutex;int(*bind)(struct net *net, int group);void(*unbind)(struct net *net, int group);bool(*compare)(struct net *net, struct sock *sk);};

netlink_kernel_create接口的返回值是一个socket描述符,类似于socket编程中返回的socket,主要用于msg 发送时的参入参数,便于与应用层套接字接口建立连接。

1.2、netlink_unicast接口

netlink_unicast接口如下,几个参数说明见接口下方:

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,u32 portid, int nonblock)

ssk: sock结构体指针,netlink_kernel_create返回的套接字描述符skb: skb存放消息,它的data字段指向要发送的 netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块portid: 端口id,与应用层对应,一般是应用层对应的进程的进程pidnonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利 用时睡眠返回: 发送数据的长度

1.3、netlink_broadcast接口

netlink_broadcast接口如下,几个参数说明见接口下方:

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,u32 group, gfp_t allocation)

ssk: sock结构体指针,netlink_kernel_create返回的套接字描述符skb: skb存放消息,它的data字段指向要发送的 netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块portid: 端口id,一般是应用层对应的进程的进程pidgroup: netlink多播组allocation: 内核内存分配类型,一般地为GFP_ATOMIC或 GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文返回: 发送数据的长度

2、netlink编程实现

2.1、应用编程

#include <stdio.h> #include <string.h> #include <malloc.h> #include <sys/socket.h> #include <sys/ioctl.h>#include <ctype.h>#include <sys/select.h>#include <netinet/in.h>#include <linux/types.h>#include <linux/if.h>#include <linux/netlink.h>#include <stdlib.h>#include <linux/wireless.h>#define MAX_PAYLOAD 2048 /* maximum payload size*/#ifndef NETLINK_CTCWIFI_DIAG_2G#define NETLINK_CTCWIFI_DIAG_2G 22#endifint main(int argc, char *argv[]){int state;struct sockaddr_nl src_addr, dest_addr;struct nlmsghdr *nlh = NULL;struct iovec iov;struct msghdr msg;int sock_fd,retval,protocol;int state_smg = 0;T_CTCWIFI_DIAG_EVENT event; //T_CTCWIFI_DIAG_EVENT 为自己定义的数据结构体类型,这里不方便给出。sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_CTCWIFI_DIAG_2G );if(sock_fd == -1){printf("socket fail,Mesg is %s\n",strerror(errno));exit(errno);}memset(&src_addr, 0, sizeof(src_addr));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = getpid(); /* self pid */src_addr.nl_groups = 0; /*单播设置成0*/retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));if (retval < 0){printf("bind error,Mesg is %s\n",strerror(errno));close(sock_fd);exit(errno);}nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));if(nlh == NULL) {close(sock_fd);return -1;}memset(&dest_addr, 0, sizeof(dest_addr));dest_addr.nl_family = AF_NETLINK;dest_addr.nl_pid = 0; dest_addr.nl_groups = 0;memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);nlh->nlmsg_pid = getpid();nlh->nlmsg_flags = 0;strcpy(NLMSG_DATA(nlh),"Hello Kernel!");memset(&iov,0,sizeof(iov));iov.iov_base = (void *)nlh;iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);memset(&msg, 0, sizeof(msg));msg.msg_name = (void *)&dest_addr;msg.msg_namelen = sizeof(dest_addr);msg.msg_iov = &iov;msg.msg_iovlen = 1;state_smg = sendmsg(sock_fd,&msg,0); //向内核发送"Hello Kernel!"便于内核获取应用层进程的pid号。if(state_smg == -1){printf("sendmsg error,Mesg is %s\n",strerror(errno));}while(1){/* Read message from kernel */memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));state = recvmsg(sock_fd, &msg, 0);if(state < 0){printf("recvmsg error,Mesg is %s\n",strerror(errno));}memcpy(&event, NLMSG_DATA(nlh), sizeof(event));/*handle kernel msg*//*handle kernel msg*//*handle kernel msg*/}free(nlh);close(sock_fd);return 0;}

2.2、内核编程

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/types.h>#include <linux/sched.h>#include <net/sock.h>#include <linux/netlink.h>#define MAX_PAYLOAD 2048static struct sock *ctcwifi_netlink_fd = NULL;#ifndef NETLINK_CTCWIFI_DIAG_2G#define NETLINK_CTCWIFI_DIAG_2G 22#endifunsigned int pid;MODULE_LICENSE("GPL"); MODULE_AUTHOR("zouchun"); MODULE_DESCRIPTION("netlink_ctcwifi_diag");void ctcwifi_diag_netlink_send(T_CTCWIFI_DIAG_EVENT *event, char *event_data, unsigned int event_datalen){struct sk_buff *skb = NULL;struct nlmsghdr *nlh;char * msg_event_data;if ((event == NULL) || (ctcwifi_netlink_fd == NULL))return;skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC);if (NULL == skb) return;skb_put(skb, NLMSG_SPACE(MAX_PAYLOAD));nlh = (struct nlmsghdr *)skb->data;memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);nlh->nlmsg_pid = 0;/* from kernel */nlh->nlmsg_flags = 0;event->datalen = 0;if(event_data) {msg_event_data = (char*)(((T_CTCWIFI_DIAG_EVENT *)NLMSG_DATA(nlh)) + 1);memcpy(msg_event_data, event_data, event_datalen);event->datalen = event_datalen;}event->datalen += sizeof(T_CTCWIFI_DIAG_EVENT);memcpy(NLMSG_DATA(nlh), event, sizeof(*event));#if LINUX_VERSION_CODE >= KERNEL_VERSION (3,10,0)NETLINK_CB(skb).portid = 0;/* from kernel */#elseNETLINK_CB(skb).pid = 0; /* from kernel */#endifNETLINK_CB(skb).dst_group = 0;/*unicast the message*/netlink_unicast(ctcwifi_netlink_fd, skb, pid, MSG_DONTWAIT);}#if LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,24)static void ctcwifi_diag_nl_receive(struct sk_buff *__skb)#elsestatic void ctcwifi_diag_nl_receive(struct sk_buff *__skb,int len)#endif{struct sk_buff *skb;struct nlmsghdr *nlh = NULL;unsigned char *data = NULL;unsigned int uid, seq;#if LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,24)skb = skb_get(__skb);#elseskb = skb_dequeue(&sk->sk_receive_queue);#endifif (skb->len >= NLMSG_SPACE(0)){nlh = nlmsg_hdr(skb);data = NLMSG_DATA(nlh);pid = NETLINK_CREDS(skb)->pid;uid = NETLINK_CREDS(skb)->uid.val;seq = nlh->nlmsg_seq;printk("recv skb from user space uid:%d pid:%d seq:%d,\n",uid,pid,seq);printk("data is :%s\n",(char *)data);}kfree_skb(skb);}static int __init wlan_ctcwifi_init(void){#if LINUX_VERSION_CODE >= KERNEL_VERSION (3,10,0)struct netlink_kernel_cfg cfg;memset(&cfg, 0, sizeof(cfg));cfg.input = &ctcwifi_diag_nl_receive;ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(&init_net, NETLINK_CTCWIFI_DIAG_2G, &cfg);#elif LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,24)ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(&init_net, NETLINK_CTCWIFI_DIAG_2G,1,ctcwifi_diag_nl_receive,NULL,THIS_MODULE);#elif LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,22)ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(NETLINK_CTCWIFI_DIAG_2G,1,ctcwifi_diag_nl_receive,NULL,THIS_MODULE);#elsectcwifi_netlink_fd = (struct sock *)netlink_kernel_create(NETLINK_CTCWIFI_DIAG_2G,1,ctcwifi_diag_nl_receive,THIS_MODULE);#endifif(!ctcwifi_netlink_fd){printk(KERN_ERR "can not create a netlink socket\n");return -1;}return 0;}static void __exit wlan_ctcwifi_exit(void){if (ctcwifi_netlink_fd) {sock_release(ctcwifi_netlink_fd->sk_socket);printk(KERN_DEBUG "test_netlink_exit!!\n");}}module_init(wlan_ctcwifi_init);module_exit(wlan_ctcwifi_exit);

注:由于协议号需要调用netlink_kernel_create后方可注册到内核,因此使用时应提供机制保证驱动在应用层之前先启动,否则会出现报错:当前协议不支持。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。