kernel源码(十五)fork.c
源码
/* * linux/kernel/fork.c * * (C) 1991 Linus Torvalds */ /* * 'fork.c' contains the help-routines for the 'fork' system call * (see also system_call.s), and some misc functions ('verify_area'). * Fork is rather simple, once you get the hang of it, but the memory * management can be a bitch. See 'mm/mm.c': 'copy_page_tables()' */ #include#include #include #include #include extern void write_verify(unsigned long address); long last_pid=0; void verify_area(void * addr,int size) { unsigned long start; start = (unsigned long) addr; size += start & 0xfff; start &= 0xfffff000; start += get_base(current->ldt[2]); while (size>0) { size -= 4096; write_verify(start); start += 4096; } } 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; } /* * 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. */ 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; p = (struct task_struct *) get_free_page(); if (!p) return -EAGAIN; task[nr] = p; *p = *current; /* 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 ) if (f=p->filp[i]) f->f_count++; 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; } int find_empty_process(void) { int i; repeat: if ((++last_pid)<0) last_pid=1; for(i=0 ; i ) if (task[i] && task[i]->pid == last_pid) goto repeat; for(i=1 ; i ) if (!task[i]) return i; return -EAGAIN; }
进程空间区域的写前验证函数。
对于80386控制器,在请求特权级0下用户空间的代码页保护机制是不起作用的,通过fork创建的子进程的写时复制机制是不起作用的,varify_area的目的就是用来保护写时复制机制。
对80486,cr0有一个写保护标志,可以通过设置这个标志来禁止特权级的代码向用户空间只读页面执行写数据,否则就会发生一个写保护异常。80486通过这个来达到和varify_area函数相同的目的
void verify_area(void * addr,int size) { unsigned long start; start = (unsigned long) addr; //addr所在页面的开始地址 size += start & 0xfff; //0xfff为12位也就是4096字节也就是1页大小。start&0xfff表示start的低12位。这里重新计算size大小 start &= 0xfffff000; //start和0xfffff000相与,这样过滤掉不到1页的内容。上面这两行作用是重新调整start和size,以便后面按照页为单位进行内存验证 start += get_base(current->ldt[2]); //start调整成页面的边界值,get_base在其他文件中定义,后面会讲解。ldt[0]为代码段,ldt[1]为数据段 while (size>0) { size -= 4096; write_verify(start); //对start开始的一个页面进行验证,如果这个页面是不可写的,我们就复制这个页面,实现写时复制。 start += 4096; } }
复制内存页表,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); //代码段描述符表的段限长。0x0f为段选择子,根据段选择子的结构我们可知,其代表用户段LDT,index=1.表示代码段。同样的道理0x17表示用户段LDT索引值为2,表示的是数据段 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; //新的数据段基地址,新任务号*64M,即每个进程地址空间相距64M p->start_code = new_code_base; //新进程的start_code 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)) {//正常情况下copy_page_table返回0,否则执行free_page_tables释放页目录项和页表项,返回出错信息 free_page_tables(new_data_base,data_limit); return -ENOMEM; } return 0; }
通过复制当前进程来创建新进程,返回新的进程号
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; p = (struct task_struct *) get_free_page();//为新任务分配内存 if (!p) return -EAGAIN; //如果分配内存失败,则返回错误码退出 task[nr] = p; //新任务的结构指针放到task数组中,nr为任务号 *p = *current; /* 当前任务的数据结构复制到刚申请的任务结构中, p->state = TASK_UNINTERRUPTIBLE; //下面是对复制过来的任务结构的内容进行修改,把进程状态置为不可中断的等待状态,防止内核调用它执行 p->pid = last_pid; //设置进程号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; //下面是修改任务状态段(TSS)信息 p->tss.esp0 = PAGE_SIZE + (long) p; //ss0和esp0是程序在内核态执行的栈 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)); //清除控制寄存器cr0当中的任务已交换的标志。fnsave用于把协处理器的所有状态保存到p的tss.i387这个内存区域当中 if (copy_mem(nr,p)) {//复制进程的页表。也就是在线性地址空间当中设置新任务代码段和数据段描述符当中的基地址和限长。并且复制页表。正常情况下返回0 task[nr] = NULL; //如果出错,则复位任务数组task当中相应的项 free_page((long) p); //释放为新任务申请的内存页 return -EAGAIN; //返回出错信息 } for (i=0; i) //如果父进程当中有文件是打开的 if (f=p->filp[i]) //对相应文件的打开次数增加1 f->f_count++; if (current->pwd)//把当前进程pwd、root、executable都增加1 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)); //在GDT中设置新任务的任务状态段tss的描述符项,set_tss_desc的定义在system.h当中,后面会讲解到 set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); //在GDT中设置新任务的ldt的描述符项 p->state = TASK_RUNNING; /* 程序的状态置为就绪态 return last_pid; //返回新的进程号 }
取得一个不重复的进程号
int find_empty_process(void) { int i; repeat: if ((++last_pid)<0) last_pid=1; //如果last_pid超出进程号范围,则last_pid再从1开始 for(i=0 ; i) if (task[i] && task[i]->pid == last_pid) goto repeat; for(i=1 ; i ) if (!task[i]) return i; return -EAGAIN; //如果没有空闲的进程号可用,则返回一个错误信息 }