Linux0.11 内存管理(未完)

Linux0.11 内存管理

页表初始化
_pg_dir放在head.s文件的开头,而head.s 在内存的0x0000 0000处,所以_pg_dir页目录在0x0000 0000处。

head.s中,在设置完idtgdt`前,需要设置页目录表。

jmp after_page_tables

...

.org 0x1000 # 从偏移0x1000 处开始是第1 个页表(偏移0 开始处将存放页表目录)。
pg0:

.org 0x2000
pg1:

.org 0x3000
pg2:

.org 0x4000
pg3:

.org 0x5000 # 定义下面的内存数据块从偏移0x5000 处开始。

...

after_page_tables:
pushl $0 
pushl $0 # 这些是调用main 程序的参数(指init/main.c)。
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $_main # '_main'是编译程序对main 的内部表示方法。
jmp setup_paging # 跳转至第198 行。

...

.align 2 # 按4 字节方式对齐内存地址边界。
setup_paging: # 首先对5 页内存(1 页目录 + 4 页页表)清零
#总共5页,每页4096字节,按照每次增长4字节,所以是1024*5
movl $1024*5,%ecx 
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
# 页目录从0x000 地址开始。
#下面这三句:cld将edi或esi设为递增方向,rep做重复(%ecx)次,
#stosl表示将edx中的值保存到es:edi指向的地址中,edi每次递增4,
#这三句实现了按四字节清空前5*1024*4字节的目的
cld;rep;stosl


# 下面4 句设置页目录中的项,我们共有4 个页表所以只需设置4 项。
# 页目录项的结构与页表中项的结构一样,4 个字节为1 项。
# "$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 /* --------- " " --------- */
# 下面6 行填写4 个页表中所有项的内容,共有:4(页表)*1024(项/页表)=4096 项(0 - 0xfff),
# 也即能映射物理内存 4096*4Kb = 16Mb。
# 每项的内容是:当前项所映射的物理内存地址 + 该页的标志(这里均为7)。
# 使用的方法是从最后一个页表的最后一项开始按倒退顺序填写。一个页表的最后一项在页表中的
# 位置是1023*4 = 4092。因此最后一页的最后一项的位置就是$pg3+4092。
movl $pg3+4092,%edi # edi??最后一页的最后一项。
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
# 最后1 项对应物理内存页面的地址是0xfff000,
# 加上属性标志7,即为0xfff007.
std # 方向位置位,edi 值递减(4 字节)。
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax # 每填写好一项,物理地址值减0x1000。
jge 1b # 如果小于0 则说明全添写好了。
# 设置页目录基址寄存器cr3 的值,指向页目录表。
xorl %eax,%eax /* pg_dir is at 0x0000 */ # 页目录表在0x0000 处。
movl %eax,%cr3 /* cr3 - 保存pg_table基地址 */
# 设置启动使用分页处理(cr0 的PG 标志,位31)
movl %cr0,%eax
orl $0x80000000,%eax # 添上PG 标志。
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */
# 在改变分页处理标志后要求使用转移指令刷新预取指令队列,这里用的是返回指令ret。
# 该返回指令的另一个作用是将堆栈中的main 程序的地址弹出,并开始运行/init/main.c 程序。
# 本程序到此真正结束了。

进程创建时内存的分配
在子进程建立时,通过copy_mem函数来分配内存,建立页表:
在这里插入图片描述

// 设置新任务的代码和数据段基址、限长并复制页表。
// nr 为新任务号;p 是新任务数据结构的指针。
int
copy_mem (int nr, struct task_struct *p)
{
  unsigned long old_data_base, new_data_base, data_limit;
  unsigned long old_code_base, new_code_base, code_limit;

  code_limit = get_limit (0x0f);	// 取局部描述符表中代码段描述符项中段限长。
  data_limit = get_limit (0x17);	// 取局部描述符表中数据段描述符项中段限长。
  old_code_base = get_base (current->ldt[1]);	// 取原代码段基址。
  old_data_base = get_base (current->ldt[2]);	// 取原数据段基址。
  
  ...
  
  new_data_base = new_code_base = nr * 0x4000000;	// 新基址=任务号*64Mb(任务大小)。
  p->start_code = new_code_base;
  set_base (p->ldt[1], new_code_base);	// 设置代码段描述符中基址域。
  set_base (p->ldt[2], new_data_base);	// 设置数据段描述符中基址域。
 
  if (copy_page_tables (old_data_base, new_data_base, data_limit))
    {				// 复制代码和数据段。
      free_page_tables (new_data_base, data_limit);	// 如果出错则释放申请的内存。
      return -ENOMEM;
    }
  return 0;
}

在完成了子进程的ldt设置以后,就给子进程分配虚拟内存,并且为虚拟内存对应建立页目录项、页表,并且使子进程的页表指向与父进程相同的页,同时将this_page的权限设置为只读,直到之后需要write时,再改变指向的物理内存,这就是copy on write,通过copy_page_tables来实现:
在这里插入图片描述

int copy_page_tables (unsigned long from, unsigned long to, long size)
{
  unsigned long *from_page_table;
  unsigned long *to_page_table;
  unsigned long this_page;
  unsigned long *from_dir, *to_dir;
  unsigned long nr;

// 源地址和目的地址都需要是在4Mb 的内存边界地址上。否则出错,死机。
  if ((from & 0x3fffff) || (to & 0x3fffff))
    panic ("copy_page_tables called with wrong alignment");
// 取得源地址和目的地址的目录项(from_dir 和to_dir)。参见对115 句的注释。
  from_dir = (unsigned long *) ((from >> 20) & 0xffc);	/* _pg_dir = 0 */
  to_dir = (unsigned long *) ((to >> 20) & 0xffc);
// 计算要复制的内存块占用的页表数(也即目录项数)。
  size = ((unsigned) (size + 0x3fffff)) >> 22;
// 下面开始对每个占用的页表依次进行复制操作。
  for (; size-- > 0; from_dir++, to_dir++)
    {
// 如果目的目录项指定的页表已经存在(P=1),则出错,死机。
      if (1 & *to_dir)
	      panic ("copy_page_tables: already exist");
// 如果此源目录项未被使用,则不用复制对应页表,跳过。
      if (!(1 & *from_dir))
      	continue;
// 取当前源目录项中页表的地址??from_page_table。
      from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
// 为目的页表取一页空闲内存,如果返回是0 则说明没有申请到空闲内存页面。返回值=-1,退出。
      if (!(to_page_table = (unsigned long *) get_free_page ()))
	return -1;		/* Out of memory, see freeing */
// 设置目的目录项信息。7 是标志信息,表示(Usr, R/W, Present)。
      *to_dir = ((unsigned long) to_page_table) | 7;
// 针对当前处理的页表,设置需复制的页面数。如果是在内核空间,则仅需复制头160 页,否则需要
// 复制1 个页表中的所有1024 页面。
      nr = (from == 0) ? 0xA0 : 1024;
// 对于当前页表,开始复制指定数目nr 个内存页面。
      for (; nr-- > 0; from_page_table++, to_page_table++)
	{
	  this_page = *from_page_table;	// 取源页表项内容。
	  if (!(1 & this_page))	// 如果当前源页面没有使用,则不用复制。
	    continue;
// 复位页表项中R/W 标志(置0)。(如果U/S 位是0,则R/W 就没有作用。如果U/S 是1,而R/W 是0,
// 那么运行在用户层的代码就只能读页面。如果U/S 和R/W 都置位,则就有写的权限。)
	  this_page &= ~2;
	  *to_page_table = this_page;	// 将该页表项复制到目的页表中。
// 如果该页表项所指页面的地址在1M 以上,则需要设置内存页面映射数组mem_map[],于是计算
// 页面号,并以它为索引在页面映射数组相应项中增加引用次数。
	  if (this_page > LOW_MEM)
	    {
// 下面这句的含义是令源页表项所指内存页也为只读。因为现在开始有两个进程共用内存区了。
// 若其中一个内存需要进行写操作,则可以通过页异常的写保护处理,为执行写操作的进程分配
// 一页新的空闲页面,也即进行写时复制的操作。
	      *from_page_table = this_page;	// 令源页表项也只读。
	      this_page -= LOW_MEM;
	      this_page >>= 12;
	      mem_map[this_page]++;
	    }
	}
    }
  invalidate ();		// 刷新页变换高速缓冲。
  return 0;
}

from_dir, to_dir对应页目录表中页目录项的物理地址,并且都是4字节对齐的,因为页目录表的基址为0,页目录项的索引虚拟地址的高10位,from_dir =(unsigned long*)(((from)>>22)<<2) = (unsigned long *) ((from >> 20) & 0xffc)
在这里插入图片描述
现在就完成了子进程的建立。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值