分享
 
 
 

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

王朝学院·作者佚名  2016-05-26
窄屏简体版  字體: |||超大  

UNIX高级环境编程(10)进程控制(PRocess Control)- 竞态条件,exec函数,解释器文件和system函数本篇主要介绍一下几个内容:

竞态条件(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》

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有