赞
踩
fork系统调用用于创建一个新的进程,称为子进程,它与进程(系统调用fork的进程)同时运行,此进程称为父进程。创建新的进程后,然后把父进程的所有值都复制到新的子进程中,只有少数值与原来的进程的值不同。相当于克隆了一个父进程。两个进程执行fork()系统调用之后的下一条指令。
fork()会返回一个整数值,下面是fork()返回的不同值。
fork()<0 //返回值小于0时,表示创建子进程失败。
fork()==0 //返回值等于0时,表示当前进程为子进程。
fork()>0 //返回值大于0时,表示当前进程为父进程,且返回的值为其子进程的进程ID。
由fork函数创建的新进程被称为子进程,注意fork函数被调用一次,但返回两次。一次是子进程中返回值0,一次是父进程中返回的子进程ID。
子进程是父进程的副本,它获得父进程的数据空间,堆,栈等资源的副本。但注意,子进程所有的是这些储存空间的副本,因此,与父进程不共享这些储存空间。Unix将复制父进程的地址空间的内容给子进程,因此,子进程有了独立的地址空间。
getpid(): 用于获取当前进程的进程ID。
getppid(): 用于获取当前进程的父进程ID。
int main()
{
pid_t pid;
int x = 1;
pid = fork();
if (pid == 0){ /*child*/
printf("child: x=%d\n",++x);
exit(0);
}
/*parent*/
printf("parent: x=%d\n",--x);
exit(0);
}
运行结果:
parent:x=0;
chlid:x=2;
如何看待这段代码,我想用进程结构图可以很容易看明白:
每一个节点代表该进程所执行的一个动作。
首先,入口函数main为第一个节点,该进程遇到“pid=fork();”创建了一个进程,为第二个节点,因为创建了一个子进程,因此有了一个分支。当pid>0时,当前为父进程,运行printf(–x),为下一个节点,然后退出。当pid=0时,当前为子进程,执行printf(++x),为一个节点,然后执行exit(0)结束子进程,因此不再执行下面的printf程序。
当然执行的结果也可能是如下:
chlid:x=2;
parent:x=0;
这是因为父进程与子进程两者处于独立的空间,两者运行互不干扰,以至于是先执行父进程还是子进程都是随机的,是由操作系统的调度器决定的。
由于两个进程的地址空间是独立的,所以在此代码中的X也是独立的,因此child:x=2而不是等于1。
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
执行结果:
L0
Bye
L1
Bye
L2
Bye
同样再次利用进程结构图帮助我们分析:
最下面这一条进程路线为主进程,其余的分支都为子进程。
通过进程结构图我们可以发现 printf(“Bye\n”)这一条语句执行了三次,因为父进程将代码,数据等资源全部复制给了子进程,所以每一个进程都会执行这条语句,且每一个进程独立运行。
进程之间的前后顺序也是随机的,所以每一次运行程序的结果都可能不一样。
调用fork后,子进程获得父进程数据段、堆和栈的副本。
缓冲区是在堆上的,所以子进程也获得了父进程的缓冲区。
由于我在缓冲区上踩了坑,所以拿出来讲一下。
缓冲区在父进程和子进程之间也是独立的
以下例子:
void main(){
printf("aa");//aa被放入缓冲区中
if(fork()==0){//子进程有和父进程相同的缓冲区,此时aa在缓冲区中未被输出。
printf("bb\n");//bb进入缓冲区,连同aa一起被输出
exit(0);
}
printf("cc\n");
exit(0);
}
输出结果:
aacc
aabb
在fork创建子进程之前,aa在缓冲区中未被释放,因此子进程拷贝了同样的缓冲区,所在输出“bb”的时,bb也加入缓存区,当遇见“\n”时,缓冲区清空,同时输出“aabb”。
可以通过fflush(stdout)释放缓冲区。
疑惑:
第一个printf(“”aa“”);到底有没有被输出? 即“aacc”中的aa是在fork()前输出还是在fork()后输出?
例子:
void main(){
printf("aa");
if(fork()==0){
printf("bb\n");
exit(0);
}
sleep(2);//让子进程先执行
printf("cc\n");
exit(0);
}
如果“aa”在fork()前输出,那么输出结果应该为:
aaaabb
cc
如果“aa”是在输出“cc”时一起被输出,那么结果应该为:
aabb
aacc
最终实际输出为:
眼见为实:
所以第一个“printf(“aa”)”为被输出,而是随着后面的cc一起被输出。
当一个进程由于某种原因终止时,内核并不是立刻把他从系统中清除。相反,进程被保持在一种已终止的状态中,直到被他的父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,该进程就不在了。一个终止了但还未被回收的进程称为僵死进程。
但是,即使僵死进程没有运行,它们仍然消耗系统的内存资源。
因此,一个进程可以调用waitpid函数来等待它的子进程终止或停止,以便父进程回收,从而没有僵死进程。
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *statusp,int options);
//返回:如果成功,则为子进程的PID,如果WNOHANG,则为0,如果其他错误,则为-1.
默认情况下(当options=0时),waitpid挂起调用进程的执行,直到它的等待集合(wait set)中的一个子进程终止。
等待集合的成员是有参数pid来确定的:
如果pid>0,等待集合就是一个单独的子进程,它的进程ID等于pid。
如果pid=-1,等待集合就是由父进程所有的子进程组成的。
wait函数是waitpid函数的简单版本:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *statusp);
//返回:如果成功,则为子进程的PID,如果出错,则为-1.
调用wait(&statusp)等价于调用waitpid(-1,&statusp,0)。
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
首先创建了N个子进程,通过调用wait函数来得到关于导致返回的子进程的状态信息wpid,再通过WIFEXITED(child_status)来验证子进程是否是正常的终止退出状态。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。