http://blog.chinaunix.net/uid-27052262-id-3259903.html因网上已有很多介绍各种通信方式的示例代码,所以在本文中只是给出各种内核空间和用户空间通信方式的介绍说明。希望给像我一样的初学者提供一定的指导。因水平有限,欢迎各位批评指点。
1 概述
Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间“)。除了进程之间的通信外,在嵌入式设计中还经常需要进行内核空间和用户空间的信息交互。本文主要讨论内核空间和用户空间信息交互的方法。
1.1 处理器状态
处理器总处于以下状态中的一种:
A、内核态,运行于进程上下文,内核代表进程运行于内核空间;
B、内核态,运行于中断上下文,包括硬中断和软中断;
C、用户态,运行于用户空间。
1.2 不同状态的限制
根据上面的状态分类,内核空间和用户空间之间的信息交互就分为两类,即中断上下文内核态空间与进程空间信息交互;进程上下文内核态空间和进程空间信息交互。
内核态环境
进入内核态的方式
局限性
说明
进程上下文
在进程中通过系统调用进入内核态,内核态代码与该进程相关。
内核空间和进程空间的虚拟地址不同,不能直接传递信息。
该进程的页表基地址依然在页表基地址寄存器(如X86中的CR3)中,内核空间中可以使用__user 强制使用用户空间的地址,从而进行数据交互。
中断上下文
硬件触发中断,或内核中挂接软中断。不与特定的进程相关。
内核空间和进程空间的虚拟地址不同,不能直接传递信息。
中断中不能睡眠,不能运行引起阻塞的函数。
由于中断触发的随机性,中断上下文内核态不与特定的进程相关。
2 各种通信方式
本节说明各种通信方式是否适合内核空间和用户空间信息交互,以及如何使用。
2.1 信号
在进程中使用函数signal()或sigaction()安装信号时指定了关联的函数。在内核空间相进程发送信号,从内核空间返回进程空间时检查并执行相应的关联函数。
在进程中可以使用pause()函数进入睡眠,在有信号产生并执行了相应的关联函数后进程被唤醒,继续执行。可以使用这种方式实现内核空间和用户空间的同步。
pause()会使当前进程挂起,直到捕捉到一个信号,对指定为忽略的信号,pause()不会返回。只有执行了一个信号处理函数,并从其返回,puase()才返回-1,并将errno设为EINTR。
2.2 信号量
虽然原理一样,但内核空间和用户空间的信号量是完全两套系统,所以信号量不能用于内核空间和用户空间信息交互。
2.3 无名管道
无名管道只适用于有关系的进程之间通信。不能用于内核空间和用户空间信息交互。
2.4 get_user()/put_user()
get_user(x, ptr):本函数是在内核中被调用,获取用户空间指定地址的数值(一个字节或字)并保存到内核变量x中,ptr为用户空间的地址。用法举例如下:get_user(val, (int __user *)arg)
put_user(x, ptr):在内核中被调用,将内核空间的变量x的数值(一个字节或字)保存到用户空间指定地址处,prt为用户空间地址。用法举例如下:put_user(val, (int __user *)arg)
注明:函数用于进程上下文内核态空间,即通常在系统调用函数中使用该函数,如设备驱动中ioctl函数中。
2.5 copy_from_user()/copy_to_user()
主要应用于设备驱动中读写函数中,通过系统调用触发,在当前进程上下文内核态运行(即当前进程通过系统调用触发)。
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
通常用在设备读函数或ioctl 中获取参数的函数中:其中“to”是用户空间的buffer地址,在本函数中将内核buffer“from”除的n个字节拷贝到用户空间的“to”buffer。
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
通常用在设备写函数或ioctl中设置参数的函数中:“to”是内核空间的buffer指针,要写入的buffer;“from”是用户空间的指针,数据源buffer。
注意:中断代码时不能用这两个函数,因为其调用了might_sleep()函数,会导致睡眠,并且这两个函数要求在进程上下文内核态空间中运行。
这两个函数不能直接在中断中使用,但是可以变通一下,在中断中向进程发送信号通知进程有数据准备好。在进程执行时调用read函数,在read函数中调用copy_to_user函数,从而实现中断触发,把数据从内核空间拷贝到用户空间的需要。
2.6 共享内存(mmap)
使用mmap()函数通常映射一个普通文件实现进程之间内存共享,即多个进程打开同一个文件,将文件映射到各自进程的虚拟空间。这样各个进程就可以通过共享的内存进行大量的数据交互,当然需要我们自己设计互斥功能。
还可以使用mmap()函数实现内核空间和用户空间内存共享的功能。网上提到的方法基本都是proc文件+mmap。
大体过程如下
1、在模块中申请一些内存页面,作为共享的内存空间。
2、创建可读的proc文件,在其读函数中把上面申请的内存空间的物理地址返回给进程空间。
3、在进程空间open /dev/mem文件,并把从proc读取的物理地址(要共享的内存的物理地址)作为文件/dev/mem的offset,以此offset 把/dev/mem文件的若干空间用mmap映射到进程空间。
注意:
1、/dev/mem 不是一个普通的文件里面的内容是所有物理内存的内容信息。所以,在上面的过程中把共享空间的物理地址作为offset使用。
2、proc文件的作用就是提供一个读取的函数,把共享内存的地址从内核空间传递到用户空间。也可以用设备的ioctl 把该物理地址数值传给用户空间。