linux0.12/kernel/fork.c
fork()系统调用函数用于创建子进程,Linux中的所有进程都是进程0的子进程
在这里我们伟大的linus运用了写时复制技术(copy-on-write),下面我来简述一下:
在fork()执行过程中,内核并不会立即为新进程分配代码和数据内存页,新进程将与父进程共同使用父进程已有的代码和数据内存页面,只有当其中的一个进程以写方式访问内存时,被操作的内存页面才会在写操作前被复制到新申请的内存页面中。
我们的子进程在创建之后会多次调用exec()函数,因此,在fork()调用之后,exec调用之前,父进程和子进程使用的是共同的内存区,子进程的数据段,代码段,堆栈段都是指向父进程的内存区。此时父进程和子进程的物理空间相同,逻辑空间不同。当父进程的段有改变时,此时会为子进程分配物理空间,此时会将子进程的数据段和堆栈段的数据复制到位子进程新分配的物理空间中,而子进程与父进程共享代码段。此时,完成了写时复制。。。
此函数对当前进程的逻辑地址从addr到addr+size这一段范围以页为单位执行写操作前的检测操作
void verify_area(void * addr,int size)
{
unsigned long start;
start = (unsigned long) addr; //得到进程起始地址
size += start & 0xfff; //得到进程起始地址处在该页的偏移地址
start &= 0xfffff000; // 此时start为进程起始的逻辑地址
start += get_base(current->ldt[2]); //此时start为整个系统中的线性空间的地址位置
while (size>0) { //循环对每个页面进行写操作验证,如果页面不可写,则复制页面
size -= 4096;
write_verify(start);
start += 4096;
}
}
此函数用来复制内存页表,上面我们介绍过了写时复制技术,这里是为子进程分配页表,此时还没有为子进程分配实际的物理页面,子进程还是和父进程共享内存页面。
nr是新任务号,p是新任务数据结构指针
由于Linux0.12还没有实习代码段和数据段分离的情况,所以要检验一下代码段和数据段的段基址是否相同,并要求数据段的长度不小于代码段的长度
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 * TASK_SIZE; //创建新进程在物理空间的基地址等于(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;
}
接下来是主要的fork子程序,复制系统进程信息,并且复制了整个代码段(也就是数据段),这也就是写时复制技术的实现。。
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx, long orig_eax,
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; //由find_empty_process()函数得到唯一进程号
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;
p->tss.ss0 = 0x10;
p->tss.eip = eip;
p->tss.eflags = eflags;
p->tss.eax = 0; //这是fork函数返回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 ; frstor %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++) //如果父进程中有文件的打开的,则将对应的文件打开次数增加1.
if (f=p->filp[i])
f->f_count++;
if (current->pwd) //子进程页将引用这些i节点
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
if (current->library)
current->library->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->p_pptr = current; //对这个子进程再进行一些设置,然后该子进程复制完毕!!返回子进程的pid号
p->p_cptr = 0;
p->p_ysptr = 0;
p->p_osptr = current->p_cptr;
if (p->p_osptr)
p->p_osptr->p_ysptr = p;
current->p_cptr = p;
p->state = TASK_RUNNING; /* do this last, just in case */
return last_pid;
}
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/7293.html