linux0.11-虚拟内存

参考 :5.3 Linux对内存的管理与使用

linuxkernelsrc-Linux文档类资源-CSDN下载

分页与分段

逻辑地址:

        Linux0.11 中,给每个程序(进程)都划分了总容量为64MB的虚拟内存空间。因此程序的逻辑地址范围:0x00-0x4000000.现在32位系统是4G.

fs\exec.c 在change_idt中设置
    data_limit = 0x4000000;//64M

static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
	unsigned long code_limit,data_limit,code_base,data_base;
	int i;

	code_limit = text_size+PAGE_SIZE -1;
	code_limit &= 0xFFFFF000;
	data_limit = 0x4000000;//64M
	code_base = get_base(current->ldt[1]);
	data_base = code_base;
	set_base(current->ldt[1],code_base);
	set_limit(current->ldt[1],code_limit);
	set_base(current->ldt[2],data_base);
	set_limit(current->ldt[2],data_limit);
/* make sure fs points to the NEW data segment */
	__asm__("pushl $0x17\n\tpop %%fs"::);
	data_base += data_limit;
	for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {
		data_base -= PAGE_SIZE;
		if (page[i])
			put_page(page[i],data_base);
	}
	return data_limit;
}

线程地址:(分段机制)

        逻辑地址(或者说是段中的偏移地址),加上相应段的基地址就生成一个线性地址。Linux 0.11 中 每个任务的在线性地址空间的起始位置是(任务号)*64M(逻辑地址)。最大任务是64.因此全部任务使用的线性地址空间范围是64*64M = 4G.

物理地址:(分页机制)

这也是为什么线性地址的计算方式是:任务号*64M 保证不同的任务的线性地址不同,这样使用同一个页目录表的使用,计算出来的物理地址空间不同

        页目录表的创建在head.s中

boot\head.s

//1. 加载内核运行时的个数据段寄存器,重新设置中断描述符表
//2. 开启内核正常运行时的协处理器等资源
//3. 设置内存管理的分页机制
//4. 跳转到main.c开始运行
/*
 *  linux/boot/head.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  head.s contains the 32-bit startup code.
 *
 * NOTE!!! Startup happens at absolute address 0x00000000, which is also where
 * the page directory will exist. The startup code will be overwritten by
 * the page directory.
 */
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:
startup_32:
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	mov %ax,%gs
	lss _stack_start,%esp
	call setup_idt
	call setup_gdt
	movl $0x10,%eax		# reload all the segment registers
	mov %ax,%ds		# after changing gdt. CS was already
	mov %ax,%es		# reloaded in 'setup_gdt'
	mov %ax,%fs
	mov %ax,%gs
	lss _stack_start,%esp
	xorl %eax,%eax
1:	incl %eax		# check that A20 really IS enabled
	movl %eax,0x000000	# loop forever if it isn't
	cmpl %eax,0x100000
	je 1b

 中调用:    

call setup_idt
call setup_gdt

缺页中断:p98 4.4 分页

       页表一共需要  1K * 1K * 4(一个表项占四字节) = 4MB.在使用过程中,通过访问页目录表中第0位p确认 二级页表是否存在,不存在则创建,因此不需要一开始就创建4MB的页表。通过缺页中断的方式,动态加载页表,同时还可以将不使用的页表放入磁盘,缺页时加载到内存。

      缺页中断的方式只能用于主内存,不能用于内核内存区

      

写时复制:13章内存管理

fork.c

*
 *  Ok, this is the main fork-routine. It copies the system process
 * information (task[nr]) and sets up the necessary registers. It
 * also copies the data segment in it's entirety.
 */
// 所谓进程创建就是对0号进程或者当前进程的复制
// 就是结构体的复制 把task[0]对应的task_struct 复制一份
//除此之外还要对栈堆拷贝 当进程做创建的时候要复制原有的栈堆
// nr就是刚刚找到的空槽的pid
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
		long ebx,long ecx,long edx,
		long fs,long es,long ds,
		long eip,long cs,long eflags,long esp,long ss)
{
	struct task_struct *p;
	int i;
	struct file *f;
	//其实就是malloc分配内存
	p = (struct task_struct *) get_free_page();//在内存分配一个空白页,让指针指向它
	if (!p)
		return -EAGAIN;//如果分配失败就是返回错误
	task[nr] = p;//把这个指针放入进程的链表当中
	*p = *current;//把当前进程赋给p,也就是拷贝一份	/* NOTE! this doesn't copy the supervisor stack */
	//后面全是对这个结构体进行赋值相当于初始化赋值
	p->state = TASK_UNINTERRUPTIBLE;
	p->pid = last_pid;
	p->father = current->pid;
	p->counter = p->priority;
	p->signal = 0;
	p->alarm = 0;
	p->leader = 0;		/* process leadership doesn't inherit */
	p->utime = p->stime = 0;
	p->cutime = p->cstime = 0;
	p->start_time = jiffies;//当前的时间
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;
	p->tss.eip = eip;
	p->tss.eflags = eflags;
	p->tss.eax = 0;//把寄存器的参数添加进来
	p->tss.ecx = ecx;
	p->tss.edx = edx;
	p->tss.ebx = ebx;
	p->tss.esp = esp;
	p->tss.ebp = ebp;
	p->tss.esi = esi;
	p->tss.edi = edi;
	p->tss.es = es & 0xffff;
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	p->tss.fs = fs & 0xffff;
	p->tss.gs = gs & 0xffff;
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)//如果使用了就设置协处理器
		__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
	if (copy_mem(nr,p)) {//老进程向新进程代码段和数据段进行拷贝
		task[nr] = NULL;//如果失败了
		free_page((long) p);//就释放当前页
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)//
		if (f=p->filp[i])//父进程打开过文件
			f->f_count++;//就会打开文件的计数+1,说明会继承这个属性
	if (current->pwd)//跟上面一样
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
	p->state = TASK_RUNNING;//把状态设定为运行状态	/* do this last, just in case */
	return last_pid;//返回新创建进程的id号
}

在    if (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]);
	if (old_data_base != old_code_base)
		panic("We don't support separate I&D");
	if (data_limit < code_limit)
		panic("Bad data_limit");
	new_data_base = new_code_base = nr * 0x4000000;
	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;
}

在父进程创建子进程后,此时只是将A进程的页目录项拷贝给B进程, 并不会拷贝页表项(4M 太大了),此时父子进程共用一段物理内存。同时将表项中的第一位  RW

至0.仅仅允许读不允许写操作。 一旦有父进程或子进程对这块内存进行写操作,就会引发页面出错:page_fault(14号)

        在该异常的中断服务函数会给写进程复制一个新的物理页面,此时父子进程就各自有一块要写的内存,然后设置该内存为可读写状态,然后返回重新进行刚刚异常的写操作

        写操作=》页面异常中断=》处理写保护异常=》重新分配物理内存页面=》重新执行写操作

代码位置:mm/memory.c

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值