一开始我想写成干博文的,后来当作的实验报告,后来就是现在决定还是发上来与大家分享!
@author:banxi1988 0800350211 李海珍
@date:2010-01-06
头文件:/usr/include/stdlib.h
声明如下:
/* Clone the calling process, creating an exact copy.
Return -1 for errors, 0 to the new process,
and the process ID of the new process to the old process. */
extern __pid_t fork ( void ) __THROW;
初看之下fork 很普通。接口往往都是这样的,把复杂的东西封装起来了。(后面的_THROW 为可能抛出 异常声明,这是对C++ 来说的。)
将注释解释下,注释说:克隆调用它的进程,创建一个几乎相同的副本。
返回-1 表示失败,(成功)返回0 给新创建的进程。
和新进程的进程号给老进程(即父进程)。
问答中心 :
那个__pid_t 是什么类型呢?
答:下面给出出处!
头文件:/usr/include/bits/types.h
定义如下:
__STD_TYPE __PID_T_TYPE __pid_t ; /* Type of process identifications. */
下面是递归定义过程。
#define __S32_TYPE int
#define __PID_T_TYPE __S32_TYPE
#define __STD_TYPE typedef
/*
* first_fork.c
*
* Created on: 2011-1-6
* Author: banxi1988
*/
#include <stdlib.h>
#include <stdio.h>
int main ( int argc , char **argv){
pid_t pid; /* 系统已经有此重定义: typedef __pid_t pid_t;*/
pid = fork ();
if (pid == -1){
perror ( "create fork failed!/a/n" );
exit (EXIT_FAILURE);
}
if (pid == 0){
printf ( " 在子进程中执行输出! /n" );
printf ( " 子进程进程号为 (PID) : %d/n 子进程的父进程号 (PPID):%d/n" , getpid (), getppid ());
} else {
printf ( " 在父进程中执行输出! /n" );
printf ( " 返回 pid 为: %d/n 父进程的进程号 (PID):%d/n" ,pid, getpid ());
}
sleep (20);
return EXIT_SUCCESS;
}
/***
* 运行结果:
banxi1988@banxi :~$ gcc -g -o firt_fork first_fork.c
banxi1988@banxi :~$ ./firt_fork
在父进程中执行输出!
返回 pid 为: 11419
父进程的进程号 (PID):11418
在子进程中执行输出!
子进程进程号为 (PID) : 11419
子进程的父进程号 (PPID):11418
*/
一 问:perror 是什么?
一答:perror 在stdio.h 中声明如下。用于打印编程人员传递的错误信息,和系统全局变量errno 所对应的错误值。
/* Print a message describing the meaning of the value of errno. */
extern void perror ( __const char *__s);
一问: getpid() ,和 getppid() 是作用?
一答:作用的话上面示例程序很好的体现了。它们在 stdlib.h 中声明,也在 unistd.h 中声明。
声明如下:
/* Get the process ID of the calling process. */
extern __pid_t getpid ( void ) __THROW;
/* Get the process ID of the calling process's parent. */
extern __pid_t getppid ( void ) __THROW;
还可以从上面找出更多的此类函数声明及用法。
上面的程序,与我们平常最为不同的就是, if 条件语句中, if 分支,和 else 分支都执行了。
fork 函数的声明注释也说明了, (成功)返回 0 给新创建的进程。和新进程的进程号给老进程(即父进程)。
我想是否可以这样理解, fork 函数执行后,创建的新进程肯定也拥有了一个名字为 pid 的变量。
但是它的这个值为 0 。
而父进程的这个 pid 值被设置为子进程的进程号,肯定是大于 1 的(后面说明)。
由于系统会进行进程的调用,也就是说会让子进程执行一段时间,父进程执行一段时间。
子进程创建后,马上与父进程的 地位相等的开始等待系统安排它执行。子进程也就从创建它的 fork 调用后的代码执行。这个时候系统觉得这个子进程刚创建也就马上让它执行。结果它执行了第一个 if 判断。
也就是如下的这个判断:
if (pid == -1){
perror ( "create fork failed!/a/n" );
exit (EXIT_FAILURE);
}
执行完这个判断之后 ,系统觉得它执行了一下就够了,就又让父进程执行了。父进程也执行上面的判断。
但是上面的那个判断执行完后,系统还没有剥夺父进程的执行权力,所以父进程继续向下执行。
执行到另一个 判断,这个时候父进程把自己的 pid 来出来与 0 对比之后 发 现不相等。
于是父进程又和往下执行(因为系统还没有剥夺它的执行权力)。所以它就执行 else 分支里面的语句。
执行都是从上次停止执行的地方开始执行。当轮到子进程执行的时候它会去对照下自己的 pid 与条件判断,如果符合就执行,如果不符合就不执行。但是有符合的,等于 0, 的这个条件符合,所以它执行了,等于 0 的这个条件分支。到了轮到父进程执行的时候,它又重复检查自己然后执行代码 .
可以看到上面的结果是父进程分支开始执行的,然后才是子进程的。 只是处理器的调度问题 .
解释 : 为什么父进程的 pid 为 0? 答 : 因为如果 fork 成功执行后返回 子进程的 ID 以表示成功 . 而创建的进程一定大于 0 的 . 为什么呢 . 后面再解释 .
现在假设下 , 如果两个分支的后面再次出现一个 fork 调用 .
而此时子进程和父进程都将执行 fork 函数调用 . 所以如果再次出现一个 fork 调用的话 , 那么就是又创建的两个子进程 . 其中一个是前一个子进程的子进程 .
下面的部分就是对这样的进程的进程树加以分析 :
先来认识一个命令就是 pstree, 以树形的方式打印当前的进程树 .
使用如下 :
banxi1988@banxi1988-desktop:~$ pstree
init─┬─NetworkManager─┬─dhclient
│ └─{NetworkManager}
├─acpid
├─apache2───5*[apache2]
├─atd
├─avahi-daemon───avahi-daemon
├─bash───tomboy───2*[{tomboy}]
├─bonobo-activati───{bonobo-activat}
├─clock-applet
├─console-kit-dae───63*[{console-kit-da}]
├─cron
├─cupsd
├─2*[dbus-daemon]
├─dbus-launch
├─dia
├─dnsmasq
├─eclipse───27*[{eclipse}]
├─fcitx───{fcitx}
….......... 略 ..
可以看出其它的所以的进程都是 init 的子进程 . 而且因为系统的进程的 id 是有空递增的 .
所以返回的子进程的 id 一定大于 0 的 . 因为系统存在运行的时候 ,init 及相关的守护进程是要存在的 .
但是 pstree 没有参数的时候打印出来的东西太多了 , 所以我们还要看下 pstree 还有哪些输出控制选项 . 如下 : 一个不错 .
banxi1988@banxi1988-desktop:~$ pstree USER banxi1988
Usage: pstree [ -a ] [ -c ] [ -h | -H PID ] [ -l ] [ -n ] [ -p ] [ -u ]
[ -A | -G | -U ] [ PID | USER ]
pstree -V
Display a tree of processes.
-a, --arguments show command line arguments
-A, --ascii use ASCII line drawing characters
-c, --compact don't compact identical subtrees
-h, --highlight-all highlight current process and its ancestors
-H PID,
--highlight-pid=PID highlight this process and its ancestors
-G, --vt100 use VT100 line drawing characters
-l, --long don't truncate long lines
-n, --numeric-sort sort output by PID
-p, --show-pids show PIDs; implies -c
-u, --uid-changes show uid transitions
-U, --unicode use UTF-8 (Unicode) line drawing characters
-V, --version display version information
PID start at this PID; default is 1 (init)
USER show only trees rooted at processes of this user
banxi1988@banxi1988-desktop:~$
我们再下最后一项 USER, 如果还要的话再加上一个 grep 就可以了 .
下面我们看一下一个简单的程序的进程树过程如下 :
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
sleep(30);
return 0;
}
banxi1988@banxi1988-desktop:~$ gcc -g -o fork_exp fork_exp.c
banxi1988@banxi1988-desktop:~$ ./fork_exp
banxi1988@banxi1988-desktop:~$ pstree banxi1988
gnome-terminal─┬─bash───pstree
├─bash───fork_exp
├─gnome-pty-helpe
└─{gnome-terminal}
上面的 banxi1988 就是我计算机的名字 . 也是系统的用户名 . 显示就少了 , 但上面还是省略了很多的 .
现在可以看出 ,fork_exp 的父进程是 bask 了吧 . 而 bash 的父进程就是 gnome-terminal( 即终端 )
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
fork();
sleep(30);
return 0;
}
banxi1988@banxi1988-desktop:~$ gcc -g -o fork_exp fork_exp.c
banxi1988@banxi1988-desktop:~$ ./fork_exp
banxi1988@banxi1988-desktop:~$ pstree banxi1988
gnome-terminal─┬─bash───pstree
├─bash───fork_exp───fork_exp
├─gnome-pty-helpe
└─{gnome-terminal}
产生了一个子进程 . 下面看下有两个 fork 调用的时候的进程树 .
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
fork();
fork();
sleep(30);
return 0;
}
banxi1988@banxi1988-desktop:~$ gcc -g -o fork_exp fork_exp.c
banxi1988@banxi1988-desktop:~$ ./fork_exp
banxi1988@banxi1988-desktop:~$ pstree banxi1988
gnome-terminal─┬─bash───pstree
├─bash───fork_exp─┬─fork_exp───fork_exp
│ └─fork_exp
├─gnome-pty-helpe
└─{gnome-terminal}
下面来看有三个 fork 的情况 .( 为了集中注意力下面把 bash 父进程的进程树去掉了 . 然后下面将用 grep
命令过虑掉其它的进程树 . 后面不再 cat 出代码了 , 只指明有几个 fork 调用 .
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
fork();
fork();
fork();
sleep(30);
return 0;
}
banxi1988@banxi1988-desktop:~$ gcc -g -o fork_exp fork_exp.c
banxi1988@banxi1988-desktop:~$ ./fork_exp
banxi1988@banxi1988-desktop:~$ pstree banxi1988
gnome-terminal─┬─bash───pstree
├─bash───fork_exp─┬─fork_exp─┬─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp───fork_exp
│ └─fork_ex
4 个 fork 调用的时候的情况 !
─bash───fork_exp─┬─fork_exp─┬─fork_exp─┬─fork_exp───fork_exp
│ │ │ └─fork_exp
│ │ ├─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp─┬─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp───fork_exp
│ └─fork_exp
5 个 fork 调用的时候 :
├─bash───fork_exp─┬─fork_exp─┬─fork_exp─┬─fork_exp─┬─fork_exp───fork_exp
│ │ │ │ └─fork_exp
│ │ │ ├─fork_exp───fork_exp
│ │ │ └─fork_exp
│ │ ├─fork_exp─┬─fork_exp───fork_exp
│ │ │ └─fork_exp
│ │ ├─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp─┬─fork_exp─┬─fork_exp───fork_exp
│ │ │ └─fork_exp
│ │ ├─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp─┬─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp───fork_exp
│ └─fork_exp
从上面的实验我们可以总结出 n 个 fork 调用产生了 2^n 个进程 ( 加上程序初始的父进程 ).
下面对原始的题目进行分析 . 如果要产生任意个 fork 的话 , 肯定得设置其它的条件 . 不能一直这样顺序执行 .
对我们一开始的程序的分支中调用一个 fork 将使总共的进程数据为 3.
过程如下 :
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
pid_t pid;
pid = fork();
if(pid == -1){
perror("create fork failed!/a/n");
exit(EXIT_FAILURE);
}
if(pid == 0){
fork();
}
sleep(30);
return 0;
}
进程树如下 :
┬─bash───pstree
├─bash───fork_exp───fork_exp───fork_exp
├─gnome-pty-helpe
└─{gnome-terminal}
因为上面是在子进程执行的代码下调用了一个 fork, 所以结果如上 . 共三个 fork.
下面给出一种创建 5 个进程的代码方案如下 :
进程树如下 :
├─bash───fork_exp─┬─fork_exp─┬─fork_exp───fork_exp
│ │ └─fork_exp
│ └─fork_exp
代码如下 :
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
pid_t pid;
pid = fork();
if(pid == -1){
perror("create fork failed!/a/n");
exit(EXIT_FAILURE);
}
if(pid == 0){
fork();
}else{
}
fork();
sleep(30);
return 0;
}
上面的做法相当于 2^2 加 1 得 5.
于是如果要创建 6 个进程的话 , 我们测试下在父进程的单独执行的代码里面调用一个 fork.
代码如下 :
banxi1988@banxi1988-desktop:~$ cat fork_exp.c
#include<stdlib.h>
#include<stdio.h>
int main(int argc ,char **argv){
pid_t pid;
pid = fork();
if(pid == -1){
perror("create fork failed!/a/n");
exit(EXIT_FAILURE);
}
if(pid == 0){
fork();
}else{
fork();
}
fork();
sleep(30);
return 0;
}
运行后进程树如下 :
─bash───fork_exp─┬─fork_exp─┬─fork_exp───fork_exp
│ │ └─fork_exp
│ ├─fork_exp───fork_exp
│ └─fork_exp
主要分析下第一个子进程创建了一个进程然后还执行了下面的也执行了后面的创建进程的代码 .
所以这个不能创建 6 个进程页是 8 个了 . 所以我们将后面公有的 fork 调用去掉转而放在父进程中 .
等到进程树显示出有 6 个进程 . 注意下面有 2*
├─bash───fork_exp─┬─2*[fork_exp───fork_exp]
│ └─fork_exp
七个进程的话要从子进程里加分支了 . 如下 :
第一个子进程代码如下 :
if(pid == 0){
pid_t cid;
cid = fork();
if(cid==0){
fork();
}
}
程序生成的进程树如下 :
├─bash───fork_exp─┬─fork_exp───fork_exp───fork_exp
│ ├─fork_exp───fork_exp
│ └─fork_exp
到此创建任意进程的基本要求实现了 !