Linux内核学习系列(5)——内存管理

前言

本章记录一下个人对linux0.12内存管理的理解。主要涉及物理内存页分配与回收,页表复制等具体操作。同样的,笔记仍然由上而下地进行知识点梳理,而不是单纯介绍函数及其作用。

内存管理

linux内存管理是对什么进行管理

  • 内核中的内存管理,主要管理的是物理内存地址空间。而用户所谓的内存管理,指的是对其当前进程线性地址空间的管理

为什么要进行内存管理

  • 内核中的内存管理,是为了在分页机制基础上,实现虚拟存储
  • 用户中的内存管理,是为了合理利用线性地址空间

什么时候需要内核内存管理

  • 内存管理是贯彻整个内核运行过程的
  • 因为内存管理负责实现并维护分页机制,并为任务分配物理内存上主内存区域内存
  • 例如,当任务执行fork的过程中,涉及新的task_truct与新的页表,这些都需要主存提供存储。如何让主存有序地提供存储,也是内存管理的职责

如何进行内存管理

  • 由分页机制,我们可以知道,按4k为一页单位分别划分物理内存地址与线性地址空间,因此两者可以实现以4k页单位的地址映射
  • 因此内存管理第一步,先将物理内存地址以4k字节为单位划分物理页。为了能够知道每个物理页的使用情况,设计一个数组mem_map[page_nums]用于存储page_nums个物理页使用情况。未使用为0,被使用则+1,被n个任务共用,则为n。同时设置共用上限,如n=100
  • 进行物理页分配。当任务需要申请主内存时,通过mem_map查找主内存地址空间中的物理页,选择未使用的先清空对应物理页,进而分配,返回线性地址。(这里涉及到一个细节问题,mem_map是基于线性地址的,为何能够直接管理物理内存地址?后续解答)
  • 进行物理页释放。将mem_map对应位置的值置减1即可,注意,无需清空具体内容,因为别的任务也可能在用。
  • 复制页表。要注意的是,linux内核运行过程中,没用从0到1创建页表的需求。因为通过fork创建进程时,都是直接复制父进程的页表。而最根源的进程0的页表,在初始化时已经设置好。复制的过程,可以简单的这么理解:首先,页表是记录线性地址到物理地址的映射关系的。to页表的复制只需要将from页表的线性地址换成新任务对应的新线性地址,并且将mem_map关于具体物理页的值+1,表示共享该物理页的任务增加这个操作使得复制页表之后,物理内存是共享的,便于实现写时复制。当然,在复制之前,需要从主存申请一个物理页,存放to页表。(这又有个细节问题,当前任务如何通过线性地址申请到物理页的呢?)
  • 释放页表。与物理页释放类似,只不过输入为页表,需要遍历页表中对应的每个物理页,将其mem_map对应值减1。
  • 以上四个功能是内存管理的基础

进一步地,我们看看源码是否如上述而言

内存管理代码

物理页分配

将物理内存地址以4k字节为单位划分物理页。为了能够知道每个物理页的使用情况,设计一个数组mem_map[page_nums]用于存储page_nums个物理页使用情况。未使用为0,被使用则+1,被n个任务共用,则为n。同时设置共用上限,如n=100

定义mem_map mm/memory.c

#define PAGING_MEMORY (15*1024*1024)  //include\linux\mm.h
#define PAGING_PAGES (PAGING_MEMORY>>12)  //include\linux\mm.h

unsigned char mem_map [ PAGING_PAGES ] = {
   0,}

物理内存16M,其中1M存放内核代码与数据,可用于分配的只有15M,PAGING_PAGES=15M/4k

get_free_page() 申请空闲物理页 mm/swap.c 判断mem_map中值为0的位置,置1,并清空对应物理页,返回物理页线性地址。物理页线性地址=物理页索引*4k+LOW_MEM(1Mb)

unsigned long get_free_page(void)
{
   
register unsigned long __res asm("ax");

repeat:
	__asm__("std ; repne ; scasb\n\t"
		"jne 1f\n\t"
		"movb $1,1(%%edi)\n\t"
		"sall $12,%%ecx\n\t"
		"addl %2,%%ecx\n\t"
		"movl %%ecx,%%edx\n\t"
		"movl $1024,%%ecx\n\t"
		"leal 4092(%%edx),%%edi\n\t"
		"rep ; stosl\n\t"
		"movl %%edx,%%eax\n"
		"1:"
		:"=a" (__res)
		:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
		"D" (mem_map+PAGING_PAGES-1)
		:"di","cx","dx");
	if (__res >= HIGH_MEMORY)
		goto repeat;
	if (!__res && swap_out())
		goto repeat;
	return __res;
}

这里涉及到一个理解内存管理中地址映射的关键问题。即如何通过线性地址能够直接管理16M物理内存。比较绕,理解了其实很简单。这与上一篇内存映射内容有关,即内核代码段与数据段为了方便管理,其使用的内核页表存储了0-16M线性地址与0-16M物理地址的一一映射关系。因此操作0-16M线性地址,就是操作0-16M物理地址。可能有人会问,当任务切换时,页表不也会切换成任务页表吗?实际上,Linux0.12在任务运行过程中,页目录的位置永远不变,因此fork的过程中,不会发生tss.cr3这一字段被修改。只是会为新任务申请一块内存并创建新的页表,存储LDT相关的地址映射,最终会放入页目录中。(这部分说起来比较拗口,看后续是否进行展开)

物理页释放

将mem_map对应位置的值置减1即可,注意,无需清空具体内容,因为别的任务也可能在用。
free_page() mm/memory.c

void free_page(unsigned long addr)
{
   
	if (addr < LOW_MEM) return;
	if (addr >= HIGH_MEMORY)
		panic("trying to free nonexistent page");
	addr -= LOW_MEM;
	addr >>= 12;
	if (mem_map[addr]--) return;
	mem_map[addr]=0;
	panic("trying to free free page");
}

复制页表

要注意的是,linux内核运行过程中,没用从0到1创建页表的需求。因为通过fork创建进程时,都是直接复制父进程的页表。而最根源的进程0的页表,在初始化时已经设置好。复制的过程,可以简单的这么理解:首先,页表是记录线性地址到物理地址的映射关系的。to页表的复制只需要将from页表的线性地址换成新任务对应的新线性地址,并且将mem_map关于具体物理页的值+1,表示共享该物理页的任务增加这个操作使得复制页表之后,物理内存是共享的,便于实现写时复制。当然,在复制之前,需要从主存申请一个物理页,存放to页表。

copy_page_tables() mm/memory.c 对具体代码的逐行解析不是本章重点,具体可自行看书学习

int copy_page_tables(unsigned long from,unsigned long to,long size)
{
   
	unsigned long * from_page_table;
	
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值