由于我调试的系统是基于LSDK的,开发按键功能时,移植了hotplug,但是内核上报了相关的事件,但是应用层没有监控。于是,自己参照写了一个netlink的应用层接口。(openwrt是用procd实现的,已经完成了一整套的接口)
Linux-3.10.36版本的内核相比linux-2.6.36版本中netlink的代码有所变化,以前的代码已经不能成功编译了。
netlink是linux内核的一套基于socket的通信机制,那么,只需要知道怎么创建套接字,发送数据,接收数据就行了。
新的netlink_kernel_create()函数只有3个参数了。
/******************************
net: linux网络命名空间结构体指针
uint: netlink协议类型
cfg: netlink内核配置参数结构体指针
返回: sock结构指针
******************************/
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
第一个参数:传入一个网络命名空间结构的地址,这块我不怎么清楚,只知道linux的网络命名空间的出现是为了让 用户创建的进程能够与系统分离得更加彻底,从而不需要使用更多的底层虚拟化技术。未深究,调用该函数时就传入&init_net吧,它是在net_namespace.c文件中定义的一个结构体。
第二个参数:是代表netlink协议类型,内核目前定义了如下一些类型:
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */
#define NETLINK_SOCK_DIAG 4 /* socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define NETLINK_CRYPTO 21 /* Crypto layer */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
#define MAX_LINKS 32
最多可以定义32中类型,若 uint > MAX_LINKS 则该函数返回NULL,源代码片段如下
__netlink_kernel_create(struct net *net, int unit, struct module *module,
struct netlink_kernel_cfg *cfg)
{
struct socket *sock;
struct sock *sk;
struct netlink_sock *nlk;
struct listeners *listeners = NULL;
struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
unsigned int groups;
BUG_ON(!nl_table);
if (unit < 0 || unit >= MAX_LINKS)
return NULL;
...
}
第三个参数:cfg存放的是netlink内核配置参数,配置参数中的input成员用于处理接收到的消息。该函数的参数变短了,实际是因为放到该结构体中来了
struct netlink_kernel_cfg {
unsigned int groups;// netlink组,应该是多播时使用,单播的情况就是0了
unsigned int flags;
void (*input)(struct sk_buff *skb); //回调函数,当收到消息时,内核会调用该函数指针指向的函数进行处理
struct mutex *cb_mutex;
void (*bind)(int group);
};
单播netlink_unicast() 和 多播netlink_broadcast()
/******************************
ssk: sock结构体指针
skb: skb存放消息,它的data字段指向要发送的 netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块
portid: 端口id
nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利 用时睡眠
返回: 发送数据的长度
******************************/
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
/******************************
ssk: sock结构体指针
skb: skb存放消息,它的data字段指向要发送的 netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块
portid: 端口id
group: netlink组
allocation: 内核内存分配类型,一般地为GFP_ATOMIC或 GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文
返回: 发送数据的长度
******************************/
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
__u32 group, gfp_t allocation);
在调用netlink_kernel_create()函数是已经向cfg结构体中指定了回调函数,这个函数就是接收函数了。内核模块收到消息后会自动调用该函数。
使用标准的socket API即可( socket(), bind(), sendmsg(), recvmsg() 和 close())
示例代码由用户层向内核发送一个字符串,内核打印接收到的消息并且将字符串发送给用户层。
/*******************************
file: k_netlink.c
description: netlink demo
kernel: linux-3.10.36
author: arvik
email: [email protected]
blog: http://blog.csdn.net/u012819339
*******************************/
#include
#include
#include
#include
#include
#include
#include
#define NETLINK_USER 22
#define USER_MSG (NETLINK_USER + 1)
#define USER_PORT 50
MODULE_LICENSE("GPL");
MODULE_AUTHOR("arvik");
MODULE_DESCRIPTION("netlink_demo");
static struct sock *netlinkfd = NULL;
int send_msg(int8_t *pbuf, uint16_t len)
{
struct sk_buff *nl_skb;
struct nlmsghdr *nlh;
int ret;
nl_skb = nlmsg_new(len, GFP_ATOMIC);
if(!nl_skb)
{
printk("netlink_alloc_skb error\n");
return -1;
}
nlh = nlmsg_put(nl_skb, 0, 0, USER_MSG, len, 0);
if(nlh == NULL)
{
printk("nlmsg_put() error\n");
nlmsg_free(nl_skb);
return -1;
}
memcpy(nlmsg_data(nlh), pbuf, len);
ret = netlink_unicast(netlinkfd, nl_skb, USER_PORT, MSG_DONTWAIT);
return ret;
}
static void recv_cb(struct sk_buff *skb)
{
struct nlmsghdr *nlh = NULL;
void *data = NULL;
printk("skb->len:%u\n", skb->len);
if(skb->len >= nlmsg_total_size(0))
{
nlh = nlmsg_hdr(skb);
data = NLMSG_DATA(nlh);
if(data)
{
printk("kernel receive data: %s\n", (int8_t *)data);
send_msg(data, nlmsg_len(nlh));
}
}
}
struct netlink_kernel_cfg cfg =
{
.input = recv_cb,
};
static int __init test_netlink_init(void)
{
printk("init netlink_demo!\n");
netlinkfd = netlink_kernel_create(&init_net, USER_MSG, &cfg);
if(!netlinkfd)
{
printk(KERN_ERR "can not create a netlink socket!\n");
return -1;
}
printk("netlink demo init ok!\n");
return 0;
}
static void __exit test_netlink_exit(void)
{
sock_release(netlinkfd->sk_socket);
printk(KERN_DEBUG "netlink exit\n!");
}
module_init(test_netlink_init);
module_exit(test_netlink_exit);
/*******************************
file: u_netlink.c
description: netlink demo
author: arvik
email: [email protected]
blog: http://blog.csdn.net/u012819339
*******************************/
#include
#include
#include
#include
#include
#include
#include
#include
#define NETLINK_USER 22
#define USER_MSG (NETLINK_USER + 1)
#define MSG_LEN 100
#define MAX_PLOAD 100
struct _my_msg
{
struct nlmsghdr hdr;
int8_t data[MSG_LEN];
};
int main(int argc, char **argv)
{
char *data = "hello kernel";
struct sockaddr_nl local, dest_addr;
int skfd;
struct nlmsghdr *nlh = NULL;
struct _my_msg info;
int ret;
skfd = socket(AF_NETLINK, SOCK_RAW, USER_MSG);
if(skfd == -1)
{
printf("create socket error...%s\n", strerror(errno));
return -1;
}
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = 50;
local.nl_groups = 0;
if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0)
{
printf("bind() error\n");
close(skfd);
return -1;
}
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // to kernel
dest_addr.nl_groups = 0;
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
memset(nlh, 0, sizeof(struct nlmsghdr));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
nlh->nlmsg_flags = 0;
nlh->nlmsg_type = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = local.nl_pid; //self port
memcpy(NLMSG_DATA(nlh), data, strlen(data));
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl));
if(!ret)
{
perror("sendto error1\n");
close(skfd);
exit(-1);
}
printf("wait kernel msg!\n");
memset(&info, 0, sizeof(info));
ret = recvfrom(skfd, &info, sizeof(struct _my_msg), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if(!ret)
{
perror("recv form kernel error\n");
close(skfd);
exit(-1);
}
printf("msg receive from kernel:%s\n", info.data);
close(skfd);
free((void *)nlh);
return 0;
}
/*******************************
file: u_netlink.c
description: netlink button
author: sean
email:
blog: http://blog.csdn.net/bingyu9875
*******************************/
#include
#include
#include
#include
#include
#include
#include
#include
#define NETLINK_USER 22
#define USER_MSG (NETLINK_USER + 1)
#define MSG_LEN 100
#define MAX_PLOAD 100
int main(int argc, char **argv)
{
int skfd = 0, ret = 0;
int button_action = 0;
struct nlmsghdr *nlh = NULL;
static char buf[4096], cmd[100];
struct sockaddr_nl nls;
memset(&nls,0,sizeof(struct sockaddr_nl));
nls.nl_family = AF_NETLINK;
nls.nl_pid = getpid();
nls.nl_groups = -1;
// skfd = socket(AF_NETLINK, SOCK_RAW, USER_MSG);
skfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if(skfd == -1)
{
printf("create socket error...%s\n", strerror(errno));
return -1;
}
if (bind(skfd, (void *)&nls, sizeof(struct sockaddr_nl)))
{
printf("Failed to bind hotplug socket: %s\n", strerror(errno));
return -1;
}
printf("wait kernel msg!\n");
while ( 1 )
{
memset(&buf, 0, sizeof(buf));
ret = recv(skfd, buf, sizeof(buf), 0);
if(!ret)
{
perror("recv form kernel error\n");
close(skfd);
exit(-1);
}
int i =0;
while ( i < ret )
{
int l = strlen(buf + i) + 1;
char *e = strstr(&buf[i], "=");
if ( e )
{
*e = '\0';
printf("msg receive from kernel:%s = %s\n", &buf[i], &e[1]);
if ( strcmp(&e[1], "released") == 0 )
{
button_action = 1;
printf("================ \n");
}
if ( strcmp(&buf[i], "SEEN") == 0 && button_action == 1)
{
printf("-------------- \n");
sprintf(cmd, "/tmp/wps %s", &e[1]);
system(cmd);
button_action = 0;
}
}
i+=l;
}
}
close(skfd);
free((void *)nlh);
return 0;
}
说明,一定要先加载内核模块,要不然用户层创建套接字会失败!(因为内核模块会注册netlink所使用的协议类型,本例子中协议类型为USER_MSG,即23)