程序建立
-->程序表
系統用一個叫做“程序表”的東西來維護系統中的程序,程序表中的一個條目維護着存儲着一個程序的相關資訊,比如程序号,程序狀态,寄存器值等等...
當配置設定給程序A的“時間片”使用完時,CPU會進行上下文切換以便運作其他程序,比如程序B,這裡所謂的“上下文切換”,主要就是在操作那個“程序表”,其将程序A的相關資訊(上下文)儲存到其對應的程序表項中, 與之相反,其會從對應于程序B的程序表項中讀取相關資訊并運作之。
那麼,如果程序A建立了一個程序C呢?程序表會多這樣一個表項,并且該表項擁有一個唯一的ID,也就是程序号(PID),程序表項的其他值大部分與程序A的相同,具體說來,就是C和A共享代碼段,并且C将A的資料空間,堆棧等複制一份 ,然後從A建立C的地方開始運作。
-->fork()函數
pid_t fork(void);
其包含在 unistd.h 頭檔案中,
- 其中pid_t是表示“type of process id”的32位整數,
- 至于函數的傳回值,取決于在哪個程序中來檢測該值,如果是在新建立的程序中,其為0;如果是在父程序中(建立新程序的程序),其為新建立的程序的id; 如果建立失敗,則傳回負值。
那麼如何建立一個新程序以運作一個新程式,那就會乃至exec函數,它們兩者相結合就可以了~
程序執行
-->執行程式
當啟動一個新程序以後,新程序會複制父程序的大部份上下文并接着運作父程序中的代碼,如果我們使新程序不運作原父程序的代碼,轉而運作另外一個程式集中的代碼,這就相當于啟動了一個新程式。這裡的代碼我們可以了解成一個可執行程式。
是以,要運作一個新程式,需要最基本的兩步:
- 建立一個可運作程式的環境,也就是程序。
- 将環境中的内容替換成你所希望的,也就是用你希望運作的可執行檔案去覆寫新程序中的原有映像,并從該可執行檔案的起始處開始執行。
要做到第一點,非常簡單,fork函數就可以,要做到第二點,則可以利用exec函數族。
exec是一族函數的簡稱,包含在<unistd.h>中它們作用都一樣,用一個可執行檔案覆寫程序的現有映像,并轉到該可執行檔案的起始處開始執行。
原型如下:
1 int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
2 int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
3 int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/);
4 int execv(const char *path, char *const argv[]);
5 int execvp(const char *file, char *const argv[]);
6 int execve(const char *path, char *const argv[], char *const envp[]);
名字這麼相近的函數,感覺好容易混淆,那就從l,v,p,e 這樣的字尾來區分:
- l:參數為一個逗号分隔的參數清單,并以char* 0作為清單結尾(NULL)
- v: 參數為字元串數組,數組的最後一個元素為char* 0
- p: 可以通過系統環境變量查找檔案位置
- e:調用者顯示傳入環境變量
值得注意的是
- arg0是可執行檔案本身.
- 最後有一個注釋/*, (char*)0 */是提醒我們最後一個參數應該傳空字元串。
- 如果函數運作成功,則不會有任何傳回值,否則傳回-1,而具體的錯誤号會被設定在errno,errno是一個全局變量,用于程式設定錯誤号,跟win32的getLastError函數類似。
- 注意exec是"覆寫"程序的現有映像(新程序執行結束後便直接退出了).
程序控制
-->wait函數
wait函數将目前程序休眠,直到該程序的某個子程序結束或者有特定的信号來喚醒。如果子程序正常結束,則講子程序的程序id(pid)作為傳回值,發生錯誤則傳回-1,而status參數講傳出子程序的結束狀态值。
原型如下
pid_t wait (int * status); //包含在 <sys/wait.h> 中
-->程序的各個狀态
TASK_RUNNING
可執行狀态。這是 “程序正在被CPU執行” 和 “程序正在可執行隊列中等待被CPU執行” 統稱。也可以将它們拆開成“RUNNING”和“READY”兩種狀态。
TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE
可中斷的睡眠狀态 和 不可中斷的睡眠狀态。處于睡眠狀态的程序不會被排程到CPU進行執行,而是否可中斷的意思是指程序是否會響應異步信号,如果是可中斷的,當程序收到某個信号時其會重新回到TASK_RUNNING狀态。值得注意的是,如果處于不可中斷的睡眠狀态時,程序将不響應異步信号,比如你無法“kill -9”
TASK_STOPPED
暫停狀态。這裡的STOPPED是指停止運作(暫停),而不是程序終止。向程序發送SIGSTOP信号可以讓程序暫停下來,相反,發送SIGCONT可以将其從TASK_STOPPED狀态喚醒而重新進入TASK_RUNNING狀态。
TASK_TRACED
被跟蹤狀态。一個程序被另一個程序“TRACE(跟蹤)"最經典的例子是DEBUG,比如使用gdb或任何一款ide的debug功能。
TASK_TRACED和TASK_STOPPED非常相近,都是讓程序暫停下來,差別是不能通過向TASK_TRACED的程序發送SIGCONT信号讓其恢複,隻能由跟蹤該程序的那個程序發送PTRACE_CONT,PTRACE_DETACH等,也就是說得讓跟蹤程序來決定是否挂起或繼續被跟蹤程序,當然,跟蹤程序如果退出,被跟蹤程序也會重新回到TASK_RUNNING狀态
TASK_DEAD
僵屍狀态。很搞笑的名字,之是以是“僵屍”而不是“死亡”是因為程序已不響應任何信号以及大部分相關資料已被清除,但其TASK_STRUCT結構仍存在,這個結構相當于程序的“軀殼”,還保留着一些資訊,父程序可以利用這些資訊得到程序終止前的一些狀态。如果你看到某些文檔上描寫的ZOMBIE也是指的這個狀态。
-->退出/終止程序
1 void _exit(int status)
2 void exit(int status)
這兩個函數都是讓程序退出.
參數status表示程序将以何種狀态退出,在<stdlib.h>中預定義了一些狀态,比如EXIT_SUCCESS(值為0)表示以成功狀态退出,EXIT_FAILURE(值為1)表示以失敗狀态退出。
調用_exit函數時,其會關閉程序所有的檔案描述符,清理記憶體以及其他一些核心清理函數,但不會重新整理流(stdin, stdout, stderr ...).
exit函數是在_exit函數之上的一個封裝,其會調用_exit,并在調用之前先重新整理流。
void abort ()
非正常地退出程序。其會産生一個SIGABORT信号,然後使程序戛然而止,也就意味着其不會進行清理工作, 但它會重新整理緩沖區。
void atexit( void (*f) () )
如果想在程序正常結束之前幹一點自定義的事情,就可以調用這個函數. 其簡單地利用你傳入的函數指針執行一個函數回調。
值得注意的是:其僅僅在調用exit函數結束程序或程序執行完所有代碼後自然結束這兩種狀态下,回調函數才會被執行,也就是說如果程序是被_exit或abort結束的,則atexit函數無效
-->暫停程序
int pause()
暫停程序,可以使用pause函數,其會挂起目前程序直到有信号來喚醒或者程序被結束。
如果你僅僅需要簡單地暫停一下(press any key to continue...), 可以使用 system("pause")這個系統調用,甚至是getch()之類的。
unsigned sleep(unsigned seconds)
sleep系列函數都是讓程序挂起一段時間,sleep隻能精确到秒,usleep能精确到微妙,而nanosleep傳說精度更高。
-->程序跟蹤
long ptrace(/*some args*/)
要像debug程式一樣去跟蹤程序,是一個比較複雜的問題。
-->waitpid 與 wait(等待子程序結束)
經常看到的關于waitpid的經典例子是:你下載下傳了某個軟體的安裝程式A,其在安裝即将結束時啟動了另外一個流氓軟體的安裝程式B,當B也安裝結束後,其告訴你所有安裝成功了。A和B分别在不同的程序中,A如何啟動B并知道B安裝完成了呢?
可以很簡單地在A中用fork啟動B,然後用waitpid(或wait)來等待B的結束。
參數pid:
- 如果大于0,表示父程序所需要等待的子程序的程序号
- 如果等于0,則表示任意任意group id和父程序相同的子程序
- 如果等于-1, 則表示等待任意子程序(有多個子程序時,任意程序結束,函數都會傳回),此時waitpid和wait相同。
- 如果小于-1,則取其絕對值作為需要等待的子程序的程序号
參數stat_loc:
表示程序退出時程序狀态的存儲位置,有一些專門的宏類根據該位置計算狀态值
另外,waitpid和wait的關系: wait(&status) 等于 waitpid(-1, &status, 0)
QQ聯系方式:[email protected]
出處:lcw.cnblogs.com
本文申明:本文版權歸作者和部落格園共有,歡迎轉載,轉載請注明出處.