笔记-操作系统-内存-内核空间与用户空间

 

1.      内核空间与用户空间

用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。

上面的话叫做正确的废话,没什么意义。

1.1.    基本概念

问题来了:

  1. 1.      谁来划分内存空间的呢?

这种划分是操作系统在逻辑上的划分,不同版本的操作系统划分的结果都是不一样的。

  1. 2.      用户空间与内核空间的具体体现形式

32位操作系统的寻址空间(虚拟存储空间)为4G(2^32)。

操心系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

  1. 为什么非要分成内核空间和用户空间

主要是为了安全

 

1.1.1.   其它相关问题

1.用户空间(进程)是否有高端内存概念?

用户进程没有高端内存概念。只有在内核空间才存在高端内存。用户进程最多只可以访问3G物理内存,而内核进程可以访问所有物理内存。

2.64位内核中有高端内存吗?

目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。 

3.用户进程能访问多少物理内存?内核代码能访问多少物理内存?

32位系统用户进程最大可以访问3GB,内核代码可以访问所有物理内存。

64位系统用户进程最大可以访问超过512GB,内核代码可以访问所有物理内存。

 

1.1.2.   关于内核态与用户态

内核态和用户态是操作系统运行的两种级别,intel x86提供Ring0-Ring3四种级别的运行模式,0最高,3最低。

Linux使用了3作为用户态,0作为内核态,没有使用1和2级。

Ring3状态不能访问Ring0的地址空间,包括代码和数据。

 

内核态与用户态转换:

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

 

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

  1. 系统调用:

可以认为是主动的;比如fork()

  1. 异常:
  2. 外围设备的中断:

 

附:,对于ARM体系的CPU 一共有七种运行模式,分别是:用户模式(usr)、快速中断模式(fiq)、中断模式(irq)、管理模式(svc)、系统模式(sys)、数据访问终止模式(abt)和未定义指令终止模式(und)。除了用户模式外,其他6中工作模式都属于特权模式,而特权模式中除了系统模式外,其他5种模式又称为异常模式。

 

1.2.    内核地址空间划分

基本划分模型:

X86 CPU采用了段页式地址映射模型,进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。

再重复一次,通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。

当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0×3,0xc0000004对应的物理地址为0×4,… …,逻辑地址与物理地址对应的关系为

物理地址 = 逻辑地址 – 0xC0000000

逻辑地址

物理内存地址

0xc0000000

0×0

0xc0000001

0×1

0xc0000002

0×2

0xc0000003

0×3

0xe0000000

0×20000000

0xffffffff

0×40000000 ??

 

 

问题1:大于1G的内存怎么访问?

在上面所描述的模型中,一对一的地址映射模式下不能访问1G以后的内存空间。

因此x86架构中将内核地址空间划分三部分:ZONE_DMA、ZONE_NORMAL和 ZONE_HIGHMEM;ZONE_HIGHMEM即为高端内存。

 

在x86结构中,三种类型的区域如下:

ZONE_DMA        内存开始的16MB

ZONE_NORMAL       16MB~896MB

ZONE_HIGHMEM       896MB ~ 结束

 

问题2:如何使用128MB高端内存地址空间实现访问其它内存空间?

当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。如下图。

高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。

 

问题3:高端内存占用

上面说了,通过抢占(/注册?)、映射、使用、释放方式达到复用的目的。

那么,肯定会有一个机制决定占用及释放。

内核将高端内存划分为3部分:VMALLOC_START~VMALLOC_END、KMAP_BASE~FIXADDR_START和FIXADDR_START~4G。

 

 

对应高端内存的3部分,高端内存映射有三种方式:

  1. 映射到”内核动态映射空间”(noncontiguous memory allocation)
  2. 持久内核映射(permanent kernel mapping)
  3. 临时映射(temporary kernel mapping)

至于具体是什么公式,超纲了,不讨论。

 

问题4:为什么不能“访问”用户空间

用户态不能访问内核空间很好理解,但为什么内核态不能访问用户空间,非要做映射呢?

这里说的是不能“直接访问/操作”,

个人理解,主要是为了安全

网上文档提出可能的原因:

  1. 内核态和用户态是异步的。所以两者不同步,按虚拟地址也找不到。
  2. 驱动程序架构不同或者内核配置不同,用户空间数据指针可能运行在内核模式下是无效的。
  3. 用户空间的内存数据是分页的,运行在内核模式下的用户空间指针可能直接就不在内存上,而是在swap上,这样就会发生页面错误,页面错误是内核不允许的,会导致进程死亡。
  4. 内核代码可以直接访问用户空间指针,就开了后门,不安全。

 

2.      小结:

进程寻址空间0~4G  

进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G  

进程通过系统调用进入内核态

每个进程虚拟空间的3G~4G部分是相同的  

进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变

 

日拱一卒无有尽,功不唐捐终入海