参考文章:
linux系统内核空间与用户空间通信的实现与分析:
http://www.ibm.com/developerworks/cn/linux/l-netlink/
进程上下文VS中断上下文:
http://www.2cto.com/os/201212/177668.html
内核态和用户态的最大区别在于特权级不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发 sys_fork()的执行时,就切换到了内核态。
用户态切换到内核态的3种方式:
1、系统调用(主动)
用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作
2、异常(被动)
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3、外围设备中断(中断)
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。
cpu总是处于以下3种状态:
内核态:进程上下文环境(内核代表进程运行于内核空间)
内核态:中断上下文(硬中断、软中断,内核代表硬件运行于内核空间)
用户态
上下文context:上下文简单来说就是一个环境
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文和系统级上下文
用户级上下文:正文、数据、用户堆栈以及共享存储区
存储器上下文:通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP)
系统级上下文:进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈
进程上下文:一个进程在执行的时候,CPU所有的寄存器的值、进程状态以及堆栈内容,当内核切换到另一个进程中,需要保存当前进程所有状态,即保存当前进程上下文,以便在此执行该进程时,能够恢复当时状态继续执行。
中断上下文:硬件传递的参数和内核需要保存的其他环境(主要是当时被中断的进程环境)
运行于进程上下文的内核代码是可以抢占的,但中断上下文会一直运行到结束,不会被抢占。
因此,内核会限制中断上下文的工作,不允许执行如下操作:
1、进入睡眠状态或主动放弃CPU;(中断上下文不属于任何进程,一旦进入睡眠无法唤醒,所以也叫原子上下文)
2、占用互斥体;为了保护中断句柄临界区资源,不允许使用mutexes,如果获取不到信号量,代码会睡眠,进入上文状况,如果必须使用锁,则使用spinlock
3、执行耗时任务;中断处理尽可能快,占用CPU时间太长会严重影响系统功能,如果需执行耗时任务,应该交由中断处理底半部来处理
4、访问用户空间虚拟内存;因为中断上下文运行于内核空间,无法访问用户空间虚拟地址
5、中断处理例程不应该设置成reentrant;(可被并行或递归调用的例程)
6、被更高级别的IRQ中断;将中断处理例程设置成快速处理例程,相当于告诉CPU,该例程运行时,禁止CPU所有中断请求,但这样将导致其他中断响应延迟,性能下降
linux内核态和用户态通信的方法:
1、进程上下文环境:
运行在进程上下文环境的代码是可以阻塞的,可以使用消息队列和UNIX域套接字实现内核态与用户态的通信,但是这种方法效率较低。
linux内核提供copy_from_user()和copy_to_user()来实现内核态和用户态的数据拷贝,但是这两个函数会引发阻塞,所以不能用在中断上下文。
一般将这两个函数用在系统调用一类函数中,工作原理如下:
2、中断上下文环境:
中断上下文与用户态进程毫无关系,运行过程不能阻塞。
2.1 使用一般进程间通信方法
通过使用自旋锁spinlock来实现中断环境与内核线程的同步,而内核线程是运行在进程上下文的,内核线程可以使用套接字或消息队列取得用户空间数据,再将数据通过临界区传递给中断过程,如下:
2.2 使用netlink套接字
linux2.4以后版本,使用netlink实现中断过程与用户态的通信,同时还使用了netlink实现了ip queue工具,但是ip queue的使用有局限性,不能自由用于各种中断过程。