之前只是了解到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
至于总共有多少个进程,可以看分割线 ===== 条数 ,因为每个进程都会跑最后的共用代码.
我初步的理解分析就到这里吧~ 后续有机会深入….