一、介紹
進程與進程間的用戶空間相互獨立,內核空間共享。
1.傳統的進程間通信機制
? ? ? ? a.無名管道 pipe
? ? ? ? b.有名管道 fifo
? ? ? ? c.信號? ? ? ? ?signal
2.system V中的IPC對象
? ? ? ? a.消息隊列 message queue
? ? ? ? b.共享內存 shared memory
? ? ? ? c.信號燈集 semaphoare
3.可用于跨主機傳輸的通信機制
? ? ? ? a.套接字 socket
二、管道
1.管道可以看成是一個特殊文件,一般文件存儲在外存中,而管道內容存儲在內存中
2.管道遵循先進先出原則
3.管道的讀操作是一次性的,內容被讀出后就會從管道中刪除
4.管道是一種半雙工的通信方式
5.管道只能使用文件IO函數,因為需要直接操作內核空間,如open,close,read,write,但不能使用lseek
6.管道的大小為64k
2.1無名管道
無名管道即在文件系統(用戶系統)不可見的管道文件
無名管道不可以用open打開,因為不知道路徑以及名字
無名管道只能用于具有親緣關系的進程間通信。由于無名管道在文件系統中不可見,兩個無關的進程,無法拿到同一根管道的讀寫段,只有具有親緣關系的進程,在父進程中創建一根管道,拿到讀寫端后,調用fork函數,創建出來的子進程也會有該管道的讀寫端。
從管道中讀取數據:
1.讀寫端均存在時,當管道中沒有數據時,read會阻塞2.當管道的寫端不存在時,若管道中有數據,會先將數據讀取完畢,沒有數據時,read函數不會阻塞,直接返回0
向管道中寫入數據:
1.讀寫段均存在時,write會阻塞
2.當管道的讀端不存在時,調用write函數,嘗試向管道中寫入數據會導致管道破裂。
#include <head.h>
int main(int argc, char const *argv[])
{// 管道的創建必須放在fork前// 若放在fork后,會導致父子進程各自創建一個內管道,無法通信int pfd[2] = {0};if (pipe(pfd) < 0) // pf[0]為讀的文件描述符,pf[1]為寫的文件描述符{perror("pipe");return -1;}printf("管道創建成功\n");pid_t pid = fork();if (pid > 0){// 父進程發數據給子進程char buf[128] = "";while (1){fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0; // 從終端獲取數據,將最后獲取到的\n變成\0// 將數據寫入到管道中if (write(pfd[1], buf, sizeof(buf)) < 0){perror("write");return -1;}printf("寫入成功\n");}}else if (pid == 0){// 子進程接收父進程發送過來的數據char buf[128] = "";int res = 0;while (1){// 管道中沒有數據時,read會阻塞res = read(pfd[0], buf, sizeof(buf));printf("讀取成功\n");printf("%s\n", buf);}}else{perror("fork");return -1;}return 0;
}
?2.2有名管道
寫端
#include <head.h>
int main(int argc, char const *argv[])
{umask(0);// 創建有名管道if (mkfifo("./myfifo", 0664) < 0){if (errno != 17){ // 文件已存在的錯誤是一個合法的錯誤,需要排除,代碼允許正常運行perror("mkfifo");return -1;}}printf("有名管道創建成功\n");// 以只寫的方式打開有名管道int fd = open("./myfifo", O_WRONLY); // 當只有一個寫端時,open函數會阻塞if (fd < 0){perror("open");return -1;}printf("open succcess\n");char buf[128] = "";while (1){printf("請輸入>>>");fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;if (write(fd, buf, sizeof(buf)) < 0){perror("write");return -1;}if (strcmp(buf, "quit") == 0)break;printf("寫入成功\n");}close(fd);return 0;
}
讀端
#include <head.h>
int main(int argc, char const *argv[])
{umask(0);// 創建有名管道if (mkfifo("./myfifo", 0664) < 0){if (errno != 17){ // 文件已存在的錯誤是一個合法的錯誤,需要排除,代碼允許正常運行perror("mkfifo");return -1;}}printf("有名管道創建成功\n");// 以只讀的方式打開有名管道int fd = open("./myfifo", O_RDONLY); // 當只有一個讀端時,open函數會阻塞if (fd < 0){perror("open");return -1;}printf("open succcess\n");char buf[128] = "";int res = 0;while (1){bzero(buf, sizeof(buf));res = read(fd, buf, sizeof(buf));if (res < 0){perror("read");return -1;}else if ((res == 0) || strcmp(buf, "quit") == 0){printf("寫端退出\n");break;}printf("buf=%s\n", buf);}close(fd);return 0;
}
三、信號
原理
信號是一種異步通信的方式
異步:任務與任務之間無關系,根據CPU輪詢機制來運行多個任務
同步:任務與任務之間有先后關系,必須要等任務A結束2后才能執行任務B
1.signal
#include <head.h>void handler(int sig)
{printf("sig=%d\n", sig);return;
}
int main(int argc, char const *argv[])
{// 捕獲2)SIGINT信號if (signal(2, handler) == SIG_ERR)//第一個參數:指定要捕獲的信號,天對應的編號或宏//第二個參數:可以填SIG_IGN:忽略信號//SIG_DFL:執行默認操作//捕獲信號信號:填寫函數指針變量{perror("signal");return -1;}printf("捕獲信號成功\n");while (1){printf("主函數\n");sleep(1);}return 0;
}
?
練習:用信號的方式回收僵尸進程
#include <head.h>
int count = 0;void handler(int sig)
{while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char const *argv[])
{// 捕獲17)SIGCHLD__sighandler_t s = signal(17, handler);if (SIG_ERR == s){perror("signal");return -1;}int i = 0;while (i < 100){int res = fork();if (res == 0)exit(0);i++;}while (1)sleep(1);return 0;
}
2.kill
當收到quit時,父子進程全部退出
#include <head.h>
int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid > 0){while (1){printf("父進程 %d %d\n", getpid(), pid);sleep(1);}}else if (pid == 0){char buf[128] = "";bzero(buf, sizeof(buf));while (1){printf("請輸入>>>");fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;printf("buf=%s", buf);if (strcmp(buf, "quit") == 0){break;}}// 子進程給父進程發信號,請求父進程退出kill(getppid(), 9); // 9號是殺死信號}else{perror("fork");return -1;}return 0;
}
3.alarm
設置3秒的定時器
#include <head.h>
void callback(int sig)
{printf("超時了\n");alarm(3);return;
}
int main(int argc, char const *argv[])
{// 捕獲14號信號if (signal(14, callback) == SIG_ERR){perror("signal");return -1;}alarm(3);while (1){printf("signal....\n");sleep(1);}return 0;
}
四、消息隊列
消息隊列按照先進先出的原則,但也可以限制消息類型讀取
消息隊列獨立于進程,等進程結束后,消息隊列以及其中的內容不會刪除,除非重啟操作系統或手動刪除
發送數據
#include <head.h>
struct msgbuf
{long mtype; // 消息類型,必須大于0char mtext[128]; // 消息內容
};
int main(int argc, char const *argv[])
{// 創建key值key_t key = ftok("/home/ubuntu/", 1);// 第二個參數:填非0if (key < 0){perror("ftok");return -1;}// 創建消息隊列int msqid = msgget(key, IPC_CREAT | 0664);if (msqid < 0){perror("msgget");return -1;}struct msgbuf sndbuf;while (1){printf("請輸入人消息類型>>>");scanf("%ld", &sndbuf.mtype);getchar();if (sndbuf.mtype == 0){ // 若輸入類型為0,退出循環break;}printf("請輸入消息內容>>>");fgets(sndbuf.mtext, sizeof(sndbuf.mtext), stdin);sndbuf.mtext[strlen(sndbuf.mtext) - 1] = 0;// 向消息隊列中發送數據if (msgsnd(msqid, &sndbuf, sizeof(sndbuf.mtext), 0) < 0){perror("msgsnd");return -1;}printf("發送成功\n");}// 刪除消息隊列if (msgctl(msqid, IPC_RMID, NULL) < 0){perror("msgctl");return -1;}printf("刪除消息隊列成功\n");return 0;
}
?
接收數據
#include <head.h>
struct msgbuf
{long mtype; // 消息類型,必須大于0char mtext[128]; // 消息內容
};
int main(int argc, char const *argv[])
{// 創建key值key_t key = ftok("/home/ubuntu/", 1);// 第二個參數:填非0if (key < 0){perror("ftok");return -1;}// 創建消息隊列int msqid = msgget(key, IPC_CREAT | 0664);if (msqid < 0){perror("msgget");return -1;}struct msgbuf recvbuf;int res = 0;while (1){// 從指定的消息隊列中讀取數據// 讀取消息隊列中的第一條消息,先進先出res = msgrcv(msqid, &recvbuf, sizeof(recvbuf.mtext), 0, IPC_NOWAIT);if (res < 0){perror("msgrcv");return -1;}printf("接收到的消息為%s\n", recvbuf.mtext);}// 刪除消息隊列if (msgctl(msqid, IPC_RMID, NULL) < 0){perror("msgctl");return -1;}printf("刪除消息隊列成功\n");return 0;
}
五、共享內存
共享內存是最高效的進程間通信方式
多個進程可以同時訪問共享內存,修改其中的內容,所以共享內存其實是臨界資源,需要注意進程間的同步互斥
共享內存獨立于進程,等進程結束后,共享內存以及其中的內容不會刪除,除非重啟操作系統或者手動刪除
發送數據
#include <head.h>
int main(int argc, char const *argv[])
{// 創建key值key_t key = ftok("./", 10);if (key < 0){perror("ftok");return -1;}printf("key=%#x\n", key);// 創建共享內存,獲得shmid號int shmid = shmget(key, 32, IPC_CREAT | 0664);if (shmid < 0){perror("shmget");return -1;}printf("shmid=%d\n", shmid);// 映射共享內存到用戶空間void *addr = shmat(shmid, NULL, 0);if (addr == (void *)-1){perror("shmat");return -1;}// 先往共享內存中存儲一個int類型的數據*(int *)addr = 10;// 再往int類型數據后面存儲一個字符串char *ptr = (char *)addr + 4;strcpy(ptr, "hello world");return 0;
}
接收數據
#include <head.h>
int main(int argc, char const *argv[])
{// 創建key值key_t key = ftok("./", 10);if (key < 0){perror("ftok");return -1;}// 創建共享內存,獲得shmid號int shmid = shmget(key, 32, IPC_CREAT | 0664);if (shmid < 0){perror("shmget");return -1;}// 映射共享內存到用戶空間void *addr = shmat(shmid, NULL, 0);if (addr == (void *)-1){perror("shmat");return -1;}printf("%d\n", *(int *)addr);printf("%s\n", (char *)((int *)addr + 1));return 0;
}