linux操作系统:内存管理,虚拟内存是如何映射成为物理地址的

规划虚拟空间的时候,是将空间分成多个段进行保存,而x86 CPU中就有一种分段机制,可以用这个分段机制来完成

分段机制的原理

在这里插入图片描述

  • 分段机制下的虚拟地址由两部分组成,段选择子段内偏移量
    • 段选择子就保存在段寄存器中,段选择子中有一个段号,可以用作段表的索引
    • 段表里面保存的是这个段的基地址、段的界限、特权等级
  • 虚拟地址中的段内偏移量应该位于0和段界限之间。
  • 如果段内偏移量是合理的,就将段基地址加上段内偏移量得到物理内存地址

比如,我们将上面的虚拟空间分为下面4个段,用0~3来编号。每个段在短标中有一个项目,在物理空间中,段的排列如下图右边所示:

在这里插入图片描述
如果要访问段 2 中偏移量 600 的虚拟地址,我们可以计算出物理地址为,段 2 基地址2000 + 偏移量 600 = 2600

Linux是如何使用这个分段机制的

  • 在Linux中,段表的全称是段描述符表(segment descriptors),放在全局描述符表GDT(Global Descriptor Table)里面,会有下面的宏来初始化段描述符表里面的表项:
#define GDT_ENTRY_INIT(flags, base, limit) { { { \
		.a = ((limit) & 0xffff) | (((base) & 0xffff) << 16), \
		.b = (((base) & 0xff0000) >> 16) | (((flags) & 0xf0ff) << 8) | \
			((limit) & 0xf0000) | ((base) & 0xff000000), \
	} } }
  • 一个段表项由段基地址base、段界限limit,以及一些标识符组成
DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page) = { .gdt = {
#ifdef CONFIG_X86_64
	[GDT_ENTRY_KERNEL32_CS]		= GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),
	[GDT_ENTRY_KERNEL_CS]		= GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),
	[GDT_ENTRY_KERNEL_DS]		= GDT_ENTRY_INIT(0xc093, 0, 0xfffff),
	[GDT_ENTRY_DEFAULT_USER32_CS]	= GDT_ENTRY_INIT(0xc0fb, 0, 0xfffff),
	[GDT_ENTRY_DEFAULT_USER_DS]	= GDT_ENTRY_INIT(0xc0f3, 0, 0xfffff),
	[GDT_ENTRY_DEFAULT_USER_CS]	= GDT_ENTRY_INIT(0xa0fb, 0, 0xfffff),
#else
	[GDT_ENTRY_KERNEL_CS]		= GDT_ENTRY_INIT(0xc09a, 0, 0xfffff),
	[GDT_ENTRY_KERNEL_DS]		= GDT_ENTRY_INIT(0xc092, 0, 0xfffff),
	[GDT_ENTRY_DEFAULT_USER_CS]	= GDT_ENTRY_INIT(0xc0fa, 0, 0xfffff),
	[GDT_ENTRY_DEFAULT_USER_DS]	= GDT_ENTRY_INIT(0xc0f2, 0, 0xfffff),
......
#endif
} };
EXPORT_PER_CPU_SYMBOL_GPL(gdt_page);
  • 这里面对于64位和32位的,都定义了内核代码段、内核数据段、用户代码段和用户数据段。

  • 另外,还会定义下面四个段选择子,指向上面的段描述符表项。在内核初始化的时候,启动第一个用户态的进程,就是将这四个值赋值给段寄存器

#define __KERNEL_CS			(GDT_ENTRY_KERNEL_CS*8)
#define __KERNEL_DS			(GDT_ENTRY_KERNEL_DS*8)
#define __USER_DS			(GDT_ENTRY_DEFAULT_USER_DS*8 + 3)
#define __USER_CS			(GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
  • 通过分析,我们发现,所有段的起始地址都是一样的,但是0。也就是说,压根就没有分段。这里的分段仅仅用于做权限审核,比如用户态DPL是3,内核态DPL是0。当用户态试图访问内核态时,会因为权限不足而报错

其实linux倾向于另外一种从虚拟地址到物理地址的转换方式,叫做分页(paging)

  • 对于物理内存,操作系统把它分成一块一块大小相同的页,这样更方便管理。比如有的内存页长时间不用了,可以暂时写到硬盘上,称为换出;一旦需要的时候,再加载进来,叫做换入。这样可以扩大可用物理内存的大小,提高物理内存的利用率
  • 这个换入和换出都是以页为单位的。页面的大小一般为4KB。为了能够定位和访问每个页,需要有个页表,保存每个页的起始地址,在加上在页内的偏移量,组成线性地址,就能对内存中的每个位置进行访问了
    在这里插入图片描述
  • 虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址

总结

内存管理系统主要做了下面三件事情:

  • 第一,虚拟内存空间的管理,将虚拟内存分为大小相等的页
  • 第二,物理内存的管理,将物理内存分为大小相等的页
  • 第三,内存映射,将虚拟内存和物理内存映射起来,并且在内存紧张的时候可以换出到硬盘中

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值