進程的概念
進程是操作系統(tǒng)的概念,每當(dāng)我們執(zhí)行一個程序時,對于操作系統(tǒng)來講就創(chuàng)建了一個進程,在這個過程中,伴隨著資源的分配和釋放。可以認為進程是一個程序的一次執(zhí)行過程。
進程通信的概念
進程用戶空間是相互獨立的,一般而言是不能相互訪問的。但很多情況下進程間需要互相通信,來完成系統(tǒng)的某項功能。進程通過與內(nèi)核及其它進程之間的互相通信來協(xié)調(diào)它們的行為。
進程通信的應(yīng)用場景
數(shù)據(jù)傳輸:一個進程需要將它的數(shù)據(jù)發(fā)送給另一個進程,發(fā)送的數(shù)據(jù)量在一個字節(jié)到幾兆字節(jié)之間。
共享數(shù)據(jù):多個進程想要操作共享數(shù)據(jù),一個進程對共享數(shù)據(jù)的修改,別的進程應(yīng)該立刻看到。
通知事件:一個進程需要向另一個或一組進程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進程終止時要通知父進程)。
資源共享:多個進程之間共享同樣的資源。為了作到這一點,需要內(nèi)核提供鎖和同步機制。
進程控制:有些進程希望完全控制另一個進程的執(zhí)行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態(tài)改變。
進程通信的方式
管道( pipe ):
管道包括三種:
- 普通管道PIPE:通常有兩種限制,一是單工,只能單向傳輸;二是只能在父子或者兄弟進程間使用.
- 流管道s_pipe: 去除了第一種限制,為半雙工,只能在父子或兄弟進程間使用,可以雙向傳輸.
- 命名管道:name_pipe:去除了第二種限制,可以在許多并不相關(guān)的進程之間進行通訊.
信號量( semophore ) :
信號量是一個計數(shù)器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內(nèi)不同線程之間的同步手段。
消息隊列( message queue ) :
消息隊列是由消息的鏈表,存放在內(nèi)核中并由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點。
信號 ( sinal ) :
信號是一種比較復(fù)雜的通信方式,用于通知接收進程某個事件已經(jīng)發(fā)生。
共享內(nèi)存( shared memory ) :
共享內(nèi)存就是映射一段能被其他進程所訪問的內(nèi)存,這段共享內(nèi)存由一個進程創(chuàng)建,但多個進程都可以訪問。共享內(nèi)存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設(shè)計的。它往往與其他通信機制,如信號兩,配合使用,來實現(xiàn)進程間的同步和通信。
套接字( socket ) :
套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用于不同機器間的進程通信。
各進程間通信的原理
管道
管道是如何通信的
管道是由內(nèi)核管理的一個緩沖區(qū),相當(dāng)于我們放入內(nèi)存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩沖區(qū)不需要很大,它被設(shè)計成為環(huán)形的數(shù)據(jù)結(jié)構(gòu),以便管道可以被循環(huán)利用。當(dāng)管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當(dāng)管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當(dāng)兩個進程都終結(jié)的時候,管道也自動消失。
管道是如何創(chuàng)建的
從原理上,管道利用fork機制建立,從而讓兩個進程可以連接到同一個PIPE上。最開始的時候,上面的兩個箭頭都連接在同一個進程Process 1上(連接在Process 1上的兩個箭頭)。當(dāng)fork復(fù)制進程的時候,會將這兩個連接也復(fù)制到新的進程(Process 2)。隨后,每個進程關(guān)閉自己不需要的一個連接 (兩個黑色的箭頭被關(guān)閉; Process 1關(guān)閉從PIPE來的輸入連接,Process 2關(guān)閉輸出到PIPE的連接),這樣,剩下的紅色連接就構(gòu)成了如上圖的PIPE。
管道通信的實現(xiàn)細節(jié)
在 Linux 中,管道的實現(xiàn)并沒有使用專門的數(shù)據(jù)結(jié)構(gòu),而是借助了文件系統(tǒng)的file結(jié)構(gòu)和VFS的索引節(jié)點inode。通過將兩個 file 結(jié)構(gòu)指向同一個臨時的 VFS 索引節(jié)點,而這個 VFS 索引節(jié)點又指向一個物理頁面而實現(xiàn)的。如下圖
有兩個 file 數(shù)據(jù)結(jié)構(gòu),但它們定義文件操作進程地址是不同的,其中一個是向管道中寫入數(shù)據(jù)的進程地址,而另一個是從管道中讀出數(shù)據(jù)的進程地址。這樣,用戶程序的系統(tǒng)調(diào)用仍然是通常的文件操作,而內(nèi)核卻利用這種抽象機制實現(xiàn)了管道這一特殊操作。
關(guān)于管道的讀寫
管道實現(xiàn)的源代碼在fs/pipe.c中,在pipe.c中有很多函數(shù),其中有兩個函數(shù)比較重要,即管道讀函數(shù)pipe_read()和管道寫函數(shù)pipe_wrtie()。管道寫函數(shù)通過將字節(jié)復(fù)制到 VFS 索引節(jié)點指向的物理內(nèi)存而寫入數(shù)據(jù),而管道讀函數(shù)則通過復(fù)制物理內(nèi)存中的字節(jié)而讀出數(shù)據(jù)。當(dāng)然,內(nèi)核必須利用一定的機制同步對管道的訪問,為此,內(nèi)核使用了鎖、等待隊列和信號。
當(dāng)寫進程向管道中寫入時,它利用標準的庫函數(shù)write(),系統(tǒng)根據(jù)庫函數(shù)傳遞的文件描述符,可找到該文件的 file 結(jié)構(gòu)。file 結(jié)構(gòu)中指定了用來進行寫操作的函數(shù)(即寫入函數(shù))地址,于是,內(nèi)核調(diào)用該函數(shù)完成寫操作。寫入函數(shù)在向內(nèi)存中寫入數(shù)據(jù)之前,必須首先檢查 VFS 索引節(jié)點中的信息,同時滿足如下條件時,才能進行實際的內(nèi)存復(fù)制工作:
- 內(nèi)存中有足夠的空間可容納所有要寫入的數(shù)據(jù);
- 內(nèi)存沒有被讀程序鎖定。
如果同時滿足上述條件,寫入函數(shù)首先鎖定內(nèi)存,然后從寫進程的地址空間中復(fù)制數(shù)據(jù)到內(nèi)存。否則,寫入進程就休眠在 VFS 索引節(jié)點的等待隊列中,接下來,內(nèi)核將調(diào)用調(diào)度程序,而調(diào)度程序會選擇其他進程運行。寫入進程實際處于可中斷的等待狀態(tài),當(dāng)內(nèi)存中有足夠的空間可以容納寫入數(shù)據(jù),或內(nèi)存被解鎖時,讀取進程會喚醒寫入進程,這時,寫入進程將接收到信號。當(dāng)數(shù)據(jù)寫入內(nèi)存之后,內(nèi)存被解鎖,而所有休眠在索引節(jié)點的讀取進程會被喚醒。
管道的讀取過程和寫入過程類似。但是,進程可以在沒有數(shù)據(jù)或內(nèi)存被鎖定時立即返回錯誤信息,而不是阻塞該進程,這依賴于文件或管道的打開模式。反之,進程可以休眠在索引節(jié)點的等待隊列中等待寫入進程寫入數(shù)據(jù)。當(dāng)所有的進程完成了管道操作之后,管道的索引節(jié)點被丟棄,而共享數(shù)據(jù)頁也被釋放。
Linux函數(shù)原型
#include < unistd.h >
int pipe(int filedes[2]);
filedes[0]用于讀出數(shù)據(jù),讀取時必須關(guān)閉寫入端,即close(filedes[1]);
filedes[1]用于寫入數(shù)據(jù),寫入時必須關(guān)閉讀取端,即close(filedes[0])。
程序?qū)嵗?/p>
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if(pipe(fd) 0){ /* 先建立管道得到一對文件描述符 */
exit(0);
}
if((pid = fork()) 0) /* 父進程把文件描述符復(fù)制給子進程 */
exit(1);
else if(pid > 0){ /* 父進程寫 */
close(fd[0]); /* 關(guān)閉讀描述符 */
write(fd[1], "nhello worldn", 14);
}
else{ /* 子進程讀 */
close(fd[1]); /* 關(guān)閉寫端 */
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
exit(0);
}
命名管道
由于基于fork機制,所以管道只能用于父進程和子進程之間,或者擁有相同祖先的兩個子進程之間 (有親緣關(guān)系的進程之間)。為了解決這一問題,Linux提供了FIFO方式連接進程。FIFO又叫做命名管道(named PIPE)。
實現(xiàn)原理
FIFO (First in, First out)為一種特殊的文件類型,它在文件系統(tǒng)中有對應(yīng)的路徑。當(dāng)一個進程以讀(r)的方式打開該文件,而另一個進程以寫(w)的方式打開該文件,那么內(nèi)核就會在這兩個進程之間建立管道,所以FIFO實際上也由內(nèi)核管理,不與硬盤打交道。之所以叫FIFO,是因為管道本質(zhì)上是一個先進先出的隊列數(shù)據(jù)結(jié)構(gòu),最早放入的數(shù)據(jù)被最先讀出來,從而保證信息交流的順序。FIFO只是借用了文件系統(tǒng)(file system,命名管道是一種特殊類型的文件,因為Linux中所有事物都是文件,它在文件系統(tǒng)中以文件名的形式存在。)來為管道命名。寫模式的進程向FIFO文件中寫入,而讀模式的進程從FIFO文件中讀出。當(dāng)刪除FIFO文件時,管道連接也隨之消失。FIFO的好處在于我們可以通過文件的路徑來識別管道,從而讓沒有親緣關(guān)系的進程之間建立連接
函數(shù)原型:
#include < sys/types.h >
#include < sys/stat.h >
int mkfifo(const char *filename, mode_t mode);
int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );
其中filename是被創(chuàng)建的文件名稱,mode表示將在該文件上設(shè)置的權(quán)限位和將被創(chuàng)建的文件類型(在此情況下為S_IFIFO),dev是當(dāng)創(chuàng)建設(shè)備特殊文件時使用的一個值。因此,對于先進先出文件它的值為0。
程序?qū)嵗?/p>
#include < stdio.h >
#include < stdlib.h >
#include < sys/types.h >
#include < sys/stat.h >
int main()
{
int res = mkfifo("/tmp/my_fifo", 0777);
if (res == 0)
{
printf("FIFO created/n");
}
exit(EXIT_SUCCESS);
}
信號量
什么是信號量
為了防止出現(xiàn)因多個程序同時訪問一個共享資源而引發(fā)的一系列問題,我們需要一種方法。比如在任一時刻只能有一個執(zhí)行線程訪問代碼的臨界區(qū)域。臨界區(qū)域是指執(zhí)行數(shù)據(jù)更新的代碼需要獨占式地執(zhí)行。而信號量就可以提供這樣的一種訪問機制,讓一個臨界區(qū)同一時間只有一個線程在訪問它,也就是說信號量是用來調(diào)協(xié)進程對共享資源的訪問的。
信號量是一個特殊的變量,程序?qū)ζ湓L問都是原子操作,且只允許對它進行等待(即P(信號變量))和發(fā)送(即V(信號變量))信息操作。最簡單的信號量是只能取0和1的變量,這也是信號量最常見的一種形式,叫做二進制信號量。而可以取多個正整數(shù)的信號量被稱為通用信號量。
信號量的工作原理
由于信號量只能進行兩種操作等待和發(fā)送信號,即P(sv)和V(sv),他們的行為是這樣的:
- P(sv):如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進程的執(zhí)行
- V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復(fù)運行,如果沒有進程因等待sv而掛起,就給它加1.
舉個例子,就是兩個進程共享信號量sv,一旦其中一個進程執(zhí)行了P(sv)操作,它將得到信號量,并可以進入臨界區(qū),使sv減1。而第二個進程將被阻止進入臨界區(qū),因為當(dāng)它試圖執(zhí)行P(sv)時,sv為0,它會被掛起以等待第一個進程離開臨界區(qū)域并執(zhí)行V(sv)釋放信號量,這時第二個進程就可以恢復(fù)執(zhí)行。
Linux的信號量機制
Linux提供了一組精心設(shè)計的信號量接口來對信號進行操作,它們不只是針對二進制信號量,下面將會對這些函數(shù)進行介紹,但請注意,這些函數(shù)都是用來對成組的信號量值進行操作的。它們聲明在頭文件sys/sem.h中。
semget函數(shù)
它的作用是創(chuàng)建一個新信號量或取得一個已有信號量,原型為:
int semget(key_t key, int num_sems, int sem_flags);
第一個參數(shù)key是整數(shù)值(唯一非零),不相關(guān)的進程可以通過它訪問一個信號量,它代表程序可能要使用的某個資源,程序?qū)λ行盘柫康脑L問都是間接的,程序先通過調(diào)用semget函數(shù)并提供一個鍵,再由系統(tǒng)生成一個相應(yīng)的信號標識符(semget函數(shù)的返回值),只有semget函數(shù)才直接使用信號量鍵,所有其他的信號量函數(shù)使用由semget函數(shù)返回的信號量標識符。如果多個程序使用相同的key值,key將負責(zé)協(xié)調(diào)工作。
第二個參數(shù)num_sems指定需要的信號量數(shù)目,它的值幾乎總是1。
第三個參數(shù)sem_flags是一組標志,當(dāng)想要當(dāng)信號量不存在時創(chuàng)建一個新的信號量,可以和值IPC_CREAT做按位或操作。設(shè)置了IPC_CREAT標志后,即使給出的鍵是一個已有信號量的鍵,也不會產(chǎn)生錯誤。而IPC_CREAT | IPC_EXCL則可以創(chuàng)建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。
semget函數(shù)成功返回一個相應(yīng)信號標識符(非零),失敗返回-1.
semop函數(shù)
它的作用是改變信號量的值,原型為:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信號量標識符,sembuf結(jié)構(gòu)的定義如下:
struct sembuf{
short sem_num;//除非使用一組信號量,否則它為0
short sem_op;//信號量在一次操作中需要改變的數(shù)據(jù),通常是兩個數(shù),一個是-1,即P(等待)操作,
//一個是+1,即V(發(fā)送信號)操作。
short sem_flg;//通常為SEM_UNDO,使操作系統(tǒng)跟蹤信號,
//并在進程沒有釋放該信號量而終止時,操作系統(tǒng)釋放信號量
};
semctl函數(shù)
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四個參數(shù),它通常是一個union semum結(jié)構(gòu),定義如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
前兩個參數(shù)與前面一個函數(shù)中的一樣,command通常是下面兩個值中的其中一個
SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union semun中的val成員設(shè)置,其作用是在信號量第一次使用前對它進行設(shè)置。
IPC_RMID:用于刪除一個已經(jīng)無需繼續(xù)使用的信號量標識符。
消息隊列
什么是消息隊列
消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權(quán)限的進程可以向隊列中添加消息,被賦予讀權(quán)限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點。消息隊列是隨內(nèi)核持續(xù)的。
每個消息隊列都有一個隊列頭,用結(jié)構(gòu)struct msg_queue來描述。隊列頭中包含了該消息隊列的大量信息,包括消息隊列鍵值、用戶ID、組ID、消息隊列中消息數(shù)目等等,甚至記錄了最近對消息隊列讀寫進程的ID。讀者可以訪問這些信息,也可以設(shè)置其中的某些信息。
結(jié)構(gòu)msg_queue用來描述消息隊列頭,存在于系統(tǒng)空間:
struct msg_queue {
struct kern_ipc_perm q_perm;
time_t q_stime; /* last msgsnd time */
time_t q_rtime; /* last msgrcv time */
time_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
pid_t q_lspid; /* pid of last msgsnd */
pid_t q_lrpid; /* last receive pid */
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
結(jié)構(gòu)msqid_ds用來設(shè)置或返回消息隊列的信息,存在于用戶空間:
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 */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
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 */
};
消息隊列與內(nèi)核的聯(lián)系
下圖說明了內(nèi)核與消息隊列是怎樣建立起聯(lián)系的:
從上圖可以看出,全局數(shù)據(jù)結(jié)構(gòu) struct ipc_ids msg_ids 可以訪問到每個消息隊列頭的第一個成員:struct kern_ipc_perm;而每個struct kern_ipc_perm能夠與具體的消息隊列對應(yīng)起來是因為在該結(jié)構(gòu)中,有一個key_t類型成員key,而key則唯一確定一個消息隊列。 kern_ipc_perm結(jié)構(gòu)如下:
struct kern_ipc_perm{ //內(nèi)核中記錄消息隊列的全局數(shù)據(jù)結(jié)構(gòu)msg_ids能夠訪問到該結(jié)構(gòu);
key_t key; //該鍵值則唯一對應(yīng)一個消息隊列
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
unsigned long seq;
}
消息隊列的操作
打開或創(chuàng)建消息隊列
息隊列的內(nèi)核持續(xù)性要求每個消息隊列都在系統(tǒng)范圍內(nèi)對應(yīng)唯一的鍵值,所以,要獲得一個消息隊列的描述字,只需提供該消息隊列的鍵值即可;
注:消息隊列描述字是由在系統(tǒng)范圍內(nèi)唯一的鍵值生成的,而鍵值可以看作對應(yīng)系統(tǒng)內(nèi)的一條路經(jīng)。
讀寫的操作
消息讀寫操作非常簡單,對開發(fā)人員來說,每個消息都類似如下的數(shù)據(jù)結(jié)構(gòu):
struct msgbuf{
long mtype;
char mtext[1];
};
mtype成員代表消息類型,從消息隊列中讀取消息的一個重要依據(jù)就是消息的類型;mtext是消息內(nèi)容,當(dāng)然長度不一定為1。因此,對于發(fā)送消息來說, 首先預(yù)置一個msgbuf緩沖區(qū)并寫入消息類型和內(nèi)容,調(diào)用相應(yīng)的發(fā)送函數(shù)即可;對讀取消息來說,首先分配這樣一個msgbuf緩沖區(qū),然后把消息讀入該緩沖區(qū)即可。
獲得或設(shè)置消息隊列屬性:
消息隊列的信息基本上都保存在消息隊列頭中,因此,可以分配一個類似于消息隊列頭的結(jié)構(gòu),來返回消息隊列的屬性;同樣可以設(shè)置該數(shù)據(jù)結(jié)構(gòu)。
信號
信號本質(zhì)
信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。
信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發(fā)生了。信號機制經(jīng)過POSIX實時擴展后,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。
信號來源
信號事件的發(fā)生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發(fā)送信號的系統(tǒng)函數(shù)是kill, raise, alarm和setitimer以及sigqueue函數(shù),軟件來源還包括一些非法運算等操作。
信號的種類
可以從兩個不同的分類角度對信號進行分類:(1)可靠性方面:可靠信號與不可靠信號;(2)與時間的關(guān)系上:實時信號與非實時信號。
可靠信號和不可靠信號
不可靠信號
Linux信號機制基本上是從Unix系統(tǒng)中繼承過來的。早期Unix系統(tǒng)中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題,因此,把那些建立在早期機制上的信號叫做”不可靠信號”,信號值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。這就是”不可靠信號”的來源。它的主要問題是:
- 進程每次處理信號后,就將對信號的響應(yīng)設(shè)置為默認動作。在某些情況下,將導(dǎo)致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號處理函數(shù)結(jié)尾再一次調(diào)用signal(),重新安裝該信號。
- 信號可能丟失 因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應(yīng)以及信號可能丟失。
- Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調(diào)用完信號處理函數(shù)后,不必重新調(diào)用該信號的安裝函數(shù)(信號安裝函數(shù)是在可靠機制上的實現(xiàn))。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。
可靠信號
- 隨著時間的發(fā)展,實踐證明了有必要對信號的原始機制加以改進和擴充,力圖實現(xiàn)”可靠信號”。由于原來定義的信號已有許多應(yīng)用,不好再做改動,最終只好又新增加了一些信號,并在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。
- 信號值位于SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數(shù)sigation()以及信號發(fā)送函數(shù)sigqueue()的同時,仍然支持早期的signal()信號安裝函數(shù),支持信號發(fā)送函數(shù)kill()
注意:可靠信號是指后來添加的新信號(信號值位于SIGRTMIN及SIGRTMAX之間);不可靠信號是信號值小于SIGRTMIN的信號。信號的可靠與不可靠只與信號值有關(guān),與信號的發(fā)送及安裝函數(shù)無關(guān)。
實時信號與非實時信號
非實時信號都不支持排隊,都是不可靠信號,編號是1-31,0是空信號;實時信號都支持排隊,都是可靠信號。
進程對信號的響應(yīng)
- 忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILL及SIGSTOP;
- 捕捉信號。定義信號處理函數(shù),當(dāng)信號發(fā)生時,執(zhí)行相應(yīng)的處理函數(shù);
- 執(zhí)行缺省操作,Linux對每種信號都規(guī)定了默認操作
注意:進程對實時信號的缺省反應(yīng)是進程終止。
信號的發(fā)送和安裝
- 發(fā)送信號的主要函數(shù)有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
- 如果進程要處理某一信號,那么就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關(guān)系,即進程將要處理哪個信號;該信號被傳遞給進程時,將執(zhí)行何種操作。
注意:inux主要有兩個函數(shù)實現(xiàn)信號的安裝:signal()、sigaction()。其中signal()在可靠信號系統(tǒng)調(diào)用的基礎(chǔ)上實現(xiàn), 是庫函數(shù)。它只有兩個參數(shù),不支持信號傳遞信息,主要是用于前32種非實時信號的安裝;而sigaction()是較新的函數(shù)(由兩個系統(tǒng)調(diào)用實現(xiàn):sys_signal以及sys_rt_sigaction),有三個參數(shù),支持信號傳遞信息,主要用來與 sigqueue() 系統(tǒng)調(diào)用配合使用,當(dāng)然,sigaction()同樣支持非實時信號的安裝。sigaction()優(yōu)于signal()主要體現(xiàn)在支持信號帶有參數(shù)。
共享內(nèi)存
共享內(nèi)存可以說是最有用的進程間通信方式,也是最快的IPC形式。是針對其他通信機制運行效率較低而設(shè)計的。兩個不同進程A、B共享內(nèi)存的意思是,同一塊物理內(nèi)存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內(nèi)存中數(shù)據(jù)的更新,反之亦然。由于多個進程共享同一塊內(nèi)存區(qū)域,必然需要某種同步機制,互斥鎖和信號量都可以。
系統(tǒng)V共享內(nèi)存原理
進程間需要共享的數(shù)據(jù)被放在一個叫做IPC共享內(nèi)存區(qū)域的地方,所有需要訪問該共享區(qū)域的進程都要把該共享區(qū)域映射到本進程的地址空間中去。系統(tǒng)V共享內(nèi)存通過shmget獲得或創(chuàng)建一個IPC共享內(nèi)存區(qū)域,并返回相應(yīng)的標識符。內(nèi)核在保證shmget獲得或創(chuàng)建一個共享內(nèi)存區(qū),初始化該共享內(nèi)存區(qū)相應(yīng)的shmid_kernel結(jié)構(gòu)體的同時,還將在特殊文件系統(tǒng)shm中,創(chuàng)建并打開一個同名文件,并在內(nèi)存中建立起該文件的相應(yīng)dentry及inode結(jié)構(gòu),新打開的文件不屬于任何一個進程(任何進程都可以訪問該共享內(nèi)存區(qū))。所有這一切都是系統(tǒng)調(diào)用shmget完成的。
系統(tǒng)V共享內(nèi)存API
shmget()用來獲得共享內(nèi)存區(qū)域的ID,如果不存在指定的共享區(qū)域就創(chuàng)建相應(yīng)的區(qū)域。shmat()把共享內(nèi)存區(qū)域映射到調(diào)用進程的地址空間中去,這樣,進程就可以方便地對共享區(qū)域進行訪問操作。shmdt()調(diào)用用來解除進程對共享內(nèi)存區(qū)域的映射。shmctl實現(xiàn)對共享內(nèi)存區(qū)域的控制操作。
套接字(socket)
最早出現(xiàn)在UNIX系統(tǒng)中,是UNIX系統(tǒng)主要的信息傳遞方式。
Socket相關(guān)概念
兩個基本概念:客戶方和服務(wù)方。當(dāng)兩個應(yīng)用之間需要采用SOCKET通信時,首先需要在兩個應(yīng)用之間(可能位于同一臺機器,也可能位于不同的機器)建立SOCKET連接。
發(fā)起呼叫連接請求的一方為客戶方
在客戶方呼叫連接請求之前,它必須知道服務(wù)方在哪里。所以需要知道服務(wù)方所在機器的IP地址或機器名稱,如果客戶方和服務(wù)方事前有一個約定就好了,這個約定就是PORT(端口號)。也就是說,客戶方可以通過服務(wù)方所在機器的IP地址或機器名稱和端口號唯一的確定方式來呼叫服務(wù)方。
接受呼叫連接請求的一方成為服務(wù)方。
在客戶方呼叫之前,服務(wù)方必須處于偵聽狀態(tài),偵聽是否有客戶要求建立連接。一旦接到連接請求,服務(wù)方可以根據(jù)情況建立或拒絕連接。當(dāng)客戶方的消息到達服務(wù)方端口時,會自動觸發(fā)一個事件(event),服務(wù)方只要接管該事件,就可以接受來自客戶方的消息了。
Socket類型
流式Socket(STREAM):是一種面向連接的Socekt,針對面向連接的TCP服務(wù)應(yīng)用,安全,但是效率低;
數(shù)據(jù)報式Socket(DATAGAM):是一種無連接的Socket,對應(yīng)于無連接的UDP服務(wù)應(yīng)用。不安(丟失,順序混亂,在接受端要分析重排及要求重發(fā)),但效率高。
Socket一般應(yīng)用模式(服務(wù)端和客戶端)
Socket通信基本流程圖
-
通信
+關(guān)注
關(guān)注
18文章
6026瀏覽量
135950 -
Linux
+關(guān)注
關(guān)注
87文章
11294瀏覽量
209343 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4327瀏覽量
62573
發(fā)布評論請先 登錄
相關(guān)推薦
評論