[單刷APUE系列]第八章——進程控制[2]

目錄

[單刷APUE系列]第一章——Unix基礎知識[1]
[單刷APUE系列]第一章——Unix基礎知識[2]
[單刷APUE系列]第二章——Unix標準及實現
[單刷APUE系列]第三章——文件I/O
[單刷APUE系列]第四章——文件和目錄[1]
[單刷APUE系列]第四章——文件和目錄[2]
[單刷APUE系列]第五章——標準I/O庫
[單刷APUE系列]第六章——系統數據文件和信息
[單刷APUE系列]第七章——進程環境
[單刷APUE系列]第八章——進程控制[1]
[單刷APUE系列]第八章——進程控制[2]
[單刷APUE系列]第九章——進程關係
[單刷APUE系列]第十章——信號[1]前端

waitid函數

因爲前文中waitwaitpid函數有不少不靈活的地方,SUS標準規定了之外一個進程終止狀態獲取函數算法

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

int waitid(idtype_t, id_t, siginfo_t *, int) __DARWIN_ALIAS_C(waitid);

上面兩個一個是原著實現,還有一個是蘋果系統實現,可是筆者沒有在函數手冊中找到具體描述,因此這裏就不介紹,各位直接看原著。shell

wait3和wait4函數

除了上面的等待函數,還有兩個從BSD時代延續下來的函數,也成爲了事實意義上的Unix標準,segmentfault

pid_t wait3(int *stat_loc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage);

其實從參數的個數和命名基本也能猜出這兩個函數幹嗎用的了。不過仍是附上Unix系統手冊數組

The wait4() call provides a more general interface for programs that need to wait for certain child processes, that need resource utilization statistics accumulated by child processes, or that require options.  The other wait functions are implemented using wait4().
The waitpid() call is identical to wait4() with an rusage value of zero.  The older wait3() call is the same as wait4() with a pid value of -1.

wait4函數提供更通用的接口,並且還包括了資源的統計數據,網絡

struct rusage {
        struct timeval ru_utime; /* user time used */
        struct timeval ru_stime; /* system time used */
        long ru_maxrss;          /* max resident set size */
        long ru_ixrss;           /* integral shared text memory size */
        long ru_idrss;           /* integral unshared data size */
        long ru_isrss;           /* integral unshared stack size */
        long ru_minflt;          /* page reclaims */
        long ru_majflt;          /* page faults */
        long ru_nswap;           /* swaps */
        long ru_inblock;         /* block input operations */
        long ru_oublock;         /* block output operations */
        long ru_msgsnd;          /* messages sent */
        long ru_msgrcv;          /* messages received */
        long ru_nsignals;        /* signals received */
        long ru_nvcsw;           /* voluntary context switches */
        long ru_nivcsw;          /* involuntary context switches */
}

這兩個函數的最後一個參數就是一個結構體,可以讓咱們經過這個結構體來了解子進程的資源統計。ide

競爭條件

資源是有限的,因此操做系統的一個功能就是提供了資源的分配,競爭條件是操做系統原理概念中的資源競爭。例如,fork函數調用後有某種邏輯的正確執行須要依賴父進程和子進程運行前後,那麼這就是一種競爭條件,在現代操做系統中,通常都是多種調度算法,因此咱們沒法預料到哪一個進程先運行。
在原著中一個例程,須要第二個子進程在第一個子進程以前運行。就如同事件循環能立刻想到while (true)同樣,當碰到這種問題,第一反應就是輪詢,while (getppid() != -1),很容易理解的代碼,可是這樣就浪費了CPU時間。
爲了防止這種惡性的競爭和沒必要要的輪詢,Unix系統引入了信號機制,可是這裏暫且先不討論具體實現,等到後面章節再討論。函數

exec函數族

exec函數族是很重要的一個學習概念,在學習fork函數的時候講到過有兩種用法,一種是網絡服務的master-worker進程模型,一種是子進程執行程序,一般叫作spawn,在Unix系統中,沒有提供spawn相似的函數,可是將其拆分開來,forkexec組合就是spawn,當進程調用一種exec函數時,該進程的正文段、數據段、bss段和堆棧將徹底被替換,可是其餘的進程屬性不變。學習

int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvP(const char *file, const char *search_path, char *const argv[]);

原著上講有7個函數,可是實際上筆者在蘋果系統中只找到6個函數族,而在CentOS系統上也只找到5個函數。實際上這些函數都是execve函數的前端,最終都是execve函數來完成實際工做。ui

int execve(const char *path, char *const argv[], char *const envp[]);

這是最終調用的函數,從這個函數中咱們能夠看到,exec函數族想要運行須要三個參數,文件路徑、傳入的參數數組和環境變量。其餘基本就是這個函數的簡化版本或者變體。在前面的描述中咱們能夠看到有兩個函數不使用path做爲參數,而是以file做爲參數,當file參數爲絕對路徑時,則使用絕對路徑的位置,不然在PATH環境變量的指示中搜索文件。從前面能夠看到,只有一個函數execle帶有環境變量指針參數,若是沒有帶此參數的函數,都會複製現有的進程的環境變量,每一個系統基本都有不一樣的實現,因此應當使用共有的函數,並結合使用條件編譯,來作跨平臺開發。
在前面的Unix文件章節,提到了FD_CLOEXEC標誌,進程的每一個文件描述符都有一個執行時關閉標誌,若是設置了這個參數,則在exec執行時自動關閉該文件。默認狀況下,exec後仍然保持描述符打開。
在前面也提到了,用戶組ID能夠分爲實際用戶族ID和有效用戶組ID,實際用戶組ID在exec後依舊是不變的,可是有效用戶組ID變化與不然取決於執行程序的設置用戶組ID,若是有,則有效用戶組ID改成擁有者用戶組ID,不然不變,這個很好理解,shell啓動一個程序,實際用戶組ID是確定不變的,由於要標識一個進程的使用者,可是用於權限檢查的有效用戶組ID則是取決於設置用戶組ID,sudo命令也是相似。

更改用戶組ID

在Unix系統中,進程擁有實際用戶組ID和有效用戶組ID,在權限檢查的時候,檢查的是有效用戶組ID,在設計開發的時候,Unix的哲學就是最小特權,咱們常常須要修改進程的權限,因此這裏也有了系統提供的函數。

int setuid(uid_t uid);
int setgid(gid_t gid);
int seteuid(uid_t euid);
int setegid(gid_t egid);

在講解函數以前,先講解一下進程的三種ID,實際用戶組ID用於標識進程是誰擁有負責的,有效用戶組ID則是用於權限檢查的,保存的設置用戶組ID則是用於恢復有效用戶ID的,在早期Unix系統設計中,只存在一種用戶組ID,它承擔了全部的功能,後來,因爲set-uid bit的存在,就分離出了實際用戶組ID和有效用戶組ID,可是,在不少狀況下,並非一直須要有效用戶組ID等於擁有者用戶組ID的,因此就有了保存設置用戶組ID。保存設置用戶組ID是最近一次setuid系統調用或是exec一個setuid程序的結果,用於恢復最先的有效用戶組ID。

The setuid() function sets the real and effective user IDs and the saved set-user-ID of the current process to the specified value. The setuid() function is permitted if the effective user ID is that of the super user, or if the specified user ID is the same as the effective user ID. If not, but the specified user ID is the same as the real user ID, setuid() will set the effective user ID to the real user ID.

setuid函數爲當前進程設置實際用戶ID、有效用戶ID和保存的set-user-ID爲指定值。若是有效用戶ID是超級用戶,setuid函數則被放行,換言之,root權限是有特權的,或者說若是指定的有效用戶ID等同現有的有效用戶ID,也是能夠放行的,若是不等同,可是指定的有效用戶ID等同於實際用戶ID,setuid函數將會將有效用戶ID設置爲實際用戶ID。
對於內核維護的三種ID,有幾點須要注意:

  1. root權限進程才能更改實際用戶組ID

  2. 只有程序設置了設置用戶ID位,exec函數族纔會設置有效用戶ID,有效用戶ID只能是實際用戶ID或者保存的設置用戶ID

  3. 保存的設置用戶ID是exec函數複製原先的有效用戶ID獲得的,若是程序設置了設置用戶ID位,那麼就會存在這個值。

seteuid和setegid相似setuid和setgid,可是它們只更改有效用戶組ID,root權限進程能夠任意修改有效用戶組ID,可是非特權用戶只能將其設置爲實際用戶組ID或者保存設置用戶組ID,

setreuid和setregid函數

int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

這兩個函數用於分別設置實際用組ID和有效用戶組ID,固然,只有root權限才能任意更改,而非特權用戶通常都是用於交換實際用戶組ID和有效用戶組ID。
其實一言以蔽之,上面的六個函數實際上都有root權限運行和非root權限運行兩種形式,任何狀況下,只有root權限才能更改實際用戶組ID,而有效用戶組ID表明的是權限檢查,因此root權限進程能夠下降自身權限,可是其餘進程不行。

解釋器文件

解釋器文件實際上就是普通文件,只是權限位增長了執行權限,而且在頭部以#!pathname[optional-arguments]的形式代表瞭解釋器。解釋器文件通常用於腳本編寫,沒什麼可說的,因此這裏就省略了。

system函數

int system(const char *command);

The system() function hands the argument command to the command interpreter sh(1).  The calling process waits for the shell to finish executing the command, ignoring SIGINT and SIGQUIT, and blocking SIGCHLD.

If command is a NULL pointer, system() will return non-zero if the command interpreter sh(1) is available, and zero if it is not.

system函數將字符串傳遞給sh解釋器,而且一直會等待shell結束執行,若是command參數是NULL指針,而且sh解釋器存在,則返回非0,不然就是0,這主要用於判斷sh存在與否。這裏也沒什麼可講,原著中都講完了,須要注意的是,這是一個很是實用的函數。

進程會計

進程會計不是任何標準規定的東西,因此各個Unix系統實現都將其按照本身的理解方式實現,因此在跨平臺開發中就很是的麻煩,並且因爲各個平臺提供的查詢命令都不一致,因此並無實際上的意義所在,這節能夠忽略。

用戶標識

在實際的Unix系統中,uid和gid是標誌一個用戶的方式,也叫做憑據,可是用戶不須要以數字標誌的形式管理系統,因此就有了以英文形式提供的用戶標識,系統也提供了對應的映射

char *getlogin(void);
int setlogin(const char *name);

登陸名在一個會話中是不改變的,即便是一些更改uid的程序,例如su命令,而setlogin則是設置登陸名,這個函數只能由root權限調用。

進程調度

進程調度是由操做系統決定,開發者和用戶不可得知系統會如何調度本身的進程,可是卻能夠設置進程的優先級。Unix系統也提供了相關接口用於修改和查詢。固然,因爲CPU也是一種資源,因此就如同其餘的資源限制同樣,只有root權限進程才能提升自身的優先級,其餘進程只能下降自身優先級。

int nice(int incr);

nice函數用於得到設置自身的有限度,而且這是一個累加的值,因爲這個函數侷限性,系統還提供了另外一個函數

int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int prio);

which參數取RIO_PROCESS, PRIO_PGRP, or PRIO_USER,而who參數的解釋取決於which參數,當who爲0時,表示當前的進程、進程組或用戶,而prio則是-20到20的值,默認爲0,低優先級則有高CPU使用。

進程時間

前面講過Unix系統有三種時間:牆上時鐘時間、用戶CPU時間和系統CPU時間

clock_t times(struct tms *buffer);

struct tms {
    clock_t tms_utime;
    clock_t tms_stime;
    clock_t tms_cutime;
    clock_t tms_cstime;
};

上面是結構體和函數聲明,結構體沒有包含牆上時鐘時間,可是函數返回了時鐘時間,結構體按照順序包含了用戶CPU時間、系統CPU時間、包含子進程的用戶CPU時間和包含子進程的系統CPU時間。

相關文章
相關標籤/搜索