linux内核编程--2自定义内核与用户态通信方式(sock选项)
来源:互联网 发布:大外交 知乎 编辑:程序博客网 时间:2024/06/06 02:11
1. 简介
前篇博文(linux内核编程一:模块的装载和卸载)中我们已经知道了自定义内核模块的装载和卸载。由于linux内核本身非常强大,编写自定义的内核模块往往是为了实现我们独特的功能或需求,这经常涉及到内核态与用户态之间通信。Linux本身提供丰富的系统调用(如ioctl、open)来实现用户态与内核态交互,此文基于getsockopt系统调用,介绍一种简单的用户态与内核态通信方法:增加sockopt命令字。
2. Socket选项
我们知道,系统调用getsockopt/ setsockopt可以操作socket文件描述符(FD)的相关选项,常用的有:
SO_BROADCAST----Set or get 描述符广播标志。Set socket文件描述符SO_BROADCAST可以使FD能发送广播报文;
SO_OOBINLINE ---- 使FD发送的带外数据直接放置在报文中(默认情况下是通过MSG_OOB标识接收到另外一个缓冲区中);
SO_RCVBUF ----- set or get FD的接收缓存区长度,如在卫星通信中,可能需要将缓存区长度扩大已接收超大报文;
其实这些选项就相对于命令字,内核根据命令字对FD的内核结构进行相关操作。内核通过以下数据结构区分不同命令字的不同处理方法,并用全局链表串联起来:
static LIST_HEAD(nf_sockopts);
struct nf_sockopt_ops { structlist_head list; -------------链表,用于串联不同的命令字 u_int8_tpf; ------------ 协议类型,如PF_INET、PF_INET6等 /*Non-inclusive ranges: use 0/0/NULL to never get called. */ intset_optmin; ------------set操作命令字最小值 intset_optmax; ----------- set操作命令字最大值 int(*set)(struct sock *sk, int optval, void __user *user, unsigned int len); ------set操作回调指针#ifdef CONFIG_COMPAT int(*compat_set)(struct sock *sk, int optval, void__user *user, unsigned int len);#endif intget_optmin; ------------get操作命令字最小值 intget_optmax; ------------get操作命令字最大值 int(*get)(struct sock *sk, int optval, void __user *user, int *len); ------get操作回调指针#ifdef CONFIG_COMPAT int(*compat_get)(struct sock *sk, int optval, void__user *user, int *len);#endif /*Use the module struct to lock set/get code in place */ structmodule *owner; ------------此结构所属模块};
不同的命令字初始化不同的上述结构,然后调用函数nf_register_sockopt()注册此结构,此函数先检测注册节点是否与链表中已有节点重叠:若重叠,则返回错误,注册失败;若不重叠,则挂接到链表中;
nf_register_sockopt() 内核源码:
/* Functions to register sockopt ranges(exclusive). */int nf_register_sockopt(structnf_sockopt_ops *reg){ structnf_sockopt_ops *ops; int ret = 0; mutex_lock(&nf_sockopt_mutex); list_for_each_entry(ops,&nf_sockopts, list) { #检查是否重叠 if(ops->pf == reg->pf && (overlap(ops->set_optmin,ops->set_optmax, reg->set_optmin,reg->set_optmax) ||overlap(ops->get_optmin, ops->get_optmax, reg->get_optmin, reg->get_optmax))) { NFDEBUG("nf_sockoverlap: %u-%u/%u-%u v %u-%u/%u-%u\n", ops->set_optmin,ops->set_optmax, ops->get_optmin,ops->get_optmax, reg->set_optmin,reg->set_optmax, reg->get_optmin,reg->get_optmax); ret= -EBUSY; gotoout; } } list_add(®->list, &nf_sockopts); #-----挂接链表out: mutex_unlock(&nf_sockopt_mutex); return ret;}
3. 自定义sock选项
有了上述理论,再结合前面博文(内核模块装载和卸载),我们就可以注册自己的sock选项用于内核态与用户态通信,模块代码如下:
内核态module_sock.c
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/types.h>#include <linux/netfilter_ipv4.h> /* for struct nf_sockopt_ops */#include <asm/uaccess.h>/* for copy_from_user */#define SOCKET_OPS_BASE 128 #define SOCKET_OPS_SET (SOCKET_OPS_BASE) #define SOCKET_OPS_GET (SOCKET_OPS_BASE) #define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1) static struct nf_sockopt_ops g_stSockOpt_Self;MODULE_LICENSE("GPL");MODULE_AUTHOR("zhaogang"); MODULE_DESCRIPTION("This is a simple example!/n"); MODULE_ALIAS("A simplest example"); static int sock_self_Recv(struct sock *sk, int optval, void __user *user, unsigned int len){ int iRet = 0; char szMsg[1000] = {0, }; printk(KERN_INFO "sockopt: sock_self_Recv()\n"); if (optval == SOCKET_OPS_SET) { /* 从用户空间拷贝 */ iRet = copy_from_user(szMsg, user, sizeof(szMsg)); printk("sock_self_Recv: szMsg = %s. ret = %d. success\n", szMsg, iRet); } return iRet; }static int sock_self_Send(struct sock *sk, int optval, void __user *user, unsigned int len){ int iRet = 0; printk(KERN_INFO "sockopt: send_msg()\n"); char szSendBuf[1000] = "This is kernel msg"; if (optval == SOCKET_OPS_GET) { iRet = copy_to_user(user, szSendBuf, strlen(szSendBuf)); printk("sock_self_Send: szSendBuf = %s. iRet = %d. success\n", szSendBuf, iRet); } return 0; }static void sock_Self_Init(struct nf_sockopt_ops *pstSock){ memset(pstSock, 0, sizeof(struct nf_sockopt_ops)); pstSock->pf = PF_INET; pstSock->set_optmax = SOCKET_OPS_MAX; pstSock->set_optmin = SOCKET_OPS_SET; pstSock->set = sock_self_Recv; pstSock->get_optmax = SOCKET_OPS_MAX; pstSock->get_optmin = SOCKET_OPS_GET; pstSock->get = sock_self_Send; pstSock->owner = THIS_MODULE; return;}static int __init test_init(void){ int iRet = 0; printk(KERN_ALERT "Linux cai ji.------start----\n"); sock_Self_Init(&g_stSockOpt_Self); iRet = nf_register_sockopt(&g_stSockOpt_Self); return iRet;}static void __exit test_fini(void){ printk(KERN_ALERT "Linux cai ji.------end----\n"); nf_unregister_sockopt(&g_stSockOpt_Self); return;}module_init(test_init);module_exit(test_fini);
用户态:在一个比较大的工程测试中,此处只贴出了一个使用命令字的函数:
ULONG CV_Comm_Init(){/* 开启TCP服务 */struct sockaddr_in stAddr;INT iFd = ZHAOGANG_INVALID_FD;INT iRet;ULONG ulRet = ERROR_SUCCESS;/* socket */iFd = socket(AF_INET, SOCK_STREAM, 0);if (iFd < 0){return ERROR_FAILED;}stAddr.sin_addr.s_addr = INADDR_ANY;stAddr.sin_port = htons(SERVER_OPENCVD_TCP_PORT); /* 这里端口地址要统一定义 */stAddr.sin_family = AF_INET;/* bind & listen */iRet = bind(iFd, (SockAddr_S *)&stAddr, sizeof(stAddr)); iRet |= listen(iFd, 10);if (iRet < 0){return ERROR_FAILED;} /* 内核命令字测试 */ CHAR szKernelBuf[1000] = {0, }; socklen_t stLen; (VOID)getsockopt(iFd, IPPROTO_IP, SOCKET_OPS_GET, szKernelBuf, &stLen); CVD_TRACE("Get kernel cmdmsg: (%s) .", szKernelBuf); LIST_HEAD /* 设置非阻塞 */ ulRet = UTIL_SetFdFlag(iFd, O_NONBLOCK | O_CLOEXEC); if (ERROR_SUCCESS != ulRet) { return ERROR_FAILED; } g_iCvTcpListenFd = iFd; ulRet = CV_Func_FdCtl(iFd, EPOLL_CTL_ADD, cv_Comm_ConnProc); if (ERROR_SUCCESS != ulRet) { close(g_iCvTcpListenFd); g_iCvTcpListenFd = ZHAOGANG_INVALID_FD; }return ERROR_SUCCESS;}
代码分析:
1. 注册了SOCKET_OPS_SET、SOCKET_OPS_GET命令字,都是128,此宏定义一般放在include目录的某个.h中,内核态与用户态用相同的宏定义;
2. Set操作:sock_self_Recv回调只是简单的将命令字所带的内容拷贝进内核,然后通过printk打印出来(用dmesg可以查看),其中函数:copy_from_user()用于从用户态拷贝内存;
3. Get操作:sock_self_Send回调也是简单的将字符串"Thisis kernel msg"拷贝到用户态,其中函数copy_to_user()用户将内核buffer拷贝到用户态空间;
4. 默认装载时注册命令字:nf_register_sockopt,模块卸载时去注册命令字:nf_unregister_sockopt
4. Makefile文件
如何编译、装载、卸载内核模块请参阅博文。
obj-m += module_sock.o #generate the path CURRENT_PATH:=$(shell pwd)#the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path--根据获取的内核版本拼装绝对路径LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules#clean clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
5. 测试
内核态:
用户态:
- linux内核编程--2自定义内核与用户态通信方式(sock选项)
- linux内核态与用户态通信方式
- 内核态与用户态通信方式——Nelink
- linux内核空间与用户空间通信
- linux 内核与用户空间通信
- Linux 内核与用户空间通信
- linux用户态与内核态通信netlink
- Linux内核态与用户态通信的常用方法
- Linux内核态与用户态通信的常用方法
- Linux内核态与用户态通信的常用方法
- Linux内核态与用户态通信的常用方法
- linux用户态和内核态通信
- Linux用户态和内核态通信
- linux内核与用户之间的通信方式——虚拟文件系统、ioctl以及netlink
- linux内核与用户之间的通信方式——虚拟文件系统、ioctl以及netlink .
- linux系统内核空间和用户空间的通信方式
- 用户空间与内核的通信方式之netlink
- 内核空间与用户空间的通信方式
- API 网关设计 (Rest 风格)
- HDOJ2087 剪花布条
- GB2312、GBK字库偏移地址的计算
- Android 广播组件的简单认识+应用
- html模板渲染引擎有什么作用
- linux内核编程--2自定义内核与用户态通信方式(sock选项)
- 从一个小栗子说说export,export default
- Android移动开发-在Android 5.0 以上版本自定义Toolbar的实现
- 【机房】登录
- ORA-01034: 异常分析处理
- 数据结构实验之图论三:判断可达性
- spring事务
- 《Unsupervised Learning of Depth and Ego-Motion from Video》读书笔记
- javascriptBOM