Unix/Linux程式設計-程序
程序
6.1 程序的開始與終止
6.1.1 mian函式
當核心執行C程式時,在呼叫main前先呼叫一個特殊的啟動例程。可執行程式檔案將此啟動例程指定為程式的其實地址,這時由連線編輯器設定的,而連線編輯器則由C編譯器呼叫。啟動歷程從核心取得命令列引數和環境變數值,然後為按上述方式呼叫main函式做好安排。6.1.2 程序終止
有8種方式使程序終止,其中1-5為正常終止,6-8為異常終止,它們是:(1) 從main返回;
(2) 呼叫exit;
(3) 呼叫_exit或_Exit;
(4) 最後一個執行緒從其啟動例程返回;
(5) 從最後一個執行緒呼叫pthread_exit
(6)
(7) 接到一個訊號
(8) 最後一個執行緒對取消請求做出響應。
6.1.3 退出函式
#include <stdlib.h> void exit(int status); void _Exit(int status);
#include <unistd.h> void _exit(int status); |
3個函式都帶一個整型引數,稱為終止狀態。
6.1.4 函式atexit
按照ISO C的規定,一個程序可登記多至32個函式,這些函式將由exit自動呼叫。我們稱這些函式為終止處理函式,並呼叫atexit函式來登記這些函式 #include <stdlib.h> int atexit(void (*fun) (void)); 返回值:成功返回0,錯處返回非0 |
下圖顯示了一個C程式時如何啟動的,以及它終止的各種方式:
6.2 命令列引數與環境表
6.2.1 命令列引數
當執行一個程式時,呼叫 exec的 程序可將命令列引數傳遞給新程式。6.2.2 環境表
每個程式都接收到一張環境表,環境表是一個字元指標陣列,全域性變數environ則包含了該指標陣列的地址:extern char **environ;
其中每個字串的結尾處都顯示的有一個null位元組。
6.2.3 環境變數
如同前述,環境字串的形式是:
name=value
Unix核心並不檢視這些字串,它們的解釋完全取決於各個應用程式。
ISO C定義了一個函式getenv,可以用其取環境變數值,但是該標準又稱環境的內容是由實現定義的:
#include <stdlib.h> char *getenv(const char *name); 返回值:指向與name關聯的value的指標;未找到返回NULL |
除了獲取環境變數值,有時也需要設定環境變數。我們可能希望改變現有變數的值,或者是增加新的環境變數,但並不是所有系統都支援這種能力。
#include <stdlib.h> int putenv(char *str); 返回值:成功返回0,出錯返回非0 int setenv(const char *name, const char *value, int rewite); int unsetenv(const char *name); 返回值:成功返回0,出錯返回-1 |
(1) putenv取形式為name=value的字串,將其放到環境表中。如果name已經存在,則先刪除其原來的定義。
(2) setenv將name設定為value。如果在環境中name已經存在,那麼若rewrite非0,則首先刪除其現有的定義,若rewrite為0,則不刪除現有定義(name不設定為新的value,而且也不出錯)。
(3) unsetenv刪除name的定義。即使不存在這種定義也不算出錯。
putenv和setenv的區別:
setenv必須分配儲存空間,以便依據其引數建立name=value字串。putenv可以自由地將傳遞給他的引數字串直接放到環境中。確實,許多實現就是這麼做的。因此,將存放在棧中的字串作為引數傳遞給putenv就會發生錯誤,其原因是,從當前函式返回時,其棧幀佔用的儲存區可能被重用。
6.3 程序標識
每個程序都要一個非負整數表示的唯一程序ID。雖然是唯一的,但是程序ID是可複用的。當一個簡稱終止後,其程序ID就成為複用的候選者。大多數Unix系統實現延遲複用演算法,使得賦予新建程序的ID不同於最近終止程序所使用的ID。系統中有一些專用程序,ID為0的程序通常是排程程序,場場被稱為交換程序,該程序是核心的一部分,它並不執行任何磁碟上的程式,因此被稱為系統程序。ID為1的程序通常是init程序,在自舉過程結束時由核心呼叫。init通常讀取與系統有關的初始化檔案,並將系統引導到一個狀態,init程序絕不會終止。
#include <unistd> 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 |
6.4 建立一個新程序:fork
6.4.1 fork函式
#include <unistd.h> pid_t fork(void); 返回值:子程序返回0,父程序返回子程序ID;出錯返回-1 |
由於fork之後經常跟著exec,所以現在很多實現並不執行一個父程序資料段、堆和棧的完全副本。所謂替代,使用了寫時賦值技術。這些區域由父程序和子程序共享,而且核心將他們的訪問許可權改變為只讀。如果父程序和子整合中的任何一個試圖修改程序共享,則核心只為修改區域的那塊記憶體製作一個副本,通常是虛擬儲存系統中的一頁。
一般來說,在fork之後是父程序先執行還是子程序先執行是不確定的,這取決於核心所使用的排程演算法。
6.4.2 父程序和子程序之間的區別
(1) fork的返回值不同。(2) 程序ID不同。
(3) 兩個程序的父程序ID不同;子程序的父程序ID是建立它的程序的ID,而父程序的父程序ID不變。
(4) 子程序的tms_utime、tms_stime、tms_cutime和tms_ustime的值設定為0.
(5) 子程序不繼承父程序設定的檔案鎖。
(6) 子程序的未處理訊號集合為空集。
6.4.3 fork失敗的原因
(1) 系統中已經有了太多的程序。(2) 該實際使用者ID的程序總數超過了系統限制
6.4.4 fork的應用場景
(1) 一個父程序希望複製自己,使父程序和子程序同時執行不同的程式碼段。(2) 一個程序要執行不同的程式。這種情況下,子程序從fork返回後立即呼叫exec。
6.5 wait和waitpid
當一個程序正常或異常終止時,核心就向其父程序傳送SIGCHLD訊號。子程序終止是非同步時間,所以這種訊號也是核心向父程序發的非同步通知。父程序可以選擇忽略該訊號,或者提供一個該訊號發生時被呼叫的執行函式(訊號處理程式)。對於這種訊號的系統預設動作是忽略它。 #include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); 返回值:成功返回程序ID,錯處返回0或-1 |
(1) 如果其所有子程序都還在執行,則阻塞。
(2) 如果一個子程序已終止,正等待父程序獲取其終止狀態,則取得該子程序的終止狀態立即返回。
(3) 如果他沒有任何子程序,則立即出錯返回。
引數statloc如果不是一個空指標,則終止程序的終止狀態就存放在它所指向的單元內。終止狀態用第一在<sys/wait.h>中的巨集來檢視。有4個互斥的巨集可用來取得程序終止原因。
巨集 |
說明 |
WIFEXITED(status) |
若為正常終止程序返回的狀態,則為真。可執行WEXITSTATUS(status),獲取子程序傳送給exit或_exit引數的低8位 |
WIFSIGNALED(status) |
若為異常終止子程序返回的狀態,則為真(接到一個不捕獲的訊號)。可執行WTERMSIG(status),獲取使子程序終止的訊號編號。 |
WIFSTOPPED(status) |
若為當前暫停子程序的返回狀態,則為真。可執行WSTOPSIG(status),獲取使子程序暫停的訊號編號 |
WIFCONTUNUED(status) |
若在作業控制暫停後已經繼續的子程序返回了狀態,則為真。 |
(1) pid==-1,等待任一程序,與wait等效。
(2) pid>0,等待程序ID與pid相等的子程序。
(3) pid==0,等待組ID等於呼叫程序ID的任一子程序。
(4) pid<-1,等待組ID等於pid絕對值的任子程序。
對於waitpid,如果指定的程序或程序組不存在,或者引數pid指定的程序不是呼叫程序的子程序,都可以能出錯。
options引數使我們能進一步控制waitpid的操作,此引數或者是0,或者是下面的常量按位或運算的結果。
常量 |
說明 |
WCONTNUED |
若實現支援作業控制,那麼由pid指定的任一子程序在停止後已經繼續,但其狀態尚未報告,則返回其狀態。 |
WNOHANG |
若pid指定的子程序並不是立即可用的,則waitpid不阻塞,此時其返回值為0。 |
6.6 waitid函式
#include <sys/wait.h> int waited(idtype_t idtype, id_t id, siginfo_t *infop, int options); 返回值:若成功,返回0,出錯返回-1 |
常量 |
說明 |
P_PID |
等待一特定程序:id包含要等待子程序的程序ID |
P_PGID |
等待一特定程序組中的任一子程序:id包含要等待子程序的程序組ID |
P_ALL |
等待任一子程序:忽略id |
常量 |
說明 |
WCONTINUED |
等待一程序,它以前曾被停止,此後已繼續,但其狀態尚未報告。 |
WEXITED |
等待已退出的程序。 |
WNOHANG |
如無可用的子程序退出狀態,立即返回而非阻塞。 |
WNOWAIT |
不破壞子程序退出狀態。該子程序退出狀態可由後續的wait、waitid或waitpid呼叫取得。 |
WSTOPPED |
等待一程序,它已經停止,但其狀態尚未報告。 |
infop引數是指向siginfo結構的指標。該結構包含了在成子程序狀態改變的有關訊號的詳細資訊。
6.7 exec函式族
當程序呼叫一種exec函式時,該程序執行的程式完全替換為新程式,而新程式則從其main函式開始執行。因為呼叫exec並不建立新程序,所以前後的程序ID並未改變。exec只是用磁碟上的一個新程式替換了當前程式的正文段、資料段、堆段和棧段。 #include <unistd.h> int execl(const char *pathname, const char *arg0, … /* (char *) 0 */); int execv(const char *pathname, car *const argv[]); int execle(const char *pathname, const char *arg0,…/* (char *)0 , char *const envp[] */); int execve(const char *pathname, char *const argv[], char *const envp[]); int execlp(const char *filename, const char *arg0,… /* (char *) 0 */); int execvp(const char *filename, char *const srgv[]); int fexecve(int fd, char *const argv[], char *const envp[]); 出錯返回-1,成功不返回。 |
(1) 如果filename包含/,則就將其視為路徑名;
(2) 否則就按PATH環境變數,在它所指定的各目錄中搜索可執行檔案。
如果execlp或execvp使用路徑字首中的一個找到了一個可執行檔案,但是該檔案不是由連結編輯器產生的機器可執行檔案,則就認為該檔案是一個shell指令碼。
execl、execlp和execle要求將新程式的每一個命令列引數都說明為一個單獨的引數,以空指標結尾。對於另外4個函式,則應先構造一個指向各引數的指標陣列,然後將該陣列地址作為4個函式的引數。
以e結尾的3個函式可以傳遞一個指向環境字串指標陣列的指標。
6.8 更改使用者ID和更改組ID
可以用setuid函式設定實際使用者ID和有效使用者ID。於此類似,可以用setgid函式設定實際組ID和有效組ID。 #include <unistd.h> int setuid(uid_t uid); int setgid(gid_t gid); 函式返回值:成功返回0,出錯返回-1 |
(1) 若程序具有超級使用者特權,則setuid函式將實際使用者ID、有效使用者ID以及儲存的設定使用者ID設定為uid。
(2) 若程序沒有超級使用者去特權,但是uid等於實際使用者ID或儲存的設定使用者ID,則setuid將有效使用者ID設定為uid。
(3) 若上面兩個條件都不滿足,則errno設定為EPERM,並返回-1。
關於核心所維護的3個使用者ID,還需要注意一下幾點:
(1) 只有超級使用者程序可以更改實際使用者ID。通常,實際使用者ID是在使用者登入時,有login(1)程式設定的,而且決不會改變它。因為login是一個超級使用者程序,呼叫它時,設定所有3個使用者ID。
(2) 僅當對程式檔案設定了設定使用者ID位時,exec才設定有效使用者ID。如果設定使用者ID位沒有設定,exec函式不會改變有效使用者ID,而將維持其現有值。 任何時候都可以呼叫setuid,將有效使用者ID設定為實際使用者ID或儲存的設定使用者ID。自然地,不能將有效使用者ID設定為任一隨機值。
(3) 儲存的設定使用者ID是由exec賦值有效使用者ID而得到的。如果設定了檔案的設定使用者ID位,則在exec根據檔案的使用者ID設定了程序的有效使用者ID以後,這個副本就被儲存起來了。
更改3個使用者ID不同的方法:
ID |
exec |
setuid |
||
設定使用者ID關閉 |
設定使用者ID為開啟 |
超級使用者 |
非特權使用者 |
|
實際使用者ID |
不變 |
不變 |
設為uid |
不變 |
有效使用者ID |
不變 |
設定為程式檔案的使用者ID |
設為uid |
設為uid |
儲存的設定使用者ID |
從有效使用者ID複製 |
從有效使用者ID複製 |
設為uid |
不變 |
6.9 程序排程
Unix系統歷史上對程序提供的只是基於排程優先順序的粗粒度的控制,排程策略和排程優先順序是由核心確定的。程序通過調整nice值選擇以更低優先順序執行,只有特權程序允許提高排程許可權。nice值越小,優先順序越高。程序可以通過nice函式獲取或更改他的nice值,隻影響自己的nice值,不能影響其他任何程序的nice值。
6.9.1 nice函式
#include <unistd.h> int nice(int incr); 返回值:成功返回新的nice值NZERO,出錯返回-1 |
6.9.2 getpriority函式
getpriority函式可以像nice函式那樣用於獲取程序的nice值,但是getpriority還可以獲取一組相關程序的nice值。 #include <sys/resource.h> int getpriority(int which, id_t who); 返回值:成功返回-NZERO~NZERO-1之間的nice值,出錯返回-1. |
6.10 程序時間
任一程序都可以呼叫times函式獲得它自己以及已終止子程序的上述值 #include <sys/timies.h> clock_t times(struct tms *buf); 車才能更改,返回流逝的牆上時間(以時鐘滴答數為單位),出錯返回-1 |
struct tms { clock_t tms_utime; /* user time */ clock_t tms_stime; /* system time */ clock_t tms_cutime; /* user time of children */ clock_t tms_cstime; /* system time of children */ }; |