阅读 845

操作系统 虚拟地址空间、用户空间、内核空间、用户态与内核态

内存分页

为了节约内存,提高使用效率,操作系统会将内存拆成一个个的小块来使用,在 Linux 中,这每一小块叫做 page(页) ,大小为4k

什么是虚拟地址空间

多任务操作系统中,每个进程都运行在属于自己的虚拟内存中,这块空间被称为 Virtual Address Space(虚拟地址空间)

为什么要有虚拟地址空间

为了让进程之间相互隔离。

假如让进程直接操作物理内存,很有可能会出现,不同进程都操作了同一物理内存地址,造成相互影响。于是就抽象出来 虚拟地址空间 这样一个中间层,让一块 虚拟地址空间 映射到一块 物理地址空间

image.png

如上图,两块虚拟内存通过page table(页表) 将自己映射到物理内存上,进程只能看到虚拟内存(当然它自己是不知道内存是虚拟的),进程只能"运行"在虚拟地址空间,只会操作属于自己的虚拟内存,因此进程之间不会相互影响

虚拟地址空间的大小及分配

操作系统需要为每一个进程分配属于自己的虚拟内存,那这个虚拟内存要分配多大呢?

在没有虚拟地址空间之前,是根据进程的需要按需分配物理内存的。但有了虚拟地址空间,分配策略可以变一下,先把虚拟地址空间分配的大些,但不立马建立与物理内存的映射,而是用到的时候,用多少,建立多少

image.png

这样物理内存的大小虽然不变,但是内存分配的灵活性大大的提高了,进程也不用担心地址会跟别的进程冲突,尽管用就是

32 位操作系统中,操作系统会为每个进程分配最大为 4G(2 的 32 次方)的虚拟地址空间

用户空间与内核空间

操作系统虽然为每个进程都分配了虚拟地址空间,但 虚拟地址空间中并不是所有的区域都可以为进程所用

操作系统将虚拟地址空间 分为用户空间内核空间,对于 32 位的操作系统,在 Linux虚拟地址空间中,用户空间内核空间的大小比例为 3:1,而在 window 中则为 2:2

4b08d479b5d94ebc870a25c5217599d9~tplv-k3u1fbpfcp-watermark.jpg

为什么会有内核空间

为了系统的安全,现代的操作系统一般都强制用户进程不能直接操作内核的,所有的系统调用都要交给内核完成(应用程序所有的操作,最终都是由内核代为完成的)。

但是内核也要运行在内存中,为了防止用户进程干扰,操作系统为内核单独划分了一块内存区域,这块区域就是内核空间,系统内核运行在内核空间中,

内核空间与用户空间的映射

Linux 中,系统启动时,就需要将内核加载到物理内存的内核空间上运行。

但对于进程,物理内存对它是不可见的,但它又需要使用内核来完成各种系统调用,而内核实际又在物理内存上。怎么解决这个矛盾?

Linux 想了一个办法,将进程的虚拟地址空间中的内核空间映射到物理内存中的内核空间上,内核就“搬到”虚拟内存中了。而在进程看了,自己的内存中就有了内核了,就可以通过内核进行各种系统调用了

Linux 中,内核空间是持续的,并且所有进程虚拟地址空间中的内核空间都映射到同样的物理内存内核空间

image.png

如上图 进程a 和 进程b 的内核空间都映射到了同一块物理内存区域,而用户空间的地址,则被映射到了不同的物理内存区域

用户态与内核态

当一个进程执行系统调用而陷入内核代码中执行时,就称进程处于内核运行态(或简称为内核态)。

比如有一个写文件的的 python 程序

with open('/test.txt','a+') as fw: # 打开文件
     fw.write('内容')
复制代码

运行起来之后就是一个进程了,也有了自己的虚拟地址空间,包括用户空间和内核空间。刚开始这个进程是运行在用户空间,但当执行到 fw.write('内容') 时,发现要往磁盘中写入一个文件,而读写磁盘这种事,只有内核才能操作,内核提供了一个 write() 系统级函数,fw.write('内容') 这段代码最终是执行 write() 系统调用来实现文件写入的

image.png

刚开始的 python 代码是加载到用户空间的内存中运行的,当执行 write() 系统调用是,write()是在内核空间运行的,但他们都属于同一进程,只是在执行系统调用的时候发生一个状态的切换。

程序运行在用户空间的时候,进程处于用户态,程序进入到内核运行后,进程处于内核态,这两种状态的切换就被称为上行文切换

上行文切换是很消耗资源的,所以要尽量避免上下文切换

用户态切换到内核态的 3 种方式

除了上面提到的系统调用,还有两张方式会导致用户态切换到内核态

  • 系统调用 : 这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如 Linux 的 int 80h 中断。
  • 异常 : 当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
  • 外围设备的中断 : 当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。