每天理解一点Linux内核之地址映射

我们知道Linux内核对内存的管理主要都是通过分页来完成的,那么实际的物理页和虚拟页是怎样的对应的。这篇blog我们试图去聊着这个机制。

1.虚拟地址划分

32位地址意味着4G的虚存空间,Linux内核将这4G的空间分成两个部分,将高的1G字节(0xC00000000-0xFFFFFFFF)用于内核本身,成为系统空间。而较低的3G字节作为各个进程的用户空间,这样理论上各个进程都可以使用空间是3G字节。虽然各个进程有3G空间用户空间,每当一个进程进入通过系统调用进入内核,该进程就在共享的系统空间中运行,不再有自己独立的空间。
这里写图片描述

1.1 内核地址映射

有意思的是,内核这个虚拟的高地址的1G空间(0xC00000000-0xFFFFFFFF)对应的物理地址却是从0开始的,所以内核提供了一个转化的方法

#define __pa(x)  ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x)   ((void *)((unsigned long)(x)+PAGE_OFFSET))

上面的PAGE_OFFSET和我们所知的TASK_SIZE都是0xC0000000,这样内核段的虚拟地址和物理地址就可以随意转化了。

2.地址映射

2.1 各种地址

物理地址就是物理内存正真的地址,在实模式下上面的”段基址+段内便偏移”就是真实的物理地址,而在保护模式下,称为线性地址,不过此时的段基址已经不再是真正的地址了,而是一个索引,通过索引可以在GDT中找到相应的段描述符,改描述符中记录了该段的起始、大小等信息,这样便得到了段基址。如果此时未开启分页,这个线性地址就被当做物理地址,如果开启,就叫做虚拟地址(虚拟地址和线性地址在分页机制下是一样的),虚拟地址需要经过CPU转化成物理地址。无论是实模式还是保护模式,段内偏移地址叫做逻辑地址,这是程序员可见的地址。最终地址还是由段基址+段内偏移地址组合而成。段基址要么是在
实模式下默认的段寄存器中,要么在保护模式下选择子寄存器指向的段描述符中,所以只要给一个段内偏移地址就可以了。
我们拿一个32位的举个例子
程序中给出的32位地址(实际上被看做段内偏移地址),再根据代码段寄存器CS中的16位段选择子,可在GDT或LDT中查找相应的段描述符。从段描述符中提取段的基地址,与程序给出的32位地址相加,得到结果为线性地址。
根据此线性地址查找系统页目录表,再查二级或是多级页表,最终得到物理地址。

2.2 内核具体实现

上面大概说清楚整个过程。下面详细的内容。由于Linux也得服从intel的分段机制,所以必须先分段然后分页。
主要是上面的两步,第一个逻辑地址转为线性地址,第二步是线性地址转化为物理地址。
先说第一步,通过CPU通过CS:IP找到下一条要执行的指令,CS是代码段寄存器,IP指向的就是程序编译完的逻辑地址。CS格式如下
这里写图片描述
T1确定了是使用GDT还是LDT,Linux一般都是使用GDT(每个CPU一个)的。RPL确定程序的级别,0-3,总计四个级别。这样通过GDT就可以知道逻辑的地址的基地址了,在Linux里面段基址基本都是0,所以一般我们所的逻辑地址(虚拟地址)就是线性地址。

第二步是线性地址转化为物理地址

这里写图片描述
下面是一个三级页表线性地址的转化过程。

  1. 用线性地址最高位的段作为PGD的下标
  2. 用线性地址的第二段位作为PMD的下标
  3. 用线性地址的第三段位作为PTE的下标
  4. 最后加入偏移量
    这样就完成的线性地址到物理地址的转化了。

那么PGD的地址在哪里呢,答案是CPU里面的CR3寄存器,有个细节是CR3存储的是物理地址,程序的只知道系统空间的虚拟地址,就是通过上面的__pa方法,在内核空间将虚拟地址转为物理地址。
这样就能完成地址的转化了。还有一些细节,后面blog慢慢说清楚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳清风09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值