進程間通信簡介(摘自《Linux網路編程》p85) AT&T 在 UNIX System V 中引入了幾種新的進程通訊方式,即消息隊列( MessageQueues),信號量( semaphores)和共用記憶體( shared memory),統稱為 System V IPC。在Linux 系統編程 ...
進程間通信簡介(摘自《Linux網路編程》p85)
AT&T 在 UNIX System V 中引入了幾種新的進程通訊方式,即消息隊列( MessageQueues),信號量( semaphores)和共用記憶體( shared memory),統稱為 System V IPC。在Linux 系統編程中,它們有著廣泛的應用。
System V IPC 的一個顯著的特點,是它的具體實例在內核中是以對象的形式出現的,我們稱之為 IPC 對象。每個 IPC 對象在系統內核中都有一個唯一的標識符。通過標識符內核可以正確的引用指定的 IPC 對象.。需要註意的是,標識符的唯一性只在每一類的 IPC 對象內成立。比如說,一個消息隊列和一個信號量的標識符可能是相同的,但絕對不會出現兩個有相同標識符的消息隊列。
標識符只在內核中使用, IPC 對象在程式中是通過關鍵字( key)來訪問的。和 IPC 對象標識符一樣,關鍵字也必須是唯一的。而且,要訪問同一個 IPC 對象, Server 和 Client必須使用同一個關鍵字。因此,如何構造新的關鍵字使之不和已有的關鍵字衝突,並保證Server 和 Client 使用的關鍵字是相同的,是建立 IPC 對象時首先要解決的一個問題。(具體在後邊的msg通信中詳解)
通信方法還有:半雙工管道pipe,命名管道fifo,消息隊列,信號量,共用內,socket套接字等,下麵一一介紹:
①半雙工管道:
int pipe(int filedes[2]);
管道是將兩個進程之間的標準輸入輸出相互對接的機制
linux命令中使用的管道 | : ls -l | grep *.c //顯示文件(輸入端)-(|)-(輸出端)>找到.c結尾文件
實現:因為半雙工緣故,所以只能實現一段輸入,一段輸出,而不能雙向通信。所以:實現為,通過管道連接進程,一端開放讀文件描述,一端開放寫文件描述
//管道的性質就是,一個進程的輸出作為另一個進程的輸入 //那麼我們可以關閉一個進程讀端使之作為輸入端, //另一個進程關閉寫端,讀取數據,接收數據作為管道輸出端 //FIFO命名管道 //文件系統中,命名管道是特殊文件的方式存在的 //不同進程可以通過命名管道共用數據 //命名管道一直是阻塞方式的,且必須是顯示的通過open建立連接到管道的通道 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> int main() { int result = 1; int fd[2]; pid_t pid; int *write_fd = &fd[1]; //寫文件描述 int *read_fd = &fd[0]; //讀文件描述 int nbytes; char str[] = "管道,你好\n"; char readBuffer[80]; memset(readBuffer,0,sizeof(readBuffer)); result = pipe(fd); //創建管道 if(-1==result) { printf("管道創建失敗!\n"); return -1; } pid = fork(); //進程創建分叉程式 if(-1 == pid) { printf("fork失敗"); return -1; } if(0==pid) //子進程關閉讀端,寫入字元 { close(*read_fd); result = write(*write_fd,str,strlen(str)); printf("寫入%d個數據\n",result); } else //父進程關閉寫端,讀取數據 { close(*write_fd); nbytes = read(*read_fd,readBuffer,sizeof(readBuffer)); printf("接收到%d個數據,內容為%s",nbytes,readBuffer); } return 0; }
②命名管道
int mkfifo(const char* pathname,mode_t mode);
類似於普通管道,只是
a.在文件系統中以設備特殊文件的形式存在
b.不同進程之間可以通過命名管道共用數據
操作區別於普通管道:FIFO中必須顯式通過open建立連接到管道的通道,且總是處於阻塞狀態的
③消息隊列
消息隊列是內核地址空間的內部鏈表,通過內核在各個進程之間傳遞內容。每個消息隊列通過唯一IPC標識符標識,不同隊列相對獨立。
//file: msg.h
/* message buffer for msgsnd and msgrcv calls */ struct msgbuf { __kernel_long_t mtype; /* type of message */ char mtext[1]; /* message text */ }; /* Obsolete, used only for backwards compatibility and libc5 compiles */ 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 */ };
//filename
/* Obsolete, used only for backwards compatibility and libc5 compiles */ struct ipc_perm { __kernel_key_t key; //函數msgget()使用的鍵值 __kernel_uid_t uid; //用戶UID __kernel_gid_t gid; //用戶GID __kernel_uid_t cuid; //創建者UID __kernel_gid_t cgid; //創建者GID __kernel_mode_t mode; //許可權 unsigned short seq; //序列號 };
內核中的消息隊列
註:結構list_head 形成一個鏈表,結構msg_msg之中的m_list使得消息形成鏈表,查找,插入時,對m_list域進行偏移找到位置
相關函數:
鍵值構建 key_t ftok(const char* pathname,int proj_id);
獲取消息 int msgget(key_t key,int msgflg);
發送消息 int msgsnd(int msqid, const void * msgp,size_t msgsz,int msgflg);
接收消息 ssize_t msgrcv(int msqid, void * msgp, size_t msgsz, long msgtype, int msgflg);
消息控制 int msgctl(int msqid, int cmd, struct msqid_ds *buf); //向內核發送cmd命令判斷進行何種操作
一個簡單例子
④信號量
信號量是一種計數器,用來控制對多個進程共用的資源所進行的訪問。常用作鎖機制(生產者消費者模型是個典型使用)
信號量結構
//filename sys/sem.h
/* arg for semctl system calls. */ union semun { int val; /* value for SETVAL */ struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */ unsigned short *array; /* 數組結構 */ struct seminfo *__buf; /* 信號量內部結構 */ void *__pad; };
相關函數
新建信號量 int semget(key_t key, int nsems, int semflg);
//key 來自於ftok()
信號量操作函數 int semop(int semid,struct sembuf* sops, unsigned nsops);
//信號量的P,V操作通過向已經建立好的信號量發送命令完成
控制信號量參數
int semctl(int semid, int semnum ,int cmd,.....);
//用於在信號量集合上執行控制操作
#include<stdio.h> #include<unistd.h> #include<sys/ipc.h> #include<sys/sem.h> #include<sys/types.h> typedef int sem_t; union semun { int val; struct semid_ds * buf; unsigned short *array; }arg; sem_t CreateSem(key_t key, int value) { union semun sem; sem_t semid; sem.val = value; semid = semget(key,0,IPC_CREAT); if(-1 == semid) { printf("create semaphore error\n"); return -1; } semctl(semid,0,SETVAL,sem); return semid; } int Sem_P(sem_t semid) { struct sembuf sops = {0,+1,IPC_NOWAIT}; return (semop(semid,&sops,1)); } int Sem_V(sem_t semid) { struct sembuf sops = {0,-1,IPC_NOWAIT}; return (semop(semid,&sops,1)); } void SetvalueSem(sem_t semid , int value) { union semun sem; sem.val = value; semctl(semid,0,SETVAL,sem); } int GetvalueSem(sem_t semid) { union semun sem; return semctl(semid,0,GETVAL,sem); } void DestroySem(sem_t semid) { union semun sem; sem.val = 0; semctl(semid,0,IPC_RMID,sem); } int main() { key_t key; int semid; char i; int value = 0; key = ftok("/ipc/sem",'a'); semid = CreateSem(key,100); for( i = 0;i <= 3;++i) { Sem_P(semid); Sem_V(semid); } value = GetvalueSem(semid); DestroySem(semid); return 0; }
⑤共用記憶體(最快捷的方法)沒有中間過程,管道等
在多個進程之間共用記憶體區域的一種進程間通信方式,在多個進程之間對記憶體段進行映射的方式實現記憶體共用。
相關函數
創建共用記憶體函數 int shmget(key_y key, size_t size, int shmflg);
獲得共用記憶體地址void * shmat(int shmid,const void* shmaddr, int shmflg);
刪除共用記憶體函數 int shmdt(const void* shmadddr);
共用記憶體控制函數 int shmctl(int shmid ,int cmd, struct shmid_ds * buf);
⑥信號
用於在一個或多個進程之間傳遞非同步信號。
相關函數
信號截取 sighandler signal(int signum ,sighandler handler);
發送信號 int kill(pid_t pid, int sig);
int raise(int sig);