您的位置:首页 > 运维架构 > Linux

linux用户空间与内核空间的通信技术总结

2012-04-11 22:27 423 查看
原文地址: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;

}

static struct vm_operations_struct simple_nopage_vm_ops = {

.open = simple_vma_open,

.close = simple_vma_close,

.nopage = simple_vma_nopage,

};

static int simple_open (struct inode *inode, struct file *filp)

{

return 0;

}

static int simple_release(struct inode *inode, struct file *filp)

{

return 0;

}

static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)

{

unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;

printk("enter simple_nopage_mmap: offset=%u, vma->vm_pgoff=%u\n", offset, vma->vm_pgoff);

if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))

vma->vm_flags |= VM_IO;

vma->vm_flags |= VM_RESERVED;

vma->vm_ops = &simple_nopage_vm_ops;

simple_vma_open(vma);

return 0;

}

/*

* Set up the cdev structure for a device.

*/

static void simple_setup_cdev(struct cdev *dev, int minor,

struct file_operations *fops)

{

int err, devno = MKDEV(simple_major, minor);

cdev_init(dev, fops);

dev->owner = THIS_MODULE;

dev->ops = fops;

err = cdev_add (dev, devno, 1);

/* Fail gracefully if need be */

if (err)

printk (KERN_NOTICE "Error %d adding simple%d", err, minor);

}

static struct file_operations simple_nopage_ops = {

.owner = THIS_MODULE,

.open = simple_open,

.release = simple_release,

.mmap = simple_nopage_mmap,

};

/*

* We export two simple devices. There's no need for us to maintain any

* special housekeeping info, so we just deal with raw cdevs.

*/

static struct cdev SimpleDevs;

/*

* Module housekeeping.

*/

static int simple_init(void)

{

int result;

//unsigned int addr1, addr2;

dev_t dev = MKDEV(simple_major, 0);

/* Figure out our device number. */

if (simple_major)

result = register_chrdev_region(dev, 1, "simple_nopage");

else {

result = alloc_chrdev_region(&dev, 0, 1, "simple_nopage");

simple_major = MAJOR(dev);

}

if (result < 0) {

printk(KERN_WARNING "simple_nopage: unable to get major %d\n", simple_major);

return result;

}

if (simple_major == 0)

simple_major = result;

/* Now set up two cdevs. */

simple_setup_cdev(&SimpleDevs, 0, &simple_nopage_ops);

myaddr = __get_free_pages(GFP_KERNEL, 1);

if (!myaddr)

return -ENOMEM;

// for test

strcpy(myaddr, "1234567890");

strcpy(myaddr+PAGE_SIZE, "abcdefghij");

return 0;

}

static void simple_cleanup(void)

{

cdev_del(&SimpleDevs);

unregister_chrdev_region(MKDEV(simple_major, 0), 1);

}

module_init(simple_init);

module_exit(simple_cleanup);

复制代码

user:

#include </work/apue/ourhdr.h>

#include <fcntl.h>

#include <sys/mman.h>

int main(int argc, char *argv[])

{

int fdin, fdout;

void *src, *dst;

struct stat statbuf;

unsigned char sz[1024]={0};

if ((fdin = open("/dev/simple_nopage", O_RDONLY)) < 0)

err_sys("can't open /dev/simple_nopage for reading");

if ((src = mmap(NULL, 4096*2, PROT_READ, MAP_SHARED,

fdin, 0)) == MAP_FAILED)

err_sys("mmap error for simplen");

memcpy(sz, src, 11);

sz[10]='\0';

printf("%x\n", src);

printf("%s\n\n", sz);

memcpy(sz, src+4096, 11);

printf("%x\n", src+4096);

printf("%s\n", sz);

exit(0);

}

复制代码

mmap加载文件后注意还要mknod

三. netlink

看看duanjigang兄的这两篇文章就可以了

netlink socket 编程之 why & how

http://linux.chinaunix.net/bbs/viewthread.php?tid=1031932&extra=page%3D2%26amp%3Bfilter%3Ddigest

使用netlink通讯时需要注意的一些问题

http://linux.chinaunix.net/bbs/viewthread.php?tid=1144547&extra=page%3D2%26amp%3Bfilter%3Ddigest

四. proc/seq

记得proc和seq是我面试实习的时候一个小笔试题,当时小弟我很是无助在dreamice大哥的无私的指点,甚至可以说你替我完成了作业,小弟我真是惭愧,也正是下面两个帖子诞生的背景^_^

proc文件系统剖析

http://linux.chinaunix.net/bbs/viewthread.php?tid=1044497&extra=page%3D2%26amp%3Bfilter%3Ddigest

Seq_file File System实例剖析

http://linux.chinaunix.net/bbs/viewthread.php?tid=1044672&extra=page%3D2%26amp%3Bfilter%3Ddigest

[ 本帖最后由 ubuntuer 于 2010-1-16 16:10 编辑 ]
已有 1 人评分可用积分收起理由


Godbach
+ 15多谢分享
总评分: 可用积分 + 15 查看全部评分

IT168文库精选文档推荐
引入PHP包含文件 重用PHP程序代码.doc
Pro Drupal 7 Development 3rd Edition.pdf
第1章__Visual_C#.NET_2008开发环境概述.ppt
professional eclipse 3 for java web developers.pdf
http://ubuntuer.cublog.cn欢迎做做
数据库SQL Plus登陆成功一闪而过怎么办? | 有奖活动:PL/SQL
Challenge 每日一题:201 ... | asm实例的迁移 | 加班案例一则。single-task
message
ubuntuer





白手起家

帖子
2838
主题
668
精华
9
可用积分
41
专家积分
100
在线时间
1600 小时
注册时间
2008-08-15
最后登录
2011-12-01

串门

好友

博客

消息

论坛徽章:
0

2楼[报告]


发表于
2010-01-16 16:07:26 |只看该作者

五. 文件

使用文件来通信其实是有点牵强的,不过这个当时在bell的一个项目中,我确实这个干过就来出来了




#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/fs.h>

#include <asm/uaccess.h>

#include <linux/mm.h>

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Kenthy@163.com.");

MODULE_DESCRIPTION("Kernel study and test.");

struct file *filp;

mm_segment_t fs;

void SLEEP_MILLI_SEC(int nMilliSec)

{

long timeout = (nMilliSec) * 100 / 1000;

while(timeout > 0)

{

timeout = schedule_timeout(timeout);

}

}

void fileread(const char * filename)

{

struct file *filp;

struct inode *inode;

mm_segment_t fs;

off_t fsize;

char *buf;

unsigned long magic;

filp=filp_open(filename,O_RDONLY,0);

inode=filp->f_dentry->d_inode;

magic=inode->i_sb->s_magic;

printk("file system magic:%li \n",magic);

printk("super blocksize:%li \n",inode->i_sb->s_blocksize);

printk("inode %li \n",inode->i_ino);

fsize=inode->i_size;

printk("file size:%i \n",(int)fsize);

buf=(char *) kmalloc(fsize+1,GFP_ATOMIC);

fs=get_fs();

set_fs(KERNEL_DS);

filp->f_op->read(filp,buf,fsize,&(filp->f_pos));

set_fs(fs);

buf[fsize]='\0';

printk("The File Content is:\n");

printk("%s\n",buf);

kfree(buf);

filp_close(filp,NULL);

}

void filewrite(char* filename, char* data)

{

fs=get_fs();

set_fs(KERNEL_DS);

filp->f_op->write(filp, data, strlen(data),&filp->f_pos);

set_fs(fs);

}

void test_write(char* filename)

{

int i;

char data[20];

filp = filp_open(filename, O_RDWR|O_APPEND, 0644);

if(IS_ERR(filp))

{

printk("open error...\n");

return;

}

for(i=0;i<10;i++)

{

sprintf(data, "%s, %d\n", "kernel write test", i);

filewrite(filename, data);

SLEEP_MILLI_SEC(1000);

}

}

int init_module()

{

char *filename="/root/log";

printk("%s\n", "begin write data");

test_write(filename);

printk("%s\n", "begin read data");

fileread(filename);

return 0;

}

void cleanup_module()

{

filp_close(filp,NULL);

printk("Good,Bye!\n");

}

复制代码

上面的代码还附送了内核中如何sleep


六. copy_xxx_user

copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.这么简单的一个函数却含盖了许多关于内核方面的知识,比如内核关于异常出错的处理.从用户空间拷贝数据到内核中时必须很小心,假如用户空间的数据地址是个非法的地址,或是超出用户空间的范围,或是那些地址还没有被映射到,都可能对内核产生很大的影响,如oops,或被造成系统安全的影响.所以copy_from_user函数的功能就不只是从用户空间拷贝数据那样简单了,他还要做一些指针检查连同处理这些

问题的方法. 函数原型在[arch/i386/lib/usercopy.c]中

unsigned long

copy_from_user(void *to, const void __user *from, unsigned long n)

{

might_sleep();

if (access_ok(VERIFY_READ, from, n))

n = __copy_from_user(to, from, n);

else

memset(to, 0, n);

return n;

}

复制代码

具体的代码实现我就不讲了,我自己也看的懵懂!!!还是来使用使用吧

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/sched.h>

#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Kenthy@163.com.");

MODULE_DESCRIPTION("Kernel study and test.");

#define DP_MAJOR 251//主设备号

#define DP_MINOR 0 //次设备号

#define DEV_NAME "kenthy"

static int char_read(struct file *filp, char __user *buffer, size_t, loff_t*);

static int char_open(struct inode*, struct file*);

static int char_write(struct file *filp, const char __user *buffer, size_t, loff_t*);

static int char_release(struct inode*, struct file*);

static int chropen;

struct cdev *chardev;

static int len;

static char *to;

static const struct file_operations char_ops={

.read = char_read,

.write = char_write,

.open = char_open,

.release = char_release,

};

static int __init char_init(void)

{

dev_t dev;

printk(KERN_ALERT"Initing......\n");

dev = MKDEV(DP_MAJOR, DP_MINOR);

chardev = cdev_alloc();

if(chardev == NULL){

return -1;

}

if(register_chrdev_region(dev, 10, DEV_NAME)){

printk(KERN_ALERT"Register char dev error\n");

return -1;

}

chropen = 0;

len = 0;

cdev_init(chardev, &char_ops);

if(cdev_add(chardev, dev, 1)){

printk(KERN_ALERT"Add char dev error!\n");

}

return 0;

}

static int char_open(struct inode *inode, struct file *file)

{

if(chropen == 0)

chropen++;

else{

printk(KERN_ALERT"Another process open the char device\n");

return -1;

}

try_module_get(THIS_MODULE);

return 0;

}

static int char_release(struct inode *inode,struct file *file)

{

chropen--;

module_put(THIS_MODULE);

return 0;

}

static int char_read(struct file *filp,char __user *buffer,size_t length,loff_t *offset)

{

unsigned long nn;

nn = copy_to_user(buffer, to, length);

printk("nn = %ld\n", nn);

printk("buffer = %s\n", buffer);

return length;

}

static int char_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset)

{

unsigned long n;

to = (char *)kmalloc((sizeof(char)) * (length+1), GFP_KERNEL);

memset(to, '\0', length+1);

n = copy_from_user(to, buffer, length);

printk("n = %ld\n", n);

printk("to = %s\n", to);

return length;

}

static void __exit module_close(void)

{

len=0;

printk(KERN_ALERT"Unloading..........\n");

unregister_chrdev_region(MKDEV(DP_MAJOR,DP_MINOR),10);

cdev_del(chardev);

}

module_init(char_init);

module_exit(module_close);

复制代码

这个与mmap的类似都依赖于一个字符设备文件

sudo insmod chardev.ko

在使用测试程序前我们要创建一个字符设备:

#mknod /dev/chardev0 c 251 0

测试程序代码如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <fcntl.h>

int main(int args, char *argv[])

{

int testdev;

int i, rf = 0;

char buf[15];

memset(buf, '\0', 15);

testdev = open("/dev/chardev0", O_RDWR);

if(testdev == -1){

perror("open\n");

exit(0);

}

if((write(testdev, "1111111111", 10)) < 0){

perror("Write error!\n");

exit(0);

}

close(testdev);

printf("write finish!\n");

testdev = open("/dev/chardev0", O_RDWR);

rf = read(testdev, buf, 3);

if(rf < 0)

perror("read error\n");

printf("Read: %s\n", buf);

close(testdev);

return 0;

}

复制代码

运行程序:

#./test

Read: 111

看一下内核信息:

#dmesg

最后几行信息如下:

nn = 0

buffer = 1111111111

n = 0

to = 111

这就说明设备注册正确,从用户到内核copy_from_user和从内核到用户copy_to_user都顺利完成了。呵呵,你是不是觉得还少了点什么东西,对我前面说还有ioctl没有讲

在内核空间中ioctl是很多内核操作结构的一个成员函数,如文件操作结构struct file_operations(include/linux/fs.h)、协议操作结构struct proto_ops(include/linux/net.h)等、tty操作结构struct tty_driver(include/linux/tty_driver.h)等,而这些操作结构分别对应各种内核设备,只要在用户空间打开这些设备,如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就可以在这个描述符上调用ioctl(2)来向内核交换数据。

ioctl(2)函数的基本使用格式为:

int ioctl(int fd, int cmd, void *data)

第一个参数是文件描述符;cmd是操作命令,一般分为GET、SET以及其他类型命令,GET是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始位置指针,

cmd命令参数是个32位整数,分为四部分:

dir(2b) size(14b) type(8b) nr(8b)

详细定义cmd要包括这4个部分时可使用宏_IOC(dir,type,nr,size)来定义,而最简单情况下使用_IO(type, nr)来定义就可以了,这些宏都在include/asm/ioctl.h中定义

本文cmd定义为:

#define NEWCHAR_IOC_MAGIC 'M'

#define NEWCHAR_SET _IO(NEWCHAR_IOC_MAGIC, 0)

#define NEWCHAR_GET _IO(NEWCHAR_IOC_MAGIC, 1)

#define NEWCHAR_IOC_MAXNR 1

要定义自己的ioctl操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,比如想通过socket描述符进行ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可,不过这种方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的ioctl()来操作,可以编成模块,这样不影响原有的内核,这是最通常的做法。

大致过程如下

进行ioctl调用的基本处理函数

static int newchar_ioctl(struct inode *inode, struct file *filep,

unsigned int cmd, unsigned long arg)

{

int ret;

// 首先检查cmd是否合法

if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;

if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;

// 错误情况下的缺省返回值

ret = EINVAL;

switch(cmd)

{

case KNEWCHAR_SET:

// 设置操作,将数据从用户空间拷贝到内核空间

{

struct newchar nc;

if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)

return -EFAULT;

ret = do_set_newchar(&nc);

}

break;

case KNEWCHAR_GET:

// GET操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部

// 数据后重新写回缓冲区

// 当然也可以根据具体情况什么也不传入直接向内核获取数据

{

struct newchar nc;

if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)

return -EFAULT;

ret = do_get_newchar(&nc);

if(ret == 0){

if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)

return -EFAULT;

}

}

break;

}

return ret;

}

复制代码

由于能力有限,难免会有错误和纰漏,还望给位海涵并指正, 当然也还存在其他的user kernel ipc, 也希望给位指出来。工作量比较大我的代码很多都只是提供了一种思路,要像深究研究的话,还必须DIY了!!!马上放寒假了,要回家过最后一个舒服的大年了,也要离开cu一段时间了,念念不舍啊...

参考文献:

Linux 系统内核空间与用户空间通信的实现与分析:

http://www.ibm.com/developerworks/cn/linux/l-netlink/index.html

Linux Netfilter实现机制和扩展技术:

http://www.ibm.com/developerworks/cn/linux/l-ntflt/index.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: