本篇主要介紹一下幾個內容:
?
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》
新聞熱點
疑難解答