Linux内核空间和用户空间及系统调用

 linux驱动程序一般工作在内核空间,但也可以工作在用户空间。下面我们将详细解析,什么是内核空间,什么是用户空间,以及如何判断他们。

  Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G.Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为"内核空间".而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为"用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

  Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。从图中可以看出(这里无法表示图),每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

  内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。

  虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开始。对内核空间来说,其地址映射是很简单的线性映射,0xC0000000就是物理地址与线性地址之间的位移量,在Linux代码中就叫做PAGE_OFFSET.

  内核空间和用户空间之间如何进行通讯?

  内核空间和用户空间一般通过系统调用进行通信

  如何判断一个驱动是用户模式驱动还是内核模式驱动? 判断的标准是什么?

  用户空间模式的驱动一般通过系统调用来完成对硬件的访问,如通过系统调用将驱动的io空间映射到用户空间等。因此,主要的判断依据就是系统调用。

  内核空间和用户空间上不同太多了,说不完,比如用户态的链表和内核链表不一样;用户态用printf,内核态用printk;用户态每个应用程序空间是虚拟的,相对独立的,内核态中却不是独立的,所以编程要非常小心。等等。

  还有用户态和内核态程序通讯的方法很多,不单单是系统调用,实际上系统调用是个不好的选择,因为需要系统调用号,这个需要统一分配。

  可以通过ioctl、sysfs、proc等来完成。


         OS为用户态运行的进程与硬件设备进行交互提供了一组接口,有三个优点:从低级编程中解放、提高安全性、可移植性。

API与系统调用:前者是函数定义,后者通过陷阱明确向内核发请求。每个系统调用一般对应于一个C库中的封装例程,而这个封装例程,即为应用程序API。一个单独的API函数可能调用N个系统调用,也可能没对应任何系统调用,也有可能几个API对应同一个系统调用如mallocfree都是brk系统调用。POSIX标准针对API而非系统调用,编程者来看无差别,内核设计者观点看系统调用属内核,用户态库函数不属于内核。用户态进程调用一系统调用时,CPU切换到内核态并开始执行一个内核函数,即“系统调用处理程序”。内核中系统调用按号码识别,用户态的系统调用在封装例程中会自动生成系统调用号,存在eax寄存器中,如fork()存编号2。系统调用返回也经过封装例程处理后返回给用户态进程。调用系统调用时,在封装例程中,将参数处理完后,由汇编指令如int0x80进入内核的系统调用处理程序,它将寄存器入内核栈,调用系统调用服务例程,最后从处理程序返回。

封装例程中进入系统调用有两种:一是int0x80,这是老linux内核进入内核态的唯一方式。内核初始化时,将中断描述符表项相关字段初始化为内核代码段、将系统调用处理程序指针初始化为eip、设为陷阱、特权等级为3。此int指令跳转到之前所说的system_call(),该函数将系统调用号和异常处理程序所用的CPU寄存器入栈(不包括硬件自动保存的eflagscseipssesp)、检查有无调试程序跟踪、检查系统调用号。系统调用号与系统调用服务例程的对应关系存在系统调用分派表中。如果没问题,就调用分派表中对应的服务例程。用户态给的其它参数怎么办?不能靠栈传,因为这里涉及用户栈与内核栈,所以只能用寄存器间接传。事实上在封装例程中参数已入寄存器。系统调用处理程序中将寄存器入栈,造成服务例程可直接从内核栈中取参数。用Regs传参数的限制是参数不可多于6个,否则寄存器数量不够。此外参数长度也受限制。参数传来后,得检查其中的地址是否属于此进程的地址空间,但这花时间多且此错误发生可能性较小。后来,仅仅判断地址是否越入内核地址空间、之前的错误放到后面去捕捉,单独将是否侵入内核地址空间的检查提前是因为用户态进程可能误将内核地址空间的地址作为参数传,还能在不引起缺页异常的情况对内存中现有任何页读写。在系统调用服务例程中,需频繁访问进程地址空间数据,若之前粗略检查的地址号虽在0xc0000000前但不属于进程地址空间会发生什么?先回顾缺页异常在内核可能发生的条件:1.访问用户态地址空间的页,但页框不存在或试图写只读页。这时用请求调页分配页框即可;2.内核寻址到属于其他进程地址空间的页,但表项未初始化,此时只要在页表中建立相应的表项(发生在非连续内存访问);3.内核函数编程错误或硬件出问题;4.读一个由系统调用参数传入的地址,但它不属于进程的地址空间。后两种可如此解决:考虑访问进程地址空间的指令有限,因此可将它们列一个表,叫异常表,每个表由指令的线性地址和发生这种情况时要调用的修正措施的汇编代码地址,后者称作修正代码。这样,当缺页异常的34两种情况发生时,查异常表,若有,则说明调用参数非法,若无,则说明内核出bug了。异常表在建立内核程序映像时由C编译器生成,存放在内核代码段的__ex_table节中。此外,动态装载的内核模块也有自己的局部异常表,也在建立模块映像时C编译器生成。修正通常是强制服务例程返回出错码给用户态进程。

另一种新的进入系统调用指令是sysenter,它因为有特殊寄存器的帮助,所以速度快,总体的过程差不多。

内核线程也可调用系统调用,但因为它不能用库函数,Linux7个宏来完成对应工作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值