内核空间和用户空间,内核态和用户态

内核空间和用户空间,内核态和用户态(转载)

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

1.虚拟内核空间到物理空间的映射
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。可能有人会问,系统启动时,内核的代码和数据不是被装入到物理内存吗?它们为什么也处于虚拟内存中呢?这和编译程序有关,后面我们通过具体讨论就会明白这一点。
虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开始。对内核空间来说,其地址映射是很简单的线性映射,0xC0000000就是物理地址与线性地址之间的位移量,在Linux代码中就叫做PAGE_OFFSET。
内核的虚拟地址空间到物理地址空间的映射我们可以在 include/asm/i386/page.h中看到对内核空间中地址映射的说明及定义: /*
143 /*
144 * This handles the memory map.. We could make this a config
145 * option, but too many people screw it up, and too few need
146 * it.
147 *
148 * A __PAGE_OFFSET of 0xC0000000 means that the kernel has
149 * a virtual address space of one gigabyte, which limits the
150 * amount of physical memory you can use to about 950MB.
151 *
152 * If you want more physical memory than this then see the CONFIG_HIGHMEM4G
153 * and CONFIG_HIGHMEM64G options in the kernel configuration.
154 */
...
173 #define __PAGE_OFFSET CONFIG_PAGE_OFFSET
...
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
源代码的注释中说明,如果你的物理内存大于950MB,那么在编译内核时就需要加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G选项,这种情况我们暂不考虑。如果物理内存小于950MB,则对于内核空间而言,给定一个虚地址x,其物理地址为 “x-PAGE_OFFSET”,给定一个物理地址 x,其虚地址为“ x+ PAGE_OFFSET”。
需要注意的是,宏 __pa()仅仅把一个内核空间的虚地址映射到物理地址,而决不适用于用户空间,用户空间的地址映射要复杂得多。
2.内核映像
在下面的描述中,把内核的代码和数据就叫内核映像( kernel image)。当系统启动时, Linux内核映像被安装在物理地址 0x00100000开始的地方,即 1MB开始的区间 (第1M留作它用)。然而,在正常运行时,整个内核映像应该在虚拟内核空间中,因此,连接程序在连接内核映像时,在所有的符号地址上加一个偏移量 PAGE_OFFSET,这样,内核映像在内核空间的起始地址就为 0xC0100000。
例如,进程的页目录 PGD(属于内核数据结构)就处于内核空间中。在进程切换时,要将寄存器 CR3设置成指向新进程的页目录 PGD,而该目录的起始地址在内核空间中是虚地址,但 CR3所需要的是物理地址,这时候就要用 __pa()进行地址转换。在 mm_context.h中就有这么一行语句:
asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));
这是一行嵌入式汇编代码,其含义是将下一个进程的页目录起始地址 next_pgd,通过__pa()转换成物理地址,存放在某个寄存器中,然后用 mov指令将其写入CR3寄存器中。经过这行语句的处理, CR3就指向新进程 next的页目录表 PGD了。



内核态和用户态区别
当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

 


1、用系统调用时进入核心态。Linux对硬件的操作只能在核心态,这可以通过写驱动程序来控制。在用户态操作硬件会造成core    dump.   
 2、要注意区分系统调用和一般的函数。系统调用由内核提供,如read()、write()、open()等。而一般的函数由软件包中的函数库提供,如sin()、cos()等。在语法上两者没有区别。   
 3、一般情况:系统调用运行在核心态,函数运行在用户态。但也有一些函数在内部使用了系统调用(如fopen),这样的函数在调用系统调用是进入核心态,其他时候运行在用户态。

大概是    当用户程序调用系统的API时,就产生中断,进入内核态的API,处理完成后,用中断再退出,返回用户态的调用函数。   
   user    api    -->    interrupt    -->    kernel    api    -->    interrupt
---------------------------------------------------------------------

简单来讲一个进程由于执行系统调用而开始执行内核代码,我们称该进程处于内核态中. 一个进程执行应用程序自身代码则称该进程处于用户态.


intel x86 架构的 CPU 分为好几个运行级别, 0--3 , 0 为最高级别, 3 为最低级别


针对不同的级别,有很多的限制,比如说传统的 in ,out 指令,就是端口的输入输出指令, 0 级下是可以用的,但在 3 级下就不能用,你用就产生陷阱,告诉你出错了,当然限制还有很多了,不只是这一点


操作系统下是利用这个特点,当操作系统自己的代码运行时, CPU 就切成 0 ,当用户的程序运行是就只让它在 3 级运行,这样如果用户的程序想做什么破坏系统的事情的话,也没办法做到


当然,低级别的程序是没法把自己升到高级别的,也就是说用户程序运行在 3 ,他想把自己变成 0 级自己是做不到的,除非是操作系统帮忙,利用这个特性,操作系统就可以控制所有的程序的运行,确保系统的安全了. 平时把操作系统运行时的级别就叫内核态(因为是操作系统内核运行时的状态),而且普通用户程序运行时的那个级别叫用户态...
当操作系统刚引导时, CPU 处于实模式,这时就相当于是 0 ,于是操作系统就自动得到最高权限,然后切到保护模式时就是 0 ,这时操作系统就占了先机,成为了最高级别的运行者,由于你的程序都是由操作系统来加载的,所以当它把你加载上来后,就把你的运行状态设为 3 ,即最低级,然后才让你运行,所以没办法,你只能在最低级运行了,因为没办法把自己从低级上升到高级, 这就是操作系统在内核态可以管理用户程序,杀死用户程序的原因.

 


你可能感兴趣的:(linux编程)