Linux進程間通訊機制

Linux支持管道、信號、unix system V三種IPC(Inter-Process-Communication)機制。如下分別對三種機制加以簡單介紹。linux

 

1、信號機制:shell

信號又稱做軟中斷,用來通知進程發生了異步事件;主要用於向一個或多個進程發異步事件信號,信號能夠經過鍵盤中斷觸發、當進程訪問虛擬內存中不存在的線性地址或者禁止訪問的地址時也會觸發。也能夠用於shell做業任務時向子進程發送控制命令。編程

Linux系統共有30多種信號,每一個信號名稱都以 SIG 三個字符開頭,例如異常終止信號名爲 SIGABRT。在頭文件<signal.h>中,這些信號都被定義爲正整數。如下爲經常使用信號:數組

 

Linux 可使用 kill 命令向指定進程發送指定信號,kill命令的用法爲:數據結構

kill [選項]PID異步

進程能夠選擇忽略上面的大多數信號,但和SIGKILL是不可忽略的。其中SIGSTOP信號,使進程中止執行;而SIGKILL信號使進程停止。對於其餘狀況,進程能夠自主決定如何處理各類信號:它能夠阻塞信號;若是不阻塞,也能夠選擇由進程本身處理信號或者由內核來處理。由內核來處理信號時,內核對每一個信號使用相應的缺省處理動做,例如:當進程收到SIGFPE信號(浮點異常)時,內核的缺省動做是進行內核轉貯 (core dump),而後停止該進程。函數

信號之間不存在內在的相對優先級。若是對同一個進程同時產生兩個信號的話,它們會按照任意順序提交給該進程,而且對同種信號沒法區分信號的數量。測試

Linux使用存貯在每一個進程task_struct結構中的信息實現信號機制它支持的信號數受限於處理器的字長,具備32位字長的處理器有32種信號,64位字長的處理器最多有64種信號,task_struct以下所示:優化

struct task_struct {ui

         volatile long state;  /* -1 unrunnable, 0 runnable, >0 stopped */

         void *stack;

         atomic_t usage;

         unsigned int flags;   /* per process flags, defined below */

         unsigned int ptrace;

 

         int lock_depth;                   /* BKL lock depth */        

  …

  /* signal handlers */

  struct signal_struct *signal;

  struct sighand_struct *sighand;

         …

}

當前未處理的信號記錄在signal域中,並把阻塞信號掩碼對應位設置爲阻塞狀態。但對 SIGSTOP和SIGKILL信號來講,全部的信號都被設置爲阻塞狀態。若是一個被阻塞的信號產生了,就將一直保持未處理狀態,直到阻塞被取消。

並非系統中的每一個進程均可以向其餘的進程發消息,只有內核和超級用戶能夠作到這一點。普通的進程只能向同一進程組或具備相同的uid和gid的進程發送信號。信號能夠經過設置task_struct結構signal域中相應中的位來產生。若是一個進程沒有阻塞信號,正處於可中斷的等待信號狀態中,當等待的信號出現時 ,系統能夠經過把該進程的狀態變成運行狀態 ,而後放入候選運行隊列中的方法來喚醒它。經過上面的方法在下次調度時 ,進程調度器會把該進程做爲候選運行進程進行調度。

Linux兼容POSIX標準,進程在某個信號處理例程被調用時,能指出哪些信號能夠被阻塞。這意味着在調用進程信號處理例程時須要改變阻塞掩碼,當信號處理例程結束時,阻塞掩碼必需要恢復到初始值。所以linux增長了一次對清理例程的調用。清理例程按照信號處理例程的調用棧來恢復初始的阻塞掩碼。在幾個信號處理例程都須要被調用時,linux也提供了優化方案。linux把這些例程壓入棧中,這樣每當一個處理例程退出時,下一個處理例程當即被調用,當全部處理例程都完成後清理例程被調用。

2、管道機制:

管道是一個進程鏈接數據流到另外一個進程的通道, 它一般是用做把一個進程的輸出經過管道鏈接到另外一個進程的輸入。在 Linux 命令中一般經過符號「 |」來使用管道,例如:

$ ps -ef | grep init

此命令中 ps 是一個獨立的進程, grep 也是一個獨立的進程,中間的管道把原本要輸出到屏幕的數據輸出到 grep 這個進程中,做爲 grep 這個進程的輸入。

在Linux系統中,管道用兩個指向同一個臨時性VFS索引節點的文件數據結構來實現。這個臨時性的VFS索引節點指向內存中的一個物理頁面。下圖代表每一個文件數據結構包含指向不一樣文件操做例程向量的指針。一個例程用於寫管道,另外一個用於從管道中讀數據。從通常讀寫普通文件的系統調用的角度來看,這種實現方法隱藏了下層的差別。當寫進程執行寫管道操做時,數據被複制到共享的數據頁面中 ;而讀進程讀管道時,數據又從共享數據頁中複製出來。Linux必須同步對管道的訪問,使讀進程和寫進程步調一致。爲了實現同步, Linux使用鎖、等待隊列和信號量這三種方式。

 

管道分爲匿名管道和命名管道兩種,匿名管道主要用於兩個進程間有父子關係的進程間通訊,命名管道主要用於沒有父子關係的進程間通訊。

2.1 匿名管道

匿名管道是不能在文件系統中以任何方式看到的半雙工管道。半雙工管道意味着管道的一端只讀或只寫。父子進程間匿名管道通訊示意圖如圖:

 

2.2命名管道

命名管道也被稱爲 FIFO 文件, 它突破了匿名管道沒法在無關進程之間通訊的限制,使得同一主機內的全部的進程均可以通訊。同時命名管道是一個特殊的文件類型, 它在文件系統中以文件名的形式存在,在stat結構中 st_mode 指明一個文件結點是否是命名管道。

struct stat {

         unsigned long  st_dev;              /* Device.  */

         unsigned long  st_ino;               /* File serial number.  */

         unsigned int   st_mode;                  /* File mode.  */

};

mkfifo()函數用來建立一個命名管道,它的原型以下:

int mkfifo(const char *pathname, mode_t mode);

mkfifo()建立一個真實存在於文件系統中的命名管道文件,參數 pathname指定了文件名,參數 mode 則指定了文件的讀寫權限。 函數成功返回 0,不然返回-1 並設置 errno。

mkfifo()建立命名管道文件後,須要經過命名管道通訊的進程須要打開該管道文件,而後經過 read、 write 函數像操做普通文件同樣進行通訊。

3、System V機制:

System V是Unix中出現最先的三種進程間通訊機制,linux也支持;它們是消息隊列、信息量和共享存儲器。這些 System V的進程間通訊機制使用相同的認證方法,即經過系統調用向內核傳遞這些資源的全局惟一標識來訪問它們,linux使用訪問許可的方式覈對對 System V-IPC對象的訪問,這種方式與文件訪問權限的檢查十分類似。

System V IPC對象的訪問權限是由該對象的建立者經過系統調用來實現的。 linux的每種IPC機制都把 IPC對象的訪問標識做爲對系統資源表的索引 ,但訪問標識不是一種直接的索引,而是由索引標識經過某些運算來產生的對象索引。

Linux系統中全部表明 System V IPC 對象的數據結構中都包括ipc_perm數據結構,在ipc_perm結構中有擁有者和建立者進程的用戶標識和組標識、該對象的訪問模式以及 IPC對象的密鑰。密鑰的用處是肯定 System V IPC 對象的索引標識。 Linux系統中支持兩種密鑰:公鑰和私鑰。若是 IPC對象的密鑰是公共的,那麼系統中的進程在經過權限檢查後就能夠獲得System V IPC對象的索引標識。但要注意 System V IPC對象不是經過密鑰而是經過它們的索引標識來訪問的。

如下爲ipc_perm數據結構:

struct ipc_perm

{

         __kernel_key_t        key;

         __kernel_uid_t        uid;

         __kernel_gid_t        gid;

         __kernel_uid_t        cuid;

         __kernel_gid_t        cgid;

         __kernel_mode_t    mode;

         unsigned short         seq;

};

3.1 消息隊列

消息隊列容許一個或多個進程向隊列中寫入消息, 而後由一個或多個讀進程讀出(見下圖)。Linux系統維護一個消息隊列的表。該表是msqid_ds結構的數組,數組中每一個元素指向一個能徹底描述消息隊列的msqid_ds數據結構。一旦一個新的消息隊列被建立,則在系統內存中會爲一個新的msqid_ds數據結構分配空間,並把它插入到數組中。每一個msqid_ds結構都包含ipc_perm數據結構以及指向進入該隊列的消息的指針。除此以外,Linux還記錄像隊列最後被更改的時間等隊列時間更改信息。 msqid_d結構還包括兩個等待隊列;一個用於存放寫進程的消息,另外一個用於消息隊列。

每次進程要向寫隊列寫入消息時,系統都要把它的有效用戶標識和組標識與該隊列的ipc_perm數據結構中的訪問模式進行比較。若是進程能夠寫隊列,那麼消息會從進程的地址空間複製到一個 msg數據結構中,而後系統把該msg數據結構放在消息隊列的尾部。因爲Linux限制寫消息的數量和消息的長度,因此可能會出現沒有足夠的空間來存放消息的狀況。這時當前進程會被放入對應消息的寫等待隊列中,系統調用進程調度器選擇合適的進程運行。在該消息隊列中有一個或多個消息被讀出時,睡眠的進程會被喚醒。

 struct msqid_ds {

         struct ipc_perm msg_perm;

         struct msg *msg_first;             /* first message on queue,unused  */

         struct msg *msg_last;              /* last message in queue,unused */

         unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */

         unsigned long  msg_lqbytes;         /* ditto */

         unsigned short msg_cbytes;   /* current number of bytes on queue */

         unsigned short msg_qnum;     /* number of messages in queue */

         unsigned short msg_qbytes;   /* max number of bytes on queue */

         __kernel_ipc_pid_t msg_lspid;        /* pid of last msgsnd */

         __kernel_ipc_pid_t msg_lrpid;        /* last receive pid */

};

從隊列中讀消息的過程與前面類似,進程對寫隊列的訪問權限會再次被覈對。一個讀進程能夠選擇得到隊列中的第一個消息而不考慮消息的類型,仍是讀取某種特別類型的消息。若是沒有符合要求的消息的話,讀進程會被加入到該消息的讀等待隊列中,系統喚醒進程調度器調度新進程運行。一旦有新消息被寫入消息隊列。睡眠的進程被喚醒,並再次運行。

3.2 信號量

多進程編程中須要關注進程間同步及互斥。同步是指多個進程爲了完成同一個任務相互協做運行,而互斥是指不一樣的進程爲了爭奪有限的系統資源(硬件或軟件資源)而相互競爭運行。

信號量是用來解決進程間的同步與互斥問題的一種進程間通訊機制,它是一個特殊的變量,變量的值表明着關聯資源的可用數量。信號量只能進行的兩個原子操做: P 操做: V 操做。

P 操做:若是有可用的資源(信號量值>0),則佔用一個資源(給信號量值減 1);若是沒有可用的資源(信號量值=0),則進程被阻塞直到系統將資源分配給該進程(進入信號量的等待隊列,等到資源後喚醒該進程)。

V 操做:若是在該信號量的等待隊列中有進程在等待資源,則喚醒一個阻塞進程;若是沒有進程等待它,則釋放一個資源(給信號量值加 1)。

System V的每一個IPC信號量對象都對應一個信號量數組,在 Linux中用semid_ds數據結構來表示它,以下:

struct semid_ds {

         struct ipc_perm       sem_perm;                /* permissions .. see ipc.h */

         __kernel_time_t      sem_otime;               /* last semop time */

         __kernel_time_t      sem_ctime;               /* last change time */

         struct sem        *sem_base;              /* ptr to first semaphore in array */

         struct sem_queue *sem_pending;          /* pending operations to be processed */

         struct sem_queue **sem_pending_last;        /* last pending operation */

         struct sem_undo     *undo;                        /* undo requests on this array */

         unsigned short         sem_nsems;             /* no. of semaphores in array */

};

系統中全部的semid_ds數據結構都被一個叫semary的指針向量指向。在每一個信號量數組中都有 sem_nsems域,這個域由sem_base指向的sem數據結構來描述。全部容許對System V IPC信號量對象的信號量數組進行操做的進程,都必須經過系統調用來執行這些操做。在系統調用中能夠指出有多少個操做。而每一個操做包含三個輸入項:信號量的索引、操做值和一組標誌位。信號量索引是對信號量數組的索引值,而操做值是加到當前信號量值上的數值。首先Linux會測試是否全部的操做都會成功 (操做成功指操做值加上信號量當前值的結果大於 0,或者操做值和信號量的當前值都是 0 )。若是信號量操做中有任何一個操做失敗, Linux在操做標誌沒有指明系統調用爲非阻塞狀態時,會掛起當前進程。若是進程被掛起了,系統會保存要執行的信號量操做的狀態,並把當前進程放入等待隊列中。經過在棧中創建一個sem_queue數據結構,並填入相應的信息的方法來實現前面的保存信號量操做狀態的。新的 sem_queu 數據結構被放在對應信號量對象的等待隊列的末尾 ( 經過使用sem_pending和sem_pending_lastt指針),當前進程被放在 sem_queue數據結構的等待隊列中,而後系統喚醒進程調度器選擇其餘進程執行。

 

信號量存在着死鎖的問題,當一個進程進入了關鍵段,改變了信號量的值後,因爲進程崩潰或被停止等緣由而沒法離開關鍵段時,就會形成死鎖。 Linux經過爲信號量數組維護一個調整項列表來防止死鎖。主要的想法是在使用調整項後,信號量會被恢復到一個進程的信號量操做集合執行前的狀態。調整項被保存在sem_undo數據結構中,而這些sem_undo數據結構則按照隊列的形式放在 semid_ds數據結構和進程使用信號量數組的task_struct數據結構中。

當進程被刪除時,退出時Linux會用這些sem_undo數據結構集合對信號量數組進行調整。若是信號量集合被刪除了,那麼這些sem_undo數據結構還存在於進程的sem_undo結構的隊列中,而僅把信號量數組標識標記爲無效。在這種狀況下,信號量清理程序僅僅丟掉這些數據結構而不釋放它們所佔用的空間。

3.3 共享內存

共享內存是容許兩個不相關的進程訪問同一個邏輯內存的進程間通訊方法,是在兩個正在運行的進程之間共享和傳遞數據的一種很是有效的方式。

不一樣進程之間共享的內存一般安排爲同一段物理內存。進程能夠將同一段共享內存鏈接到它們本身的地址空間中,全部進程均可以訪問共享內存中的地址,就好像它們是由用 C語言 malloc()分配的內存同樣。兩個進程使用共享內存通訊機制以下圖所示。

 

共享存儲區不須要在全部進程的虛存中佔有相同的虛地址。像全部的 System V IPC對象同樣,共享存儲區的訪問控制是經過密鑰和訪問權限檢查來實現的。一旦某一內存區域被共享了,系統就沒法檢查進程如何使用這部份內存區域。所以系統必須使用信號量等其餘的機制來同步對存儲器的訪問。

相關文章
相關標籤/搜索