笔记-操作系统-内存-内核空间与用户空间
1. 内核空间与用户空间
用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。
上面的话叫做正确的废话,没什么意义。
1.1. 基本概念
问题来了:
- 1. 谁来划分内存空间的呢?
这种划分是操作系统在逻辑上的划分,不同版本的操作系统划分的结果都是不一样的。
- 2. 用户空间与内核空间的具体体现形式
32位操作系统的寻址空间(虚拟存储空间)为4G(2^32)。
操心系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
- 为什么非要分成内核空间和用户空间
主要是为了安全
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的地址空间,包括代码和数据。
内核态与用户态转换:
- 当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
- 当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。
用户态切换到内核态的3种方式:
- 系统调用:
可以认为是主动的;比如fork()
- 异常:
- 外围设备的中断:
附:,对于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部分,高端内存映射有三种方式:
- 映射到”内核动态映射空间”(noncontiguous memory allocation)
- 持久内核映射(permanent kernel mapping)
- 临时映射(temporary kernel mapping)
至于具体是什么公式,超纲了,不讨论。
问题4:为什么不能“访问”用户空间
用户态不能访问内核空间很好理解,但为什么内核态不能访问用户空间,非要做映射呢?
这里说的是不能“直接访问/操作”,
个人理解,主要是为了安全
网上文档提出可能的原因:
- 内核态和用户态是异步的。所以两者不同步,按虚拟地址也找不到。
- 驱动程序架构不同或者内核配置不同,用户空间数据指针可能运行在内核模式下是无效的。
- 用户空间的内存数据是分页的,运行在内核模式下的用户空间指针可能直接就不在内存上,而是在swap上,这样就会发生页面错误,页面错误是内核不允许的,会导致进程死亡。
- 内核代码可以直接访问用户空间指针,就开了后门,不安全。
2. 小结:
进程寻址空间0~4G
进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G
进程通过系统调用进入内核态
每个进程虚拟空间的3G~4G部分是相同的
进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变
日拱一卒无有尽,功不唐捐终入海