进程间通信(总结)
轉(zhuǎn)自:http://blog.sina.com.cn/s/blog_726c4bd20100ryan.html
信號
進(jìn)程間通信的方法有: 信號、文件鎖、管道、FIFO、信號量、共享內(nèi)存、消息隊(duì)列最有效,最快的方法是:共享內(nèi)存
信號量、共享內(nèi)存、消息隊(duì)列是system V IPC
必須要求是親屬進(jìn)程間才能通信的方法是:管道
?
信號機(jī)制:
收到信號后是異步運(yùn)行的。
信號(signal)機(jī)制是Linux系統(tǒng)中最為古老的進(jìn)程之間的通信機(jī)制
信號事件的發(fā)生有兩個來源:
硬件來源,比如我們按下了鍵盤或者其它硬件故障;
軟件來源,最常用發(fā)送信號的系統(tǒng)函數(shù)是kill(), raise(), alarm()和setitimer()等函數(shù),軟件來源還包括一些非法運(yùn)算等操作。
? ? ? ? ?kill()函數(shù)可以對任何進(jìn)程發(fā)送信號,raise()只能對自己發(fā)信號,alarm()和setitimer()發(fā)送定時(shí)信號。
? ? ? ? ?可以用kill ?-l 命令列出所有的信號。
? ? ? ? ?程序在休眠時(shí)搜索不到信息。
? ? ? ? ?信息未處理完成時(shí),系統(tǒng)無法運(yùn)行。
進(jìn)程對信號的處理
v ? ? ? 進(jìn)程可以通過三種方式來響應(yīng)和處理一個信號:
忽略信號、捕捉信號、執(zhí)行缺省操作
但有兩個信號不能忽略:SIGKILL 和SIGSTOP
信號處理函數(shù)的安裝
#include<signal.h>
void( *signal(int sig, void( *func)(int)))(int);
? ? ? ? ?int sigaction(int ?signum,const struct sigacton *act,struct sigaction *oldact);
? ? ? ? ?其中:signal()在可靠的信號系統(tǒng)調(diào)用的基礎(chǔ)上實(shí)現(xiàn),是庫函數(shù)。它只是兩個參數(shù),不支持信號傳遞信息。第一個參數(shù)是要安裝的信號量,第二個參數(shù)是對第一個參數(shù)指定的信號的處理。
如果func不是函數(shù)指針,必須是下列兩個宏:
SIG_IGN:忽略信號。
SIG_DEF:采用系統(tǒng)默認(rèn)的方式處理信號,執(zhí)行缺省操作。
返回值:返回先前的信號處理函數(shù)指針,如果有錯誤則返回-1。
?
信號的發(fā)送
除了內(nèi)核和超級用戶,并不是每個進(jìn)程都可以向其他的進(jìn)程發(fā)送信號。
一般的進(jìn)程只能向具有相同uid和gid的進(jìn)程發(fā)送信號,或向相同進(jìn)程組中的其他進(jìn)程發(fā)送信號。
常用的發(fā)送信號的函數(shù)有kill()、raise ()、alarm()、setitimer()、abort() 等。
?
kill()函數(shù):給指定的進(jìn)程發(fā)送某一個信號
#include <sys/types.h>
#include <signal.h>
?int kill(pid_t pid, int sig);
pid>0 ?給PID為pid的進(jìn)程發(fā)送信號
pid=0 ?給同一個進(jìn)程組的所有進(jìn)程發(fā)送信號
pid<0 且 pid!=-1 ?給進(jìn)程組ID為-pid的所有進(jìn)程發(fā)送信號
pid=-1 ?給出了自身之外的PID大于1的進(jìn)程發(fā)送信號
第二個參數(shù)是要發(fā)送的信號值,當(dāng)?shù)诙€參數(shù)為0的時(shí)候,實(shí)際上不會發(fā)送任何信號
返回值:成功為0;失敗返回-1。
EINVAL:所發(fā)送的信號無效
EPERM:沒有向目標(biāo)進(jìn)程發(fā)送信號的權(quán)限
ESRCH:目標(biāo)進(jìn)程不存在或者進(jìn)程已經(jīng)終止,處于僵尸。
raise()函數(shù):給進(jìn)程本身發(fā)送一個信號
? ? #include <signal.h>
? ? int raise(int sig); ?(給自己發(fā)信號)
相當(dāng)于kill(getpid(),sig);
返回值:成功為0;失敗返回-1
alarm()函數(shù):是一個簡單定時(shí)器,專為SIGALRM信號設(shè)計(jì)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
在指定的seconds秒之后,給進(jìn)程本身發(fā)生一個SIGALRM信號
?
setitimer()功能強(qiáng)大更強(qiáng)大的定時(shí)器函數(shù),支持3種類型的定時(shí)器,但是從本質(zhì)上,它是和alarm共享同一個進(jìn)程內(nèi)的定時(shí)器
#include <sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);?
abort():向進(jìn)程發(fā)送SIGABORT信號,默認(rèn)情況下進(jìn)程會異常退出
?
文件鎖
當(dāng)多個進(jìn)程都需要對同一個文件進(jìn)行讀寫操作的時(shí)候,有時(shí)候需要確保進(jìn)程在一次讀寫操作完成之前,文件不被其他進(jìn)程修改。文件鎖機(jī)制提供了這樣的同步功能。
int fcntl(int fd, int cmd, struct flock *lock);
fd 文件描述符
cmd參數(shù):F_GETLK:獲取文件鎖當(dāng)前的狀態(tài)。
F_SETLK:設(shè)置文件鎖。
F_SETLKW:這是F_SETLK的阻塞版本,如果新加的鎖被拒絕,那么進(jìn)程被阻塞直到可以加鎖。
struct flock{
? ? ? ? …
? ? ? ? ?short l_type; ? ? ? ? ??
? ? ? ? ?short l_whence; ? ? ? ??
? ? ? ? ?off_t l_start; ? ? ? ? ? ? ?
? ? ? ? ?off_t l_len; ? ? ? ? ? ?
? ? ? ? ?…};
l_type類型:
F_RDLCK:讀鎖
F_WRLCK:寫鎖
F_UNLCK:解鎖
為了鎖定整個文件,通常的方法是將l_start指定為0,l_whence指定為SEEK_SEK, l_len指定為0。
?
管道
?
管道是針對于本地計(jì)算機(jī)的兩個進(jìn)程之間的通信而設(shè)計(jì)的通信方法,管道建立后,實(shí)際獲得兩個文件描述符:一個用于讀取而另外一個用于寫入。
管道是半雙工的,數(shù)據(jù)只能向一個方向流動,需要雙方通信時(shí),需要建立起兩個管道。
只能用于父子進(jìn)程或者兄弟進(jìn)程之間(具有親緣關(guān)心的進(jìn)程)。
單獨(dú)構(gòu)成一種獨(dú)立的文件系統(tǒng):管道對于管道兩端的進(jìn)程而言,就是一個文件,但它不是普通的文件,它不屬于某種文件系統(tǒng),而是單獨(dú)構(gòu)成一種文件系統(tǒng),并且只存在于內(nèi)存中。
數(shù)據(jù)的讀出和寫入:一個進(jìn)程向管道中寫的內(nèi)容被管道另一端的進(jìn)程讀出。寫入的內(nèi)容每次都添加在管道緩沖區(qū)的末尾,并且每次都是從緩沖區(qū)的頭部讀出數(shù)據(jù)。
?
管道的創(chuàng)建
系統(tǒng)調(diào)用pipe()用于創(chuàng)建一個管道
int pipe(int filedes[2]);
建立管道: ?
filedes[0]: 為pipe的讀出端
filedes[1]: 為pipe的寫入端
兩個文件描述符數(shù)組。
?
FIFO
FIFO不同于管道之處在于它提供一個路徑名與之關(guān)聯(lián),以FIFO的文件形式存在于文件系統(tǒng)中
在文件系統(tǒng)中是一個有名字的管道
任何進(jìn)程都可以打開
進(jìn)程間無需關(guān)聯(lián)
注:直接映射到磁盤上,有文件名,故任何進(jìn)程都可以打開。
FIFO的創(chuàng)建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
?
信號量
System IPC中,對于每一個新建的信號量、消息隊(duì)列以及共享內(nèi)存,都有一個在整個系統(tǒng)中唯一的標(biāo)識符。每個標(biāo)識符也都有唯一對應(yīng)的關(guān)鍵字,關(guān)鍵字的數(shù)據(jù)類型由系統(tǒng)定義為key_t。
創(chuàng)建/獲取信號量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
Key:鍵值
1. 指定鍵值
2. IPC_PRIVATE
系統(tǒng)指定鍵值
nsems:信號量的數(shù)目
信號量集合的數(shù)目
Smeflg:信號量標(biāo)志
1.IPC_CREATE 如果內(nèi)核中沒有此隊(duì)列,則創(chuàng)建它。
2.IPC_EXECL ? ? 當(dāng)和IPC_CREAT一起使用時(shí),如果隊(duì)列已經(jīng)存在,則返回錯誤。
3.mode_flags: ? ? ? 類似于文件的權(quán)限
?
信號量操作
當(dāng)進(jìn)程需要申請或者釋放公共資源的時(shí)候,可以調(diào)用semop()來對信號量進(jìn)行操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
sops:指向sembuf結(jié)構(gòu)的數(shù)組指針
unsigned short sem_num:組中包含的信號量數(shù)量
short ?sem_op:操作類型的整數(shù)
整數(shù):加到信號量的值上
負(fù)數(shù):信號量的值減去絕對值,如果小于零,進(jìn)程阻塞,直到信號量的值至少等于其絕對值
0:導(dǎo)致操作阻塞,直到信號量的值為0才繼續(xù)。
?
short ?sem_flg:一個符號位
IPC_NOWAIT: 非阻塞操作
SEM_UNDO:linux會在進(jìn)程退出的時(shí)候自動撤銷該次操作。
?
信號量控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
第一個參數(shù)semid指定信號量集,第二個參數(shù)semnum指定了讀信號量集里面的哪一個信號集進(jìn)行操作(編號),第三個參數(shù)cmd指定具體的操作類型(指令)。
當(dāng)進(jìn)程創(chuàng)建了新的信號量集的時(shí)候,調(diào)用了atexit(&sem_delete)函數(shù),使得程序退出時(shí),信號量集被釋放。
?
消息隊(duì)列
消息隊(duì)列是系統(tǒng)內(nèi)核地址空間中的一個內(nèi)部的鏈表。消息可以按照順序發(fā)送到隊(duì)列中,也可以以幾種不同的方式從隊(duì)列中讀取。每一個消息隊(duì)列用一個唯一的IPC標(biāo)識符表示
Msgbuf數(shù)據(jù)結(jié)構(gòu)
struct msgbuf {
? ? ? ?long mtype; ? ? ??
? ? ? ?char mtext[1]; ? ? ?
};
mtype指消息的類型,它由一個整數(shù)來代表,并且它只能是大于0的整數(shù)。
mtext是消息數(shù)據(jù)本身
?
創(chuàng)建消息隊(duì)列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h> ??
int msgget(key_t key, int msgflg);
Key:鍵值
1. 指定鍵值(可有ftok()函數(shù)獲得)
2. IPC_PRIVATE ?系統(tǒng)指定鍵值
msgflg :信號量標(biāo)志
1.IPC_CREATE ?如果內(nèi)核中沒有此隊(duì)列,則創(chuàng)建它。
2.IPC_EXECL ? 當(dāng)和IPC_CREAT一起使用時(shí),如果隊(duì)列已經(jīng)存在,則返回錯誤。
3.mode_flags:類似于文件的權(quán)限
發(fā)送和接收消息
msgsnd()系統(tǒng)調(diào)用用于向隊(duì)列發(fā)送一條消息:
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);
msgrcv()系統(tǒng)調(diào)用用于從消息隊(duì)列讀取一條消息:
ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);
如果msgtype=0,接收隊(duì)列的第一個消息(按時(shí)間順序依次收到一個消息),大于0接收隊(duì)列中消息類型等于這個值的第一個消息(特定值的消息),小于0接收消息隊(duì)列中小于或等于msgtype絕對值的所有消息中的最小一個消息(按編號順序依次收消息)
?
消息隊(duì)列的控制
通過msgctl()可以對消息隊(duì)列進(jìn)行控制或者一些屬性的修改:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd:消息隊(duì)列的操作
IPC_STAT:讀取消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)msqid_ds,并將其存儲在buf指定的地址中。
IPC_SET:設(shè)置消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)msqid_ds中的ipc_perm、msg_qbytes、msg_ctime元素的值。這個值取自buf參數(shù)。 ?
IPC_RMID:從系統(tǒng)內(nèi)核中移走消息隊(duì)列。
msgid_ds結(jié)構(gòu)體
struct msqid_ds{
? struct ipc_perm msg_perm; ? ??
? time_t ? ? ? ? ?msg_stime; ? ?
? time_t ? ? ? ? ?msg_rtime; ? ?
? time_t ? ? ? ? ?msg_ctime; ? ?
? unsigned long ? __msg_cbytes;?
? msgqnum_t ? ? ? msg_qnum; ? ??
? msglen_t ? ? ? ?msg_qbytes; ??
? pid_t ? ? ? ? ? msg_lspid; ? ?
? pid_t ? ? ? ? ? msg_lrpid; ? ?
}
共享內(nèi)存
兩個不同進(jìn)程A、B共享內(nèi)存的基本原理是,同一塊物理內(nèi)存被映射到進(jìn)程A、B各自的進(jìn)程地址空間。進(jìn)程A可以即時(shí)看到進(jìn)程B對共享內(nèi)存中數(shù)據(jù)的更新,反之亦然。
?
創(chuàng)建和獲取共享內(nèi)存
系統(tǒng)調(diào)用shmget()用于創(chuàng)建共享內(nèi)存或者獲取一個已經(jīng)存在的共享內(nèi)存的標(biāo)識符:
int shmget(key_t key, size_t size, int shmflg);
key:鍵值
1. 指定鍵值
2. IPC_PRIVATE ?系統(tǒng)指定鍵值
size:共享內(nèi)存大小
msgflg :共享內(nèi)存標(biāo)志
1.IPC_CREATE ?如果內(nèi)核中沒有此隊(duì)列,則創(chuàng)建它。
2.IPC_EXECL ? 當(dāng)和IPC_CREAT一起使用時(shí),如果隊(duì)列已經(jīng)存在,則返回錯誤。
3.mode_flags: ? 類似于文件的權(quán)限
?
?
系統(tǒng)調(diào)用shmat()可以獲取一個共享內(nèi)存的地址,并將其連接到進(jìn)程中:
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:
由shmget返回的共享內(nèi)存標(biāo)志
shmaddr:
映射該共享內(nèi)存塊的進(jìn)程內(nèi)存地址
如果為NULL,Linux將自動選擇合適的地址
shmflg:
SHM_RND
SHM_RDONLY
?
通過shmctl()可以對消息隊(duì)列進(jìn)行控制或者一些屬性的修改:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd:共享內(nèi)存的操作
IPC_STAT:讀取一個共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)shmid_ds,并將其存儲在buf指定的地址中。
IPC_SET:設(shè)置消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)shmid_ds中各個元素的值。這個值取自buf參數(shù)。
IPC_RMID:把共亨內(nèi)存標(biāo)記為可刪除,當(dāng)最后一個進(jìn)程脫連此共享內(nèi)存的時(shí)候,系統(tǒng)將刪除該共享內(nèi)存
總結(jié)
- 上一篇: 理解ARC在Objective-C中的应
- 下一篇: 区块链项目包装指南