1. 程式人生 > >UNIX環境高階程式設計——第八章—程序控制

UNIX環境高階程式設計——第八章—程序控制

8.2 程序標識

1、每個程序都有一個非負整型表示唯一程序ID。

2、程序ID是可複用的。當一個程序終止後,其程序ID就複用的候選者。

3、程序ID為0的程序通常是排程程序,常常被稱為交換程序(swapper)。該程序是核心的一部分,它並執行任何磁碟上的程式。

4、程序ID1通常是init程序,在自舉過程結束時由核心呼叫。此程序負責在自舉核心後啟動一個UNIX系統。病將系統引導到一個狀態(如多使用者)。init程序決不會終止。它是一個普通使用者程序(與交換程序不同,它不是核心中的系統程序),但是它以超級使用者特權執行。

5、程序ID2是頁守護程序(page daemon),此程序負責支援虛擬儲存器系統的分頁操作。

6、除了程序ID,每個程序還有一些其他識別符號。下列函式返回這些識別符號。

#include <unistd.h>
pid_t getpid(void);
                                            返回值:呼叫程序的程序ID。
pid_t getppid(void);
                                            返回值:呼叫程序的父程序ID。
uid_t getuid(void); 
                                            返回值:呼叫程序的實際使用者ID。
uid_t geteuid(void
); 返回值:呼叫程序的有效使用者ID。 gid_t getgid(void); 返回值:呼叫程序的實際組ID。 gid_t getegid(void); 返回值:呼叫程序的有效組ID。

8.3 函式fork

1、一個現有的程序可以呼叫fork函式建立一個新程序。

#include <unistd.h>
pid_t fork(void); 返回值:子程序返回0,父程序返回子程序ID;若出錯,返回-1

(1)由fork建立的新程序被稱為子程序(child process)。

(2)fork函式被呼叫一次,但返回兩次,兩次返回的區別是子程序的返回值是0,而父程序的返回值則是建立子程序的程序ID。

(3)將子程序ID返回給父程序的理由是:一個程序的子程序可以由多個,並沒有一個函式使一個程序可以獲得其所有子程序的程序ID。

(4)fork使子程序得到返回值0的理由是:一個程序只會有一個父程序,所以子程序總是可以呼叫getppid以獲取其父程序的程序ID(程序ID0總是由核心交換程序使用,所以一個子程序的程序ID不可能為0)。

2、子程序是父程序的副本。子程序獲得父程序資料空間、堆和棧的副本。父程序和子程序並不共享儲存空間部分。父程序和子程序共享正文段。

3、由於fork之後經常跟隨exec,所以現在的很多實現並不執行一個父程序資料段、棧和堆的完全副本。作為替代,使用了寫時複製(Copy-On-Write,COW)技術,這些區域由父程序和子程序共享,而且核心將它們的訪問許可權改變為只讀。如果父程序和子程序中的任一個試圖修改這些區域,則核心只為修改區域的那塊記憶體製作一個副本,通常是虛擬儲存系統中的一”頁“。

4、演示fork函式,從中可以看到子程序對變數所做的改變並不能影響父程序中該變數的值。

/*************************************************************************
    > File Name: ins8_1.c
    > Author: Elliot
 ************************************************************************/

/*
 * 程式演示了fork函式,從中可以看到子程序對變數所做的改變並不影響父程序中該變數的值。
 */

#include "apue.h"

int     globvar = 6;            /*  external variable in initialized data  */
char    buf[] = "a write to stdout\n";

int
main(void)
{
    int     var;                /*  automatic variable on the stack  */
    pid_t   pid;

    var = 88;
    if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
        err_sys("write error");

    printf("before fork\n");            /*  we don't flush stdout  */

    if ((pid = fork()) < 0)
        err_sys("fork error");
    else if (pid == 0)                  /*  child */
    {
        globvar++;                      /*  modify variables  */
        var++;
    }
    else
        sleep(2);                       /*  parent  */

    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
    exit (0);
}

5、一般來說,在fork之後是父程序先執行還是子程序先執行是不確定的。這取決於核心所使用的排程演算法。如果要求父程序和子程序之間互相同步,則要求某種形式的程序通訊。

6、在重定向父程序的標準輸出時,子程序的標準輸出也被重定向。

7、實際上,fork的一個特性是父程序的所有開啟檔案描述符都被複制到子程序中。就像執行dup函式一樣。

8、父程序和子程序共享同一個檔案偏移量。如果父程序和子程序寫同一描述符指向的檔案,但又沒有任何形式的同步(如果父程序等待子程序),那麼它們的輸出就會互相混合。

9、處理開啟檔案之外,父程序的很多其他屬性也由子程序繼承,包括:

1)實際使用者ID、實際組ID、有效使用者ID、有效組ID。

(2)附屬組ID。

(3)程序組ID。

(4)會話ID。

(5)控制終端。 

(6)設定使用者ID標誌和設定組ID標誌。

(7)當前工作目錄

(8)根目錄

(9)檔案模式建立遮蔽字

(10)訊號遮蔽和安排。

(11)對任一開啟檔案描述符的執行關閉標誌(close-on-exec)標誌。12)環境

(13)連線的共享儲存段

(14)儲存映像。

(15)資源限制。

10、父程序與子程序之間的區別具體如下:

1fork的返回值不同
(2)這兩個程序的父程序ID不同。
(3)子程序的 tms_utime、tms_stime、tms_cutime和 tms_ustime 的值設定為0.
(4)子程序不繼承父程序設定的檔案鎖。
(5)子程序的未處理鬧鐘被清除。
(6)子程序的未處理訊號集設定為空集。

11、fork失敗的兩個主要原因是:
(1)系統已經有了太多的程序
(2)該實際使用者ID的程序總數超過了系統限制。

12、fork有以下兩種用法:
(1)一個父程序希望複製自己,使父程序和子程序同事執行不同的程式碼段。

(2)一個程序要執行一個不同的程式,子程序從fork返回後立即呼叫exec。

8.4 函式vfork

1、vfork函式的呼叫序列和返回值與fork相同,但兩者的語義不同。

#include <unistd.h>
pid_t vfork(void);
                                            返回值:子程序返回0,父程序返回子程序ID;若出錯,返回-1

(1)vfork函式用於建立一個新程序,而該新程序的目的是exec一個新程式。

(2)vfork和fork一樣會建立一個子程序,但是它並不將父程序的地址空間完全複製到子程序中,因為子程序會立即呼叫exec(或exit),於是也就不會引用該地址空間。

(3)vfork在子程序呼叫exec或exit之前,它在父程序的空間中執行。

(4)如果子程序修改資料(除了用於存放vfork返回值的變數)。進行函式呼叫、或者沒有呼叫exec或exit就返回都可能會帶來未知的結果。

(5)vfork在呼叫exec或exit之後父程序才可能被排程執行,當子程序呼叫這兩個函式中的任一一個時,父程序會恢復執行。(如果在呼叫這兩個函式之前子程序依賴於父程序的進一步動作,則會導致死鎖)

2、例項:

/*************************************************************************
    > File Name: ins8_3.c
    > Author: Elliot
 ************************************************************************/

#include "apue.h"
int     globvar = 6;                /*  external variable in initialized data  */

int
main(void)
{
    int     var;                    /*  automatic variable on the stack  */
    pid_t   pid;

    var = 88;

    printf("before vfork\n");           /*  we don't flush stdio  */
    if ((pid = vfork()) < 0)
        err_sys("vfork error");
    else if(pid == 0)
    {
        globvar++;                      /*  child  */
        var++;                          /*  modify parent's variables  */
        _exit(0);
    }

    /*  parent continus here  */
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
            var);
    exit(0);

}

8.5 exit函式

1、程序有5種正常終止及3種異常終止方式。
(1)在main函式內執行return語句。這等效於呼叫exit。

(2)呼叫exit函式,其操作包括呼叫個終止處理程式(終止處理程式在呼叫atexit函式時登記),然後關閉所有標準I/O流等。

(3)呼叫 _exit 或 _Exit 函式。其目的是為程序提供了一種無需執行終止處理程式或訊號處理程式而終止的方法。對標準 I/O 流 是否進行重啟,這取決於實現。在UNIX系統中,_Exit 和 _exit 是同義的,並不沖洗標準I/O流。

(4)程序最後一個執行緒在其啟動例程中執行return語句。但該執行緒的返回值不用作程序的返回值。當最後一個執行緒從其啟動例程返回時,該程序以終止狀態0返回。

(5)程序的最後一個執行緒呼叫 pthread_exit函式。程序的終止狀態總是0,與傳遞給 pthread_exit 的引數無關。
三種異常終止具體如下:
(6)呼叫abort。它產生SIGABRT訊號。

(7)當程序接收到某些訊號時。訊號可由程序自身(如呼叫abort函式)、其他程序或核心產生。

(8)最後一個執行緒對“取消”(cancellation)請求作出相應。

2、不管程序如何終止,最後都會執行核心中的同一段程式碼。這段程式碼為相應程序關閉所有開啟描述符,釋放它所使用的儲存器等。

3、如果一個父程序在子程序之前終止,對於父程序已經終止的所有程序,它們的父程序都改變為init程序。我們稱這些程序由init程序收養。

4、init收養孤兒程序的操作大致是“在一個程序終止時,核心逐個檢查所有活動程序,以判斷它是否是正要終止程序的子程序,如果是,則該程序的父程序ID就更改為1(init程序的ID)。這種處理方法保證了每個程序都有一個父程序。

5、核心為每個終止程序儲存了一定量的資訊,所以當終止程序的父程序呼叫wait或waitpid時,可以得到這些資訊。這些資訊至少包括
(1)程序ID
(2)該程序的終止狀態
(3)該程序使用的CPU時間總量

6、一個已經終止、但是其父程序尚未對其進行善後處理(獲取終止進子程序的有關資訊、釋放它扔佔用的資源)的程序被稱為僵死程序(zombie)

8.6 函式wait 和 waitpid

1、當一個程序正常或異常終止時,核心就向其父程序傳送SIGCHLD訊號。因為子程序終止是一個非同步事件(這可以在父程序執行的任何時候發生),所以這種訊號也是核心向父程序發的非同步通知。

2、呼叫wait或waitpid的程序可能會發生什麼?
(1)如果其所有子程序都還在執行,則阻塞。

(2)如果一個子程序已終止,正等待父程序獲取其終止狀態,則取得該子程序的終止狀態立即返回。

(3)如果它沒有任何子程序,則立即出錯返回。

3、如果程序由於接收到SIGCHLD訊號而呼叫wait,我們期望wait會立即返回。如果在隨機時間點呼叫wait,則程序可能會阻塞。

4、wait函式和waitpid函式

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
                                        兩個函式返回值:若成功,返回程序ID;若出錯,返回0

(1)在一個程序終止前,wait使其呼叫者阻塞,waitpid有一選項,可使呼叫者不阻塞。

(2)waitpid並不等待在其呼叫之後的第一個終止子程序,它由若干個選項,可以控制它所等待的程序。

(3)這兩個函式的引數statloc如果不是一個空指標,則終止程序的終止狀態就存放在它所指向的單元內。
若不關心終止狀態,則可將引數指定為空指標。

(4)waitpid函式中pid引數的作用如下:

pid == -1                           等待任一程序。此種情況下,waitpidwait等效。
pid > 0                             等待程序ID與pid相等的子程序。
pid == 0                            等待組ID等於呼叫程序組ID的任一子程序。
pid < -1                            等待組ID等於pid絕對值的任一子程序。

(5)waitpid函式中的options常量

WCONTINUED                         實現支援作業控制,那麼pid指定的任一子程序在停止後已經繼續,但其狀                           
                                態尚未報告,則返回其狀態。

WNOHANG                            若由pid指定的子程序並不是立即可用的,則waitpid不阻塞,此時其返回值為0。

WUNTRACED                          若某實現支援作用控制,而由pid指定的任一子程序已處於停止狀態,
                                並且其狀態子停止以來還未報告過,則返回其狀態。WIFSTOPPED巨集確定返
                                回值是否對應於一個停止的程序。

5、有4個互斥的巨集取得程序終止的原因:
這裡寫圖片描述

6、例項:函式pr_exit使用上圖的巨集列印程序終止狀態的說明。

#include "apue.h"
#include <sys/wait.h>
void
pr_exit(int status)
{
    if (WIFEXITED(status))
        printf("nomal termination, exit status %d\n",
                WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("abnormal termination, signal number = %d%s\n",
                WTERMSIG(status),
#ifdef  WCOREDUMP
    WCOREDUMP(status) ? " (core file generated) " : "");
#else
        "");
#endif
    else if (WIFSTOPPED(status))
        printf("child stopped, signal number = %d\n",
                WSTOPSIG(status));
}

演示不同的exit值。

/*************************************************************************
    > File Name: ins8_6.c
    > Author: Elliot
 ************************************************************************/
#include "apue.h"
#include <sys/wait.h>


void
pr_exit(int status);


int
main(void)
{
    pid_t   pid;
    int     status;

    if ((pid = fork()) < 0)
        err_sys("fork error");
    else if (pid == 0)              /*  wait for child  */
        exit(7);

    if (wait(&status) != pid)       /*  wait child  */
        err_sys("wait error");
    pr_exit(status);                /*  and print its status  */

    if ((pid = fork()) < 0)         
        err_sys("fork error");
    else if (pid == 0)              /*  child  */
        abort();                    /*  generates SIGABRT  */

    if (wait(&status) != pid)       /*  wait child  */
        err_sys("wait error");
    pr_exit(status);                /*  and print its status  */

    if ((pid = fork()) < 0)
        err_sys("fork error");      
    else if (pid == 0)              /*  child  */
        status /= 0;                /*  divide by 0 generates SIGFPE  */

    if (wait(&status) != pid)       /*  wait for child  */
        err_sys("wait error");
    pr_exit(status);                /*  and print its status  */
    exit(0);
}

7、waitpid函式提供了wait函式沒有提供的3個功能:
(1)waitpid可等待一個特定的程序,而wait則返回任一終止子程序的狀態。

(2)waitpid提供了一個wait的非阻塞版本。

(3)waitpid通過 WUNTRACED 和 WCONTINUED 項支援作業控制。

8、如果一個程序fork一個子程序,但不要它等待子程序終止,也不希望子程序處於僵死狀態直到父程序終止。實現這一要求的訣竅是呼叫fork兩次。

/*************************************************************************
    > File Name: ins8_6_2.c
    > Author: Elliot
 ************************************************************************/

/*
 * 如果一個程序fork一個子程序,但不要它等待子程序終止,也不希望程序
 * 處於僵死狀態直到父程序終止,實現這一要求的訣竅是呼叫fork兩次。
 */

#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)                  /*  first child  */
    {
        if ((pid = fork()) < 0)             
            err_sys("fork error");
        else if (pid > 0)
            exit(0);                        /*  parent from second fork == first child  */
        /*
        * We're the second child; our parent become init as soon
        * as our real parent calls exit() in the statement above.
        * Here's where we'd continue executing, knowing that when
        * We're done, init will reap our status.
        */
        sleep(2);
        printf("second child, parent pid = %ld\n", (long)getppid());
        exit(0);
    }
    if (waitpid(pid, NULL, 0) != pid)       /* wait for first child and don't care its ending state  */
        err_sys("waitpid error");

    /*
     * We're the parent (the original process); we continue executing,
     * knowing that we're not the parent of the second child.
     */
    exit(0);
}

8.7 函式waitid

1、函式waitid類似於waitpid,但更靈活。

#include <sys/wait.h>
int waitdid(idtype_t idtype, siginfo_t *infop, int options);
                                            返回值:若成功,返回0;若出錯,返回-1

(1)waitid允許一個程序指定要等待的程序,但它使用兩個單獨引數表示要等待的子程序所屬的型別。

(2)id引數作用與idtype的值相關:

P_PID                               等待一特定程序,id包含要等待子程序的程序ID。
P_PGID                              等待一特定程序組中的任一子程序,id包含要等待子程序的程序組ID
P_ALL                               等待任一程序,忽略id

(3)options 引數指示呼叫者關注哪些狀態變化。

WCONTINUED                          等待一程序,它以前曾被停止,此後又已繼續,但其狀態尚未報告。
WEXITED                             等待已退出的程序。
WNOHANG                             如無可用的程序退出狀態,立即返回而非阻塞。
WNOWAIT                             不破壞子程序的退出狀態。該子程序退出狀態可由後續的wait、
                                waitid或waitpid呼叫取得
WSTOPPED                            等待一程序,它已經停止,但其狀態尚未報告。

(4)infop引數是指向signof結構的指標。該結構包含了造成子程序狀態改變有關訊號的詳細資訊。

8.8 函式wait3 和 wait4

1、函式wait3 和 wait4 添加了資源引數:

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
                                        兩個函式返回值:若成功,返回程序ID;若出錯,返回-1

(1)資源統計資訊包括使用者CPU時間總量、系統CPU時間總量、缺頁次數、接收到訊號的次數等。

8.9 競爭條件

1、當多個程序都企圖對共享資料進行某種處理,而最後的結果又取決於程序執行的順序時,我們認為發生了競爭條件(race condition)。

2、一個由子程序輸出,另一個由父程序輸出。因為輸出依賴於核心使這兩個程序執行的順序及每個程序執行的時間長度,所以程式包含了一個競爭條件。

/*************************************************************************
    > File Name: ins8_9.c
    > Author: Elliot
 ************************************************************************/

/*
 * 程式輸出兩個字串:一個由子程序輸出,另一個由父程序輸出。
 * 因為輸出依賴於核心使這兩個程序執行的順序及每個程序執行的時間,
 * 所以該程式包含了一個競爭條件。
 */

#include "apue.h"

static void charactatime(char *);

int
main(void)
{
    pid_t   pid;
    if ((pid = fork()) < 0)
        err_sys("fork error");
    else if (pid == 0)
        charactatime("output from child\n");
    else
        charactatime("output from parent\n");

    exit(0);
}

static void
charactatime(char *str)
{
    char    *ptr;
    int     c;

    setbuf(stdout, NULL);               /*  set unbuffered  */

    for(ptr = str; (c = *ptr++) != 0;)
        putc(c, stdout);
}

8.10 函式exec

1、當程序呼叫一種exec函式時,該程序的程式完全替換為新程式,而新程式則從main函式開始執行。

2、因為呼叫exec並不建立新程序,所以前後的程序ID並未改變。exec只是用磁碟上的一個新程式替換了當前程序的正文段、資料段、堆段和棧段。

3、有7種不同的exec函式可供使用:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
                       char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
                                           7個函式返回值:若出錯,返回-1;若成功,不返回。

(1)函式之間的區別,有4個函式取路徑名為引數,有兩個函式則取檔名作為引數,最後一個取檔案描述符作為引數。

(2)當指定filename作為引數時,如果filename中包含/,則就將其視為路徑名。否則就按PATH環境變數,在它所指定的各目錄中搜尋可執行檔案。

(3)如果execlp或execvp使用路徑名字首中的一個找到了可執行檔案,但是該檔案不是由連結編輯器產生的機器可執行檔案,則就認為該檔案是一個shell指令碼,於是試著呼叫/bin/sh並以該filename作為shell的輸入。

(4)fexecve 函式避免了尋找正確的可執行檔案,而是以來呼叫程序來完成這項工作。呼叫程序可以使用檔案描述符驗證所需要的檔案並且無競爭地執行該檔案。

(5)第二個卻別與引數表的傳遞有關(l表示列表list,v表示向量)。函式execl、execlp和execle要求將新程式的每個命令列引數都說明為一個單獨的引數。這種引數表以空指標結尾。
對於另外4個函式(execv、execvp、execve和execlp)三個函式,則應先構造一個指向各引數的指標陣列,然後將該陣列的地址作為這4個函式的引數。

(6)最後一個卻別與向新程式傳遞環境表相關。以e結尾的3個函式(execle、execve和fexecve可以傳遞一個指向環境字串指標陣列的指標)。其他四個函式則使用呼叫程序中的environ變數為新程式複製現有的環境。

4、字母p表示該函式取 filename 作為引數,並且PATH環境變數尋找可執行檔案。字母l表示該函式取一個引數表,它與字母v互斥。v表示該函式取一個argv[]向量。最後,字母e表示該函式取 envp[]陣列,而不適用當前環境。

5、在執行exec後,程序ID沒有改變,但新程式從呼叫程序集成了下列屬性:

(1)程序ID和父程序ID

(2)實際使用者ID、實際組ID

(3)附屬組ID。

(4)程序組ID。

(5)會話ID。

(7)控制終端。 

(8)鬧鐘尚餘留的時間

(9)當前工作目錄

(10)根目錄

(11)檔案模式建立遮蔽字

(12)檔案鎖

(13)程序訊號遮蔽

(14)未處理訊號

(14)資源限制

(15)nice值

(16)tms_utime、tms_stime、tms_cutime以及tms_cstime值

6、對開啟檔案的處理與每個檔案的執行時關閉(close-on-exec)標誌值有關,程序中每個開啟描述符都有一個執行時關閉標誌。若設定了此標誌,則在執行exec時關閉該描述符。否則,該描述符仍開啟。
除非特地用fcntl清除該執行時關閉標誌,否則系統的預設操作是在exec後仍保持這種描述符開啟。

7、exec時關閉開啟目錄流。這通常是由opendir函式實現的,它呼叫fcntl函式為對應於開啟目錄的描述符設定執行時關閉標誌。

8、exec前後實際使用者ID和實際組ID保持不變,而有效使用者ID是否改變取決於所執行程式檔案的設定使用者ID和設定組ID位是否設定。

9、該7個函式中只有execve是核心的系統呼叫。這7個函式的關係如下圖所示:
這裡寫圖片描述

10、exec函式的使用:

/*************************************************************************
    > File Name: ins8_10.c
    > Author: Elliot
 ************************************************************************/

#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");
    else if (pid == 0)              /*  specify pathname, specify environment  */
        if (execle("/home/king/bin/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("wait error");
    else if (pid == 0)              /*  specify filename, inherit environment  */
        if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
            err_sys("execlp error");

    exit(0);

}
/*************************************************************************
    > File Name: echoall.c
    > Author: Elliot
 ************************************************************************/

#include "apue.h"

int
main(int argc, char *argv[])
{
    int     i;
    char    **ptr;
    extern char     **environ;

    for (i = 0; i < argc; i++)          /*  echo all command-line args  */
        printf("argv[%d]: %s\n", i, argv[i]);

    for (ptr = environ; *ptr != 0; ptr++)       /*  and all env strings  */
        printf("%s\n", *ptr);

    exit(0);
}

8.11 更改使用者ID和更改組ID

1、可以使用setuid函式設定實際使用者ID和有效使用者ID。可以用setgid函式設定實際組ID和有效組ID。

#include <unistd.h>
int setiod(uid_t uid);
int setgid(gid_t gid);
                                        兩個函式返回值:若成功,返回0;若出錯,返回-1

(1)若程序具有超級使用者特權,則setuid函式將實際使用者ID、有效使用者ID以及儲存的設定使用者ID(save set-user-ID)設定為uid。

(2)若程序具有超級使用者特權,但是uid等於實際使用者ID或儲存的設定使用者ID,則setuid只將有效使用者ID設定為uid。不更改實際使用者ID和儲存的設定使用者ID。

(3)如果上面兩個條件都不滿足,則errno設定為EPERM,並返回-1。

2、核心所維護的3個使用者ID,注意以下幾點:
(1)只有超級使用者程序可以更改實際使用者ID

(2)僅當程式檔案設定了設定使用者ID位時,exec函式才設定有效使用者ID。如果設定使用者ID位沒有設定,exec函式不會改變有效使用者ID位,而將維持其現有值。

(3)儲存的設定使用者ID是由exec複製有效使用者ID而得到的。如果設定了檔案使用者ID位,則在exec根據檔案的使用者ID設定了程序的有效使用者ID以後,這個副本就被儲存起來了。

這裡寫圖片描述

3、函式setreuid和setregid

#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

(1)一個非特權使用者總能交換實際使用者ID和有效使用者ID,這就允許了一個設定使用者ID程式交換成使用者的普通許可權,以後又可再次交換會設定使用者ID許可權。它允許一個非特權使用者將其有效使用者ID設定為儲存的設定使用者ID

4、函式seteuid和setegid,只更改有效使用者ID和有效組ID:

#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
                                        兩個函式返回值:若成功,返回0;若出錯,返回-1

(1)一個非特權使用者可將其有效使用者ID設定為其實際使用者ID或其儲存設定使用者ID對於特權使用者則可將有效使用者ID設定為uid

8.12 直譯器檔案

1、UNIX系統都支援直譯器檔案(interpreter file)。這種檔案是文字檔案,其起始行的形式是:

#! pathname [optional-argument]

(1)pathname通常是絕對路徑名,對它不進行什麼特殊的處理(不適用PATH進行路徑搜尋),對這種檔案的識別是由核心作為exec系統呼叫處理的一部分來完成的。

2、例項:執行一個直譯器檔案:

/*************************************************************************
    > File Name: ins8_12.c
    > Author: Elliot
 ************************************************************************/

#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)
        if (execl("/home/king/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);
}

8.13 函式system

1、函式system:

#include <stdlib.h>
int system(const char *cmdstring);

(1)cmdstring是一個空指標,則僅當命令處理程式可用時,system返回非0值。

(2)因為system在實現中呼叫了fork、exec和waitpid,因此有3種返回值。

(3)fork失敗或者waitpid返回除EINTR之外的出錯,則system返回-1,並且設定errno以指示錯誤型別。

(4)如果exec失敗(表示不能執行shell),則其返回值如同shell執行了exit(127)。

(5)否則所有3個函式(fork、exec和waitpid)都成功,那麼system的返回值是shell的終止狀態。

2、system函式(沒有對訊號進行處理)例項:

int
system(const char *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)
    {
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);                     /*  execl error  */
    }
    else
    {
        while (waitpid(pid, &status, 0) < 0)
            if (errno != EINTR)
            {
                status = -1;                /*  error other than EINTR from waitpid()  */
                break;
            }
    }
    return(status);

}
/*************************************************************************
    > File Name: ins8_13.c
    > Author: Elliot
 ************************************************************************/

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

3、設定使用者ID或設定組ID程式決不應呼叫system函式。

8.14 程序會計

1、大多數UNIX系統提供了一個選項以進行程序會計(process accounting)處理。啟動該選項後,每當程序結束時核心就寫一個會計記錄。典型的會計記錄包含總量較小的二進位制資料,一般包括命令名、所使用的CPU時間總量、使用者ID和組ID、啟動時間等。

2、我們不能獲取永遠不終止的程序的會計記錄。像init這樣的程序在系統生命週期一直在執行,並不會產生會計記錄。核心守護程序它們通常不會終止。

3、會計檔案中記錄順序對應於程序終止順序,而不是它們啟動的順序。

4、會計記錄對應於程序而不是程式。在fork之後,核心為子程序初始化一個記錄,而不是在一個新程式被執行時初始化。雖然exec並不建立一個新的會計記錄,但相應記錄中的命令名改變了,AFORK標誌則被清楚。

8.15 使用者標識

1、用getlogin函式可以獲取此登入名

#include <unistd.h>
char *getlogin(void);
                                         返回值:若成功,返回指向登入名字的字串指標;若出錯,返回NULL。

(1)如果呼叫此函式的程序滅有連線到使用者登入時所用的終端,則函式會失敗。通常這些程序為守護程序(daemon)。

8.16 程序排程

1、排程策略和排程優先順序是核心確定的。程序可以通過調整nice值選擇以更低優先順序執行(通過nice值降低它對CPU的戰友,因此該程序是“友好的”)。只有特權程序允許提高排程許可權。

2、Single UNIX Specification 中 nice值的範圍在0~(2*NZERO)- 1 之間,有些實現支援 0~2*NZERO。nice值越小,優先順序越高。NZERO是系統預設的nice值。

3、Linux 3.2.0 可以通過非標準的sysconf引數(_SC_NZERO)來訪問NZERO的值。

4、程序可以通過nice函式獲取或更改它的nice值。使用這個函式,程序只能影響自己的nice值,不能影響任何其他程序的nice值。

#include <unistd.h>
int nice(int incr);
                                        返回值:若成功,返回新的nice值NZERO。若出錯,返回-1

(1)incr引數被增加到呼叫程序的nice值上。如果incr太大,系統直接把它降到最大合法值。如果incr太小,系統也會把它提高到最小合法值。

(2)由於-1是合法的成功返回值,在呼叫nice函式之前需要清楚errno,在nice函式返回-1時,需要檢查它的值。如果nice呼叫成功,並返回值為-1,那麼errno仍然為0。如果errno不為0,說明nice呼叫失敗

5、getpriority函式可以像nice函式那樣用於獲取程序的nice值,但是getpriority還可以獲取一組相關程序的nice值。

#include <sys/resource.h>
int getpriority(int which, id_t who);
                                        返回值:若成功,返回-NZERO~NZERO-1之間的nice值;若出粗,返回-1

(1)which引數可以取以下三個值之一:

PRIO_PROCESS 表示程序。
PRIO_PGRP  表示程序組
PRIO_USER  表示使用者ID。

(2)who引數選擇感興趣的一個或多個程序,如果who引數為0,表示呼叫程序、程序組或者使用者(取決於which引數的值)

(3)當which設為PRIO_USER並且who為0時,使用呼叫程序的實際使用者ID。

(4)如果which引數作用於多個程序,則返回所有作用程序中優先順序最高的(最小的nice值)。

6、setpriority函式可用於為程序、程序組和屬於特定使用者ID的所有程序設定優先順序。

#include <sys/resource.h>
int setpriority(int which, id_t who, int value);
                                        返回值:若成功,返回0;若出錯,返回-1

(1)引數which和who與getpriority函式中相同。value增加到NZERO上,然後變為新的nice值

7、程式度量了nice值的效果。

/*************************************************************************
    > File Name: ins8_16.c
    > Author: Elliot
 ************************************************************************/

/*
 * 程序度量了呼叫程序nice值的效果。兩個程序並行執行,各自增加自己的計數器。
 * 父程序使用了預設的nice值,子程序以可選命令引數指定的調整後的nice值執行。
 * 執行10s後,兩個程序都列印各自的計數值並中止。通過比較不同nice值的程序計數值的差異,
 * 我們可以瞭解nice值是如何影響程序排程的。
 */

#include "apue.h"
#include <errno.h>
#include <sys/time.h>

#if     defined(MACOS)
#include <sys/syslimits.h>
#elif   defined(SOLARIS)
#include <limits.h>
#elif   defined(BSD)
#include <sys/param.h>
#endif

unsigned long long count;
struct  timeval end;

void
checktime(char *str)
{
    struct  timeval tv;

    gettimeofday(&tv, NULL);

    if (tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec)
    {
        printf("%s count = %lld\n", str, count);
        exit(0);
    }
}

int
main(int argc, char *argv[])
{
    pid_t   pid;
    char    *s;
    int     nzero, ret;
    int     adj = 0;

    setbuf(stdout, NULL);           /*  關閉緩衝  */

#if     defined(NZERO)
    nzero = NZERO;
#elif   defined(_SC_NZERO)
    nzero = sysconf(_SC_NZERO);
#else
#error  NZERO   undefined
#endif 
    printf("NZERO = %d\n", nzero);
    if (argc == 2)
        adj = strtol(argv[1], NULL, 10);
    gettimeofday(&end, NULL);
    end.tv_sec += 10;                       /*  run for 10 seconds  */

    if ((pid = fork()) < 0)
        err_sys(