netlink通信——用户态与内核态通信具体实现

本文介绍如何利用Netlink协议实现用户态与内核态之间的数据通信。通过定义自定义协议号并编写用户空间和内核空间代码,实现了双向数据交换。

这篇文章主要讲解通过netlink通信机制,具体实现用户态与内核态的数据传送。我们的内核环境是2.6.35.6版本。

</usr/include/linux/netlink.h>文件里包含了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       /* Firewalling hook                             */
#define NETLINK_INET_DIAG       4       /* INET 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_MYTEST 20

如果我们在Netlink协议簇里开发一个新的协议,只要在该文件中定义协议号即可,例如我们定义一种基于Netlink协议簇的、协议号是20的自定义协议,如上所示。同时记得,将内核头文件目录中的netlink.h也做对应的修改,在我的系统中它的路径是:/usr/src/kernels/2.6.35.6-45.fc14.i686/include/linux/netlink.h

接下来我们在用户空间以及内核空间模块的开发过程中就可以使用这种协议了,需要说明的是,如果在这两个文件中不添加协议号,就需要在具体通信实现代码文件中添加这两个协议号,在我的具体实现中,就是采用这种方法。

用户态通信代码(mynlusr.c):

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <linux/netlink.h>
#include <linux/socket.h>

#define NETLINK_MYTEST 20
#define MAX_PAYLOAD 1024

int main(int argc, char ** argv)
{
	struct sockaddr_nl src_addr,dest_addr;
	struct nlmsghdr *nlh = NULL;
	struct msghdr msg;
	struct iovec iov;
	int sock_fd = -1;
	/*创建套接字*/
	if(-1 == (sock_fd = socket(AF_NETLINK,SOCK_RAW,NETLINK_MYTEST))){
		printf("can't create netlink socket!\n");
		return -1;
	}
	/*设置src_addr结构*/
	memset(&src_addr, 0, sizeof(src_addr));
	src_addr.nl_family = AF_NETLINK;
	src_addr.nl_pid = getpid();/*自己的pid*/
	src_addr.nl_groups = 0;
	
	/*将套接字和netlink地址结构进行绑定*/
	if(-1 == bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr))){
		printf("can't bind sock_fd with sockaddr_nl");
		return -1;
	}
	if(NULL == (nlh=(struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD)))){
		printf("alloc mem failed!\n");
		return -1;
	}
	memset(nlh, 0, MAX_PAYLOAD);
	/*填充netlink的消息头部*/
	nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	nlh->nlmsg_pid = getpid();
	nlh->nlmsg_type = NLMSG_NOOP;
	nlh->nlmsg_flags = 0;
	
	/*设置netlink的消息内容*/
	strcpy(NLMSG_DATA(nlh), argv[1]);
	
	memset(&iov, 0, sizeof(iov));
	iov.iov_base = (void *)nlh;
	iov.iov_len = nlh->nlmsg_len;
	
	/*设置dest_addr结构*/
	memset(&dest_addr, 0, sizeof(dest_addr));
	dest_addr.nl_family = AF_NETLINK;
	dest_addr.nl_pid = 0;/*目的地址的pid,这里是发送给内核*/
	dest_addr.nl_groups = 0;
	
	memset(&msg, 0, sizeof(msg));
	msg.msg_name = (void *)&dest_addr;
	msg.msg_namelen = sizeof(dest_addr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	
	/*通过netlink socket向内涵发送消息*/
	sendmsg(sock_fd, &msg, 0);
	
	/*接受内核消息*/
	printf("waiting message from kernel!\n");
	memset((char *)NLMSG_DATA(nlh),0,1024);
	recvmsg(sock_fd,&msg,0);
	printf("response:%s\n",NLMSG_DATA(nlh));
	
	/*关闭netlink套接字*/
	close(sock_fd);
	free(nlh);
	return 0;
}

内核态通信代码(mynlkern.c):

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/ip.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/netlink.h>
#include <net/sock.h>
#include <net/netlink.h>

#define NETLINK_MYTEST 20
#define MAX_MSGSIZE 1024
struct sock *nl_sk = NULL;

static void sendnlmsg(char *message, int dstpid)
{
	struct sk_buff *skb;
	struct nlmsghdr *nlh;
	int len = NLMSG_SPACE(MAX_MSGSIZE);
	int slen = 0;
	if(!message || !nl_sk){
		return ;
	}
	/*为新的sk_buffer申请空间*/
	skb = alloc_skb(len,GFP_KERNEL);
	if(!skb){
		printk(KERN_ERR "my_net_link:alloc_skb Error!\n");
		return ;
	}
	slen = strlen(message);
	
	/*用nlmsg_put来设置netlink消息头部*/
	nlh = nlmsg_put(skb,0,0,0,MAX_MSGSIZE,0);
	
	/*设置Netlink的控制块*/
	NETLINK_CB(skb).pid = 0;/*消息发送者的id标识,如果是内核发的则置0*/
	NETLINK_CB(skb).dst_group = 0;/*如果目的的组为内核或某一进程,该字段为0*/
	
	message[slen] = '\0';
	memcpy(NLMSG_DATA(nlh),message,slen+1);
	
	/*通过netlink_unicast将消息发送给用户空间*/
	printk("kernel:begin send\n");
	netlink_unicast(nl_sk,skb,dstpid,0);
	printk("kernel:send OK!\n");
	return ;
}

static void nl_data_ready(struct sk_buff* skb)
{
	struct nlmsghdr *nlh;
	u64 rlen;
	void *data;
	
	while(skb->len >= NLMSG_SPACE(0)){
		nlh = nlmsg_hdr(skb);
		printk("nlh->nlmsg_pid=%u skb->len =%d NLMSG_SPACE(0)=%d\n",nlh->nlmsg_pid,skb->len,NLMSG_SPACE(0));
		if(nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
			return ;
		
		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
		if(rlen > skb->len)
			rlen = skb->len;
		
		data = NLMSG_DATA(nlh);
		printk("received netlink message: %s\n",(char *)data);
		skb_pull(skb, rlen);
		
		sendnlmsg("send from kernel",nlh->nlmsg_pid);
	}
	printk("received finished!\n");
}

static int __init myinit_module(void)
{
	printk("my netlink in!\n");
	nl_sk = netlink_kernel_create(&init_net, NETLINK_MYTEST, 0, nl_data_ready, NULL, THIS_MODULE);
	if(!nl_sk){
        printk(KERN_ERR "my_net_link: create netlink socket error!\n");
        return 1;
    }else{
		return 0;
	}
}

static void __exit myexit_module(void)
{
	printk("my netlink out!\n");
	if(nl_sk != NULL){
		sock_release(nl_sk->sk_socket);
	}
}

MODULE_AUTHOR("Fang Xieyun");
MODULE_LICENSE("GPL");

module_init(myinit_module);
module_exit(myexit_module);


<think>首先,用户的问题是:“学习进程间通信内核模块编程后有什么心得体会”。我需要用中文回答这个问题。 我的回答应该包括: 1. 对学习进程间通信(IPC)内核模块编程的心得体会。 2. 回答完问题后,必须在末尾提出3-5个相关问题。 3. 第一行必须是“§§相关问题§§”,并且不能换行分散。 4. 相关问题必须基于用户的问题我的回答。 用户提供的示例格式是: - 先回答一个问题(如快速排序的基本思想)。 - 然后有一个代码块(可选,但在这个问题中可能不需要)。 - 最后是“§§相关问题§§”后跟3-5个相关问题。 所以,我的结构应该是: - 第一部分:回答用户的问题。 - 第二部分:输出“§§相关问题§§”,然后列出3-5个相关问题。 现在,针对问题:学习进程间通信内核模块编程后有什么心得体会。 心得体会应该基于我的知识: - **进程间通信(IPC)**:涉及进程如何交换数据,如管道、消息队列、共享内存、信号等。学习后可能体会到系统资源管理的重要性、同步问题、避免死锁等。 - **内核模块编程**:涉及编写内核模块,如设备驱动、文件系统等。学习后可能体会到操作系统的核心机制、内存管理、硬件交互、安全性问题等。 总结心得体会: - 加深了对操作系统内部工作原理的理解。 - 认识到并发同步的挑战。 - 提高了编程技能,特别是在低级系统编程方面。 - 体会到安全性稳定性在系统设计中的重要性。 - 可能对Linux内核或其他操作系统有更深的 appreciation。 然后,提出相关问题: - 必须3-5个。 - 基于用户的问题我的回答。 - 例如: 1. 进程间通信有哪些常见方法? 2. 内核模块编程中如何避免内核崩溃? 3. 学习这些后如何应用到实际项目中? 4. 有哪些常见的IPC同步机制? 5. 内核模块用户空间程序的主要区别是什么? 确保相关问题以列表形式呈现,如“1. ... 2. ... 3. ...”。 完整输出: - 先写心得体会。 - 然后换行(但在“§§相关问题§§”后不能分散,意思是“§§相关问题§§”必须在一行,然后直接跟问题列表)。 - 在示例中是: ``` §§相关问题§§ 1. ··· 2. ··· 3. ··· ``` 所以,输出“§§相关问题§§”后换行,然后列出问题。 最后,确保所有内容用中文。 大纲回答: 1. **心得体会部分**: - 学习IPC让我理解了进程如何安全高效地通信。 - 内核模块编程让我深入操作系统核心,学习资源管理硬件交互。 - 整体上,提升了系统编程能力问题解决技能。 2. **相关问题部分**: - 输出“§§相关问题§§” - 然后列出3-5个问题,如: 1. 进程间通信中最常用的方法是什么? 2. 编写内核模块时需要注意哪些安全风险? 3. 如何调试内核模块中的错误? 4. IPC在分布式系统中的应用有哪些? 5. 学习内核模块编程对职业发展有什么帮助?</think>学习进程间通信(IPC)内核模块编程后,我的心得体会可以总结为以下几点: - **对操作系统内部机制的深入理解**:通过IPC学习,我掌握了进程间如何高效、安全地交换数据(如管道、消息队列、共享内存等),体会到并发控制同步机制(如信号量、互斥锁)的重要性,这帮助我避免了死锁资源竞争问题。例如,在共享内存编程中,必须严格管理访问权限,否则会导致数据不一致。 - **系统稳定性安全性的提升**:内核模块编程让我直接接触操作系统核心,学习如何编写设备驱动或文件系统模块。这让我深刻认识到内核空间的脆弱性——任何错误(如内存泄漏或空指针)都可能引发系统崩溃,因此必须谨慎处理硬件交互内存管理,同时强化代码验证测试。 - **实践技能问题解决能力的增强**:结合IPC内核模块,我学会了在复杂系统中设计高效通信方案(如使用Netlink套接字实现内核-用户空间交互)。这提升了我的调试能力,例如通过`printk`日志排查内核问题,并体会到模块化设计对可维护性的价值。 - **整体感悟**:这些知识不仅加深了对计算机底层原理的认知,还让我意识到系统编程的挑战乐趣——它要求精确性、效率性可靠性,是构建高性能应用(如嵌入式系统或云服务)的基础。同时,学习过程也强化了持续学习文档查阅的习惯,因为内核APIIPC机制更新频繁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值