一次性講明白Linux系統呼叫(1)
什麼是系統呼叫
- Linux核心中設定了很多可以實現各種系統功能的子程式,這些子程式就叫系統呼叫。而系統呼叫和普通函式呼叫的區別主要是在系統呼叫是系統提供的,函式一般是函式庫或者自己提供的。
為什麼要用系統呼叫
- 其實很多我們平時用的C語言標準函式,在Linux上都是通過系統呼叫完成的。隨著深入學習,有時候發現系統呼叫是完成某些功能最簡單最有效的途徑。在很多情況下對程式設計有意想不到的幫助。
程序的一生
1. 程序通過fork()複製產生新程序
一個程序呼叫fork()之後,系統會給新程序分配資源。並且把原來程序的所有內容複製(應用到了寫時複製技術,在下面會講到)到新程序當中,相當於複製了一個一模一樣的自己。
ptd_t fork(void) //pid_t是fork返回值型別 可用int替換
fork()函式將會有兩次返回;其中父程序將會返回子程序的PID(程序ID),而子程序返回值為0。發生錯誤時將會返回一個負值。
2. 子程序呼叫exec()以注入靈魂
通過fork()產生的子程序和父程序幾乎完全一樣,但產生新程序一般就是為了讓ta執行新的程式啦,所以要呼叫exec來為新程序注入靈魂。
exec其實指的是一組函式,一共六個。
#include <unistd.h>
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 *path, char *const argv[], char *const envp[ ]);
exec函式族的作用是根據指定的路徑或者檔名找到可執行檔案,並用它來取代原本程序的內容。換句話說,就是把呼叫程序內部替換為另一個可執行檔案。
這些裡面只有execve是真正的系統呼叫,其他的都是基於他包裝的庫函式。exec函式族的函式執行成功後不會返回,因為呼叫程序的實體,包括程式碼段,資料段和堆疊等都已經被新的內容取代,只留下程序ID等一些表面上的資訊。只有呼叫失敗了,才會返回一個-1。
Linux當要執行新程式的時候,如果有程序認為自己不能為系統和使用者做出任何貢獻了,他就可以發揮最後一點餘熱,呼叫任何一個exec,讓自己以金蟬脫殼;或者,更普遍的情況其實是,如果一個程序想執行另一個程式,它就可以fork()出一個新程序,然後呼叫任何一個exec,這樣看起來就好像通過執行應用程式而產生了一個新程序一樣。
3.程序的結束
之後程序就要執行ta的任務啦。而程序結束有三種可能性,一個是主函式結束,或者主函式返回值,再或者是因為其他原因最終程序關閉。
在程序結束後,並不是直接消失,而是會產生一個殭屍程序。
$ ps -ax
1511 pts/0 Z 0:00 [zombie <defunct>]
其中中間的"Z"標識就是殭屍程序的標誌了。殭屍程序雖然對其他程序幾乎沒有什麼影響,不佔CPU資源,消耗的記憶體也幾乎可以忽略不計,但就這樣一個殭屍住在系統裡,還是讓人覺得不爽。而且Linux系統中程序數目是有限制的,在一些特殊的情況下,如果存在太多的殭屍程序,也會影響產生新程序。
而為什麼要有一個殭屍程序吶?殭屍程序中儲存著很多非常重要的資訊,首先,這個程序是怎麼死的?是正常退出呢,還是出現了錯誤,還是被其它程序強迫退出的?其次,這個程序佔用的總系統CPU時間和總使用者CPU時間分別是多少?發生頁錯誤的數目和收到訊號的數目。這些資訊都被儲存在殭屍程序中,如果沒有殭屍程序,程序一退出,所有這個程序存在過的痕跡都灰飛煙滅,而此如果還要用到程序資訊,那就沒轍了。
那麼,何收集這些資訊,並幹掉這些殭屍程序呢?就要靠下面要說的waitpid()和wait()了。這兩個都是收集殭屍程序留下的資訊,同時徹底殺掉這個程序的。下面就對這兩個呼叫分別作詳細介紹。
4.wait()與waitpid()
wait():
#include <sys/types.h> /* 提供型別pid_t的定義 */
#include <sys/wait.h>
pid_t wait(int *status)/* pid_t同int*/
某個程序一旦呼叫了wait(),就立即阻塞自己,由wait()自動分析是否當前程序的某個子程序已經退出,如果讓它找到了這樣一個已經變成殭屍的子程序,wait就會收集這個子程序的資訊,並把它徹底銷燬後返回。如果沒有找到這樣一個子程序,wait就會一直等待殭屍程序的出現。
pid = wait(NULL);
-如果收集到殭屍程序,wait()會返回被收集的程序的程序ID,如果呼叫程序沒有子程序,呼叫就會失敗,此時wait()返回-1。
waitpid():
#include <sys/types.h> /* 提供型別pid_t的定義 */
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options)
從本質上講,系統呼叫waitpid()和wait()的作用是完全相同的,但waitpid()多出了兩引數pid和options,從而為提供了另一種更靈活的使用方式。
- pid
從引數的名字pid和型別pid_t中就可以看出,這裡需要的是一個程序ID。但當pid取不同的值時,在這裡有不同的意義。
- pid>0時,只等待程序ID等於pid的子程序,不管其它已經有多少子程序執行結束退出了,只要指定的子程序還沒有結束,waitpid就會一直等下去。
- pid=-1時,等待任何一個子程序退出,沒有任何限制,此時waitpid和wait的作用一模一樣。
- pid=0時,等待同一個程序組中的任何子程序,如果子程序已經加入了別的程序組,waitpid不會對它做任何理睬。
- pid<-1時,等待一個指定程序組中的任何子程序,這個程序組的ID等於pid的絕對值。
options
- options
options提供了一些額外的選項來控制waitpid(),目前在Linux中只支援WNOHANG和WUNTRACED兩個選項,這是兩個常數,可以用"|"運算子把它們連線起來使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我們不想使用它們,也可以把options設為0,如:
ret=waitpid(-1,NULL,0);
如果使用了WNOHANG引數呼叫waitpid,即使沒有子程序退出,它也會立即返回,不會像wait那樣永遠等下去。
看到這裡,相信已經有人發現了,wait()不就是經過包裝的waitpi()d嗎?沒錯,在<核心原始碼目錄>/include/unistd.h檔案裡能發現這些:
static inline pid_t wait(int * wait_stat)
{
return waitpid(-1,wait_stat,0);
}
【完】
- 如果有錯誤的地方請多指出
相關主題:
系統呼叫跟我學(3)–程序管理相關的系統呼叫之二 雷鎮,2002
Linux man pages
Advanced Programming in the UNIX Environment by W. Richard Stevens, 1993
Linux核心原始碼分析 彭曉明,王強,2000