Linux中1号进程的创建剖析


在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的啦。

相关阅读:Linux中0号进程的创建 

相关内容