fork调用的内核实现

fork调用的内核实现

  进程和线程是我们平时接触的比较多的两个概念,特别是线程机制,很多语言原生就支持它。前段时间主要演示了下linux下进程和线程的创建,这篇文章对其创建的过程做一个简单的分析,错误之处,还请您斧正。

  在linux下,线程其实就是一个轻量级的进程,所以其实现都是通过调用给do_fork函数传入不同的参数实现的。先来看下这几个函数:

1   int  sys_fork( struct  pt_regs  * regs)
2   {
3             return  do_fork(SIGCHLD, regs -> sp, regs,  0 , NULL, NULL);
4   }

 

1   int  sys_vfork( struct  pt_regs  * regs)
2   {
3            return  do_fork(CLONE_VFORK  |  CLONE_VM  |  SIGCHLD, regs -> sp, regs,  0 ,
4                          NULL, NULL);
5   }   

 

 

1   sys_clone(unsigned  long  clone_flags, unsigned  long  newsp,
2              void  __user  * parent_tid,  void  __user  * child_tid,  struct  pt_regs  * regs)
3   {
4            if  ( ! newsp)
5                   newsp  =  regs -> sp;
6            return  do_fork(clone_flags, newsp, regs,  0 , parent_tid, child_tid);
7   }

 

    上面的代码中,并没有看到fork()函数的实现,其实fork函数的执行过程大致像这样:普通程序调用fork()-->库函数 fork()-->系统调用(fork功能号)-->由功能号在 sys_call_table[]中寻到sys_fork()函数地址-->调用sys_fork(),这就完成拉从用户态到内核态的变化过程。所 以,实际上,fork函数对应的实现就是sys_fork。

  和上面的过程类似,上面的几个函数分别对应与fork,vfork和clone,可以看到,其实际上都是通过一个调用do_fork函数实现的,不同处只是其传入的参数不同。 首先来看看传入的参数的,先看看这些传入的参数分别代表的含义:

cloning flags
/*
* cloning flags:
*/
#define  CSIGNAL             0x000000ff      /* signal mask to be sent at exit */
#define  CLONE_VM            0x00000100      /* set if VM shared between processes */
#define  CLONE_FS           0x00000200      /* set if fs info shared between processes */
#define  CLONE_FILES       0x00000400      /* set if open files shared between processes */
#define  CLONE_SIGHAND  0x00000800      /* set if signal handlers and blocked signals shared */
#define  CLONE_PTRACE      0x00002000      /* set if we want to let tracing continue on the child too */
#define  CLONE_VFORK       0x00004000      /* set if the parent wants the child to wake it up on mm_release */
#define  CLONE_PARENT     0x00008000      /* set if we want to have the same parent as the cloner */
#define  CLONE_THREAD     0x00010000      /* Same thread group? */
#define  CLONE_NEWNS     0x00020000      /* New namespace group? */
#define  CLONE_SYSVSEM  0x00040000      /* share system V SEM_UNDO semantics */
#define  CLONE_SETTLS       0x00080000      /* create a new TLS for the child */
#define  CLONE_PARENT_SETTID      0x00100000      /* set the TID in the parent */
#define  CLONE_CHILD_CLEARTID    0x00200000      /* clear the TID in the child */
#define  CLONE_DETACHED              0x00400000      /* Unused, ignored */
#define  CLONE_UNTRACED             0x00800000      /* set if the tracing process can't force CLONE_PTRACE on this clone */
#define  CLONE_CHILD_SETTID        0x01000000      /* set the TID in the child */
#define  CLONE_STOPPED             0x02000000      /* Start in stopped state */
#define  CLONE_NEWUTS                0x04000000      /* New utsname group? */
#define  CLONE_NEWIPC                 0x08000000      /* New ipcs */
#define  CLONE_NEWUSER              0x10000000      /* New user namespace */
#define  CLONE_NEWPID                 0x20000000      /* New pid namespace */
#define  CLONE_NEWNET                0x40000000      /* New network namespace */
#define  CLONE_IO                          0x80000000      /* Clone io context */

 

  然后来看看do_fork的具体过程:

  1.  p = copy_process(clone_flags, stack_start, regs, stack_size,
    child_tidptr, NULL, trace);
  2.   wake_up_new_task(p, clone_flags);

 

   第一步是调用copy_process函数来复制一个进程,并对相应的标志位等进行设置,接下来,如果copy_process调用成功的话,那么系统 会有意让新开辟的进程运行,这是因为子进程一般都会马上调用exec()函数来执行其他的任务,这样就可以避免写是复制造成的开销,或者从另一个角度说, 如果其首先执行父进程,而父进程在执行的过程中,可能会向地址空间中写入数据,那么这个时候,系统就会为子进程拷贝父进程原来的数据,而当子进程调用的时 候,其紧接着执行拉exec()操作,那么此时,系统又会为子进程拷贝新的数据,这样的话,相比优先执行子程序,就进行了一次“多余”的拷贝。

 

   从上面的分析中可以看出,do_fork()的实现,主要是靠copy_process()完成的,这就是一环套一环,所以在看内核的时候,会觉得一下 子跳到这,一下子又跳到那,一下子就看晕了的一个很大的原因。不过我觉得这也是linux的一大好处,因为其提高了函数的可重用行,比如本文一开始提到的 几个函数的实现,归根到底,都是通过do_fork()实现的。

 

  接着再来看看copy_process()的实现:

  1. p = dup_task_struct(current); 为新进程创建一个内核栈、thread_iofo和task_struct,这里完全copy父进程的内容,所以到目前为止,父进程和子进程是没有任何区别的。
  2. 检查所有的进程数目是否已经超出了系统规定的最大进程数,如果没有的话,那么就开始设置进程描诉符中的初始值,从这开始,父进程和子进程就开始区别开了。
  3. 设置子进程的状态为不可被TASK_UNINTERRUPTIBLE,从而保证这个进程现在不能被投入运行,因为还有很多的标志位、数据等没有被设置。
  4. 复制标志位(falgs成员)以及权限位(PE_SUPERPRIV)和其他的一些标志。
  5. 调用get_pid()给子进程获取一个有效的并且是唯一的进程标识符PID。
  6. 根据传入的cloning flags(具体表示上面有)对相应的内容进行copy。比如说打开的文件符号、信号等。
  7. 父子进程平分父进程剩余的时间片。
  8. return p;返回一个指向子进程的指针。

 

  至此,do_fork的工作就基本结束了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值