在init/main.c中的main函数中可以发现如下语句:
if (!fork()) {
init();//1号进程要运行的代码
}
for(;;) pause();//0号进程要运行的代码
上面的注释中已经写的很清楚了,1号进程的创建是通过调用fork函数创建的,然后运行相应的init()函数,init函数即为进程1的主体,fork函数的声明位于include/unistd.h中
int fork(void);
可知fork函数是一个系统调用,其实现是通过相应的汇编代码来实现的(linux 0.11/0.12)
_sys_fork:
call _find_empty_process//为进程找到一个空进程号
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process//开始拷贝一个进程
addl %20,%esp
addl $20,%esp
1: ret
看到了吧,其实汇编代码还是很好理解的,为了理解上述代码,我们还必须要看的一个文件就是kernel/fork.c这个文件,这个文件主要涉及的是进程相关的创建,拷贝操作
//先找到一个目前没有使用的PID,然后任务表中找到一个目前没有使用的空间
//将该空间的索引号返回,如果没有找到空间就返回-EAGAIN(-11)
//last_pid是一个全局变量被设置成0
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid)<0) last_pid=1;//只要在last_pid超过了数据的表示范围,才会出现last_pid<0
//找到没有使用的pid号,个人觉的写的不好,浪费时间
for(i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat;
//看看task_struct中有没有空闲的PCB块,如果有的话就返回其索引号
for(i=1 ; i<NR_TASKS ; i++)
if (!task[i])
return i;
return -EAGAIN;
}
下面是另一个比较关键的函数copy_process
//这个函数可以说是fork的核心,复制父进程的操作是由它完成的,按照执行顺序可以将其分解成
//5个部份,进程常规变量的设定,TSS的设置 ,分配内存,文件设定及其它
//具有17个参数
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;
//其中p是任务数据结构指针,tss是任务状态段结构,内核为新任务申请内存用作保存其
//task_struct结构数据,而tss结构段是task_struct中的一个段,该任务的内核栈段值也被设置成为
//0x10,即内核数据段选择符,而tss.esp0则指向保存task_struct结构页面的未端。
int i;
struct file *f;
//为新的进程分配一页的内存空间
p = (struct task_struct *) get_free_page();
//如果分配失败就返回错误码
if (!p)
return -EAGAIN;
//将新进程空间的首地址赋给任务表
task[nr] = p;
//由于当前进程就是父进程,所以这里就是对父进程的完整copy
*p = *current; /* NOTE! this doesn't copy the supervisor stack */
//新进程的状态是不可中断的等待状态
p->state = TASK_UNINTERRUPTIBLE;
//设置子进程的ID
p->pid = last_pid;
//设置子进程的父进程ID
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;
//设置新进程的TSS内容
//设置新进程的TSS的内容
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;
}
//如果父进程中有文件 是打开的,则将对应的文件打开次数增1
for (i=0; i<NR_OPEN;i++)
if (f=p->filp[i])
f->f_count++;
//当前进程的pwd,root,和executable引用次数均增1
if (current->pwd)
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
//在内存的TSS描述符表和LDT描述符表中建立新进程的描述符
//新进程的描述符对应的TSS与LDT是从进程中取得的
//目前的LDT与父进程的LDT是相同的
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;
}
从这里应该可以在往回看,就应该明白了sys_fork是如何创建进程1的啦。