一、wait和waitpid函数

当一个进程正常或异常终止时会向父进程发送SIGCHLD信号。对于这种信号系统默认会忽略。调用wait/waidpid的进程可能会:

  • 阻塞(如果其子进程都还在运行);
  • 立即返回子进程的终止状态(如果一个子进程已经终止正等待父进程存取其终止状态);
  • 出错立即返回(如果它没有任何子进程);
    如果进程由于收到SIGCHLD信号而调用wait,则可期望wait会立即返回。但是在任一时刻调用则进程可能阻塞。
#include <sys/types.h>
#include <sys/wait.h> pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
返回值: 成功返回进程ID, 出错-1.

这两个函数区别:

  • wait如果在子进程终止前调用则会阻塞,而waitpid有一选项可以使调用者不阻塞。
  • waitpid并不等待第一个终止的子进程--它有多个选项,可以控制它所等待的进程。

如果调用者阻塞而且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回子进程ID,所以调用者知道是哪个子进程终止了。
  参数statloc是一个整型指针。如果statloc不是一个空指针,则终止状态就存放到它所指向的单元内。如果不关心终止状态则将statloc设为空指针。
  这两个函数返回的整型状态由实现定义。其中某些位表示退出状态(正常退出),其他位则指示信号编号(异常返回),有一位指示是否产生了一个core文件等等。POSIX.1规定终止状态用定义在<sys/wait.h>中的各个宏来查看。有三个互斥的宏可用来取得进程终止的原因,它们的名字都已WIF开始。基于这三个宏中哪一个值是真,就可选用其他宏(这三个宏之外的其他宏)来取得终止状态、信号编号等。
  

下面的程序中pr_exit函数使用上表中的宏以打印进程的终止状态。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h> void pr_exit(int status)
{
if (WIFEXITED(status)) {
printf("normal termination, exit status=%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("abnormal termination, signal number = %d\n", WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? "(core file generated)" : "");
#else
"");
#endif
} else if (WIFSTOPPED(status)) {
printf("child stopped, signal number = %d\n", WSTOPSIG(status));
}
} int main(void)
{
pid_t pid;
int status; if ((pid = fork()) < 0) {
fprintf(stderr, "fork error");
} else if (pid == 0) {
exit(7);
} if (wait(&status) != pid) {
fprintf(stderr, "wait error");
} pr_exit(status); if ((pid = fork()) < 0) {
fprintf(stderr, "fork error");
} else if (pid == 0) {
abort();
} if (wait(&status) != pid) {
fprintf(stderr, "wait error");
} pr_exit(status); if ((pid = fork()) < 0) {
fprintf(stderr, "fork error");
} else if (pid == 0) {
status /= 0;
} if (wait(&status) != pid) {
fprintf(stderr, "wait error");
} pr_exit(status); return 0;
}

编译运行结果:

wait是只要有一个子进程终止就返回,waitpid可以指定子进程等待。对于waitpid的pid参数:

  • pid == -1, 等待任一子进程。这时waitpid与wait等效。
  • pid > 0, 等待子进程ID为pid。
  • pid == 0, 等待其组ID等于调用进程的组ID的任一子进程。
  • pid < -1 等待其组ID等于pid的绝对值的任一子进程。

对于wait,其唯一的出错是没有子进程(函数调用被一个信号中断,也可能返回另一种出错)。对于waitpid, 如果指定的进程或进程组不存在,或者调用进程没有子进程都能出错。   options参数使我们能进一步控制waitpid的操作。此参数或者是0,或者是下表中常数的逐位或运算。
  

二、 竞态条件

当多个进程都企图对某共享数据进行某种处理,而最后的结果又取决于进程运行的顺序,则我们认为这发生了竞态条件(race condition)。如果在fork之后的某种逻辑显式或隐式地依赖于在fork之后是父进程先运行还是子进程先运行,那么fork函数就会是竞态条件活跃的孽生地。
  如果一个进程希望等待一个子进程终止,则它必须调用wait函数。如果一个进程要等待其父进程终止,则可使用下列形式的循环:

while(getppid() != 1)
sleep(1);

这种形式的循环(称为定期询问(polling))的问题是它浪费了CPU时间,因为调用者每隔1秒都被唤醒,然后进行条件测试。
  为了避免竞态条件和定期询问,在多个进程之间需要有某种形式的信号机制。在UNIX中可以使用信号机制,各种形式的进程间通信(IPC)也可使用。
  在父、子进程的关系中,常常有以下情况:在fork之后,父、子进程都有一些事情要做。例如:父进程可能以子进程ID更新日志文件中的一个记录,而子进程则可能要为父进程创建一个文件。在本例中,要求每个进程在执行完它的一套初始化操作后要通知对方,并且在继续运行之前,要等待另一方完成其初始化操作。这种情况可以描述为如下:

TELL_WAIT();

if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
TELL_PARENT(getppid());
WAIT_PARENT();
exit(0);
} TELL_CHILD(pid);
WAIT_CHILD();
exit(0);

三、exec函数

当进程调用exec函数时,该进程完全由新进程代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID不会改变。exec只是用另一个程序替换了当前进程的正文、数据、堆和栈段。

#include <unistd.h>

int execl(const char *pathname, const char *arg0, ... /* (char *) 0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *) 0 */);
int execvp(const char *filename, char *const argv[]);
返回值:出错-1,若成功不返回

这些函数之间的第一个区别是前四个取路径名作为参数,后两个取文件名作为参数。当制定filename作为参数时:

  • 如果filename中包含/,则就将其视为路径名。
  • 否则按PATH环境变量。

如果excelp和execvp中的任意一个使用路径前缀中的一个找到了一个可执行文件,但是该文件不是机器可执行代码文件,则就认为该文件是一个shell脚本,于是试着调用/bin/sh,并以该filename作为shell的输入。
  第二个区别与参数表的传递有关(l 表示表(list),v 表示矢量(vector))。函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。另外三个函数execv,execvp,execve则应先构造一个指向个参数的指针数组,然后将该数组地址作为这三个函数的参数。
  最后一个区别与向新程序传递环境表相关。以 e 结尾的两个函数excele和exceve可以传递一个指向环境字符串指针数组的指针。其他四个函数则使用调用进程中的environ变量为新程序复制现存的环境。
  六个函数之间的区别:
  

每个系统对参数表和环境表的总长度都有一个限制。当使用shell的文件名扩充功能产生一个文件名表时,可能会收到此值的限制。例如,命令:

grep _POSIX_SOURCE /usr/include/*/*.h

在某些系统上可能产生下列形式的shell错误。

arg list too long

执行exec后进程ID没改变。除此之外,执行新程序的进程还保持了原进程的下列特征:

  • 进程ID和父进程ID。
  • 实际用户ID和实际组ID。
  • 添加组ID。
  • 进程组ID。
  • 对话期ID。
  • 控制终端。
  • 闹钟尚余留的时间。
  • 当前工作目录。
  • 根目录。
  • 文件方式创建屏蔽字。
  • 文件锁。
  • 进程信号屏蔽。
  • 未决信号。
  • 资源限制。
  • tms_utime,tms_stime,tms_cutime以及tms_ustime值。

对打开文件的处理与每个描述符的exec关闭标志值有关。进程中每个打开描述符都有一个exec关闭标志。若此标志设置,则在执行exec时关闭该文件描述符,否则该描述符仍打开。除非特地用fcntl设置了该标志,否则系统的默认操作是在exec后仍保持这种描述符打开。
  POSIX.1明确要求在exec时关闭打开目录流。这通常是由opendir函数实现的,它调用fcntl函数为对应于打开目录流的描述符设置exec关闭标志。
  在exec前后实际用户ID和实际组ID保持不变,而有效ID是否改变则取决于所执行程序的文件的设置-用户-ID位和设置-组-ID位是否设置。如果新程序的设置-用户-ID位已设置,则有效用户ID变成程序文件的所有者的ID,否则有效用户ID不变。对组ID的处理方式与此相同。

在很多UNIX实现中,这六个函数只有一个execve是系统调用。另外5个是库函数

[APUE]进程控制(中)的更多相关文章

  1. [APUE]进程控制(上)

    一.进程标识 进程ID 0是调度进程,常常被称为交换进程(swapper).该进程并不执行任何磁盘上的程序--它是内核的一部分,因此也被称为系统进程.进程ID 1是init进程,在自举(bootstr ...

  2. [APUE] 进程控制

    APUE 一书的第八章学习笔记. 进程标识 大家都知道使用 PID 来标识的. 系统中的一些特殊进程: PID = 0: 调度进程,也称为交换进程 (Swapper) PID = 1: init 进程 ...

  3. [APUE]进程控制(下)

    一.更改用户ID和组ID 可以用setuid设置实际用户ID和有效用户ID.可以用setgid函数设置实际组ID和有效组ID. #include <sys/types.h> #includ ...

  4. (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  5. APUE(8)---进程控制(1)

    一.进程标识 每个进程都有一个非负整型标识的唯一进程ID.因为进程ID标识符总是唯一的,常将其用做其他标识符的一部分以保证其唯一性.进程ID虽然是唯一的, 但是却是可以复用的.ID为0的进程通常是调度 ...

  6. 进程控制(Note for apue and csapp)

    1. Introduction We now turn to the process control provided by the UNIX System. This includes the cr ...

  7. 【APUE | 08】进程控制

    函数fork 博文链接: 1. 代码示例: #include "apue.h" ; char buf[] = "a write to stdout\n"; in ...

  8. apue学习笔记(第八章 进程控制)

    本章介绍UNIX系统的进程控制,包括创建新进程.执行程序和进程终止. 进程标识 每一个进程都有一个非负整数表示的唯一进程ID,除了进程ID,每个进程还有一些其他标识符.下列函数返回这些标识符 #inc ...

  9. 《UNIX环境高级编程》(APUE) 笔记第八章 - 进程控制

    8 - 进程控制 Github 地址 1. 进程标识 每个进程都有一个非负整型表示的 唯一进程 ID .进程 ID 是可复用的(延迟复用算法). ID 为 \(0\) 的进程通常是调度进程,常常被称为 ...

随机推荐

  1. C#泛型回顾点滴

    前言 C#的泛型一直是学习者津津乐道的课题了,这确实是一个非常有用的特性,不过在实际使用中,还是有很多需要注意的地方,我们需要通过自己动手实践以及结合理论进行理解,最终总结出自己的编码规范和最佳实践 ...

  2. 华丽的NHibernate

    华丽的NHibernate http://www.cnblogs.com/kissdodog/archive/2013/02/21/2919886.html 华丽的NHibernate NHibern ...

  3. every、some、filter、map、forEach 方法的区别总结

    API功能描述: [every]:Boolean 遍历数组并执行回调,如果每个数组元素都能通过回调函数的测试则返回true,否则返回false.一旦返回false,将立即终止循环. [some]:Bo ...

  4. ASP.NET MVC应用程序使用异步及存储过程

    ASP.NET MVC应用程序使用异步及存储过程 是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译 ...

  5. Javascript模块化编程之Why

    说到模块化编程,大家比较容易想到Java, C++等语言,感觉这事和Javascript沾不上一丁点边.虽说Javascript看上去好像同Java有莫大的关系,但那也只是一厢情愿,不过是挂羊头卖狗肉 ...

  6. 原生Js 两种方法实现页面关键字高亮显示

    原生Js 两种方法实现页面关键字高亮显示 上网看了看别人写的,不是兼容问题就是代码繁琐,自己琢磨了一下用两种方法都可以实现,各有利弊. 方法一 依靠正则表达式修改 1.获取obj的html2.统一替换 ...

  7. 修改Ubuntu Server的分辨率

    修改Ubuntu Server的分辨率,其实就是找到关键的文件,然后修改参数,重启一下,就可以看见效果了. 1.打开文件并修改参数 文件位置:/etc/default/grub 修改位置:GRUB_C ...

  8. C#使用文件监控对象FileSystemWatcher 实现数据同步

    在C#使用文件监控对象FileSystemWatcher 实现数据同步 2013-12-12 18:24 by 幕三少, 352 阅读, 3 评论, 收藏, 编辑 最近在项目中有这么个需求,就是得去实 ...

  9. [Usaco2008 Mar]Cow Travelling游荡的奶牛[简单DP]

    Description 奶牛们在被划分成N行M列(2 <= N <= 100; 2 <= M <= 100)的草地上游走,试图找到整块草地中最美味的牧草.Farmer John ...

  10. [转]Inside Swift

    原文地址:http://www.eswick.com/2014/06/inside-swift/   Inside Swift Swift is Apple's new programming lan ...