fork()函数:
(1)fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
(2)一次调用,两次返回。如果返回值为0,则表示当前子进程中;若大于0,则说明在父进程中,返回值为父进程的pid。
程序进程图画法:进程图是刻画程序语句的偏序的一种简单的前驱图。每个顶点a对应一条程序语句的执行,有向边a->b表示语句a发生在语句b之前。边上可以标注出一些信息。每张图从一个顶点开始,对应于调用main的父进程,每个进程的顶点序列结束于一个对应于exit调用的顶点。这个顶点只有入边,没有出边。
例:
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
运行结果:
分析:
fork函数,调用一次,返回两次,这个运行结果可以很直观看出这一特点。
代码1
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
运行结果:
简单分析进程图:
可理解输出结果。
代码2
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
运行结果:
同样分析进程图即可。
代码3:
#include "csapp.h"
/* $begin fork */
/* $begin wasidefork */
int main(int argc, char *argv[])
{
pid_t pid;
int x = 1;
pid = Fork(); //line:ecf:forkreturn
if (pid == 0) { /* Child */
printf("child : x=%d\n", ++x);
printf("child : x=%d\n", ++x);
fflush(stdout);
return 0;
}
/* Parent */
printf("parent: x=%d\n", --x);
printf("parent: x=%d\n", --x);
fflush(stdout);
return 0;
}
/* $end fork */
/* $end wasidefork */
运行结果:
ffiush()函数:清除文件缓冲区,文件以写方式打开时将缓冲区内容写入文件。
如果成功刷新,fflush返回0。指定的流没有缓冲区或者只读打开时也返回0值。返回EOF指出一个错误。
注意:如果fflush返回EOF,数据可能由于写错误已经丢失。当设置一个重要错误处理器时,最安全的是用setvbuf函数关闭缓冲或者使用低级I/0例程,如open、close和write来代替流I/O函数。
结果分析: 根据进程,父进程走两个减,子进程走两个加,输出结果如图
一些关于进程的函数
wait()函数:如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
waitpid的返回值比wait稍微复杂一些,一共有3种情况:
1、当正常返回的时候,waitpid返回收集到的子进程的进程ID;
2、如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
两个宏
1、WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数---指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了)
2、WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。