当前位置 博文首页 > 积水成渊:linux下用户程序同内核通信实例一(netlink)

    积水成渊:linux下用户程序同内核通信实例一(netlink)

    作者:[db:作者] 时间:2021-07-29 12:37


    linux下用户程序同内核通信的方式一般有ioctl, proc文件系统,剩下一个就是Netlink套接字了。 这里先介绍下netlink。

    先抄一段前辈对这几种通信方式的比较:

    传送门http://blog.csdn.net/tqyou85/article/details/3939931

    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 更加混乱。

    2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。

    3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。

    4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在 后面的文章中将介绍这一机制的使用。

    5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。

    6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。


    下面这两部分代码主要的目的是用netlink机制实现用户程序和内核的通信。 具体就是用户程序执行./netlink -S [我是参数] 或./netlink -G 时,内核会返回"S know you!" 和“I know you!” 这两种字符串, 然后输出。 内核和用户程序均加有打印。

    一、内核模块

    1. Makefile依赖的编译规则 ruler.dir

    PWD := $(shell pwd)
    
    all: modules romfs
    
    modules:
    	$(MAKE) -C $(KDIR) M=$(PWD) modules
    	@echo $(OBJ)
    
    modules_install:
    	$(MAKE) -C $(KDIR) M=$(PWD) modules_install
    
    romfs:
    	cp -rf *.ko $(MODULES_BUILD_DIR)
    
    clean:
    	rm *.o *.ko *.mod.* Module.* modules.* 
    	rm -rf $(MODULES_BUILD_DIR)
    
    obj-m := $(MOD_NAME).o
    

    2.Makefile

    KERNEL_MODULES := netlink
    
    export MODULES_ROOT_DIR := $(shell pwd)
    export MODULES_BUILD_DIR := $(MODULES_ROOT_DIR)/build
    export KDIR := /lib/modules/$(shell uname -r)/build #这行是为了取出系统下内核的目录(ubuntu)
    
    all: init modules romfs
    
    init:
    	mkdir -p $(MODULES_BUILD_DIR)
    
    modules:$(patsubst %, _dir_%, $(KERNEL_MODULES))
    
    $(patsubst %, _dir_%, $(KERNEL_MODULES)):
    	@echo
    	@echo Building $(patsubst _dir_%, %, $@)
    	$(MAKE) -C $(patsubst _dir_%, %, $@) all
    
    
    romfs: $(patsubst %, _romfs_%, $(KERNEL_MODULES))
    $(patsubst %, _romfs_%, $(KERNEL_MODULES)):
    	$(MAKE) -C $(patsubst _romfs_%, %, $@) romfs
    
    
    clean: $(patsubst %, _clean_%, $(KERNEL_MODULES))
    	$(RM) $(BUILD_DIR)
    $(patsubst %, _clean_%, $(KERNEL_MODULES)):
    	@echo
    	@echo Cleaning $(patsubst _dir_%, %, $@)
    	$(MAKE) -C $(patsubst _clean_%, %, $@) clean
    
    .PHONY:
    

    3. ./netlink/netlink.c

    /*
     * netlink.c
     *
     *  Created on: 2014 *      Author: cr
     */
    
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/skbuff.h>
    #include <linux/ip.h>
    #include <linux/types.h>
    #include <linux/sched.h>
    #include <linux/netlink.h>
    #include <net/sock.h>
    #include "usrlink.h"
    
    MODULE_LICENSE("Dual BSD/GPL");
    MODULE_AUTHOR("MDAXIA");
    
    struct sock *netlink_fd;
    
    
    static void netlink_to_user(int dest, void *buf, int len)
    {
    	struct nlmsghdr *nl;
    	struct sk_buff *skb;
    	int size;
    
    	size = NLMSG_SPACE(len);
    	skb = alloc_skb(size, GFP_ATOMIC);
    	if(!skb || !buf)
    	{
    		printk(KERN_ALERT "netlink_to_user skb of buf null!\n");
    		return;
    	}
    	nl = nlmsg_put(skb, 0, 0, 0, NLMSG_SPACE(len) - sizeof(struct nlmsghdr), 0);
    	NETLINK_CB(skb).pid = 0;
    	NETLINK_CB(skb).dst_group = 0;
    
    	memcpy(NLMSG_DATA(nl), buf, len);
    	nl->nlmsg_len = (len > 2) ? (len - 2):len;
    
    	netlink_unicast(netlink_fd, skb, dest, MSG_DONTWAIT);
    	printk(KERN_ALERT "K send packet success\n");
    }
    
    static int process_hello_get(int dest, void *buf, int len)
    {
    	printk(KERN_ALERT "In process_hello get!\n");
    	memcpy(buf, "I known you !", 13);
    	netlink_to_user(dest, buf, 13);
    	return NET_OK;
    }
    
    static int process_hello_set(int dest, void *buf, int len)
    {
    	printk(KERN_ALERT "In process_hello set! %s\n", (char *)buf);
    	memcpy(buf, "S known you !", 13);
    	netlink_to_user(dest, buf, 13);
    	return NET_OK;
    }
    
    
    static void netlink_process_packet(struct nlmsghdr *nl)
    {
    	int ret;
    
    	switch(nl->nlmsg_type)
    	{
    	case HELLO_GET:
    		ret = process_hello_get(nl->nlmsg_pid, NLMSG_DATA(nl), nl->nlmsg_len);
    		break;
    	case HELLO_SET:
    		ret = process_hello_set(nl->nlmsg_pid, NLMSG_DATA(nl), nl->nlmsg_len);
    		break;
    	default:break;
    	}
    }
    
    static void netlink_recv_packet(struct sk_buff *__skb)
    {
    	struct sk_buff *skb;
    	struct nlmsghdr *nlhdr;
    
    	skb = skb_get(__skb);
    	if(skb->len >= sizeof(struct nlmsghdr))
    	{
    		nlhdr = (struct nlmsghdr *)skb->data;
    		if(nlhdr->nlmsg_len >= sizeof(struct nlmsghdr) &&
    				__skb->len >= nlhdr->nlmsg_len)
    		{
    			netlink_process_packet(nlhdr);
    		}
    	}
    	else
    		printk(KERN_ALERT "Kernel receive msg length error!\n");
    }
    
    static int __init netlink_init(void)
    {
    	netlink_fd = netlink_kernel_create(&init_net, USER_NETLINK_CMD, 0, netlink_recv_packet, NULL, THIS_MODULE);
    	if(NULL == netlink_fd)
    	{
    		printk(KERN_ALERT "Init netlink!\n");
    		return -1;
    	}
    	printk(KERN_ALERT "Init netlink success!\n");
    	return 0;
    }
    
    static void __exit netlink_exit(void)
    {
    	netlink_kernel_release(netlink_fd);
    	printk(KERN_ALERT "Exit netlink!\n");
    }
    
    module_init(netlink_init);
    module_exit(netlink_exit);
    

    4. ./netlink/usrlink.h

    /*
     * usrlink.h
     *
     *  Created on: 2014骞?鏈?7鏃? *      Author: cr
     */
    #ifndef USRLINK_H_
    #define USRLINK_H_
    
    #define USER_NETLINK_CMD	25
    #define MAXMSGLEN 			1024
    
    typedef enum error_e {
    	NET_ERROR,
    	NET_OK,
    	NET_PARAM,
    	NET_MEM,
    	NET_SOCK,
    } netlink_err;
    
    typedef enum module_e {
    	HELLO_CMD = 1,
    } netlink_module;
    
    typedef enum type_e {
    	HELLO_SET,
    	HELLO_GET,
    } netlink_type;
    
    #endif /* USRLINK_H_ */
    

    5. ./netlink/Makefile

    MOD_NAME := netlink
    
    $(MOD_NAME)-objs : netlink.o
    
    -include $(MODULES_ROOT_DIR)/rules.dir
    
    .PHONY:
    


    5. 编译方式。

    其中Makefile、rulers.dir 在Knetlink/下, netlink.c 、netlink.h 、Makefile在Knetlink/netlink/目录下。 编译时在Knetlink目录下执行Make即可


    二、用户程序

    用户程序的Makefile 这里就不放出了。 我是直接在eclipse下建的工程 自动编译的、...

    1. netlink.c

    /*
     * usrlink.c
     *
     *  Created on: 2014骞?鏈?7鏃? *      Author: cr
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include "usrlink.h"
    
    int netlink_sock_init(netlink_sock *netlink_s, int module, int protocol)
    {
    	netlink_s->sock = socket(PF_NETLINK, SOCK_RAW, protocol);
    	if(netlink_s->sock < 0)
    		return NET_SOCK;
    	memset(&netlink_s->src, 0 ,sizeof(netlink_s->src));
    	netlink_s->src.nl_family = AF_NETLINK;
    	netlink_s->src.nl_pid = module;
    	netlink_s->src.nl_groups = 0;
    
    	if(bind(netlink_s->sock, (struct sockaddr *)&netlink_s->src, sizeof(netlink_s->src)) < 0)
    		return NET_SOCK;
    
    	netlink_s->dest.nl_family = AF_NETLINK;
    	netlink_s->dest.nl_pid = 0;
    	netlink_s->dest.nl_groups = 0;
    
    	return NET_OK;
    }
    
    int netlink_send(netlink_sock *netlink_s, int type, char *sbuf, int slen, char *rbuf, int *rlen)
    {
    	struct msghdr msg;
    	struct nlmsghdr *nlhdr = NULL;
    	struct iovec iov;
    	int ret;
    
    	nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAXMSGLEN));
    	if(NULL == nlhdr)
    		return NET_MEM;
    
    	memcpy(NLMSG_DATA(nlhdr), sbuf, slen);
    	nlhdr->nlmsg_len = NLMSG_SPACE(slen);
    	nlhdr->nlmsg_pid = netlink_s->src.nl_pid;
    	nlhdr->nlmsg_type = type;
    	nlhdr->nlmsg_flags = 0;
    
    	iov.iov_base = (void *)nlhdr;
    	iov.iov_len = nlhdr->nlmsg_len;
    
    	msg.msg_name = (void *)&(netlink_s->dest);
    	msg.msg_namelen = sizeof(netlink_s->dest);
    	msg.msg_iov = &iov;
    	msg.msg_iovlen = 1;
    
    	ret = sendmsg(netlink_s->sock, &msg, 0);
    	if(ret < 0)
    	{
    		printf("Send fail\n");
    		goto error;
    	}
    	ret = recvmsg(netlink_s->sock, &msg, 0);
    	if(ret < 0)
    	{
    		printf("Read fail\n");
    		goto error;
    	}
    	memcpy(rbuf, NLMSG_DATA(nlhdr), nlhdr->nlmsg_len);
    	*rlen = nlhdr->nlmsg_len;
    	return NET_OK;
    
    error:
    	free(nlhdr);
    	return NET_SOCK;
    }
    
    int netlink_sock_deinit(netlink_sock *netlink_s)
    {
    	close(netlink_s->sock);
    	memset(netlink_s, 0, sizeof(netlink_sock));
    	return NET_OK;
    }
    
    2. netlink.h

    /*
     * usrlink.h
     *
     *  Created on: 2014 *      Author: cr
     */
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <asm/types.h>
    #include <linux/socket.h>
    #include <linux/netlink.h>
    #ifndef USRLINK_H_
    #define USRLINK_H_
    
    #define USER_NETLINK_CMD	25
    #define MAXMSGLEN 			1024
    
    typedef enum error_e {
    	NET_ERROR,
    	NET_OK,
    	NET_PARAM,
    	NET_MEM,
    	NET_SOCK,
    } netlink_err;
    
    typedef enum module_e {
    	HELLO_CMD = 1,
    } netlink_module;
    
    typedef enum type_e {
    	HELLO_SET,
    	HELLO_GET,
    } netlink_type;
    
    typedef struct usr_sock_h {
    	int sock;
    	struct sockaddr_nl dest;
    	struct sockaddr_nl src;
    } netlink_sock;
    
    int netlink_sock_init(netlink_sock *netlink_s, int module, int protocol);
    int netlink_sock_deinit(netlink_sock *netlink_s);
    int netlink_send(netlink_sock *netlink_s, int type, char *sbuf, int slen, char *rbuf, int *rlen);
    
    #endif /* USRLINK_H_ */
    
    3. main.c

    /*
     * main.c
     *
     *  Created on: 2014骞?鏈?7鏃? *      Author: cr
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "usrlink.h"
    
    int parse_ret(int ret)
    {
    	switch(ret)
    	{
    	case NET_OK:
    		return ret;
    	case NET_ERROR:
    		printf("error\n");
    		goto exit_p;
    	case NET_MEM:
    		printf("Memory error\n");
    		goto exit_p;
    	case NET_PARAM:
    		printf("Param error\n");
    		goto exit_p;
    	case NET_SOCK:
    		printf("Socket error\n");
    		goto exit_p;
    	default:break;
    	}
    exit_p:
    	return NET_ERROR;
    }
    
    void usage(void)
    {
    	printf("Usage: Netlink -G <param>\n\t-S <param>\n");
    }
    
    int main(int argc, char **argv)
    {
    	netlink_sock h_sock;
    	char rbuf[1024];
    	char sbuf[1024];
    	int ret, type, slen = 0, rlen = 0;
    
    	ret = netlink_sock_init(&h_sock, HELLO_CMD, USER_NETLINK_CMD);
    	if(NET_OK != parse_ret(ret))
    		goto exit_p;
    
    	bzero(&rbuf, sizeof(rbuf));
    	bzero(&sbuf, sizeof(sbuf));
    	if(argc < 3)
    	{
    		usage();
    		goto exit_p;
    	}
    	if(!strncmp("-G", argv[1], 2))
    		type = HELLO_GET;
    	else if(!strncmp("-S", argv[1], 2))
    		type = HELLO_SET;
    
    	strcpy(sbuf, argv[2]);
    	slen = strlen(sbuf);
    	ret = netlink_send(&h_sock, type, sbuf, slen, rbuf, &rlen);
    	if(NET_OK != parse_ret(ret))
    		goto exit_p;
    
    	if(rlen > 0)
    	{
    		rbuf[rlen] = '\0';
    		printf("K rep [len = %d]:%s\n", rlen, rbuf);
    	}
    	printf("K[len = %d]: %s\n", rlen, rbuf);
    
    exit_p:
    	netlink_sock_deinit(&h_sock);
    	return 0;
    }
    

    cs