本篇主要介绍一下几个内容:

  • 竞态条件(race condition)
  • exec系函数
  • 解释器文件 

 

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函数是一组函数,函数声明如下:

函数细节:

  • 前四个函数的参数pathname为文件路径,后两个函数的参数filename为文件名,最后一个为文件描述符。如果filename中又’/‘号,则认为是一个文件路径,否则函数以环境变量为前缀对指定的文件进行搜索;
  • 如果execlp和execvp函数发现目标文件不是可执行文件,则会尝试把它当做一个脚本调用/bin/sh去执行;
  • fexecve函数依赖调用者去保证文件的可执行,并且防止恶意用户在时间差将目标可执行文件替换。
  • 函数名中的l代表list,v代表vector。l系函数的参数为命令行中传入的参数(在参数列表中分别由arg0,arg1,arg2...表示),v系函数则需要将参数的指针放入一个数组中,将数组的地址传入函数。
  • 环境变量列表的传递方式。函数名以e结尾的函数允许修改环境变量列表,函数的最后一个参数是一个指向一个指针数组的指针,数组中的指针指向环境变量的各个字符串。

 这7个函数非常难记,了解函数名中得特别字母有助于记忆:

  • 字母p代表函数获取一个filenam参数和环境变量来查找可执行文件;
  • 字母l代表函数获取一个参数列表
  • 字母v代表函数获取一个argv[]作为参数
  • 字母e代表函数获取一个envp[]作为参数,取代环境变量列表,用户可以修改环境变量然后传递给子进程

 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 

 要区分清楚解释器文件和解释器:

  • 解释器文件:第一行以#!pathname XXX开始的文本文件
  • 解释器:解释器文件第一行#!pathname xxx中指定的xxx可执行文件

 需要注意的一点是:解释器文件的第一行的长度是有限制的,长度计算包含了空格,’#!’和换行符。

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);

}

输出结果:

 

 输出结果说明:

  • 程序的作用是输出命令行中的每一个参数
  • 需要注意的是,第一个参数argv[0]是解释器的据对路径
  • 第二个参数是解释器文件第一行的可选参数
  • 第三个参数是替换程序文件的路径
  • 需要注意的是,参数’’testinterp”并没有被输出,因为内核认为第一个参数pathname包含更多的内容

 

4 system函数(system Function)

在程序执行一个命令字符串是很方便的。

例如:

system(“date > file");

 将日期重定向至file文件中。

函数声明:

#include <stdlib.h>

int system(const char* cmdstring);

 函数细节:

  • 如果cmdstring是一个Null指针,则在system函数可以正常调用时返回非零值。这个特性可以用来检查系统是否支持system函数。
  • 因为system函数是基于fork, exec和waitpid实现,所以system有三种返回值
    • 如果fork失败或者waitpid返回错误并且不是EINTR,system函数返回-1;
    • 如果exec失败,表明shell不能被执行,返回值和shell退出返回值(127)相同;
    • 如果fork,exec和waitpid都执行成功,并且system返回值是shell的终止状态值,该值的形式由waitpid函数指定。

 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);

}

 代码细节:

  • shell中的-c参数说明将后面的一个参数作为命令行输入,而不是从标准输入或者指定文件读取;
  • 我们调用_exit而不是exit,防止子进程退出时,会将从父进程拷贝到的buffer打印。

使用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》

UNIX高级环境编程(10)进程控制(Process Control)- 竞态条件,exec函数,解释器文件和system函数的更多相关文章

  1. UNIX高级环境编程1

    UNIX高级环境编程1 故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详. 首先,了解一下进程的基本概念,进程在内存中布局和内容. 此外,还需要知道运行时是如何为动态数据结构(如链表 ...

  2. Unix高级环境编程

    [07] Unix进程环境==================================1. 进程终止    atexit()函数注册终止处理程序.    exit()或return语句:    ...

  3. UNIX高级环境编程(14)文件IO - O_DIRECT和O_SYNC详解 < 海棠花溪 >

    春天来了,除了工作学习,大家也要注意锻炼身体,多出去运动运动.  上周末在元大都遗址公园海棠花溪拍的海棠花.   进入正题. O_DIRECT和O_SYNC是系统调用open的flag参数.通过指定o ...

  4. UNIX高级环境编程(9)进程控制(Process Control)- fork,vfork,僵尸进程,wait和waitpid

    本章包含内容有: 创建新进程 程序执行(program execution) 进程终止(process termination) 进程的各种ID   1 进程标识符(Process Identifie ...

  5. UNIX高级环境编程(11)进程控制(Process Control)- 进程快照,用户标识符,进程调度

    1 进程快照(Process Accounting) 当一个进程终止时,内核会为该进程保存一些数据,包括命令的小部分二进制数据.CPU time.启动时间.用户Id和组Id.这样的过程称为proces ...

  6. UNIX高级环境编程(8)进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表

    在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境. 本章我们将了解一下的内容: 程序运行时,main函数是如何被调用的: 命令行参数是如何被传入到程序中的: 一个典型的内存布局是怎样的: ...

  7. Unix高级环境编程—进程控制(一)

    一.函数fork #include<unistd.h> pid_t  fork(void)                                                 ...

  8. UNIX高级环境编程(15)进程和内存分配 < 故宫角楼 >

    故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详.   首先,了解一下进程的基本概念,进程在内存中布局和内容. 此外,还需要知道运行时是如何为动态数据结构(如链表和二叉树)分配额外内 ...

  9. UNIX高级环境编程(4)Files And Directories - umask、chmod、文件系统组织结构和链接

    本篇主要介绍文件和文件系统中常用的一些函数,文件系统的组织结构和硬链接.符号链接. 通过对这些知识的了解,可以对Linux文件系统有更为全面的了解.   1 umask函数 之前我们已经了解了每个文件 ...

随机推荐

  1. json.stringify()的妙用,json.stringify()与json.parse()的区别

    一.JSON.stringify()与JSON.parse()的区别 最近做项目,发现JSON.stringify()使用场景真的挺多,我们都知道JSON.stringify()的作用是将 JavaS ...

  2. 基于Hadoop2.6.5(HA)的Hive1.2.1的MySQL方式配置

    1.Hive配置MySQL Hive只是一个工具,无需配置多台机器,我在CentOS7One机器上配置Hive /usr/local/hive/apache-hive-1.2.1-bin/conf c ...

  3. 获取CheckBox的Text值

    有在网上看到一个问题,就是尝试去获取Checkbox的值.技术难度并不高,不过有时间,还是做做练习.创建一个网页: 写click事件,在CheckBox被选取时,才去获取CheckBox的Text的值 ...

  4. SQL SERVER TRANSACTION 事物

    1.事务的概念 事物是一种机制,是一种操作序列,它包含了数据库一组操作命令,这组命令要么全部执行,要么都不执行.因此事物是一组不可分割的事物逻辑单元,在数据库进行并发操作时候,事物是作为最小的控制单元 ...

  5. C#常见几道面试题

    首先碰到的是这样的一首题目:计算数组{1,1,2,3,5,8.......} 第30位值,不用递归,我写出了以下这样的代码: static void Main(string[] args) { ]; ...

  6. 检测SQLServer复制订阅进度

    --检测复制订阅进度 --SQLSERVER2012 --创建链接服务器ReadServer,WriteServer,它们分别链接发布服务器.订阅服务器 --修改数据库名称 --在分发服务器执行 US ...

  7. 设计模式之观察者模式(Observer)(4)

    简介 观察者模式(Observer)完美的将观察者和被观察的对象分离开.举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上.面向对象设 ...

  8. python正则表达式3-模式匹配

    re.S,使 '.'  匹配换行在内的所有字符 >>> pattern=r'ghostwu.com' >>> import re >>> re.f ...

  9. 自定义MVC框架之工具类-分页类的封装

    以前写过一个MVC框架,封装的有点low,经过一段时间的沉淀,打算重新改造下,之前这篇文章封装过一个验证码类. 这次重新改造MVC有几个很大的收获 >全部代码都是用Ubuntu+Vim编写,以前 ...

  10. SPOJ7001(SummerTrainingDay04-N 莫比乌斯反演)

    Visible Lattice Points Consider a N*N*N lattice. One corner is at (0,0,0) and the opposite one is at ...