进入copy_prossess函数后,调用get_free_page()函数,在主内存申请一个空闲页面,并将申请到的页面清0。将这个页面的指针强制类型转化成task_struct类型的指针,并挂接在task[nr]上,nr是在find_empty_process中返回的任务号。
  接下来的*p=*current将当前进程的指针赋给了子进程的,也是说子进程继承了父进程一些重要的属性,当然这是不够的,所以接下来的一大堆代码都是为子进程做个性化设置的。
  一般来讲,每个进程都要加载属于自己的代码、数据,所以copy_process设置子进程的内存地址。通过copy_mem来设置新任务的代码和数据段基址、限长并复制页表。
  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)   // 0.11 版不支持代码和数据段分立的情况。 
  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;   // 新基址=任务号*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; 
  }
  然后是对文件,pwd等资源的修改,接着要设置子进程在GDT中的表项,后将进程设置为绪状态,并返回进程号。
  三、创建过程总结
  可以将上面繁琐的创建过程总结为一下的几步:
  1、调用fork()函数引发0×80中断
  2、调用sys_fork
  3、通过find_empty_process为新进程分配一个进程号
  4、通过copy_process函数使子进程复制父进程的资源,并进行一些个性化设置后,返回进程号。