Linux下的 fork 函数

之前只是了解到linux中的fork函数是用来创建进程,并没有太多的去学习,这里学习记录如下。

撰写不易,转载需注明出处:http://blog.csdn.net/jscese/article/details/44401389 本文来自 【jscese】的博客!

定义:

来自百科的解释:fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

通俗的来理解,就是程序跑到fork的时候,进行了一次分身,然后两个基本基本相同的家伙都会独立的接着往下跑.

原型:

pid_t fork( void);

返回值:

这里的pid_t 是一个宏,由一系列的types.h文件定义后,实际是int类型
而一次调用fork函数是会有两次返回,一次是调用进程的返回,一次是新建进程的返回.
上面说到调用到fork的时候,在fork当中就会进行“分身”,资源状态的复制,所以创建的新进程也是停留在fork函数当中的,调用fork函数的进程可以称做父进程,新建的为子进程.

fork返回值:
父进程就会返回创建的子进程的进程ID.
子进程刚刚被创建,并没有子进程,所以直接返回 0.
如果出错返回负数 .

fork出错可能有两种原因:
1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。

实现:

而fork函数的定义是在/usr/include/unistd.h中有定义,实际调用时是通过系统调用.
在kernel源码中的unistd.h中可以找到这样的宏定义:

#define __NR_fork 1079
#ifdef CONFIG_MMU
__SYSCALL(__NR_fork, sys_fork)
#else
__SYSCALL(__NR_fork, sys_ni_syscall)
#endif /* CONFIG_MMU */

继续跟进systemcall的实现可以看syscalls.h以及sys_arm.c,分别看下sys_fork的定义和实现:

/* Fork a new task - this creates a new program thread.
 * This is called indirectly via a small wrapper
 */
asmlinkage int sys_fork(struct pt_regs *regs)
{
#ifdef CONFIG_MMU
    return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
#else
    /* can not support in nommu mode */
    return(-EINVAL);
#endif
}

可以看到实际上是调用了一个 do_fork 的函数.这才是fork 被系统调用进的接口.

继续看下这个do_fork函数,实现是在kernel源码下的/kernel/fork.c(3.1.10):

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
struct task_struct *p;   // 而这个task_struct结构体即是Linux中对一个进程的描述符
...

    p = copy_process(clone_flags, stack_start, regs, stack_size,
             child_tidptr, NULL, trace);//这里进行copy复制 代码段,数据段,BSS段,堆,栈等所有用户空间的信息


}

上面只是简单的追踪了一下fork的实现,特别是 系统调用 以及 进程资源复制 都是比较复杂的,我接触不多,这里暂时放下,后续有机会去学习.

使用:

作为函数使用,自然离不开代码,下面附上我自己写的一个小测试:

#include <stdio.h>
#include <unistd.h>


int main(int argc,char* argv[])
{

printf("begain parent process pid ==%x \n",getpid());

pid_t ipid1=fork();

if(ipid1==0)
{
 printf("first child process pid==%x ,parent process pid==%x \n",getpid(),getppid());

}else
{

printf("first fork child process pid==%x ,parent process pid==%x\n",ipid1,getpid());

}


pid_t ipid2=fork();

if(ipid2==0)
{
  printf("second child process pid==%x ,parent process pid == %x \n",getpid(),getppid());

}else
{

printf("second fork child process pid==%x ,parent process pid==%x\n",ipid2,getpid());
}

printf("=========================================  \n");
sleep(1);

}

gcc编译运行之后结果如下:

begain parent process pid ==36fd 
first fork child process pid==36fe ,parent process pid==36fd
first child process pid==36fe ,parent process pid==36fd 
second fork child process pid==36ff ,parent process pid==36fd
=========================================  
second fork child process pid==3700 ,parent process pid==36fe
=========================================  
second child process pid==36ff ,parent process pid == 36fd 
=========================================  
second child process pid==3700 ,parent process pid == 36fe 
=========================================  

可以看到最开始main进程的PID是 36fd ,第一次 fork之后,上面有说到是会返回两个值的,并且都会继续往下执行代码段(两份相同的代码,并不共享数据).

其中36fd(父) 进程返回的是子进程的PID,所以 ipid1 肯定不会等于 0,所以运行:

first fork child process pid==36fe ,parent process pid==36fd

显然返回的子进程PID 为36fe.
(getpid()获取当前进程PID,getppid()获取父进程PID)

而上面创建的这个36fe的子进程返回的必然就是 0 了,往下执行 ipid 就等于 0,所以运行:

first child process pid==36fe ,parent process pid==36fd 

我这里是父进程先跑出fork往下运行,所以先打印了first fork,这个应该是跟进程调度策略有关,我是直接在ubuntu上运行.

代码继续往下,这两个进程(36fd(父),36fe(子)) 都会再进入第二个fork函数.
分别又会像上面分析到的那样创建一个新的进程分别是 36ff 3670 ,运行模式同上分析。

最后如果不加个sleep(1);
最后两行的打印结果会是:

second child process pid==36ff ,parent process pid == 1 
=========================================  
second child process pid==3700 ,parent process pid == 1 
=========================================  

因为等最新的两个新进程开跑的时候, 前面最开始的那两个老进程跑完了~getppid()返回 1

至于总共有多少个进程,可以看分割线 ===== 条数 ,因为每个进程都会跑最后的共用代码.

我初步的理解分析就到这里吧~ 后续有机会深入….

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。