收藏 分享(赏)

III. Linux系统编程_ 30进程_3 进程控制.doc

上传人:fmgc7290 文档编号:7607302 上传时间:2019-05-22 格式:DOC 页数:17 大小:101.50KB
下载 相关 举报
III. Linux系统编程_ 30进程_3 进程控制.doc_第1页
第1页 / 共17页
III. Linux系统编程_ 30进程_3 进程控制.doc_第2页
第2页 / 共17页
III. Linux系统编程_ 30进程_3 进程控制.doc_第3页
第3页 / 共17页
III. Linux系统编程_ 30进程_3 进程控制.doc_第4页
第4页 / 共17页
III. Linux系统编程_ 30进程_3 进程控制.doc_第5页
第5页 / 共17页
点击查看更多>>
资源描述

1、第 30 章 进程3. 进程控制3.1. fork 函数#include #include pid_t fork(void);fork 调用失败则返回-1,调用成功的返回值见下面的解释。我们通过一个例子来理解 fork 是怎样创建新进程的。例 30.3. fork#include #include #include #include int main(void)pid_t pid;char *message;int n;pid = fork();if (pid 0; n-) printf(message);sleep(1);return 0;$ ./a.out This is the chil

2、dThis is the parentThis is the childThis is the parentThis is the childThis is the parentThis is the child$ This is the childThis is the child这个程序的运行过程如下图所示。图 30.4. fork1. 父进程初始化。2. 父进程调用 fork,这是一个系统调用,因此进入内核。3. 内核根据父进程复制出一个子进程,父进程和子进程的 PCB 信息相同,用户态代码和数据也相同。因此,子进程现在的状态看起来和父进程一样,做完了初始化,刚调用了 fork 进入内核

3、,还没有从内核返回。4. 现在有两个一模一样的进程看起来都调用了 fork 进入内核等待从内核返回(实际上 fork 只调用了一次),此外系统中还有很多别的进程也等待从内核返回。是父进程先返回还是子进程先返回,还是这两个进程都等待,先去调度执行别的进程,这都不一定,取决于内核的调度算法。5. 如果某个时刻父进程被调度执行了,从内核返回后就从 fork 函数返回,保存在变量 pid 中的返回值是子进程的 id,是一个大于 0的整数,因此执下面的 else 分支,然后执行 for 循环,打印“This is the parentn“三次之后终止。6. 如果某个时刻子进程被调度执行了,从内核返回后就

4、从 fork 函数返回,保存在变量 pid 中的返回值是 0,因此执行下面的 if (pid = 0)分支,然后执行 for 循环,打印“This is the childn“六次之后终止。fork 调用把父进程的数据复制一份给子进程,但此后二者互不影响,在这个例子中,fork 调用之后父进程和子进程的变量 message 和 n 被赋予不同的值,互不影响。7. 父进程每打印一条消息就睡眠 1 秒,这时内核调度别的进程执行,在 1 秒这么长的间隙里(对于计算机来说 1 秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消息就睡眠 1 秒,在这 1 秒期间父进程也很有可能被调度到。所以程

5、序运行的结果基本上是父子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以把sleep(1);去掉看程序的运行结果如何。8. 这个程序是在 Shell 下运行的,因此 Shell 进程是父进程的父进程。父进程运行时 Shell 进程处于等待状态( 第 3.3 节 “wait 和waitpid 函数 ”会讲到这种等待是怎么实现的),当父进程终止时Shell 进程认为命令执行结束了,于是打印 Shell 提示符,而事实上子进程这时还没结束,所以子进程的消息打印到了 Shell 提示符后面。最后光标停在

6、 This is the child 的下一行,这时用户仍然可以敲命令,即使命令不是紧跟在提示符后面,Shell 也能正确读取。fork 函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。从上图可以看出,一开始是一个控制流程,调用 fork 之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。子进程中 fork 的返回值是 0,而父进程中 fork 的返回值则是子进程的 id(从根本上说 fork 是从内核返回的,内核自有办法让父进程和子进程返回不同的值),这样当 fork 函数返回后,程序员可以根据返回值的不同让父进程

7、和子进程执行不同的代码。fork 的返回值这样规定是有道理的。fork 在子进程中返回 0,子进程仍可以调用 getpid 函数得到自己的进程 id,也可以调用 getppid 函数得到父进程的 id。在父进程中用 getpid 可以得到自己的进程 id,然而要想得到子进程的 id,只有将 fork 的返回值记录下来,别无它法。fork 的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个 file 结构体,也就是说,file 结构体的引用计数要增加。用 gdb 调试多进程的程序会遇到困难,gdb 只能跟踪一个进程(默认是跟踪父进程),而

8、不能同时跟踪多个进程,但可以设置 gdb 在 fork 之后跟踪父进程还是子进程。以上面的程序为例:$ gcc main.c -g$ gdb a.outGNU gdb 6.8-debianCopyright (C) 2008 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by

9、 law. Type “show copying“and “show warranty“ for details.This GDB was configured as “i486-linux-gnu“.(gdb) l2 #include 3 #include 4 #include 56 int main(void)7 8 pid_t pid;9 char *message;10 int n;11 pid = fork();(gdb) 12 if(pidint execl(const char *path, const char *arg, .);int execlp(const char *f

10、ile, const char *arg, .);int execle(const char *path, const char *arg, ., char *const envp);int execv(const char *path, char *const argv);int execvp(const char *file, char *const argv);int execve(const char *path, char *const argv, char *const envp);这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以 exe

11、c 函数只有出错的返回值而没有成功的返回值。这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母 p(表示 path)的 exec 函数第一个参数必须是程序的相对路径或绝对路径,例如“/bin/ls“或“./a.out“,而不能是“ls“或“a.out“。对于带字母 p 的函数: 如果参数中包含/,则将其视为路径名。 否则视为不带路径的程序名,在 PATH 环境变量的目录列表中搜索这个程序。带有字母 l(表示 list)的 exec 函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有.,.中的最后一个可变参数应该是 NULL,起 sent

12、inel 的作用。对于带有字母 v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是 NULL,就像 main 函数的 argv 参数或者环境变量表一样。对于以 e(表示 environment)结尾的 exec 函数,可以把一份新的环境变量表传给它,其他 exec 函数仍使用当前的环境变量表执行新程序。exec 调用举例如下:char *const ps_argv =“ps“, “-o“, “pid,ppid,pgrp,session,tpgid,comm“, NULL;char *const ps_envp =

13、“PATH=/bin:/usr/bin“, “TERM=console“, NULL;execl(“/bin/ps“, “ps“, “-o“, “pid,ppid,pgrp,session,tpgid,comm“, NULL);execv(“/bin/ps“, ps_argv);execle(“/bin/ps“, “ps“, “-o“, “pid,ppid,pgrp,session,tpgid,comm“, NULL, ps_envp);execve(“/bin/ps“, ps_argv, ps_envp);execlp(“ps“, “ps“, “-o“, “pid,ppid,pgrp,ses

14、sion,tpgid,comm“, NULL);execvp(“ps“, ps_argv);事实上,只有 execve 是真正的系统调用,其它五个函数最终都调用 execve,所以 execve 在 man 手册第 2 节,其它函数在 man 手册第 3 节。这些函数之间的关系如下图所示。图 30.5. exec 函数族一个完整的例子:#include #include int main(void)execlp(“ps“, “ps“, “-o“, “pid,ppid,pgrp,session,tpgid,comm“, NULL);perror(“exec ps“);exit(1);执行此程序则

15、得到:$ ./a.out PID PPID PGRP SESS TPGID COMMAND6614 6608 6614 6614 7199 bash7199 6614 7199 6614 7199 ps由于 exec 函数只有错误返回值,只要返回了一定是出错了,所以不需要判断它的返回值,直接在后面调用 perror 即可。注意在调用 execlp 时传了两个“ps“参数,第一个“ps“是程序名,execlp 函数要在 PATH 环境变量中找到这个程序并执行它,而第二个“ps“是第一个命令行参数,execlp 函数并不关心它的值,只是简单地把它传给 ps 程序,ps 程序可以通过 main 函数

16、的 argv0取到这个参数。调用 exec 后,原来打开的文件描述符仍然是打开的 37。利用这一点可以实现I/O 重定向。先看一个简单的例子,把标准输入转成大写然后打印到标准输出:例 30.4. upper/* upper.c */#include int main(void)int ch;while(ch = getchar() != EOF) putchar(toupper(ch);return 0;运行结果如下:$ ./upperhello THEREHELLO THERE(按 Ctrl-D 表示 EOF)$使用 Shell 重定向:$ cat file.txtthis is the f

17、ile, file.txt, it is all lower case.$ ./upper #include #include #include int main(int argc, char *argv)int fd;if (argc != 2) fputs(“usage: wrapper filen“, stderr);exit(1);fd = open(argv1, O_RDONLY);if(fd#include int main(void)pid_t pid=fork();if(pid0) /* parent */while(1);/* child */return 0; 在后台运行这

18、个程序,然后用 ps 命令查看:$ ./a.out pid_t waitpid(pid_t pid, int *status, int options);若调用成功则返回清理掉的子进程 id,若调用出错则返回-1。父进程调用 wait或 waitpid 时可能会: 阻塞(如果它的所有子进程都还在运行)。 带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。 出错立即返回(如果它没有任何子进程)。这两个函数的区别是: 如果父进程的所有子进程都还在运行,调用 wait 将使父进程阻塞,而调用 waitpid 时如果在 options 参数中指定 WNOHANG 可以使父

19、进程不阻塞而立即返回 0。 wait 等待第一个终止的子进程,而 waitpid 可以通过 pid 参数指定等待哪一个子进程。可见,调用 wait 和 waitpid 不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进程终止,起到进程间同步的作用。如果参数 status 不是空指针,则子进程的终止信息通过这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将 status 参数指定为 NULL。例 30.6. waitpid#include #include #include #include #include int main(void)pid_t pid;pid = fork

20、();if (pid 0; i-) printf(“This is the childn“);sleep(1);exit(3); else int stat_val;waitpid(pid, if (WIFEXITED(stat_val)printf(“Child exited with code %dn“, WEXITSTATUS(stat_val);else if (WIFSIGNALED(stat_val)printf(“Child terminated abnormally, signal %dn“, WTERMSIG(stat_val);return 0;子进程的终止信息在一个 in

21、t 中包含了多个字段,用宏定义可以取出其中的每个字段:如果子进程是正常终止的,WIFEXITED 取出的字段值非零,WEXITSTATUS 取出的字段值就是子进程的退出状态;如果子进程是收到信号而异常终止的,WIFSIGNALED 取出的字段值非零,WTERMSIG 取出的字段值就是信号的编号。作为练习,请读者从头文件里查一下这些宏做了什么运算,是如何取出字段值的。习题1、请读者修改 例 30.6 “waitpid”的代码和实验条件,使它产生“Child terminated abnormally”的输出。37 事实上,在每个文件描述符中有一个 close-on-exec 标志,如果该标志为1,则调用 exec 时关闭这个文件描述符。该标志默认为 0,可以用 fcntl 函数将它置 1,本书不讨论该标志为 1 的情况。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报