Linux Kernel 学习笔记9:内核与用户层通信之netlink
2017-05-27 15:21
483 查看
(本章基于:Linux-4.4.0-37)
内核与用户空间通信有很多种通信方式,netlink是其中一种,其余的还有/proc、ioctl、sockopt、共享内存等等。netlink的特点是异步全双工。
netlink使用32位端口寻址,称为pid(与进程号没有关系),其中内核的pid地址为0,。netlink主要特性如下:
1 支持全双工、异步通信(当然同步也支持)
2 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
3 在内核空间使用专用的内核API接口
4 支持多播(因此支持“总线”式通信,可实现消息订阅)
5 在内核端可用于进程上下文与中断上下文
基本数据结构
内核层操作
创建socket
unit:协议类型,可自定义,如#define NETLINK_TEST 25
cfg:配置结构,类型如下:
groups:组编号;
input:接收回调函数,接收一个sk_buff结构,数据包含一个nlmsghdr协议头;
return:返回一个sock结构,返回NULL表示创建失败;
单播发送接口:
(1) ssk:为函数 netlink_kernel_create()返回的socket。
(2) skb:存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块。
(3) portid:pid端口。
(4) nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。
多播发送接口:
allocation:内存分配类型,一般地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。
释放socket
用户层操作
nlmsghdr结构常见操作:
NLMSG_SPACE(len): 将len加上nlmsghdr头长度,并按4字节对齐;
NLMSG_DATA(nlh): 返回数据区首地址;
创建socke
bind
发送接收:
使用sendmsg、recvmsg发送接收数据
使用sendto、recvfrom发送接收数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
例:
说明:用户层pid设置为100,应用层发送一条信息到内核,内核回复同样的信息;
内核层:
应用层1:
上面例子讲的是使用sendmsg、recvmsg。在应用层我们同样可以使用sendto、recvfrom发送接收数据,操作模式和UDP通信非常相似,区别是为了能成功接收数据,我们同样需要使用bind()绑定自身地址,但对于UDP这不是必须的;
应用层2:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define NETLINK_TEST (25)
#define MAX_PAYLOAD (1024)
#define TEST_PID (100)
int netlink_create_socket(void) { //create a socket return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); }
int netlink_bind(int sock_fd) { struct sockaddr_nl addr; memset(&addr, 0, sizeof(struct sockaddr_nl)); addr.nl_family = AF_NETLINK; addr.nl_pid = TEST_PID; addr.nl_groups = 0; return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl)); }
int
netlink_send_message(int sock_fd, const unsigned char *message, int len,
unsigned int pid, unsigned int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl dest_addr;
if( !message ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
if( !nlh ) {
perror("malloc");
return -2;
}
nlh->nlmsg_len = NLMSG_SPACE(len);
nlh->nlmsg_pid = TEST_PID;
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), message, len);
memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = pid;
dest_addr.nl_groups = group;
//send message
if( sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl)) != nlh->nlmsg_len ) {
printf("send error!\n");
free(nlh);
return -3;
}
free(nlh);
return 0;
}
int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl src_addr;
socklen_t addrlen = sizeof(struct sockaddr_nl);
if( !message || !len ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if( !nlh ) {
perror("malloc");
return -2;
}
memset(&src_addr, 0, sizeof(struct sockaddr_nl));
if( recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0, (struct sockaddr *)&src_addr, (socklen_t *)&addrlen) < 0 ) {
printf("recvmsg error!\n");
return -3;
}
*len = nlh->nlmsg_len - NLMSG_SPACE(0);
memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);
free(nlh);
return 0;
}
int
main(int argc, char **argv)
{
int sock_fd;
char buf[MAX_PAYLOAD];
int len;
if( argc < 2) {
printf("enter message!\n");
exit(EXIT_FAILURE);
}
sock_fd = netlink_create_socket();
if(sock_fd == -1) {
printf("socket error!\n");
return -1;
}
if( netlink_bind(sock_fd) < 0 ) {
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {
printf("recv:%s len:%d\n", buf, len);
}
close(sock_fd);
return 0;
}
内核与用户空间通信有很多种通信方式,netlink是其中一种,其余的还有/proc、ioctl、sockopt、共享内存等等。netlink的特点是异步全双工。
netlink使用32位端口寻址,称为pid(与进程号没有关系),其中内核的pid地址为0,。netlink主要特性如下:
1 支持全双工、异步通信(当然同步也支持)
2 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
3 在内核空间使用专用的内核API接口
4 支持多播(因此支持“总线”式通信,可实现消息订阅)
5 在内核端可用于进程上下文与中断上下文
基本数据结构
struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags (unused) */ }; struct sockaddr_nl { sa_family_t nl_family; /*该字段总是为AF_NETLINK */ unsigned short nl_pad; /* 目前未用到,填充为0*/ __u32 nl_pid; /* process pid */ __u32 nl_groups; /* multicast groups mask */ };struct sockaddr_nl是netlink通信地址,和我们通常socket编程中的sockaddr_in作用一样。pid表示通信端口,groups表示组,注意这里为希望加入多播组号的掩码,也就是说最多只支持32个组。
struct nlmsghdr { __u32 nlmsg_len; /* Length of message including header */ __u16 nlmsg_type; /* Message content */ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process PID */ };Netlink报文的数据区由消息头和消息体构成,struct nlmsghdr即为消息头,消息体接在消息头后。
内核层操作
创建socket
static inline struct sock * netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);net: 一般直接填&init_net
unit:协议类型,可自定义,如#define NETLINK_TEST 25
cfg:配置结构,类型如下:
/* optional Netlink kernel configuration parameters */ struct netlink_kernel_cfg { unsigned int groups; unsigned int flags; void (*input)(struct sk_buff *skb); 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); };
groups:组编号;
input:接收回调函数,接收一个sk_buff结构,数据包含一个nlmsghdr协议头;
return:返回一个sock结构,返回NULL表示创建失败;
单播发送接口:
extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
(1) ssk:为函数 netlink_kernel_create()返回的socket。
(2) skb:存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块。
(3) portid:pid端口。
(4) nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。
多播发送接口:
extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid, __u32 group, gfp_t allocation);group:接收消息的多播组,该参数的每一个位代表一个多播组,因此如果发送给多个多播组;
allocation:内存分配类型,一般地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。
释放socket
extern void netlink_kernel_release(struct sock *sk);
用户层操作
nlmsghdr结构常见操作:
NLMSG_SPACE(len): 将len加上nlmsghdr头长度,并按4字节对齐;
NLMSG_DATA(nlh): 返回数据区首地址;
创建socke
int netlink_create_socket(void) { //create a socket return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); }
bind
int netlink_bind(int sock_fd) { struct sockaddr_nl addr; memset(&addr, 0, sizeof(struct sockaddr_nl)); addr.nl_family = AF_NETLINK; addr.nl_pid = TEST_PID; addr.nl_groups = 0; return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl)); }
发送接收:
使用sendmsg、recvmsg发送接收数据
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
使用sendto、recvfrom发送接收数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
例:
说明:用户层pid设置为100,应用层发送一条信息到内核,内核回复同样的信息;
内核层:
#include <linux/init.h> #include <linux/module.h> #include <linux/stat.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/cdev.h> #include <asm/uaccess.h> #include <net/netlink.h> #include <net/sock.h> #define NETLINK_TEST (25) static dev_t devId; static struct class *cls = NULL; struct sock *nl_sk = NULL; static void hello_cleanup(void) { netlink_kernel_release(nl_sk); device_destroy(cls, devId); class_destroy(cls); unregister_chrdev_region(devId, 1); } static void netlink_send(int pid, uint8_t *message, int len) { struct sk_buff *skb_1; struct nlmsghdr *nlh; if(!message || !nl_sk) { return; } skb_1 = alloc_skb(NLMSG_SPACE(len), GFP_KERNEL); if( !skb_1 ) { printk(KERN_ERR "alloc_skb error!\n"); } nlh = nlmsg_put(skb_1, 0, 0, 0, len, 0); NETLINK_CB(skb_1).portid = 0; NETLINK_CB(skb_1).dst_group = 0; memcpy(NLMSG_DATA(nlh), message, len); netlink_unicast(nl_sk, skb_1, pid, MSG_DONTWAIT); } static void netlink_input(struct sk_buff *__skb) { struct sk_buff *skb; char str[100]; struct nlmsghdr *nlh; if( !__skb ) { return; } skb = skb_get(__skb); if( skb->len < NLMSG_SPACE(0)) { return; } nlh = nlmsg_hdr(skb); memset(str, 0, sizeof(str)); memcpy(str, NLMSG_DATA(nlh), sizeof(str)); printk(KERN_INFO "receive message (pid:%d):%s\n", nlh->nlmsg_pid, str); printk(KERN_INFO "space:%d\n", NLMSG_SPACE(0)); printk(KERN_INFO "size:%d\n", nlh->nlmsg_len); netlink_send(nlh->nlmsg_pid, NLMSG_DATA(nlh), nlh->nlmsg_len - NLMSG_SPACE(0)); return; } static __init int netlink_init(void) { int result; struct netlink_kernel_cfg nkc; printk(KERN_WARNING "netlink init start!\n"); //动态注册设备号 if(( result = alloc_chrdev_region(&devId, 0, 1, "stone-alloc-dev") ) != 0) { printk(KERN_WARNING "register dev id error:%d\n", result); goto err; } else { printk(KERN_WARNING "register dev id success!\n"); } //动态创建设备节点 cls = class_create(THIS_MODULE, "stone-class"); if(IS_ERR(cls)) { printk(KERN_WARNING "create class error!\n"); goto err; } if(device_create(cls, NULL, devId, "", "hello%d", 0) == NULL) { printk(KERN_WARNING "create device error!\n"); goto err; } //初始化netlink nkc.groups = 0; nkc.flags = 0; nkc.input = netlink_input; nkc.cb_mutex = NULL; nkc.bind = NULL; nkc.unbind = NULL; nkc.compare = NULL; nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nkc); if( !nl_sk ) { printk(KERN_ERR "[netlink] create netlink socket error!\n"); goto err; } printk(KERN_ALERT "netlink init success!\n"); return 0; err: hello_cleanup(); return -1; } static __exit void netlink_exit(void) { hello_cleanup(); printk(KERN_WARNING "netlink exit!\n"); } module_init(netlink_init); module_exit(netlink_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Stone");
应用层1:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define NETLINK_TEST (25)
#define MAX_PAYLOAD (1024)
#define TEST_PID (100)
int netlink_create_socket(void) { //create a socket return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); }
int netlink_bind(int sock_fd) { struct sockaddr_nl addr; memset(&addr, 0, sizeof(struct sockaddr_nl)); addr.nl_family = AF_NETLINK; addr.nl_pid = TEST_PID; addr.nl_groups = 0; return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl)); }
int
netlink_send_message(int sock_fd, const unsigned char *message, int len,
unsigned int pid, unsigned int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl dest_addr;
struct iovec iov;
struct msghdr msg;
if( !message ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
if( !nlh ) {
perror("malloc");
return -2;
}
nlh->nlmsg_len = NLMSG_SPACE(len);
nlh->nlmsg_pid = TEST_PID;
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), message, len);
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = pid;
dest_addr.nl_groups = group;
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
//send message
if( sendmsg(sock_fd, &msg, 0) < 0 )
{
printf("send error!\n");
free(nlh);
return -3;
}
free(nlh);
return 0;
}
int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl source_addr;
struct iovec iov;
struct msghdr msg;
if( !message || !len ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if( !nlh ) {
perror("malloc");
return -2;
}
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
memset(&source_addr, 0, sizeof(struct sockaddr_nl));
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&source_addr;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if ( recvmsg(sock_fd, &msg, 0) < 0 ) {
printf("recvmsg error!\n");
return -3;
}
*len = nlh->nlmsg_len - NLMSG_SPACE(0);
memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);
free(nlh);
return 0;
}
int
main(int argc, char **argv)
{
int sock_fd;
char buf[MAX_PAYLOAD];
int len;
if( argc < 2) {
printf("enter message!\n");
exit(EXIT_FAILURE);
}
sock_fd = netlink_create_socket();
if(sock_fd == -1) {
printf("socket error!\n");
return -1;
}
if( netlink_bind(sock_fd) < 0 ) {
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {
printf("recv:%s len:%d\n", buf, len);
}
close(sock_fd);
return 0;
}
上面例子讲的是使用sendmsg、recvmsg。在应用层我们同样可以使用sendto、recvfrom发送接收数据,操作模式和UDP通信非常相似,区别是为了能成功接收数据,我们同样需要使用bind()绑定自身地址,但对于UDP这不是必须的;
应用层2:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define NETLINK_TEST (25)
#define MAX_PAYLOAD (1024)
#define TEST_PID (100)
int netlink_create_socket(void) { //create a socket return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); }
int netlink_bind(int sock_fd) { struct sockaddr_nl addr; memset(&addr, 0, sizeof(struct sockaddr_nl)); addr.nl_family = AF_NETLINK; addr.nl_pid = TEST_PID; addr.nl_groups = 0; return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl)); }
int
netlink_send_message(int sock_fd, const unsigned char *message, int len,
unsigned int pid, unsigned int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl dest_addr;
if( !message ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
if( !nlh ) {
perror("malloc");
return -2;
}
nlh->nlmsg_len = NLMSG_SPACE(len);
nlh->nlmsg_pid = TEST_PID;
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), message, len);
memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = pid;
dest_addr.nl_groups = group;
//send message
if( sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl)) != nlh->nlmsg_len ) {
printf("send error!\n");
free(nlh);
return -3;
}
free(nlh);
return 0;
}
int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl src_addr;
socklen_t addrlen = sizeof(struct sockaddr_nl);
if( !message || !len ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if( !nlh ) {
perror("malloc");
return -2;
}
memset(&src_addr, 0, sizeof(struct sockaddr_nl));
if( recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0, (struct sockaddr *)&src_addr, (socklen_t *)&addrlen) < 0 ) {
printf("recvmsg error!\n");
return -3;
}
*len = nlh->nlmsg_len - NLMSG_SPACE(0);
memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);
free(nlh);
return 0;
}
int
main(int argc, char **argv)
{
int sock_fd;
char buf[MAX_PAYLOAD];
int len;
if( argc < 2) {
printf("enter message!\n");
exit(EXIT_FAILURE);
}
sock_fd = netlink_create_socket();
if(sock_fd == -1) {
printf("socket error!\n");
return -1;
}
if( netlink_bind(sock_fd) < 0 ) {
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {
printf("recv:%s len:%d\n", buf, len);
}
close(sock_fd);
return 0;
}
相关文章推荐
- Linux Kernel 学习笔记9:内核与用户层通信之netlink
- Linux Kernel 学习笔记17:内核与用户层通信之sockopt
- linux kernel 内核空间与用户空间通信 netlink套接字 与 系统调用的 异同
- netlink 学习笔记 3.8.13内核
- linux kernel 学习笔记一 编译内核
- netlink 学习笔记 3.8.13内核
- PowerPC-MPC603e内核学习笔记之寄存器(二)
- [原创]W2k Driving 学习笔记(一)内核线程及同步
- 孙鑫VC学习笔记:第十七讲 (三) 用命名管道实现进程间的通信
- PowerPC-MPC603e内核学习笔记之初步认识
- Tcp通信 暑期学习笔记(二)
- 小五思科技术学习笔记之VTP和三层交换Vlan通信 推荐
- 内核通知链 学习笔记
- 【转老迈】linux内核编译学习笔记
- [原创]W2k Driving 学习笔记(二)使用GCC创建 Windows NT 下的内核DLL
- 用户空间与内核通信-netlink
- [原创]W2k Driving 学习笔记(一)内核线程及同步
- 使用netlink机制在内核与应用程序之间通信
- Windows Workflow Foundation (wwf) 在宿主中使用参数与实例通信 --学习笔记(二)
- 孙鑫VC学习笔记:第十七讲 用邮槽实现进程间的通信