国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Unix > 正文

UNIX高級環(huán)境編程(10)進程控制(Process Control)- 競態(tài)條件,exec函數(shù),解釋器文件和system函數(shù)

2024-06-28 13:21:27
字體:
供稿:網(wǎng)友
UNIX高級環(huán)境編程(10)進程控制(PRocess Control)- 競態(tài)條件,exec函數(shù),解釋器文件和system函數(shù)

本篇主要介紹一下幾個內(nèi)容:

  • 競態(tài)條件(race condition)
  • exec系函數(shù)
  • 解釋器文件?

?

1 競態(tài)條件(Race Condition)

競態(tài)條件:當多個進程共同操作一個數(shù)據(jù),并且結(jié)果依賴于各個進程的操作順序時,就會發(fā)生競態(tài)條件。

例如fork函數(shù)執(zhí)行后,如果結(jié)果依賴于父子進程的執(zhí)行順序,則會發(fā)生競態(tài)條件。

說到fork之后的父子進程的執(zhí)行順序,我們可以通過下面的方式指定執(zhí)行順序:

如果父進程等待子進程結(jié)束,則需要調(diào)用wait函數(shù)。

如果子進程等待父進程結(jié)束,則需要像下面這樣輪詢:

while (getppid() != 1)

? ? sleep(1);

輪詢的方式的缺點是非常浪費CPU時間。

?

如果希望避免競態(tài)條件和輪詢,則需要用到進程之間的信號機制,或者其他的ipC方式。

競態(tài)條件的例子:

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

}

輸出結(jié)果:

NewImage

我們可以發(fā)現(xiàn),輸出結(jié)果并不一定,依賴于父子進程的執(zhí)行順序,這里就發(fā)生了競態(tài)條件。

在例子中,我們設(shè)置了stdout得buffer為NULL,為了讓每一個字符的輸出都調(diào)用write,這樣可以盡可能多地發(fā)生進程間切換。

在下面的例子中,我們通過在父子進程間進行通信,來保證父進程先運行。

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

}

執(zhí)行結(jié)果:

NewImage

從結(jié)果可以看到,輸出是符合預期的。

所以進程間通信是解決競態(tài)條件的方式之一。

?

2 exec函數(shù)

fork函數(shù)的一個作用就是,創(chuàng)建出一個子進程,讓子進程執(zhí)行exec函數(shù),去執(zhí)行另一個程序。

exec函數(shù)的作用就是用一個新的程序代替現(xiàn)在的進程,從新程序的main函數(shù)開始執(zhí)行。

替換后,進程號不改變,被替換的內(nèi)容包括文本段,數(shù)據(jù)段,堆和棧。

exec函數(shù)是一組函數(shù),函數(shù)聲明如下:

NewImage

函數(shù)細節(jié):

  • 前四個函數(shù)的參數(shù)pathname為文件路徑,后兩個函數(shù)的參數(shù)filename為文件名,最后一個為文件描述符。如果filename中又’/‘號,則認為是一個文件路徑,否則函數(shù)以環(huán)境變量為前綴對指定的文件進行搜索;
  • 如果execlp和execvp函數(shù)發(fā)現(xiàn)目標文件不是可執(zhí)行文件,則會嘗試把它當做一個腳本調(diào)用/bin/sh去執(zhí)行;
  • fexecve函數(shù)依賴調(diào)用者去保證文件的可執(zhí)行,并且防止惡意用戶在時間差將目標可執(zhí)行文件替換。
  • 函數(shù)名中的l代表list,v代表vector。l系函數(shù)的參數(shù)為命令行中傳入的參數(shù)(在參數(shù)列表中分別由arg0,arg1,arg2...表示),v系函數(shù)則需要將參數(shù)的指針放入一個數(shù)組中,將數(shù)組的地址傳入函數(shù)。
  • 環(huán)境變量列表的傳遞方式。函數(shù)名以e結(jié)尾的函數(shù)允許修改環(huán)境變量列表,函數(shù)的最后一個參數(shù)是一個指向一個指針數(shù)組的指針,數(shù)組中的指針指向環(huán)境變量的各個字符串。

?這7個函數(shù)非常難記,了解函數(shù)名中得特別字母有助于記憶:

  • 字母p代表函數(shù)獲取一個filenam參數(shù)和環(huán)境變量來查找可執(zhí)行文件;
  • 字母l代表函數(shù)獲取一個參數(shù)列表
  • 字母v代表函數(shù)獲取一個argv[]作為參數(shù)
  • 字母e代表函數(shù)獲取一個envp[]作為參數(shù),取代環(huán)境變量列表,用戶可以修改環(huán)境變量然后傳遞給子進程

?exec函數(shù)小結(jié):

NewImage

前面提到過,執(zhí)行了exec函數(shù)后,進程的進程號不變。除了進程號,還有繼承而來的信息包括:

NewImage

exec函數(shù)替換程序之后,對于已經(jīng)打開的文件描述符的處理,取決于flag close-on-exec。如果flag close-on-exec被打開,則exec替換程序后,打開的文件描述符會被關(guān)閉,負責這些文件描述會保持打開狀態(tài),這種保持打開狀態(tài)的行為也是默認行為。

?real user ID和real group ID在exec函數(shù)后保持不變,但是effective user ID和effective group ID可以通過設(shè)置set-user-ID和set-group-ID標志位而決定是否改變。

一般實現(xiàn)時,7個exec函數(shù),只有一個exec函數(shù)會被實現(xiàn)為系統(tǒng)調(diào)用。

7個exec函數(shù)之間的關(guān)系如圖所示:

NewImage

?

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(“/*可執(zhí)行文件所在路徑*//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)

所有現(xiàn)代UNIX系統(tǒng)都支持解釋器文件(interpreter files)。

解釋器文件開始一行的格式為:

#!pathname [optional-argument]

?例如,shell腳本的開始一行為:

?#!/bin/sh?

?要區(qū)分清楚解釋器文件和解釋器:

  • 解釋器文件:第一行以#!pathname XXX開始的文本文件
  • 解釋器:解釋器文件第一行#!pathname xxx中指定的xxx可執(zhí)行文件

?需要注意的一點是:解釋器文件的第一行的長度是有限制的,長度計算包含了空格,’#!’和換行符。

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

}

輸出結(jié)果:

NewImage?

?輸出結(jié)果說明:

  • 程序的作用是輸出命令行中的每一個參數(shù)
  • 需要注意的是,第一個參數(shù)argv[0]是解釋器的據(jù)對路徑
  • 第二個參數(shù)是解釋器文件第一行的可選參數(shù)
  • 第三個參數(shù)是替換程序文件的路徑
  • 需要注意的是,參數(shù)’’testinterp”并沒有被輸出,因為內(nèi)核認為第一個參數(shù)pathname包含更多的內(nèi)容

?

4 system函數(shù)(system Function)

在程序執(zhí)行一個命令字符串是很方便的。

例如:

system(“date > file");

?將日期重定向至file文件中。

函數(shù)聲明:

#include <stdlib.h>

int system(const char* cmdstring);

?函數(shù)細節(jié):

  • 如果cmdstring是一個Null指針,則在system函數(shù)可以正常調(diào)用時返回非零值。這個特性可以用來檢查系統(tǒng)是否支持system函數(shù)。
  • 因為system函數(shù)是基于fork, exec和waitpid實現(xiàn),所以system有三種返回值
    • 如果fork失敗或者waitpid返回錯誤并且不是EINTR,system函數(shù)返回-1;
    • 如果exec失敗,表明shell不能被執(zhí)行,返回值和shell退出返回值(127)相同;
    • 如果fork,exec和waitpid都執(zhí)行成功,并且system返回值是shell的終止狀態(tài)值,該值的形式由waitpid函數(shù)指定。

?system函數(shù)的一種實現(xiàn),沒有處理信號的版本。

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

}

?代碼細節(jié):

  • shell中的-c參數(shù)說明將后面的一個參數(shù)作為命令行輸入,而不是從標準輸入或者指定文件讀??;
  • 我們調(diào)用_exit而不是exit,防止子進程退出時,會將從父進程拷貝到的buffer打印。

使用system函數(shù)的好處是system函數(shù)為我們處理了所以的異常,并且提供了所有必須的信號處理。

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

}

運行結(jié)果:

NewImage

?

?

參考資料:

《Advanced Programming in the UNIX Envinronment 3rd》


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 科技| 于都县| 扶风县| 肃南| 洪泽县| 石渠县| 积石山| 舞阳县| 宜君县| 来凤县| 定兴县| 安国市| 沁阳市| 金塔县| 特克斯县| 通州区| 灵丘县| 连南| 横山县| 荔波县| 龙海市| 准格尔旗| 虞城县| 五家渠市| 榆中县| 新巴尔虎右旗| 景泰县| 葫芦岛市| 清远市| 屏山县| 洛南县| 天峨县| 罗山县| 舒兰市| 博客| 大余县| 兰考县| 龙山县| 炉霍县| 本溪| 邮箱|