Linux 内核态与用户态通信 netlink
参考资料:
https://blog.csdn.net/zqixiao_09/article/details/77131283
https://www.cnblogs.com/lopnor/p/6158800.html
Netlink 是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大,目前在最新的 Linux 内核(2.6.14)中使用netlink 进行应用与内核通信的应用很多,包括:路由 daemon(NETLINK_ROUTE),1-wire 子系统(NETLINK_W1),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),socket 监视(NETLINK_INET_DIAG),netfilter 日志(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系统(NETLINK_ISCSI),进程审计(NETLINK_AUDIT),转发信息表查询(NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系统(NETLINK_NETFILTER),IPv6 防火墙(NETLINK_IP6_FW),DECnet 路由信息(NETLINK_DNRTMSG),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)。
Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。
Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:
1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。
问题:
增加了netlink协议后,需要重新编译内核吗?还是仅仅修改了头文件netlink.h就可以了呢?在后期的实验中,需要对这一问题进行验证。
2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
异步通信与同步通信的区别:看接受者的响应方式。异步通讯是不需要接受者立即响应的
3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
可以像编写驱动模块一样的实现方式来实现netlink部分。
4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在后面的文章中将介绍这一机制的使用。
5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
内核主动向用户应用发起数据
6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。
NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。
#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 <net/netlink.h> #include <linux/netlink.h> #define NETLINK_USER 22 #define USER_MSG (NETLINK_USER + 1) #define USER_PORT 50 /*此处是写死了,可以用进程ID,表示来自用户态的唯一进程*/ MODULE_LICENSE("GPL"); MODULE_AUTHOR("arvik"); MODULE_DESCRIPTION("netlink_demo"); static void test_netlink_rcv(struct sk_buff *skb); struct netlink_kernel_cfg cfg = { .input = test_netlink_rcv, /*...*/ }; static struct sock *test_netlink_sock = 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; } /*将header填充到skb中*/ nlh = nlmsg_put(nl_skb, 0, 0, USER_MSG, len, 0); if (nlh == NULL) { printk("put error\n"); nlmsg_free(nl_skb); return -1; } /*拷贝data*/ memcpy(nlmsg_data(nlh), pbuf, len); /*发送*/ ret = netlink_unicast(test_netlink_sock, nl_skb, USER_PORT, MSG_DONTWAIT); return ret; } static void test_netlink_rcv(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 date : %s\n", (int8_t *)data); send_msg(data, nlmsg_len(nlh)); } } } static int __init test_netlink_init(void) { printk("test netlink init\n"); test_netlink_sock = netlink_kernel_create(&init_net, USER_MSG, &cfg); if (test_netlink_sock == NULL) { printk("test netlink init error\n"); return -1; } printk("test netlink init ok\n"); return 0; } static void __exit test_netlink_exit(void) { netlink_kernel_release(test_netlink_sock); test_netlink_sock = NULL; printk("test netlink exit\n"); } module_init(test_netlink_init); module_exit(test_netlink_exit);
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <linux/netlink.h> #include <stdint.h> #include <unistd.h> #include <errno.h> #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() { char *data = "hello kernel"; socklen_t addr_len; struct sockaddr_nl local, dest_addr; int skfd; struct nlmsghdr *nlh = NULL; struct _my_msg info; int ret; //创建socket skfd = socket(AF_NETLINK, SOCK_RAW, USER_MSG); if (skfd == (-1)) { fprintf(stderr, "create socket error...%s\n", strerror(errno)); return (-1); } /*绑定*/ memset(&local, 0, sizeof(local)); local.nl_family = AF_NETLINK; local.nl_pid = 50; /*此处的pid通常用进程ID,表示来自用户态的唯一进程*/ local.nl_groups = 0; if (bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0) { fprintf(stderr, "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; dest_addr.nl_groups = 0; /*填写data*/ 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; 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 < 0) { // fprintf(stderr, "sendto error\n"); perror("sendto error: "); close(skfd); return (-1); } printf("wait kernel msg!\n"); memset(&info, 0, sizeof(info)); /*接受信息*/ ret = recvfrom(skfd, &info, sizeof(struct _my_msg), 0, (struct sockaddr *)&dest_addr, &addr_len); if (ret <= 0) { // fprintf(stderr, "recv from kernel error\n"); perror("recv from kernel error: "); close(skfd); return (-1); } printf("msg receive from kernel : %s\n", info.data); close(skfd); free((void *)nlh); close(skfd); return 0; }