1. 程式人生 > 其它 >Linux多程序開發I

Linux多程序開發I

1. 程式 vs 程序
  • 程式時包含一系列資訊的檔案,這些檔案描時瞭如何在執行時建立一個程序。(二進位制格式標識、機器語言指令、程式入口地址、資料、符號表和重定位表、共享庫和動態連結資訊)
  • 程序是正在執行的程式的例項,是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動,是作業系統動態執行的基本單元,資源分配的基本單元。
2. 單道程式 vs 多道程式
  • 單道程式:在計算機記憶體中只允許一個程式執行。
  • 多道程式:在計算機記憶體中同時存放幾道相互獨立的程式,在管理程式控制下,相互穿插執行,這些程式共享計算機系統資源。巨集觀上而言,這些程式看起來像是同時執行的,微觀上任意時刻CPU上只有一個程式在執行。(CPU執行指令是納秒級的,人眼反應速度是毫秒級的)
3. 時間片(處理器片),作業系統分配給每個正在執行的程序微觀上的一段CPU時間。由作業系統核心的排程程式分配給每個程序。 4. Linux程序排程的演算法有哪些? 5. 並行 vs 併發
  • 並行:在同一時刻,有多條指令在同個處理器上同時執行。
  • 併發:在同一時刻,一個處理器上只能有一條指令執行,但多個程序被快速輪換執行,使得在巨集觀上具有多個程序同時執行的效果。但在微觀上,只是把時間分成若干段,使多個程序快速交替執行。
6. 同一時刻多個使用者訪問伺服器,怎麼處理?併發問題。 7. ulimit -a 核心中的資源上限 8.程序的狀態。
  • 執行態:程序佔有處理器正在執行。
  • 就緒態:程序具備執行條件,已分配到除CPU之外的所有資源,等待系統分配處理器以後即可執行(被排程執行)。
  • 阻塞態:程序不具備執行條件,正在等待某個事件的完成。
  • 新建態:程序剛被建立,尚未分配資源和進入就緒佇列。
  • 終止態:程序完成任務,或出現無法克服的錯誤而異常終止,或被其他有終止權的程序終止。進入終止態的程序不再執行,但依然保留在作業系統中,等其他程序未完成對終止態程序的資訊抽取後,作業系統將該程序刪除。
9. 程序相關命令。
# 檢視程序
ps -aux/ajx
-a  顯示所有程序
-u  顯示詳細資訊
-x  顯示沒有控制終端的程序
-j  列出與作業控制相關的資訊
 
# 實時顯示程序動態
top -d 更新時間
在top命令執行後可以按鍵對顯示的結果排序:
M 記憶體
P CPU
T 程序執行時長
U 使用者名稱
K 殺死指定PID程序
 
# 殺死程序
kill 程序pid
kill -9 程序pid       # 強制殺死程序
kill -l              # 列出所有訊號
killall 程序name      # 根據程序名殺死程序

  

10. 程序由程序標識號(PID)表示。任何繼承(除init程序)都是由另一個程序建立,該程序稱為被建立程序的父程序,對應的程序號為父程序號(PPID)。 11. 程序組是一個或多個程序的集合。它們之間相互關聯,程序組可以接收同一個終端的各種訊號,關聯的程序有一個程序組號(PGID)。 12. 建立程序。
  • 程序樹結構模型。一個程序可以建立新的程序,新程序即為子程序,該子程序也可以建立新的子程序。
  • fork()之後,除了核心區的程序pid不一樣之外,子程序的虛擬地址空間與父程序的虛擬地址空間內容完全一樣。
  • 子程序只會從fork()函式的指令開始執行。後續可以根據fork()函式的返回值判斷在父程序和子程序執行不同的程式碼。
#include <sys/types>
#include <unistd.h>
pid_t fork(void);
    - 作用:用於建立子程序
    - 返回值:fork()返回值會返回兩次。一次是在父程序中,一次是在子程序中。
             在父程序中返回建立的子程序的ID,在子程序中返回0。
             在父程序中返回-1表示建立子程序失敗,並設定errno。
?如何區分父程序和子程序:通過fork()的返回值。
 
pid_t getpid(void);
pid_t getppid(void);

  

13. fork()原理。
  • 實際上,Linux的fork()函式使用是通過寫時拷貝(copy-on-write)實現。寫時拷貝是一種可以推遲甚至避免拷貝資料的技術。此時並不複製整個程序的地址空間,而是讓父子程序共享同一個地址空間。只在需要寫入時複製地址空間,從而使各個程序擁有各自的地址空間。即,資源的複製是在需要寫入時進行的,在此之前是以只讀方式共享。fork之後父子程序共享檔案,fork產生的子程序與父程序相同的檔案的檔案描述符指向相同的檔案表,引用計數增加,共享檔案偏移指標。
14.父子程序之間的關係。
  • 區別:1.fork()函式的返回值不同,父程序中>0返回子程序的ID,子程序中返回0;2.核心的PCB中的一些資料不同,當前程序的ID,PID,當前程序的父程序的ID,PPID,訊號集;
  • 共同點:1.子程序剛被創建出來,還沒有執行任何寫資料操作時,使用者區的資料和檔案描述符表是一樣的;
  • 父子程序對變數是否共享?子程序剛建立的時候是共享的,如果某些變數的值被修改了則不會繼續共享該變量了。(寫時拷貝)
15. GDB多程序除錯。GDB預設只能跟蹤一個程序,可以在fork函式呼叫之前通過指令設定跟蹤父程序還是子程序。
# 設定跟蹤父程序還是子程序
set fllow-fork-mode [parent/child]
 
# 設定除錯模式
# on表示除錯當前程序時,其他程序繼續執行,off表示除錯當前程式時其他程序被GDB掛起
set detach-on-fork [on/off]
 
# 檢視除錯的程序
info inferiors
 
# 切換當前除錯的程序
inferior id
 
# 使程序脫離GDB除錯
detach inferiors id

  

16.exec函式族。
  • 根據指定的檔名找到可執行檔案,並用它來取代呼叫程序的內容。即,在呼叫程序內部執行一個可執行檔案。
  • 找到的可執行檔案的使用者區替換原程序的使用者區(程式碼段、資料段、堆疊等),核心區的資訊(程序ID等)並沒有變,並重新從新的可執行檔案的main函式處開始執行。且原程序在呼叫exec()函授後的程式碼都不會被執行了。
  • 所有一般都會使用fork建立一個子程序,然後在子程序中呼叫exec()函式,使原程序不受影響。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);    
    - path: 需要指定的執行的檔案的路徑或名稱
    - arg: 執行可執行檔案所需要的引數列表,第一個引數為執行程式的名稱,第二個引數為程式執行所需要的引數列表,引數最後以NULL結束
    - 返回值:只有呼叫失敗才有返回值,返回-1並設定errno
 
int execlp(const char *file, const char *arg, ...);
    - 作用:會到環境變數中查詢指定的可執行檔案,如果找到就執行,找不到就執行不成功
    - file:需要執行的可執行檔案的檔名
    - exec("ps", "aux", NULL)
 
int execle(const char *path, const char *arg, ...);
    - 指定環境變數陣列,e.g.: char *envp[] = {"/home/aaa", "/home/bbb", "/bin/ps"};
    - exec("/bin/ps", "aux", NULL, envp);
 
int execv(const char *path, char *const argv[]);
    - 將可執行檔案的引數儲存在陣列中,e.g.: char *argv[] = {"ps", "aux", "NULL"};
    - exec("/bin/ps", argv);
 
int execvp(const char *file, char *const argv[])'
int execvpe(const char *file, char *const argv[], char *const envp[]);
 
int execve(const char *filename, char *const argv[], char *const envp[]);
 
l(list)            引數地址列表,空指標結尾
v(vector)          存有個引數地址的指標陣列的地址
p(path)            按path環境變數指定的目錄搜尋可執行檔案
e(environment)     存有環境變數字串地址的指標陣列的地址
 
18.程序控制。
子程序退出可以釋放使用者區的資料,但是其核心區的資料需要其父程序回收釋放。
# 程序退出
#include <stdlib.h>
void exit(int status);
    - 會呼叫退出處理函式,並重新整理IO緩衝並關閉檔案描述符後再呼叫Unix系統函式_exit();
    - status: 程序退出時的一個狀態資訊。父程序回收子程序資源的時候可以獲取到。 
 
#include <unistd.h>
void _exit(int status);
    - 不會重新整理緩衝區,被C庫函式exit()呼叫。

  

19.孤兒程序。
  • 父程序執行結束,但子程序還在執行。即沒有父程序。
  • 每當出現一個孤兒程序時,核心會把孤兒程序的父程序設定為init,而init程序(pid=1)會迴圈wait()它的已經退出的子程序。
  • 孤兒程序沒有危害。
20.殭屍程序。
  • 每個程序結束後,都會釋放自己地址空間中的使用者區資料,核心區的PCB沒有辦法自己釋放掉,需要父程序釋放。
  • 程序終止時,父程序尚未回收,子程序殘留資源存放於核心中,變成殭屍程序。
  • 殭屍程序不能被 kill -9殺死。
  • 危害:如果父程序沒有回收子程序核心區資源的話,那麼該子程序的程序號就會一直被佔用,但是系統所能使用的程序號是有限,如果產生大量殭屍程序,則導致系統不能產生新的程序。
21.程序回收。
  • 在每個程序退出的時候,核心釋放該程序所有的資源、包括開啟的檔案、佔用的記憶體等,但是仍保留一定的資訊,主要指程序控制快PCB的資訊(包括程序號、退出狀態、執行時間等)。
  • 父程序可以通過wait()或者waitpid()得到子程序的退出狀態並同時徹底清除掉這個程序。
  • 一次wait或waitpid呼叫只能清除一個子程序。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
    - 作用:等待任意一個子程序結束,如果任意一個子程序結束,此函式會回收該子程序資源。
    - wstatus: 程序退出時的狀態資訊,傳出引數
    - 返回值:成功返回被回收的子程序id;失敗(所有子程序都結束、呼叫函式失敗)返回-1
呼叫wait函式的程序會被掛起(阻塞),直到它的一個子程序退出或者收到一個不能被忽略的訊號時才被喚醒(相當於繼續往下執行),
如果沒有子程序了,函式會立刻返回-1;如果子程序都結束了,也會立即返回-1。
 
# 推出資訊相關巨集函式
WIFEXITED(status)           非0,程序正常退出
WEXITSTATUS(status)         如果上巨集為真,獲取程序退出的狀態,exit()的引數
WIFSIGNALED(status)         非0,程序異常終止
WTERMSIG(status)            如果上巨集為真,獲取使程序終止的訊號編號
WIFSTOPPED(status)          非0,程序處於暫停狀態
WSTOPSIG(status)            如果上巨集為真,獲取使程序暫停的訊號的編號
WIFCOTINUED(status)         非0,程序暫停後已經繼續執行
 
# e.g.:
int st;
int ret = wait(&st);
if(ret == -1) break;
if(WIFEXITED(st)) {
    printf("退出的狀態碼: %d\n", WEXITSTATUS(st));
}
if(WIFSIGNALED(st)) {
    printf("被哪個訊號幹掉了:%d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
 
 
pid_t waitpid(pid_t pid, int *wstatus, int options);
    - 作用:回收指定程序號的子程序,可以設定是否阻塞
    - pid: 1.pid>0,某個子程序的pid;2.pid=0,回收當前程序組的所有子程序;3.pid=-1,回收任一子程序,相當於wait();4.pid<-1,回收指定程序組(pid的絕對值)中的子程序。
    - options: 設定阻塞或者非阻塞,0:阻塞;WNOHANG:非阻塞。
    - 返回值:>0 返回子程序的id,=0 如果設定options=WNOHANG,表示沒有子程序的狀態改變;=-1 錯誤。