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


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