Linux0.11内核--内存管理之1.初始化

【版权所有,转载请注明出处。出处:http://www.cnblogs.com/joey-hua/p/5597705.html 】

Linux内核因为使用了内存分页机制,所以相对来说好理解些。因为内存分页就是为了方便管理内存。

说到内存分页,最根部的要属页目录表了,head.h中:

extern unsigned long pg_dir[1024];	// 内存页目录数组。每个目录项为4 字节。从物理地址0 开始。

然后再看head.s:

/*
* head.s 含有32 位启动代码。
* 注意!!! 32 位启动代码是从绝对地址0x00000000 开始的,这里也同样是页目录将存在的地方,
* 因此这里的启动代码将被页目录覆盖掉。
*/
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:							# 页目录将会存放在这里。
...

页目录存放的地方是从绝对地址0开始的,接下来分配页表:

/* Linus 将内核的内存页表直接放在页目录之后,使用了4 个表来寻址16 Mb 的物理内存。
* 如果你有多于16 Mb 的内存,就需要在这里进行扩充修改。
*/
# 每个页表长为4 Kb 字节(1 页内存页面),而每个页表项需要4 个字节,因此一个页表共可以存放
# 1024 个表项。如果一个页表项寻址4 Kb 的地址空间,则一个页表就可以寻址4 Mb 的物理内存。
# 页表项的格式为:项的前0-11 位存放一些标志,例如是否在内存中(P 位0)、读写许可(R/W 位1)、
# 普通用户还是超级用户使用(U/S 位2)、是否修改过(是否脏了)(D 位6)等;表项的位12-31 是
# 页框地址,用于指出一页内存的物理起始地址。
.org 0x1000				# 从偏移0x1000 处开始是第1 个页表(偏移0 开始处将存放页表目录)。
pg0:

.org 0x2000
pg1:

.org 0x3000
pg2:

.org 0x4000
pg3:

...

分配了4个页表空间,因为一个页表项对应的是一个页也就是4K,所以一个页表就是4K*1024=4M,这里有4个页表所以就是16M。并且注意这里是从.org 0x1000开始的,前面4K的空间是留给整个页目录的。

下面是设置四个页目录项的属性,因为只有4个页表,所以相应的就是4个页目录项:

# 下面4 句设置页目录表中的项,因为我们(内核)共有4 个页表所以只需设置4 项。
# 页目录项的结构与页表中项的结构一样,4 个字节为1 项。参见上面113 行下的说明。
# "$pg0+7"表示:0x00001007,是页目录表中的第1 项。
# 则第1 个页表所在的地址 = 0x00001007 & 0xfffff000 = 0x1000;
# 第1 个页表的属性标志 = 0x00001007 & 0x00000fff = 0x07,表示该页存在、用户可读写。
	movl $pg0+7,_pg_dir				/* set present bit/user r/w */
	movl $pg1+7,_pg_dir+4			/*  --------- " " --------- */
	movl $pg2+7,_pg_dir+8			/*  --------- " " --------- */
	movl $pg3+7,_pg_dir+12		/*  --------- " " --------- */

注意这里+7的意思是,先看PDE 页目录项格式 可知,每个页目录项的0-11位是属性,12-31位才是基址,7对应的二进制是111,也就是P、R/W、U/S位都为1.

好,现在页目录和4个页表都分配好了。接下来进入初始化程序mem_init(main_memory_start, memory_end);在main.c和memory.c中:

static long memory_end = 0;	// 机器具有的物理内存(字节数)。
static long buffer_memory_end = 0;	// 高速缓冲区末端地址。
static long main_memory_start = 0;	// 主内存(将用于分页)开始的位置。

  memory_end = (1 << 20) + (EXT_MEM_K << 10);	// 内存大小=1Mb 字节+扩展内存(k)*1024 字节。
  memory_end &= 0xfffff000;	// 忽略不到4Kb(1 页)的内存数。
  if (memory_end > 16 * 1024 * 1024)	// 如果内存超过16Mb,则按16Mb 计。
    memory_end = 16 * 1024 * 1024;
  if (memory_end > 12 * 1024 * 1024)	// 如果内存>12Mb,则设置缓冲区末端=4Mb
    buffer_memory_end = 4 * 1024 * 1024;
  else if (memory_end > 6 * 1024 * 1024)	// 否则如果内存>6Mb,则设置缓冲区末端=2Mb
    buffer_memory_end = 2 * 1024 * 1024;
  else
    buffer_memory_end = 1 * 1024 * 1024;	// 否则则设置缓冲区末端=1Mb
  main_memory_start = buffer_memory_end;	// 主内存起始位置=缓冲区末端;

  // 如果定义了内存虚拟盘,则初始化虚拟盘。此时主内存将减少。参见kernel/blk_drv/ramdisk.c。
#ifdef RAMDISK			// 如果定义了内存虚拟盘,则主内存将减少。
  main_memory_start += rd_init (main_memory_start, RAMDISK * 1024);
#endif

  mem_init (main_memory_start, memory_end);

这里结合下图参考:

如果定义了内存虚拟盘,主内存区开始位置main_memory_start就从虚拟盘末端开始,否则就从高速缓冲区末端开始;而主内存区结尾memory_end则为16M。

/* 下面定义若需要改动,则需要与head.s 等文件中的相关信息一起改变 */
// linux 0.11 内核默认支持的最大内存容量是16M,可以修改这些定义以适合更多的内存。
#define LOW_MEM 0x100000											// 内存低端(1MB)。
#define PAGING_MEMORY (15*1024*1024)				// 分页内存15MB。主内存区最多15M。
#define PAGING_PAGES (PAGING_MEMORY>>12)	// 分页后的物理内存页数。相当于除以4096
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)	// 指定内存地址映射为页号。
#define USED 100																	// 页面被占用标志,参见405 行。

static long HIGH_MEMORY = 0;	// 全局变量,存放实际物理内存最高端地址。

// 内存映射字节图(1 字节代表1 页内存),每个页面对应的字节用于标志页面当前被引用(占用)次数。
static unsigned char mem_map[PAGING_PAGES] = { 0, };

//// 物理内存初始化。
// 参数:start_mem - 可用作分页处理的物理内存起始位置(已去除RAMDISK 所占内存空间等)。
// end_mem - 实际物理内存最大地址。
// 在该版的linux 内核中,最多能使用16Mb 的内存,大于16Mb 的内存将不于考虑,弃置不用。
// 0 - 1Mb 内存空间用于内核系统(其实是0-640Kb)。
void
mem_init (long start_mem, long end_mem)
{
  int i;

  HIGH_MEMORY = end_mem;				// 设置内存最高端。
  for (i = 0; i < PAGING_PAGES; i++)		// 首先置所有页面为已占用(USED=100)状态,
    mem_map[i] = USED;								// 即将页面映射数组全置成USED。
  i = MAP_NR (start_mem);						// 然后计算可使用起始内存的页面号。
  end_mem -= start_mem;							// 再计算可分页处理的内存块大小。
  end_mem >>= 12;										// 从而计算出可用于分页处理的页面数。
  while (end_mem-- > 0)							// 最后将这些可用页面对应的页面映射数组清零。
    mem_map[i++] = 0;
}

首先注意PAGING_MEMORY,因为1M以下的内存空间是内核所用,不参与分页,所以主内存大小最多为15M。所以PAGING_PAGES就是主内存大小除以4096(一页就是4096字节)就是内存页面总数。

所以mem_map的作用就是内存页面映射。

这里注意一下MAP_NR(start_mem)这个宏,用主内存区开始地址减去不参与分页的1M空间,得到从可用于分页的内存地址开始的容量,再除以4096(一页就是4096字节)就可以得到页号。

然后计算主内存区的大小,并把mem_map中从主内存区start_mem开始的页号置为0.

到这里为止,内存管理的初始化就算结束了!

时间: 2024-08-27 16:37:26

Linux0.11内核--内存管理之1.初始化的相关文章

Linux-0.11内核内存管理get_free_page()函数分析

/* *Author : DavidLin*Date : 2014-11-11pm*Email : [email protected] or [email protected]*world : the city of SZ, in China*Ver : 000.000.001*history : editor time do 1)LinPeng 2014-11-11 created this file! 2)*/Linux-0.11内存管理模块是源代码中比较难以理解的部分,现在把笔者个人的理解

Linux0.11内核--内存管理之2.配合fork

[版权所有,转载请注明出处.出处:http://www.cnblogs.com/joey-hua/p/5598451.html ] 在上一篇的fork函数中,首先一上来就调用get_free_page为新任务的数据结构申请一页内存,在memory.c中: /* * 获取首个(实际上是最后1 个:-)空闲页面,并标记为已使用.如果没有空闲页面, * 就返回0. */ //// 取空闲页面.如果已经没有可用内存了,则返回0. // 输入:%1(ax=0) - 0:%2(LOW_MEM):%3(cx=

Linux0.11内核--进程调度分析之1.初始化

首先看main.c里的初始化函数main函数里面有个函数是对进程调度的初始化,sched_init()函数,次函数在sched.c中实现: // 调度程序的初始化子程序. void sched_init (void) { int i; struct desc_struct *p; // 描述符表结构指针. if (sizeof (struct sigaction) != 16) // sigaction 是存放有关信号状态的结构. panic ("Struct sigaction MUST be

Linux-0.11内核源代码分析系列:内存管理get_free_page()函数分析

Linux-0.11内存管理模块是源码中比較难以理解的部分,如今把笔者个人的理解发表 先发Linux-0.11内核内存管理get_free_page()函数分析 有时间再写其它函数或者文件的:) /*  *Author  : DavidLin  *Date    : 2014-11-11pm  *Email   : [email protected] or [email protected]  *world   : the city of SZ, in China  *Ver     : 000

Linux0.11内核剖析–内核体系结构 &#169;Fanwu

Linux0.11内核剖析–内核体系结构 ©Fanwu <Linux内核完全注释>下载:http://files.cnblogs.com/files/HanBlogs/linux-kernel.pdf(进入pdf后要点击右下角保存喔^_^) 一个完整可用的操作系统主要由 4 部分组成:硬件.操作系统内核.操作系统服务和用户应用程序,如下图所示: 用户应用程序是指那些字处理程序. Internet 浏览器程序或用户自行编制的各种应用程序: 操作系统服务程序是指那些向用户所提供的服务被看作是操作系

一站式linux0.11内核head.s代码段图表详解

阅读本文章需要的基础: 计算机组成原理:针对8086,80386CPU架构的计算机硬件体系要有清楚的认知,我们都知道操作系统是用来管理硬件的,那我们就要对本版本的操作系统所依赖的硬件体系有系统的了解,有了系统的了解后才能全面的管理它,我们对8086,80386CPU架构的计算机硬件体系如果有非常深刻的认识,我们看源代码内核的时候,就可以更可能的以一种开发者的角度去思考代码的作用,先从全局的角度去思考问题,而不是采用一种众人摸象的思维从头看到末尾. 计算机编程C语言基础:linux内核基本都是用C

Linux内核——内存管理

内存管理 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址)通常以页为单位进行处理.MMU以页大小为单位来管理系统中的页表.从虚拟内存的角度看,页就是最小单位. 32位系统:页大小4KB 64位系统:页大小8KB 在支持4KB页大小并有1GB物理内存的机器上,物理内存会被划分为262144个页.内核用 struct page 结构表示系统中的每个物理页. struct page { page_flags_t flags;   /* 表示页的状态,每

linux0.11内核fork实现分析(不看不知道,一看很简单)

pcDuino3下支持mmc启动,官方的Uboot是采用SPL框架实现的,因为内部的SRAM空间达到32K,我们完全可以在这32K空间内编写一个完整可用小巧的bootloader来完成引导Linux kernel的目的. 我们首先介绍下SPL框架,可以先看下<GNU ARM汇编--(十八)u-boot-采用nand_spl方式的启动方法>和<GNU ARM汇编--(十九)u-boot-nand-spl启动过程分析>,NAND_SPL也算是SPL框架下的一种模式. 当使用Nand f

Linux-0.11内核源码分析系列:关于线性地址,逻辑地址,物理地址的关系与区别

/* *Author : DavidLin *Date : 2014-11-22pm *Email : [email protected] or [email protected] *world : the city of SZ, in China *Ver : 000.000.001 *history : editor time do * 1)LinPeng 2014-11-22 created this file! * 2) */     以下所有描述基于Linux0.11内核及其所编写的年