原文地址:http://bbs.chinaunix.net/thread-1940094-1-1.html
多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux 进程间同步和通信的方法提供足够的支持!本文就总结下常见的ipc,
getsockopt/setsockopt mmap netlink/socket proc/seq copy_from_user/copy_to_user 文件。采用先讲解后测试代码的方式,netlink和proc由于江哥和段兄都写的比较好了我就贴了链接... 好了不废话了开始
一.getsockopt/setsockopt
最近看ebtables源码,发现与内核的ipc是采用的getsockopt, 具体实现是在内核中用nf_register_sockopt函数注册一个nf_sockopt_ops的结构体,比如说:
-
static struct nf_sockopt_ops nso = {
-
.pf = PF_INET, // 协议族
-
.set_optmin = 常数, // 定义最小set命令字
-
.set_optmax = 常数+N, // 定义最大set命令字
-
.set = do_nso_set, // 定义set处理函数
-
.get_optmin = 常数, // 定义最小get命令字
-
.get_optmax = 常数+N, // 定义最大get命令字
-
.get = do_nso_get, // 定义set处理函数
-
};
复制代码
其中命令字不能与系统已有的命令字重复。set/get处理函数是直接由用户空间的set/getsockopt函数调用的。
从这个图里面可以看出来,这种方法的本质就是调用是copy_from_user()/copy_to_user()方法完成内核和用户通信的,这样其实效率不高,多用在传递控制选项信息,不适合用做大量数据的传输。copy_from_user()/copy_to_user()我讲在后面介绍... 当然对于linux任何都是文件那么我想应该也是可以定义自己的ioctl的,这个在后面的
copy_xx_user的块设备中讲解
setsockopt/getsockopt kernel部分代码:
-
static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len)
-
{
-
int ret = 0;
-
printk(KERN_INFO "sockopt: recv_msg()\n");
-
/*
-
switch(cmd)
-
{
-
case IMP1_SET:
-
{
-
char umsg[64];
-
memset(umsg, 0, sizeof(char)*64);
-
copy_from_user(umsg, user, sizeof(char)*64);
-
printk("umsg: %s", umsg);
-
}
-
break;
-
}
-
*/
-
if (cmd == SOCKET_OPS_SET)
-
{
-
char umsg[64];
-
int len = sizeof(char)*64;
-
memset(umsg, 0, len);
-
ret = copy_from_user(umsg, user, len);
-
printk("recv_msg: umsg = %s. ret = %d\n", umsg, ret);
-
}
-
return 0;
-
}
-
-
static int send_msg(struct sock *sk, int cmd, void *user, int *len)
-
{
-
int ret = 0;
-
printk(KERN_INFO "sockopt: send_msg()\n");
-
if (cmd == SOCKET_OPS_GET)
-
{
-
ret = copy_to_user(user, KMSG, KMSG_LEN);
-
printk("send_msg: umsg = %s. ret = %d. success\n", KMSG, ret);
-
}
-
return 0;
-
}
-
-
static struct nf_sockopt_ops test_sockops =
-
{
-
.pf = PF_INET,
-
.set_optmin = SOCKET_OPS_SET,
-
.set_optmax = SOCKET_OPS_MAX,
-
.set = recv_msg,
-
.get_optmin = SOCKET_OPS_GET,
-
.get_optmax = SOCKET_OPS_MAX,
-
.get = send_msg,
-
};
-
复制代码
setsockopt/getsockopt user部分代码:
-
/*call function recv_msg()*/
-
ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN);
-
printf("setsockopt: ret = %d. msg = %s\n", ret, UMSG);
-
len = sizeof(char)*64;
-
-
/*call function send_msg()*/
-
ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len);
-
printf("getsockopt: ret = %d. msg = %s\n", ret, kmsg);
-
if (ret != 0)
-
{
-
printf("getsockopt error: errno = %d, errstr = %s\n", errno, strerror(errno));
-
}
-
复制代码
二. mmap共享内存
采用共享内存通信的一个显而易 见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而 共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就 解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存 中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的.
kernel:
-
#include <linux/config.h>
-
#include <linux/module.h>
-
#include <linux/moduleparam.h>
-
#include <linux/init.h>
-
-
#include <linux/kernel.h> /* printk() */
-
#include <linux/slab.h> /* kmalloc() */
-
#include <linux/fs.h> /* everything... */
-
#include <linux/errno.h> /* error codes */
-
#include <linux/types.h> /* size_t */
-
#include <linux/mm.h>
-
#include <linux/kdev_t.h>
-
#include <asm/page.h>
-
#include <linux/cdev.h>
-
#include <linux/device.h>
-
#include <linux/gfp.h>
-
-
static unsigned char *myaddr=NULL;
-
static int simple_major = 0;
-
module_param(simple_major, int, 0);
-
-
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("Kenthy@163.com.");
-
MODULE_DESCRIPTION("Kernel study and test.");
-
-
-
/*
-
* Common VMA ops.
-
*/
-
-
void simple_vma_open(struct vm_area_struct *vma)
-
{
-
printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n",
-
vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
-
}
-
-
void simple_vma_close(struct vm_area_struct *vma)
-
{
-
printk(KERN_NOTICE "Simple VMA close.\n");
-
}
-
-
struct page *simple_vma_nopage(struct vm_area_struct *vma,
-
unsigned long address, int *type)
-
{
-
struct page *pageptr;
-
unsigned long offset = (address - vma->vm_start);
-
if (offset>PAGE_SIZE*2)
-
{
-
printk("out of size\n");
-
return NULL;
-
}
-
printk("in vma_nopage: offset=%u\n", offset);
-
-
if(offset<PAGE_SIZE) // the first page
-
pageptr=virt_to_page(myaddr);
-
else // the second page
-
pageptr=virt_to_page(myaddr+PAGE_SIZE);
-
-
get_page(pageptr);
-
-
return pageptr;
-
}
-
|