关于linux内核空间

关于linux内核空间

linux内核对整个系统的物理内存是通过类型为struct page的数组mem_map来管理的。系统中的伙伴系统分配算法最终是通过操作这个数组来记录物理内存的分配、回收等操作。在这里不要被系统的高端内存、低端内存等概念搞混淆了,高、低端内存的分类主要在于区分物理内存地址是否可以直接映射到内核线性地址空间中。

我们知道,linux的内核地址空间大小为1G用户空间0~3G,内核空间3G~4G,这种分法最常见),因此如果把这1G线性地址空间全部拿来直接一一映射物理内存的话,在内核态的所有进程(线程)能使用的物理内存总共最多只有1G,为了能使在内核态的所有进程能使用更多的物理内存,linux采取了一种变通的形式:它将1G内核线性地址空间分为几部分,第一部分为1G的前896M,这部分内核线性空间与物理内存的0~896M一一映射(相差一个为0xc00000003G的常数),后面128M的线性空间拿来动态映射剩下的所有物理内存,由于动态映射的方法不一样,后面的128M又分成了几个部分,有兴趣的可以查看相关资料。在这里,前面896M线性空间对应的物理内存就是所谓的低端物理内存,剩下的物理内存就是高端物理内存。

从上面高、低端物理内存命名的由来我们可以知道,高、低端物理内存与具体的内存分配算法无关,它们都是被mem_map数组控制起来,再由伙伴分配系统实施管理。

 

关于进程及其内存分配

首先要明白一个概念:进程中使用的所有地址都是虚地址linux中进程可运行在用户态和内核态,(典型配置情况下)当进程运行在用户态时,它使用的线性地址只能位于0~3G范围内,当进程运行于内核态时,它使用的线性地址地址范围为3G~4G

为了把线性地址转化为物理地址,每个进程都有自己私有的页目录和页表。linux在建立进程页目录时,把用户地址空间的页目录项(0~767项)清空而将内核页目录表(swapper_pg_dir)的第768项到1023项拷贝到进程的页目录表的第768项到1023项中。由于内核在初始化时也只映射了物理内存的前896M,我们可以知道内核也目录表只能保证第768项开始的224项中有有效映射。从这里我们可以知道,所有的进程都共享了其内核线性地址空间。

当一个进程在内核空间发生缺页故障的时候,这主要发生在访问内核空间动态映射区线性地址,在其处理程序中,就要通过0号进程的页目录(swapper_pg_dir)来同步本进程的内核页目录,实际上就是拷贝0号进程的内核页目录到本进程中(内核页表与进程0共享,故不需要复制)。如果进程0的该地址处的内核页目录也不存在,则出错,具体代码可以参考vmalloc的实现源码。

当进程运行于用户态时,若其需要申请内存空间,内核首先会在其用户线性空间中分配需要的线性地址空间,再通过伙伴分配系统分配物理内存并把分配的物理内存跟用户空间线性地址映射起来,最后再修改进程的页目录项及页表项写入这些映射关系。

 

注;

逻辑地址(Logical Address  指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相 对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行 自动地址转换);逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明 的,仅由系统编程人员涉及。应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作。

线性地址(Linear Address 是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机 制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G232次方即32根地址总线寻址)。

物理地址(Physical Address 是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。

虚拟内存(Virtual Memory 是指计算机呈现出要比实际拥有的内存大得多的内存量。因此它允许程序员编制并运行比实际系统拥有的内存大得多的程序。这使得许多大型项目也能够在具有有限 内存资源的系统上实现。一个很恰当的比喻是:你不需要很长的轨道就可以让一列火车从上海开到北京。你只需要足够长的铁轨(比如说3公里)就可以完成这个任 务。采取的方法是把后面的铁轨立刻铺到火车的前面,只要你的操作足够快并能满足要求,列车就能象在一条完整的轨道上运行。这也就是虚拟内存管理需要完成的 任务。在Linux 0.11内核中,给每个程序(进程)都划分了总容量为64MB的虚拟内存空间。因此程序的逻辑地址范围是0x00000000x4000000

与物理地址空间类似,线性地址空间也是平坦的4GB地址空间,地址范围从00xFFFFFFFF,线性空间中含有为系统定义的所有段和系统表。

有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的。

逻辑地址与物理地址的差距0xC0000000,是由于虚拟地址->线性地址->物理地址映射正好差这个值。这个值是由操作系统指定的。

 

虚拟地址到物理地址的转化方法是与体系结构相关的。一般来说有分段、分页两种方式。以现在的x86 cpu为例,分段分页都是支持的。Memory Mangement Unit负责从虚拟地址到物理地址的转化。逻辑地址是段标识+段内偏移量的形式,MMU通过查询段表,可以把逻辑地址转化为线性地址。如果cpu没有开启 分页功能,那么线性地址就是物理地址;如果cpu开启了分页功能,MMU还需要查询页表来将线性地址转化为物理地址:
逻辑地址 —-(段表)—> 线性地址(页表)—> 物理地址
不同的逻辑地址可以映射到同一个线性地址上;不同的线性地址也可以映射到同一个物理地址上;所以是多对一的关系。另外,同一个线性地址,在发生换页以后,也可能被重新装载到另外一个物理地址上。所以这种多对一的映射关系也会随时间发生变化。

结构体无法比较

结构体无法比较

由于结构体的成员不一定是连续地存储在内存中,所以不能用运算符==!=来对结构体进行比较,事实上,在一个结构体的存储区域内可能会出现一些“空洞”,这是由于计算机是按照一定的边界,例如半字、字、双字边界来存储不同数据类型的变量。

位域bit field

位域bit field

       当结构体或共用体中有无符号整型或有符号整型成员时,C语言允许用户指定这些成员所占用的存储位数,即位域。通过将数据存储在它们所需的最小数目的存储位内,位域能够有效地提供存储空间的利用率,但是,要注意,位域成员必须被声明为有符号整型或无符号整型。

共用体union

共用体union

共用体通过共享存储空间,来避免当前没有被使用的变量所造成的存储空间的浪费。共用体的成员可以使用任何数据类型,但是一个共用体所占用的存储空间的字节总数,必须保证至少足以能够容纳其占用空间字节数最大的成员。并且共用体每次只允许访问一个成员,也就是一种数据类型,确保按照正确的数据类型来访问共用体中的数据,就是你的责任了。