Linux内核态用户态相关知识 & 相互通信

http://www.cnblogs.com/bakari/p/5520860.html

 

内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境

系统调用是操作系统的最小功能单位,这些系统调用根据不同的应用场景可以进行扩展和裁剪,现在各种版本的Unix实现都提供了不同数量的系统调用,如Linux的不同版本提供了240-260个系统调用,FreeBSD大约提供了320个(reference:UNIX环境高级编程)。

 

 

 

Intel的X86架构的CPU提供了0到3四个特权级,数字越小,特权越高,Linux操作系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。

 

 

一些操作需要在内核权限下才能执行,这就涉及到一个从用户态切换到内核态的过程。比如C函数库中的内存分配函数malloc(),它具体是使用sbrk()系统调用来分配内存,当malloc调用sbrk()的时候就涉及一次从用户态到内核态的切换,类似的函数还有printf(),调用的是wirte()系统调用来输出字符串,等等。

 

 

在什么情况下会发生从用户态到内核态的切换,一般存在以下三种情况:

1)当然就是系统调用:原因如上的分析。

2)异常事件: 当CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,典型的如缺页异常。

3)外围设备的中断:当外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。

  注意:系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断,这是操作系统为用户特别开放的一种中断,如Linux int 80h中断。所以,从触发方式和效果上来看,这三种切换方式是完全一样的,都相当于是执行了一个中断响应的过程。但是从触发的对象来看,系统调用是进程主动请求切换的,而异常和硬中断则是被动的。

 

(注:补充一下:Linux进程空间,也分成内核空间和用户空间,执行内核空间中的内容要在内核态)

 

内核与用户态的通信

http://www.cnblogs.com/gaiwen/articles/3002774.html

 

在一台运行 Linux 的计算机中,CPU 在任何时候只会有如下四种状态:

【1】 在处理一个硬中断。

【2】 在处理一个软中断,如 softirq、tasklet 和 bh。(softirq、tasklet和workqueue,貌似都是bh, bottom-half的机制,tasklet可以说是一种扩展,支持更多的软中断)

【3】 运行于内核态,但有进程上下文,即与一个进程相关。

【4】 运行一个用户态进程。

 

Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的System V IPC,后者则实现了基于socket的进程间通信机制。

同时Linux也遵循IEEE制定的Posix IPC标准,在三者的基础之上实现了以下几种主要的IPC机制:管道(Pipe)及命名管道(Named Pipe),信号(Signal),消息队列(Message queue),共享内存(Shared Memory),信号量(Semaphore),套接字(Socket)。

 

为了完成内核空间与用户空间通信,Linux提供了基于socket的Netlink通信机制,可以实现内核与用户空间数据的及时交换。

 

到目前Linux提供了9种机制完成内核与用户空间的数据交换,分别是内核启动参数、模块参数与 sysfs、sysctl、系统调用、netlink、procfs、seq_file、debugfs和relayfs,

其中模块参数与sysfs、procfs、debugfs、relayfs是基于文件系统的通信机制,用于内核空间向用户控件输出信息;

sysctl、系统调用是由用户空间发起的通信机制。由此可见,以上均为单工通信机制,在内核空间与用户空间的双向互动数据交换上略显不足。

Netlink是基于socket的通信机制,由于socket本身的双共性、突发性、不阻塞特点,因此能够很好的满足内核与用户空间小量数据的及时交互,因此在Linux 2.6内核中广泛使用,例如SELinux,Linux系统的防火墙分为内核态的netfilter和用户态的iptables,netfilter与iptables的数据交换就是通过Netlink机制完成。 

 

2 各种通信方式

本节说明各种通信方式是否适合内核空间和用户空间信息交互,以及如何使用。

 

2.1 信号

在进程中使用函数signal()或sigaction()安装信号时指定了关联的函数。在内核空间相进程发送信号,从内核空间返回进程空间时检查并执行相应的关联函数。

在进程中可以使用pause()函数进入睡眠,在有信号产生并执行了相应的关联函数后进程被唤醒,继续执行。可以使用这种方式实现内核空间和用户空间的同步。

pause()会使当前进程挂起,直到捕捉到一个信号,对指定为忽略的信号,pause()不会返回。只有执行了一个信号处理函数,并从其返回,pause()才返回-1,并将errno设为EINTR。

 

2.2 信号量

虽然原理一样,但内核空间和用户空间的信号量是完全两套系统,所以信号量不能用于内核空间和用户空间信息交互。

 

2.3 无名管道

无名管道只适用于有关系的进程之间通信。不能用于内核空间和用户空间信息交互。

 

2.4 get_user()/put_user()
2.5 copy_from_user()/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 把该物理地址数值传给用户空间。

 

 

经典的内核与用户空间的通信(使用read和write系统调用)

1. 用户空间需要将某些数据传递给内核,并指定数据的处理函数。

2. 用户空间需要从内核读取数据,并指定数据的读取函数。

 

后面比较难懂了,略。

 

posted @ 2017-01-08 13:57  blcblc  阅读(3516)  评论(0编辑  收藏  举报