本章介紹unix的程序控制,包括程序建立,執行程式和程序終止,程序的屬性,exec函數系列,system函數,程序會計機制。
1、程序辨別符
每一個程序都有一個非負整數辨別的唯一程序id。id為0表示排程程序,即交換程序,是核心的一部分,也稱為系統程序,不執行任何磁盤操作。id為1的程序為init程序,init程序不會終止,他是一個普通的使用者程序,需要超級使用者特權運作。擷取辨別符函數如下:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); //調用程序的程序id
pid_t getppid(void); //調用程序的父程序id
gid_t getgid(void); //調用程序的實際組id
gid_t getegid(void); //調用程序的有效組id
uid_t getuid(void); //調用程序的實際使用者id
uid_t geteuid(void); //調用程序的有效使用者id
2、fork函數
一個現有的程序可以調用fork函數建立一個新程序。函數原型為:pid_t fork(void)。有fork建立的新程序稱為子程序,fork函數調用一次傳回兩次。子程序的傳回值為0,父程序的傳回值為新子程序的id。子程序和父程序舉行執行fork調用後的指令,子程序是父程序的副本。子程序獲得父程序的資料空間、棧和隊的副本,父子程序不共享這些存儲空間部分,共享正文段。子程序相當于父程序克隆了一份自己。 建立新程序成功後,系統中出現兩個基本完全相同的程序,這兩個程序執行沒有固定的先後順序,哪個程序先執行要看系統的程序排程政策。
fork出錯可能有兩種原因:
1)目前的程序數已經達到了系統規定的上限,這時errno的值被設定為eagain。
2)系統記憶體不足,這時errno的值被設定為enomem。
注意的是:父程序設定的檔案鎖不會被子程序繼承。
寫個程式,建立一個子程序,在子程序中改變變量的值,然後父子程序同時輸出變量的值,看看有什麼變化,同時輸出子程序的辨別符資訊。程式如下:
程式執行結果如下:
從結果可以看出子程序擁有自己的資料空間,不與父程序共享資料空間。
vfork函數的調用序列和傳回值與fork相同,但是vfork并不将父程序的位址空間完全複制到子程序中,在調用exec和exit之前在父程序的空間中運作。vfork保證子程序先運作,在它調用exec或exit之後父程序才可能被排程運作。
程序終止最後都會執行核心中的同一段代碼,為相應的程序關閉所有打開描述符,釋放它所使用的存儲器等。子程序可以通過exit函數通知父程序是如何終止的,父程序調用wait或waitpid函數可以擷取終止狀态。子程序是在父程序調用fork後産生的,如果父程序在子程序之前終止,則将子程序的父程序改變為init程序,保證每一個程序都有一個父程序。一個已經終止,但其父程序尚沒有對其進行善後處理的程序稱為僵死程序(zombie)。由init程序領養的子程序不會變成僵死程序,因為init程序在子程序終止的時候會調用一個wait函數取得子程序的終止狀态。
當一個程序正常或者異常終止的時,核心就像其父程序發送sigchld信号。調用wait和waitpid函數可能發生的情況:(1)如果所有子程序都還在運作,則阻塞;(2)如果一個子程序已經終止,正等待父程序擷取程序終止狀态,則取得孩子的終止狀态立刻傳回;(3)若果沒有任何子程序,則立即出錯傳回。如果在任意時刻調用wait,則程序可能會阻塞。
pid_t wait(int *status); //在一個子程序終止前,wait使調用者阻塞
pid_t waitpid(pid_t pid, int *status, int options); //可以使調用者不阻塞
pid_t wait3(int *status, int options,struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
寫個程式,子程序給出退出狀态,父程序通過wait和waitpid擷取退出狀态。程式如下:
程式執行結果如下:
waitpid函數中的pid參數取值情況:
pid=-1 等待任一子程序,此時相當于wait
pid>0 等待期程序id與pid相等的子程序
pid==0 等待期組id等于調用程序組id的任一個子程序
pid<-1 等待其組id等于pid絕對值的任一子程序
另外提供了一種避免僵死程序的方法:調用fork兩次。
exec函數,用fork函數建立子程序後,子程序往往要調用一種exec函數以執行另外一個程式。當調用exec函數時,該程序執行的程式完全替換為新程序,exec函數不建立程序,隻是用一個全新的程式替換了目前的正文、資料、堆和棧段。執行完之後,程序id不會改變。在程序間通信的時候,經常需要調用exec函數啟動另外一個例程。函數原型如下:
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *file, char *const argv[],char *const envp[]);
其中l表示清單(list),v辨別矢量(vector)。execl、execlp、execle中每個指令行參數都是一個單獨參數,這種參數以空指針結尾。execv、execvp、execve指令行參數是一個指針數組。e辨別環境變量,傳遞參數。寫個程式進行測試,程式分為兩部分,exec調用的程式,exec執行程式。程式如下:
exec調用程式如下,可執行檔案名稱為exectest,存放在/home/anker/programs目錄下。
exec執行程式如下:存放在/home/anker/programs目錄下。
執行結果如下:
system函數,在程式中執行一個指令字元串。例如system("date > file")。函數原型如下:
int system(const char *command);、
system函數實作中調用了fork、exec和waitpid函數,是以有三種傳回值。system函數實作一下,沒有信号處理。程式如下:
system函數可以設定使用者的id,這是一個安全漏洞。
程序會計:啟用後,每當程序結束時候核心就寫一個會計記錄,包括指令名、所使用的cpu時間總量、使用者id群組id、啟動時間等。accton指令啟動會計處理,會計記錄寫到指定的檔案中,linux中位于/var/account/ pacct。會計記錄結構定義在<sys/acct.h>頭檔案中。
#define acct_comm 16
typedef u_int16_t comp_t;
struct acct {
char ac_flag; /* accounting flags */
u_int16_t ac_uid; /* accounting user id */
u_int16_t ac_gid; /* accounting group id */
u_int16_t ac_tty; /* controlling terminal */
u_int32_t ac_btime; /* process creation time(seconds since the epoch) */
comp_t ac_utime; /* user cpu time */
comp_t ac_stime; /* system cpu time */
comp_t ac_etime; /* elapsed time */
comp_t ac_mem; /* average memory usage (kb) */
comp_t ac_io; /* characters transferred (unused) */
comp_t ac_rw; /* blocks read or written (unused) */
comp_t ac_minflt; /* minor page faults */
comp_t ac_majflt; /* major page faults */
comp_t ac_swaps; /* number of swaps (unused) */
char ac_comm[acct_comm+1];/* command name (basename of lastexecuted command; null-terminated) */
char ac_pad[x]; /* padding bytes */
};
使用者辨別,用getlogin函數擷取使用者的登入名。函數原型如下:char *getlogin(void)。
程序時間:牆上時鐘時間、使用者cpu時間和系統cpu時間。任一個程序都可以調用times函數擷取它自己及已終止子程序時間。程序時間操作函數及結構如下:
clock_t times(struct tms *buf);
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 */
總結:通過本章的學習。完全的了解unix的程序控制,掌握了fork、exec簇、wait和waitpid程序控制函數。另外學習了system函數和程序會計。了解了解釋器檔案及其工作方式,使用者辨別和程序時間。