Linux下进程通信知识点学习笔记(一)
4種主要事件導致進程創建:
- 系統的初始化;
- 執行了正在運行的進程所調用的進程創建系統調用;
- 用戶請求創建一個進程;
- 一個批處理作業的初始化;
進程的終止:
- 正常退出;
- 出錯退;
- 嚴重錯誤;
- 被其他進程殺死;
當編譯器給定程序的編譯工作之后,編譯器執行一個系統調用,通知操作系統它的工作已經完成,在unix/linux系統中調用的是exit()。
進程的狀態:運行態,就緒態,阻塞態;
多線程提供了一種解決方案,有關的進程可以用一個輸入線程,一個處理線程和一個輸出線程構造。輸入線程把數據讀入到輸入到緩沖區中,處理線程從輸入緩沖區中取出數據,處理數據,并把結果放到輸出緩沖區中;輸出線程把這些結果寫到磁盤上。這種模型只有當系統調用只阻塞調用線程而不是阻塞整個進程時,才能正常工作。
進程通信(Inter Process Communication, IPC)
涉及共享內存、共享文件和共享任何資源的情況都會引起沖突,最終的結果取決于程序運行的精確時序,稱為競爭機制。需要采取“互斥”操作,使多個進程不可能同時處于臨界區中。一個好的解決方案,需要滿足4個條件:
- 任何兩個進程不能同時處于其臨界區;
- 不應對CPU的速度和數量做任何假設;
- 臨界區外運行的進程不得阻塞其他進程;
- 不得使進程無限期等待進入臨界區;
中斷是指計算機運行過程中,出現某些意外情況需主機干預時,機器能自動停止正在運行的程序并轉入處理新情況的程序,處理完畢后又返回原被暫停的程序繼續運行。
?
進程中的ID,聲明為pid_t類型:
- 進程ID:pid,利用getpid()函數可以獲得;
- 父進程ID:ppid,利用getppid()函數可以獲得;
- 有效用戶ID:euid,可以由geteuid()函數獲得;
- 有效組ID:egid,可以由getegid()函數得到;
- 實際用戶ID:uid,可以由getuid()函數獲得;
這六個ID保存在內核中的數據結構中。
進程函數:
pid_t fork(void);對于父進程返回紫禁城的進程ID,對于子進程返回0。子進程復制的是父進程的數據段和堆棧段,并和父進程共享代碼段。fork()函數出錯的原因:系統中已經有太多進程,調用fork()函數的用戶進程太多。
pid_t vfork(void);與其說是創建進程不如說是創建了一個線程,子進程與父進程共享所有資源,子進程對共享資源的修改會影響到父進程,兩者共用一個進程ID。子進程一定比父進程先運行完,子進程運行期間父進程相當于被阻塞。返回訊息與fork()函數一樣。
void exit(int status);【stdlib.h】這個退出函數會深入內核注銷進程的內核數據結構,并且釋放進程的資源。
int exec(const char * pathname, const char * arg0, ……);Linux下使用該函數執行一個新函數,該函數在文件系統中搜索指定路徑的文件;
int system(const char * cmdstring);【stdlib.h】參數cmdstring是需要執行的shell命令,該函數是一個庫函數,其中封裝了fork、exec、waitpid三個系統調用。
pid_t wait(int * statloc);調用wait()函數的進程會被阻塞,直到進程的任何一個子進程結束(在利用fork函數創建進程之后wait類似于vfork創建的子進程),該函數會返回最近結束的的子進程的進程ID。若是想要等待指定子進程結束,可以利用while(pid != wait(&status));
pid_t waitpid(pid_t pid, int * statloc, int options);等待指定的進程結束,第一個參數為指定的進程ID,第二個參數為和wait函數中相同,接收進程返回的執行結果,第三個參數是控制選項。
當父進程創建的子進程退出后,父進程沒有回收子進程的的結束信息時,子進程就變成了一個僵尸進程。當父進程在子進程結束之前結束運行,這時該子進程就成為孤兒進程,Linux中由init進程負責領養所有的孤兒進程,作為系統的守護進程,init進程被設計為永遠調用wait()函數,即init進程的子進程不會是僵尸進程,可以使用這種方法避免僵尸進程的產生。
time_t time(time * t);【time.h】Linux/Unix中由time函數提供時間,參數t為一個time_t類型的指針。
信號及信號處理
信號是一種進程通信的方式,又稱為軟中斷。一個進程一旦受到信號就會打斷原來的程序執行流程來處理信號,這種通信方式是異步的。使用 kill –l 命令可以查看當前系統所支持的信號列表即相應編號;
5種方式產生信號:
- 用戶通過終端快捷鍵組合,Ctrl+c產生SIGINT,Ctrl+z產生SIGKILL;
- 硬件異常產生信號,有硬件檢測通知內核,內核通過信號通知當前的進程;
- 一個進程調用kill(2)函數向另一個進程發送信號;
- 利用kill(1)命令發送某個信號給某個進程,kill(1)也是調用kill(2)實現的;
- 當內核檢測到某種軟件條件發生時,也可以通過信號通知進程;
處理信號
對于一個信號,Linux環境下的進程只有3種處理方式:
- 忽略此信號;
- 注冊一個信號處理函數,并要求內核在接收到信號時切換到用戶套調用該處理函數,這種方式稱為捕捉到一個信號。用戶程序需要對某些信號做一些自定義的處理;
- 執行默認動作,不同的信號有不同的系統默認動作,系統使用的默認動作只有兩種:終結此信號或者忽略此信號;
在進程捕捉到信號之后,無論進程執行到何處都會跳到信號處理函數中執行,從信號處理函數返回后再恢復之前的代碼位置繼續執行。信號處理函數不判斷此時進程執行到何處;
原子操作:不會被線程調度機制打斷的操作。volatile是一個類型修飾符,volatile的作用是作為指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值。
Linux允許用戶自定義信號處理函數,還提供借口,共使用者產生一個信號。
設置信號處理函數
void (*signal (int signo, void (* func))) (int);【signal.h】 Linux允許用戶提供自己的信號處理函數,使用signal()函數將處理函數加載,并且通知系統。該函數的第一個參數為需要加載的信號,第二個參數為信號處理函數的函數指針(函數名),該函數返回一個函數指針,指向上一次的信號處理程序。如果加載錯誤,返回SIG_ERR。常用語句“if(signal(SIGUSR1, handler) == SIG_ERR){}”
void handler(int signo);信號處理函數的原型;
int kill(pid_t pid, int signo);信號發送函數原型,發送成功返回0,否則返回-1;
發送信號需注意:
- 該進程有向另一個進程發送信號的權限;
- 系統進程不能接收信號;
int raise(int signo);向本進程發送信號的函數,相當于kill(getpid(), int signo)。該函數可以實現exit()函數功能使進程退出,所不同的是此用法不作任何善后處理。
設置Linux定時器
unsigned int alarm(unsigned int seconds);函數的參數表示設置的秒數,如果系統時間超過該時間后函數就會向調用它的進程發送一個SIGALRM信號,這個信號的默認動作是終止調用該函數的進程。當函數的參數是0的時候可以取消一個定時器。如果之前沒有設置定時器,則設置定時器時成功則返回0,若之前設置過定時器且已經超時,則調用定時器函數時依然返回0,若之前設置了定時器尚未超時調用該定時器函數返回剩余的秒數;
int pause(void);運行、阻塞、就緒是進程的三個基本狀態,就緒態的進程可以被調度,阻塞的進程由于不能運行所以不參與調度。有時候當一個進程的運行條件已經具備時仍需要進程阻塞(例如考慮到時間上的調度順序,sleep()),這種由進程自愿進入阻塞的情況稱為進程掛起,Linux下使用pause()函數掛起一個進程;pause()函數是調用該函數的進程進入到掛起狀態,直到一個信號到來,并且執行一個信號處理函數從其返回后,pause函數才返回-1。
unsigned int sleep(unsigned int nsec);pause()函數使進程無時間限制的國企,若想使進程在一定時間后恢復運行則使用sleep函數。sleep()函數返回值分兩種:
- 掛起的時間超過了指定的休眠時間,返回0;
- 掛起期間被信號喚醒,這時sleep函數返回掛起以來經過的時間。
信號集
進程所能夠捕捉并處理的信號稱為信號集,信號機的實現通常是一個位向量,其中的每一位對應產生一個Linux系統中的信號。其數據類型為sigset_t,信號集處理函數【signal.h】:
int sigemptyset(sigset_t *set);清空信號集,即所有的位置0;
int sigfillset(sigset_t *set);信號集全部填充,將所有的位置1;
int sigaddset(sigset_t *set, int signo);信號集添加某信號;
int sigdelset(sigset_t *set, int signo);信號集刪除某信號;
int sigismember(sigset_t *set, int signo);檢查某信號是否在信號集中,在返回1;
屏蔽信號
希望進程阻塞一些信號,及時接收到該信號,也不用做處理。阻塞一個信號稱為信號屏蔽。每一個進程內部有一個信號屏蔽字,標記屏蔽信號。
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);第2個參數是一個信號集,該信號集中被設置的位表示需要被屏蔽的信號,第3個參數表示原來的屏蔽信號集,第1個參數指定三種關系:
- SIG_BLOCK將set集合中信號添加到屏蔽集;
- SIG_UNBLOCK將set集合中信號從屏蔽集中解除;
- SIG_SETMASK將屏蔽集重置為set;
如果第2個參數是NULL,表示忽略原來的信號屏蔽字。restrict為類型限定符,用于告訴編譯器,對象已經被指針所引用,不能通過除該指針外所有其他直接或間接的方式修改該對象的內容。
進程通信
IPC類型:
- 管道:半雙(全雙)匿名(FIFO)/命名管道
- System V/POSIX IPC:消息隊列、信號量、共享存儲
- 網絡進程通信:Socket套接字(Streams)
管道在系統中相當于一個文件,來緩存所要傳輸的數據,某些方面與文件又不同,例如在數據讀出后管道中就沒有數據了。匿名半雙工管道的特性:數據智能在一個方向上流動,只能在具有公共祖先的進程之間進行通信。
int pipe(int fd[2]);Linux下利用pipe()函數創建一個匿名半雙工管道,參數int fd[2]是一個長度為2 的文件描述符數組,fd[0]是數據讀出端,fd[1]則是數據寫寫入端,函數返回0代表管道創建成功。
與pipe()函數配套使用的函數close(fd[0])、close(fd[1])關閉管道兩個端口,釋放文件資源。
write(fd[1], char *str, size_t length);向管道中寫入數據,數據長度可設置為BUSZ;
read(fd[0], char *str, size_t length);從管道中讀出數據;
#define BUSZ PIPE_BUF;BUSZ為管道默認一次性讀寫的數據;
在使用管道進行數據傳輸的時候,要注意維護管道的順序,當父進程創建了管道,只有子進程已經繼承了管道之后,父進程才可以執行關閉管道的操作。
創建管道的標準庫函數
FILE *popen( const char* command, const char *mode);【stdio.h】command是一個在shell中可運行的命令字符串指針,例如“cat in.txt”。參數mode是一個操作字符指針:r或者w,popen()函數返回值是一個讀或者寫文件指針。popen()函數先創建一個管道,調用/bin/bash –c來執行參數中的command命令字符串。然后函數返回一個標準的I/O文件指針。
int pclose( FILE *stream);該函數的參數stream是一個popen()函數打開的文件描述符,pclose函數即關閉管道。
總結
以上是生活随笔為你收集整理的Linux下进程通信知识点学习笔记(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求一个好听的兽药店名字!
- 下一篇: Linux文件中的stat结构