参考 :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