您的位置:首页 > 运维架构 > Linux

Linux Kernel 学习笔记9:内核与用户层通信之netlink

2017-11-27 16:24 846 查看
(本章基于: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 在内核端可用于进程上下文与中断上下文

基本数据结构



[cpp] view
plain copy

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个组。

[cpp] view
plain copy

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

[cpp] view
plain copy

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:配置结构,类型如下:

[cpp] view
plain copy

/* 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表示创建失败;

单播发送接口:

[cpp] view
plain copy

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,该函数在没有接收缓存可利用定时睡眠。

多播发送接口:

[cpp] view
plain copy

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

[cpp] view
plain copy

extern void netlink_kernel_release(struct sock *sk);

用户层操作

nlmsghdr结构常见操作:

NLMSG_SPACE(len): 将len加上nlmsghdr头长度,并按4字节对齐;

NLMSG_DATA(nlh): 返回数据区首地址;

创建socke

[cpp] view
plain copy

int netlink_create_socket(void)

{

//create a socket

return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);

}

bind

[cpp] view
plain copy

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发送接收数据

[cpp] view
plain copy

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

使用sendto、recvfrom发送接收数据

[cpp] view
plain copy

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,应用层发送一条信息到内核,内核回复同样的信息;

内核层:

[cpp] view
plain copy

#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:

[cpp] view
plain copy

#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:

[cpp] view
plain copy

#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 netlink