本篇主要介绍一下几个内容:
?
1 竞态条件(Race Condition)竞态条件:当多个进程共同操作一个数据,并且结果依赖于各个进程的操作顺序时,就会发生竞态条件。
例如fork函数执行后,如果结果依赖于父子进程的执行顺序,则会发生竞态条件。
说到fork之后的父子进程的执行顺序,我们可以通过下面的方式指定执行顺序:
如果父进程等待子进程结束,则需要调用wait函数。
如果子进程等待父进程结束,则需要像下面这样轮询:
while (getppid() != 1)
? ? sleep(1);
轮询的方式的缺点是非常浪费CPU时间。
?
如果希望避免竞态条件和轮询,则需要用到进程之间的信号机制,或者其他的ipC方式。
竞态条件的例子:
Example:
#include "apue.h"
?
static void charatatime(char *);
?
int
main(void)
{
? ? pid_t ? pid;
?
? ? if ((pid = fork()) < 0) {
? ? ? ? err_sys("fork error");
? ? } else if (pid == 0) {
? ? ? ? charatatime("output from child/n");
? ? } else {
? ? ? ? charatatime("output from parent/n");
? ? }
? ? exit(0);
}
?
staticvoid
charatatime(char *str)
{
? ? char? ? *ptr;
? ??int ? ? c;
?
? ? setbuf(stdout, NULL); ? ? ? ? ? /* set unbuffered */
? ? for (ptr = str; (c = *ptr++) != 0; )
? ? ? ? putc(c, stdout);
}
输出结果:
我们可以发现,输出结果并不一定,依赖于父子进程的执行顺序,这里就发生了竞态条件。
在例子中,我们设置了stdout得buffer为NULL,为了让每一个字符的输出都调用write,这样可以尽可能多地发生进程间切换。
在下面的例子中,我们通过在父子进程间进行通信,来保证父进程先运行。
Example:
#include "apue.h"
?
static void charatatime(char *);
?
int
main(void)
{
? ? pid_t ? pid;
?
? ? TELL_WAIT();
?
? ? if ((pid = fork()) < 0) {
? ? ? ? err_sys("fork error");
? ? } else if (pid == 0) {
? ? ? ? WAIT_PARENT();? ? ? /* parent goes first */
? ? ? ? charatatime("output from child/n");
? ? } else {
? ? ? ? charatatime("output from parent/n");
? ? ? ? TELL_CHILD(pid);
? ? }
? ? exit(0);
}
static void
charatatime(char *str)
{
? ? char? ? *ptr;
? ? int ? ? c;
?
? ? setbuf(stdout, NULL); ? ? ? ? ? /* set unbuffered */
? ? for (ptr = str; (c = *ptr++) != 0; )
? ? ? ? putc(c, stdout);
}
执行结果:
从结果可以看到,输出是符合预期的。
所以进程间通信是解决竞态条件的方式之一。
?
2 exec函数fork函数的一个作用就是,创建出一个子进程,让子进程执行exec函数,去执行另一个程序。
exec函数的作用就是用一个新的程序代替现在的进程,从新程序的main函数开始执行。
替换后,进程号不改变,被替换的内容包括文本段,数据段,堆和栈。
exec函数是一组函数,函数声明如下:
函数细节:
?这7个函数非常难记,了解函数名中得特别字母有助于记忆:
?exec函数小结:
前面提到过,执行了exec函数后,进程的进程号不变。除了进程号,还有继承而来的信息包括:
exec函数替换程序之后,对于已经打开的文件描述符的处理,取决于flag close-on-exec。如果flag close-on-exec被打开,则exec替换程序后,打开的文件描述符会被关闭,负责这些文件描述会保持打开状态,这种保持打开状态的行为也是默认行为。
?real user ID和real group ID在exec函数后保持不变,但是effective user ID和effective group ID可以通过设置set-user-ID和set-group-ID标志位而决定是否改变。
一般实现时,7个exec函数,只有一个exec函数会被实现为系统调用。
7个exec函数之间的关系如图所示:
?
Example:#include "apue.h"
#include <sys/wait.h>
?
char? ? *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };
?
int
main(void)
{
? ? pid_t ? pid;
?
? ? if ((pid = fork()) < 0) {
? ? ? ? err_sys("fork error");
? ? } elseif (pid == 0) {? /* specify pathname, specify environment */
? ? ? ? if (execle(“/*可执行文件所在路径*//echoall", "echoall", "myarg1",
? ? ? ? ? ? ? ? "MY ARG2", (char *)0, env_init) < 0)
? ? ? ? ? ? err_sys("execle error");
? ? }
?
? ? if (waitpid(pid, NULL, 0) < 0)
? ? ? ? err_sys("wait error");
?
? ? if ((pid = fork()) < 0) {
? ? ? ??err_sys("fork error");
? ? } elseif (pid == 0) {? /* specify filename, inherit environment */
? ? ? ? if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
? ? ? ? ? ? err_sys("execlp error");
? ? }
?
? ? exit(0);
}
?
3 解释器文件(Interpreter Files)所有现代UNIX系统都支持解释器文件(interpreter files)。
解释器文件开始一行的格式为:
#!pathname [optional-argument]
?例如,shell脚本的开始一行为:
?#!/bin/sh?
?要区分清楚解释器文件和解释器:
?需要注意的一点是:解释器文件的第一行的长度是有限制的,长度计算包含了空格,’#!’和换行符。
Example:#include "apue.h"
#include <sys/wait.h>
?
int
main(void)
{
? ? pid_t ? pid;
?
? ? if ((pid = fork()) < 0) {
? ? ? ? err_sys("fork error");
? ? } else if (pid == 0) {? ? ? ? ? /* child */
? ? ? ? if (execl("/home/sar/bin/testinterp",
? ? ? ? ? ? ? ? ? “testinterp", "myarg1", "MY ARG2", (char *)0) < 0)
? ? ? ? ? ? err_sys("execl error");
? ? }
? ? if (waitpid(pid, NULL, 0) < 0)? /* parent */
? ? ? ? err_sys("waitpid error");
? ? exit(0);
}
输出结果:
?
?输出结果说明:
?
4 system函数(system Function)在程序执行一个命令字符串是很方便的。
例如:
system(“date > file");
?将日期重定向至file文件中。
函数声明:
#include <stdlib.h>
int system(const char* cmdstring);
?函数细节:
?system函数的一种实现,没有处理信号的版本。
code#include? ? <sys/wait.h>
#include? ? <errno.h>
#include? ? <unistd.h>
?
int
system(constchar *cmdstring) ? /* version without signal handling */
{
? ? pid_t ? pid;
? ? int ? ? status;
?
? ? if (cmdstring == NULL)
? ? ? ? return(1);? ? ? /* always a command processor with UNIX */
?
? ? if ((pid = fork()) < 0) {
? ? ? ? status = -1;? ? /* probably out of processes */
? ? } else if (pid == 0) {? ? ? ? ? ? ? /* child */
? ? ? ? execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
? ? ? ? _exit(127); ? ? /* execl error */
? ? } else {? ? ? ? ? ? ? ? ? ? ? ? ? ? /* parent */
? ? ? ? while (waitpid(pid, &status, 0) < 0) {
? ? ? ? ? ? if (errno != EINTR) {
? ? ? ? ? ? ? ? status = -1; /* error other than EINTR from waitpid() */
? ? ? ? ? ? ? ? break;
? ? ? ? ? ??}
? ? ? ? }
? ? }
?
? ? return(status);
}
?代码细节:
使用system函数的好处是system函数为我们处理了所以的异常,并且提供了所有必须的信号处理。
Example#include "apue.h"
#include <sys/wait.h>
?
int
main(void)
{
? ? int ? ? status;
?
? ? if ((status = system("date")) < 0)
? ? ? ? err_sys("system() error");
?
? ? pr_exit(status);
?
? ? if ((status = system("nosuchcommand")) < 0)
? ? ? ? err_sys("system() error");
?
? ? pr_exit(status);
?
? ? if ((status = system("who; exit 44")) < 0)
? ? ? ? err_sys("system() error");
?
? ? pr_exit(status);
?
? ? exit(0);
}
运行结果:
?
?
参考资料:
《Advanced Programming in the UNIX Envinronment 3rd》
新闻热点
疑难解答