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(&reg->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.   测试

内核态:

 


用户态:


原创粉丝点击