進程間通信常見的有5種渠道:編程
管道、信號量、共享內存、消息隊列、套接字
下面來一一簡單說明:segmentfault
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<fcntl.h> #include<string.h> #include<signal.h> //a.c void fun(int sig) { printf("sig=%d\n", sig); } int main() { signal(SIGPIPE, fun); int fdw = open("./fifo", O_WRONLY); assert(fdw != -1); printf("fdw=%d\n", fdw); int newfdw = dup(fdw); char buff[128] = {0}; // dup2(fdw, 1); // printf("hello"); while(1) { printf("input:\n"); fgets(buff, 128, stdin); putchar('\n'); if(strncmp(buff, "end", 3) == 0) { break; } write(newfdw, buff, strlen(buff)); } close(fdw); close(newfdw); } //b.c int main() { int fdr = open("./fifo", O_RDONLY); assert(fdr != -1); printf("fdr=%d\n", fdr); char buff[128] = {0}; while(1) { memset(buff, 0, 128); int len = read(fdr, buff, 127); if(len == 0) { break; } printf("len=%d, buff=%s\n", len, buff); } close(fdr); }
管道文件的使用和普通文件是同樣的,一方讀取,一方寫入,不過在a.c當中有兩個有意思的函數,dup();和dup2();在這個例子中,dup()用newfdw表明了fdw,而dup2()的做用是用fdw去替換標準輸出,有興趣不妨試試,是兩個有趣的函數,這裏很少贅述。數組
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> int main() { int fd[2]; pipe(fd); pid_t pid = fork(); assert(pid != -1); if(pid == 0) { close(fd[0]); char buff[128] = {0}; while(1) { printf("input:\n"); fgets(buff, 128, stdin); if(strncmp(buff, "end", 3) == 0) { break; } write(fd[1], buff, 127); sleep(1); } close(fd[1]); } else { close(fd[1]); char buff[128] = {0}; while(1) { read(fd[0], buff, 127); printf("buff=%s\n", buff); } close(fd[0]); } }
無名管道是由系統建立的,用戶並不知道它姓甚名誰,也就難以在其餘進程中使用了,但在父子進程中,由於子進程會繼承父進程的文件信息,故而在子進程中仍然存在着無名管道,但在使用中通常防止出錯,乙方負責讀取時必須關閉寫端,一方負責寫入時必須關閉讀端緩存
另外管道的最大長度經過測試可得最大爲64K,可是這並不許確,這個大小應該能夠經過修改Linux下的內核參數來修改最大長度。網絡
同時在這裏咱們能夠引入一下原子操做,通常原子操做是系統調用中會實現,且原子操做會消耗很大的系統資源,而信號量機制也能夠用於實現原子操做。
貼上3個概念:
臨界資源:同一時刻只容許一個進程訪問的資源
臨界區:訪問臨界資源的代碼段
原子操做:是一種不可分割不可中斷的操做多線程
信號量機制也是實現原子操做一種方式,這些都是爲了去保證在多進程、多線程環境下的內存可見性問題,可是一樣的,這種機制對於計算機資源形成了很大的負擔,這也就產生了CAS操做,https://segmentfault.com/a/11...
關於CAS操做能夠參考一下這篇文章,這裏很少贅述了。dom
思路其實很簡單,咱們只須要分辨出臨界區——————必定要注意就是那個同一時間下只能有一個線程去訪問佔有資源的代碼處,在臨界區的上面進行p操做,在臨界區下面則進行v操做便可。socket
//sem.h #include<stdio.h> #include<sys/sem.h> #include<stdlib.h> #include<string.h> #include<assert.h> union semun { int val; }; void sem_init(); void sem_p(); void sem_v(); void sem_destroy();
//sem.c #include"sem.h" static semid = -1; void sem_init() { semid = semget((key_t)1234,1,IPC_CREAT | IPC_EXCL | 0600); if(semid == -1) { //已經存在,或者系統資源不足 //處理已經存在 semid = semget((key_t)1234, 1, 0600); if(semid == -1) { //確實系統資源不足 perror("semget error\n"); } } else { union semun a; a.val = 1; //a.val = 0; if(semctl(semid, 0, SETVAL, a) == -1) { perror("semctl error\n"); } } } void sem_p() { struct sembuf buf; buf.sem_num = 0;//下標爲0 buf.sem_op = -1;//p buf.sem_flg = SEM_UNDO;//異常結束會將所作操做復原 if(semop(semid, &buf, 1) == -1) { perror("sem_p error\n"); } } void sem_v() { struct sembuf buf; buf.sem_num = 0;//下標爲0 buf.sem_op = 1;//v buf.sem_flg = SEM_UNDO;//異常結束會將所作操做復原 if(semop(semid, &buf, 1) == -1) { perror("sem_p errori\n"); } } void sem_destroy() { if(semctl(semid, 0, IPC_RMID) == -1) { perror("destroy error\n"); } }
//sema.c #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<unistd.h> #include<string.h> #include<sys/sem.h> #include"sem.h" int main() { sem_init(); int i; for(i = 0; i < 10; i++) { sem_p(); write(1,"A",1); int n = rand() % 3; sleep(n); write(1,"A",1); sem_v(); n = rand() % 3; sleep(n); } return 0; }
這裏面出現了幾個系統調用:
在#include<sys/sem.h>中:函數
int semget(key_t key, int nsems, int semflg); int semctl(int semid, int semnum, int cmd,...); int semop(int semid, struct sembuf *sops, unsigned nsops); //內部的成員結構: union semun { short val;//SETVAL用的值 struct semid_ds *buf;//IPC_STAT、IPC_SET用的semid_ds結構 unsigned short *array;//SETALL、GETALL用的數組值 struct seminfo *buf;//爲控制IPC_INFO提供的緩存 }; struct sembuf { short semnum;//信號量集合中的信號量編號,0表明第一個信號量 short val;//進行P/V操做所加減的值 short flag; /* 0設置信號量的默認操做 IPC_NOWAIT設置信號量操做不等待 SEM_UNDO會讓內核記錄一個與調用進程相關的UNDO記錄,若是該進程崩潰,則根據這個進程的UNDO記錄自動回覆相應信號量的計數值 */ }
semctl的cmd選項列表
IPC_STAT
從信號量集上檢索semid_ds結構,並存到semun聯合體參數的成員buf的地址中IPC_SET
設置一個信號量集合的semid_ds結構中ipc_perm域的值,並從semun的buf中取出值postIPC_RMID
從內核中刪除信號量集合GETALL
從信號量集合中得到全部信號量的值,並把其整數值存到semun聯合體成員的一個指針數組中GETNCNT
返回當前等待資源的進程個數GETPID
返回最後一個執行系統調用semop()進程的PIDGETVAL
返回信號量集合內單個信號量的值GETZCNT
返回當前等待100%資源利用的進程個數SETALL
用聯合體中val成員的值設置信號量集合中所有信號量的值SETVAL
用聯合體中val成員的值設置信號量集合中單個信號量的值
關於這幾個系統調用的詳情可參考於http://blog.csdn.net/guoping1...,對應可理解上面的例子。
在#include<semaphore.h>中:
sem_init(sem_t *sem, int pshared, unsigned int value);
pshared 參數指明信號量是由進程內線程共享,仍是由進程之間共享。若是 pshared 的值爲 0,那麼信號量將被進程內的線程共享,而且應該放置在這個進程的全部線程均可見的地址上(如全局變量,或者堆上動態分配的變量)。若是 pshared 是非零值,那麼信號量將在進程之間共享,而且應該定位共享內存區域。
sem_wait(sem_t *sem);//對應P操做
sem_post(sem_t *sem);//對應V操做
//實例1 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<unistd.h> #include<semaphore.h> #include<pthread.h> static sem_t sem; static pthread_mutex_t mutex; void *fun(void *arg) { int i; for(i = 0; i < 10; i++) { sem_wait(&sem); //pthread_mutex_lock(&mutex); printf("B"); fflush(stdout); sleep(1); printf("B"); fflush(stdout); sem_post(&sem); //pthread_mutex_unlock(&mutex); sleep(1); } pthread_exit("fun exit\n"); } int main() { // sem_init(&sem, 0, 1); pthread_mutex_init(&mutex, NULL); pthread_t id; pthread_create(&id, NULL, fun, NULL); int i; for(i = 0; i < 10; i++) { // sem_wait(&sem); pthread_mutex_lock(&mutex); printf("A"); fflush(stdout); sleep(1); printf("A"); fflush(stdout); // sem_post(&sem); pthread_mutex_unlock(&mutex); sleep(1); } printf("main over\n"); char *s = NULL; pthread_join(id, (void **)&s); printf("%s", s); exit(0); }
//實例2 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<unistd.h> #include<string.h> #include<pthread.h> #include<semaphore.h> sem_t sem; void *fun(void *arg) { char *buff = (char *)arg; sem_wait(&sem); char *temp = NULL; char *token = NULL; token = strtok_r(buff, " ", &temp); printf("fun token=%s\n", token); sleep(1); while((token = strtok_r(NULL, " ", &temp)) != NULL) { printf("fun token=%s\n", token); sleep(1); } sem_post(&sem); } int main() { char main_buf[] = "1 2 3 4 5 6 7 8 9 10"; char fun_buf[] = "A B C D E F G H I J"; //sem sem_init(&sem, 0, 1); pthread_t id; pthread_create(&id, NULL, fun, (void *)fun_buf); //strtok(char *, " ") sem_wait(&sem); char *token = NULL; char *temp = NULL; token = strtok_r(main_buf, " ", &temp); printf("main token=%s\n", token); sleep(1); while((token = strtok_r(NULL, " ", &temp)) != NULL) { printf("main token=%s\n", token); sleep(1); } sem_post(&sem); pthread_join(id,NULL); exit(0); }
上面的例子中還用到了互斥鎖,也就是那個mutex,對應兩個函數pthread_mutex_lock(pthread_mutex_t mutex),pthread_mutex_unlock(pthread_mutex_t mutex),和信號量相似,也是在臨界區上方加鎖下方解鎖,這裏下次再說吧這個。
//a #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/shm.h> int main() { sem_init(); int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600); assert(shmid != -1); char *s = (char *)shmat(shmid, NULL, 0);//NULL不指定進程連接中的地址具體位置,0即便標識位,爲默認) assert((int) s != -1); //assert(s != (char *)-1); while(1) { sem_p(0); printf("input str\n"); char buff[128] = {0}; fgets(buff, 128, stdin); strcpy(s, buff); sem_v(1); if(strncmp(buff, "end", 3) == 0) { break; } } shmdt(s);//斷開連接s exit(0); }
//b #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/shm.h> int main() { sem_init(); int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600); assert(shmid != -1); char *s = (char *)shmat(shmid, NULL, 0);//NULL不指定進程連接中的地址具體位置,0即便標識位,爲默認) assert((int) s != -1); //assert(s != (char *)-1); while(1) { sem_p(1); if(strncmp(s, "end", 3) == 0) { break; } printf("read:%s\n", s); sleep(1); sem_v(0); } shmdt(s);//斷開連接s sem_destroy(); exit(0); }
這裏關於信號量的不在多提,在上面也說到,由於系統內核沒有對訪問共享內存進行同步,因此必須提供本身的同步措施,因此會用到信號量。而且這裏用的信號量是自行實現的,關於實如今前面的實例中有展開。
在頭文件#include<sys/shm.h>中:
int shmget(ket_t key, size_t size, int shmflg);
void shmat(int shmid, const void shmaddr, int shmflg);
int shmdt(const void *shmaddr);
詳情可參見:http://blog.csdn.net/guoping1...
優勢:
A. 咱們能夠經過發送消息來幾乎徹底避免命名管道的同步和阻塞問題。 B. 咱們能夠用一些方法來提早查看緊急消息。
缺點:
A. 與管道同樣,每一個數據塊有一個最大長度的限制。 B. 系統中全部隊列所包含的所有數據塊的總長度也有一個上限。
限制:
因爲消息緩衝機制中所使用的緩衝區爲共用緩衝區,所以使用消息緩衝機制傳送數據時,兩通訊進程必須知足 以下條件: (1)在發送進程把寫入消息的緩衝區掛入消息隊列時,應禁止其餘進程對消息隊列的訪問,不然,將引發消 息隊列的混亂。同理,當接收進程正從消息隊列中取消息時,也應禁止其餘進程對該隊列的訪問。 (2)當緩衝區中無消息存在時,接收進程不能接收任何消息;而發送進程是否能夠發送消息,則只由發送進 程是否可以申請緩衝區決定。
//msga #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<unistd.h> #include<memory.h> #include<sys/msg.h> typedef struct my_message { long int type; char msg[128]; }MyMsg; int main() { int msgid = msgget((key_t)1234, IPC_CREAT | 0600); if(msgid == -1) { perror("a msgget error\n"); } char buff[128]; int type; MyMsg m; while(1) { puts("input type(int)"); scanf("%d", &type); getchar(); // fflush(stdin); m.type = type; memset(buff, 0, 128); puts("input message"); fgets(buff, 127, stdin); buff[strlen(buff) - 1] = 0; strcpy(m.msg, buff); if((msgsnd(msgid, &m, sizeof(MyMsg) - sizeof(long), IPC_NOWAIT)) == -1) { perror("a msgsnd error\n"); } if(strncmp(buff, "end", 3) == 0) { break; } } exit(0); }
//msgb #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<unistd.h> #include<memory.h> #include<sys/msg.h> typedef struct my_message { long int type; char msg[128]; }MyMsg; int main() { int msgid = msgget((key_t)1234, IPC_CREAT | 0600); if(msgid == -1) { perror("b msgget error\n"); } char buff[128]; int type; MyMsg m; int i = 0; while(1) { puts("output type(int)"); scanf("%d", &type); fflush(stdin); m.type = type; memset(buff, 0, 128); if(msgrcv(msgid, &m, sizeof(MyMsg) - sizeof(long), type, IPC_NOWAIT) == -1) { perror("b msgrcv error\n"); continue; } strcpy(buff, m.msg); printf("buff[%d]=%s\n", i++, buff); if(strncmp(buff, "end", 3) == 0) { break; } } msgctl(msgid, IPC_RMID, NULL); exit(0); }
在#inlcude<sys/msg.h>中:
關於struct msgbuf
struct msgbuf{
long mtype; //這是一個消息中必須包含的消息類型,要求爲long …………………… //具體的消息
};
詳情可參見:http://blog.csdn.net/guoping1...
套接字是網絡編程的基礎,是進程間通信中一種經常使用的通信方式,按照Linux下一切皆文件的思想,那麼套接字就能夠看做是一個文件的描述符。這個部分更多的偏向於網絡編程,延伸至select,poll,epoll乃至libevent、boost::asio等等網絡庫,這個放在後面再討論吧。
在這裏額外提一下雙向管道好了,雙向管道,這是一種全雙工的通信方式,雙方均可讀可寫,那麼這也就是普通管道不能知足需求的緣由,因此誕生了一種以套接字爲基礎的輕量級別的解決方案,socketpair();
在#include<sys/socket.h>中:
int socketpair(int domain, int type, int protocol, int sv[2]);
domain參數:選擇協議族 AF_UNIX,AF_LOCAL;
type參數: 選擇類型:SOCK_STREAM,SOCK_DGRAM
pritocal 必須爲0!
這樣處理後,sv數組中會放有兩個鏈接好的套接字文件,這樣經過兩個套接字就能夠互通有無了。
參考資料:
http://blog.csdn.net/Zong__Zo...
http://blog.csdn.net/guoping1...
http://blog.csdn.net/guoping1...
http://blog.csdn.net/guoping1...
http://blog.csdn.net/ttyue_12...