日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux进程间通信(管道、消息队列、共享内存、信号、信号量)

發布時間:2023/12/10 linux 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux进程间通信(管道、消息队列、共享内存、信号、信号量) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • Linux進程間通信概述
  • 1.管道
    • 無名管道(pipe)
    • 有名管道(fifo)
  • 2.消息隊列(msg)
    • 消息隊列的通信原理
    • 消息隊列相關api
    • 消息隊列收發數據
    • 鍵值生成
    • 消息隊列移除
  • 3.共享內存(shm)
  • 4.信號(sig)
    • 信號概述
    • 信號編程(入門)
    • 信號攜帶消息(高級)
  • 5.信號量(sem)
    • P、V操作(類似信號量lock、unlock)
  • 6.通信方式總結

Linux進程間通信概述

進程間通信(IPC,InterProcess Communication)是指在不同進程之間傳播或交換信息。
IPC 的方式通常有管道(包括無名管道和命名管道)、消息隊列、共享內存、信號、信號量、Socket、Streams 等。其中 Socket 和 Streams 支持不同主機上的兩個進程 IPC。

1.管道

無名管道(pipe)

管道,通常指無名管道,是 UNIX 系統IPC最古老的形式。

特點:

1、它是半雙工的(即數據只能在一個方向上流動),具有固定的讀端(fd[0])和寫端(fd[1])。

2、它只能用于具有親緣關系的進程之間的通信(也是父子進程或者兄弟進程之間)。

3、它可以看成是一種特殊的文件,對于它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,并不屬于其他任何文件系統,并且只存在于內存中。

4、管道中的數據讀走就沒了

原型:

1 #include <unistd.h> 2 int pipe(int fd[2]); // 返回值:若成功返回0,失敗返回-1

當一個管道建立時,它會創建兩個文件描述符:fd[0]為讀而打開,fd[1]為寫而打開。要關閉管道只需將這兩個文件描述符關閉即可。如下圖:


例子:
創建管道后,在父進程中寫入,在子進程中讀

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() {int fd[2];pid_t pid;char readBuf[128];int nwrite;int nread;// int pipe(int pipefd[2]);if(pipe(fd) == -1){printf("創建管道失敗\n");}pid=fork();//創建子進程if(pid == -1){printf("創建子進程失敗\n");//1.在父進程寫入}else if(pid > 0){printf("這是父進程\n");close(fd[0]);//關閉讀文件//ssize_t write(int fd, const void *buf, size_t count);nwrite=write(fd[1],"hello from father process",strlen("hello from father process"));wait(NULL);//2.在子進程讀}else{printf("這是子進程\n");close(fd[1]);//ssize_t read(int fd, void *buf, size_t count);nread=read(fd[0],readBuf,128);//如果度端沒有內容,會阻塞等待printf("來自父進程的寫入內容是:%s\n",readBuf);exit(0);}return 0; }

注意: 管道為半雙工通信,讀和寫操作在同一時間內只能進行一個,所以在讀的時候要關閉寫端,寫的時候關閉讀端。
運行結果:

有名管道(fifo)

FIFO,也稱為命名管道,它是一種文件類型。

1、特點
FIFO可以在無關的進程之間交換數據,與無名管道不同。

FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在于文件系統中。

2、原型

1 #include <sys/stat.h> 2 // 返回值:成功返回0,出錯返回-1 3 int mkfifo(const char *pathname, mode_t mode);

其中的 mode 參數與open函數中的 mode 相同。一旦創建了一個 FIFO,就可以用一般的文件I/O函數操作它。

open 一個FIFO時,是否設置非阻塞標志(O_NONBLOCK)的區別:

1、若沒有指定O_NONBLOCK(默認),只讀 open 要阻塞到某個其他進程為寫而打開此 FIFO(參照下面的例子)。類似的,只寫 open 要阻塞到某個其他進程為讀而打開它。

2、若指定了O_NONBLOCK,則只讀 open 立即返回。而只寫 open 將出錯返回 -1 如果沒有進程已經為讀而打開該 FIFO,其errno置ENXIO。

創建管道例子

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h>int main() {//int mkfifo(const char *pathname, mode_t mode);if(mkfifo("./file",0600) == -1 && errno != EEXIST){printf("mkfifo創建失敗\n");perror("why:");}else{if(errno == EEXIST){printf("文件已經存在\n");}printf("mkfifo創建成功\n");}return 0; }

使用有名管道通信的例子

read代碼:

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main() {int nread;mkfifo("./file",0600);char buf[1024] = {0};int fd = open("./file",O_RDONLY);//若沒有指定O_NONBLOCK(默認),只讀 open 要阻塞到某個其他進程為寫而打開此 FIFOprintf("open success\n");while(1){nread = read(fd,buf,1024);printf("read %d byte ,neirong:%s\n",nread,buf);}return 0; }

write代碼:

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main() {mkfifo("./file",0600);char *str = "this is a fifo demo!";int fd = open("./file",O_WRONLY);printf("open success\n");while(1){write(fd,str,strlen(str));sleep(1);}return 0; }

運行read會阻塞,一直到運行write后read才會繼續往下執行。

個人感覺很像文件操作,不過不用進行lseek等操作,內核知道這是管道操作,數據讀出時管道數據清除。

2.消息隊列(msg)

消息隊列的通信原理

消息隊列,是消息的鏈接表(結構體),存放在內核中。一個消息隊列由一個標識符(即隊列ID)來標識。

特點
消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級。

消息隊列獨立于發送與接收進程。進程終止時,消息隊列及其內容并不會被刪除(Linux內核機制進行管理)。

消息隊列可以實現消息的隨機查詢(鏈表機制就是這樣的),消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

消息隊列的通信原理理解圖:

消息隊列相關api

1 #include <sys/msg.h>2 // 創建或打開消息隊列:成功返回隊列ID,失敗返回-1 3 int msgget(key_t key, int flag);4 // 發送消息:成功返回0,失敗返回-1 5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);6 // 接收消息:成功返回消息數據的長度,失敗返回-1 7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);8 // 控制消息隊列:成功返回0,失敗返回-1 9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下兩種情況下,msgget將創建一個新的消息隊列:

1、如果沒有與鍵值key相對應的消息隊列,并且flag中包含了IPC_CREAT標志位。
2、key參數為IPC_PRIVATE。

函數msgrcv在讀取消息隊列時,type參數有下面幾種情況:

type == 0,返回隊列中的第一個消息;
type > 0,返回隊列中消息類型為 type 的第一個消息;
type < 0,返回隊列中消息類型值小于或等于 type 絕對值的消息,如果有多個,則取類型值最小的消息。
可以看出,type值非 0 時用于以非先進先出次序讀消息。也可以把 type 看做優先級的權值。(其他的參數解釋,請自行Google之)

消息隊列收發數據

readmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf readBuf;//int msgget(key_t key, int msgflg);int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);return 0; }

sendmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf sendBuf={888,"this message from que"};;//int msgget(key_t key, int msgflg);int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞return 0; }

運行結果:
先運行readmsg阻塞在那等待發送消息


實現雙方消息通信都能發送和接收

readmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf readBuf;struct msgbuf sendBuf={998,"thank you que, i have received"};//int msgget(key_t key, int msgflg);int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf("發送完畢\n");return 0; }

sendmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf sendBuf={888,"this message from que"};struct msgbuf readBuf;//int msgget(key_t key, int msgflg);int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf("發送完畢\n");msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);return 0; }

鍵值生成

readmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf readBuf;struct msgbuf sendBuf={998,"thank you que, i have received"};//key_t ftok(const char *pathname, int proj_id);key_t key;key=ftok(".",'z');//"."當前路徑, proj_id典型的用法是將一個ASCII碼作為proj_id,隨便取值都可以 內核這兩者組合出一個鍵值printf("key = %x\n",key);//用16進制輸出//int msgget(key_t key, int msgflg);int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf("發送完畢\n");return 0; }

sendmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf sendBuf={888,"this message from que"};struct msgbuf readBuf;key_t key;key=ftok(".",'z');//"."當前路徑,proj_id典型的用法是將一個ASCII碼作為proj_idprintf("key = %x\n",key);//用16進制輸出//int msgget(key_t key, int msgflg);int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf("發送完畢\n");msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);return 0; }

消息隊列移除

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

struct msqid_ds *buf這里我們一般寫NULL
CMD類型參照下圖(箭頭指的就是i用的最多的,把消息隊列生成的鏈表在內核里移除):

例如:
readmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf readBuf;struct msgbuf sendBuf={998,"thank you que, i have received"};//key_t ftok(const char *pathname, int proj_id);key_t key;key=ftok(".",'z');//"."當前路徑,proj_id典型的用法是將一個ASCII碼作為proj_idprintf("key = %x\n",key);//用16進制輸出//int msgget(key_t key, int msgflg);int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf("發送完畢\n");msgctl(msgId,IPC_RMID,NULL);return 0; }

sendmsg

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[128]; /* message data */ };int main() {struct msgbuf sendBuf={888,"this message from que"};struct msgbuf readBuf;key_t key;key=ftok(".",'z');//"."當前路徑,proj_id典型的用法是將一個ASCII碼作為proj_idprintf("key = %x\n",key);//用16進制輸出//int msgget(key_t key, int msgflg);int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打開,沒有則創建權限是可讀刻寫可執行if(msgId == -1){printf("創建失敗\n");}//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf("發送完畢\n");msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式printf("讀取來自隊列的內容:%s\n",readBuf.mtext);//int msgctl(int msqid, int cmd, struct msqid_ds *buf);msgctl(msgId,IPC_RMID,NULL);return 0; }

3.共享內存(shm)

共享內存(Shared Memory),指兩個或多個進程共享一個給定的存儲區

特點

  • 共享內存是最快的一種 IPC,因為進程是直接對內存進行存取。

  • 因為多個進程可以同時操作,所以需要進行同步。

信號量+共享內存通常結合在一起使用(最后一節講解),信號量用來同步對共享內存的訪問。

和消息隊列的區別:

消息隊列就像兩個人聊天,一方要將要說的話寫在一張紙上放入箱子里,另外一個人去箱子里取出來閱讀。這個箱子(消息隊列)不會自行銷毀,要調用msgctl才可以。

共享內存就好像兩個學生在上課的時候,由于不能說話,就只好拿一個本子來聊天,這個本子就相當于共享的內存,雙方可以同時看到紙上的內容。比消息隊列效率高。調用shmctl刪除共享內存。

相關api原型

1 #include <sys/shm.h>2 // 創建或獲取一個共享內存:成功返回共享內存ID,失敗返回-1 3 int shmget(key_t key, size_t size, int flag);4 // 連接共享內存到當前進程的地址空間(也叫掛載、映射):成功返回指向共享內存的指針,失敗返回-1 5 void *shmat(int shm_id, const void *addr, int flag);6 // 斷開與共享內存的連接:成功返回0,失敗返回-1 //注意,這并不是從系統中刪除該共享內存,只是當前進程不能再訪問該共享內存而已。 7 int shmdt(void *addr); 8 // 控制共享內存的相關信息:成功返回0,失敗返回-1 9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

參數說明:

key為ftok生成的鍵值
size為共享內存的長度,以字節為單位
當用shmget函數創建一段共享內存時,必須指定其 size;而如果引用一個已存在的共享內存,則將 size 指定為0 。
flag為所需要的操作和權限,可以用來創建一個共享存儲空間并返回一個標識符或者獲得一個共享標識符。
----flag的值為IPC_CREAT:如果不存在key值的共享存儲空間,且權限不為0,則創建共享存儲空間,并返回一個共享存儲標識符。如果存在,則直接返回共享存儲標識符。
----flag的值為 IPC_CREAT |IPC_EXCL:如果不存在key值的共享存儲空間,且權限不為0,則創建共享存儲空間,并返回一個共享存儲標識符。如果存在,則產生錯誤。
cmd 常用的是IPC_RMID從系統中刪除該共享內存

例子
創建共享內存并寫入數據

#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main() {key_t key;char *shmaddr;// key_t ftok(const char *pathname, int proj_id);key=ftok(".",2);printf("key = %x\n",key);//int shmget(key_t key, size_t size, int shmflg);//創建或獲取一個共享內存:成功返回共享內存ID,失敗返回-1int shmId=shmget(key,1024*4,IPC_CREAT|0666);//共享內存大小必須以字節為單位if(shmId == -1){printf("創建共享內存失敗\n");exit(-1);//異常退出}//void *shmat(int shmid, const void *shmaddr, int shmflg);//連接共享內存到當前進程的地址空間:成功返回指向共享內存的指針,失敗返回-1shmaddr=shmat(shmId,0,0);//掛載映射,如果引用一個已存在的共享內存(上方改革創建),則將 size 指定為0 printf("shmat ok\n");strcpy(shmaddr,"hello sharemessage");sleep(5);//int shmdt(const void *shmaddr);// 斷開與共享內存的連接shmdt(shmaddr);//int shmctl(int shmid, int cmd, struct shmid_ds *buf);//控制共享內存的相關信息:成功返回0,失敗返回-1shmctl(shmId,IPC_RMID,0);//IPC_RMID刪除消息隊列printf("退出\n");return 0; }

讀共享內存的內容

#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h>int main() {key_t key;char *shmaddr;// key_t ftok(const char *pathname, int proj_id);key=ftok(".",2);printf("key = %x\n",key);//int shmget(key_t key, size_t size, int shmflg);//創建或獲取一個共享內存:成功返回共享內存ID,失敗返回-1int shmId=shmget(key,1024*4,0);//只要打開就行不必創建if(shmId == -1){printf("創建共享內存失敗\n");exit(-1);//異常退出}//void *shmat(int shmid, const void *shmaddr, int shmflg);//連接共享內存到當前進程的地址空間:成功返回指向共享內存的指針,失敗返回-1shmaddr=shmat(shmId,0,0);//掛載映射,如果引用一個已存在的共享內存,則將 size 指定為0 printf("shmat ok\n");printf("內容是:%s\n",shmaddr);//int shmdt(const void *shmaddr);//斷開與共享內存的連接shmdt(shmaddr);return 0; }

4.信號(sig)

本節參照博文:https://www.jianshu.com/p/f445bfeea40a

信號概述

1.信號:對Linux來說就是軟中斷,與單片機的硬件中斷(串口)類似。如在linux中輸入 ctrl+c 來停止一個程序

2.信號的名字與編號:可在linux中通過 kill -l 查詢(Linux系統一共有64個信號,編號1-64。不存在0信號,0信號有特殊的應用:在系統級的應用中被占用)

部分信號的說明:

2)SIGINT:ctrl+c 終止信號

3)SIGQUIT:ctrl+\ 終止信號

20)SIGTSTP:ctrl+z 暫停信號

26)SIGALRM:鬧鐘信號 收到此信號后定時結束,結束進程

17)SIGCHLD:子進程狀態改變,父進程收到信號

9)SIGKILL:殺死信號

3.信號處理的三種方式:忽略,捕捉和默認動作

1、忽略:就跟字面意思一樣忽略掉它(注意:SIGKILL,SIGSTOP不能被忽略)

2、捕捉:就是一些信號處理的函數,然后讓這個函數告訴內核,當信號產生時,內核調用該函數,實現某種信號的處理

3、默認動作:每個信號都有其對應的默認的處理動作,當觸發了某種信號,系統就會立刻去執行。

4.信號的使用

其實對于常用的 kill 命令就是一個發送信號的工具,kill -9 PID或者使用命令kill -SIGKILL PID (二者作用一樣)來殺死進程。比如,我在后臺運行了一個 top 工具,通過 ps 命令可以查看他的 PID,通過 kill -9 來發送了一個終止進程的信號來結束了 top 進程。如果查看信號編號和名稱,可以發現9對應的是 9) SIGKILL,正是殺死該進程的信號。而以下的執行過程實際也就是執行了9號信號的默認動作——殺死進程。

對于信號來說,最大的意義不是為了殺死信號,而是實現一些異步通訊(即信號處理方式第二種–捕捉)的手段,那么如何來自定義信號的處理函數呢?

信號編程(入門)

信號處理函數的注冊(信號綁定):signal (入門),sigaction(高級,可攜帶信號)
信號處理發送函數: kill (入門), sigqueue(高級,可攜帶信號)

信號綁定

#include <stdio.h> #include <signal.h>// typedef定義了一種類型sighandler_t//typedef void (*sighandler_t)(int);//函數指針(指向函數的指針),無返回值,傳入參數為一個int型//sighandler_t signal(int signum, sighandler_t handler); // 返回這種類型 帶有_t表示結構體 函數指針變量 void handler(int signum){switch(signum){case 2:printf("get SIGINT,signum=%d\n",signum);break;case 9:printf("get SIGKILL,signum=%d\n",signum);break;case 10:printf("get SIGUSR1,signum=%d\n",signum);break;}printf("never quit\n"); }int main(){//函數指針 指向函數signal(SIGINT,handler);//捕捉信號 SIGINT是ctrl c的指令signal(SIGKILL,handler);signal(SIGUSR1,handler);for(;;);//等于while(1);return 0; }

編譯運行,在鍵盤上輸入 ctrl+c,結果為:

信號發送(殺死程序的信號 SIGKILL)

#include <stdio.h> #include <signal.h> #include <sys/types.h> #include <stdlib.h>//typedef void (*sighandler_t)(int);//sighandler_t signal(int signum, sighandler_t handler);int main(int argc,char **argv) {int signum;int pid;char cmd[128]={0};signum=atoi(argv[1]);//將字符串str轉換成一個整數并返回結果pid=atoi(argv[2]);printf("signum=%d pid=%d\n",signum,pid);// int kill(pid_t pid, int sig);//方法一://kill(pid,signum); 發送信號x//方法二 system調用腳本sprintf(cmd,"kill -%d %d",signum,pid);system(cmd);printf("發送指令成功\n");return 0; }

運行結果:

信號的忽略:

signal(SIGINT,SIG_IGN)//可以忽略掉ctrl c 的信號

信號攜帶消息(高級)

信號攜帶消息思路:

發信號

1.用什么發 sigqueue()
2.怎么發消息

#include <signal.h>//發給誰 發什么信號(比如上面講的信號編號9 10等) int sigqueue(pid_t pid, int sig, const union sigval value); union sigval { //發送的消息(接收端也是用的這個聯合體) int sival_int;//發送整型void *sival_ptr;//發送字符串};

收信號

1.用什么綁定函數(以及收到信號如何處理動作)sigaction()
2.如何讀出消息

#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //sigactio(),會依照參數signum指定的信號編號來設置該信號的處理函數 //const struct sigaction *act,你要做什么 //struct sigaction *oldact,是否備份,不備份用NULLstruct sigaction { 凡是帶有_t說明是個結構體void (*sa_handler)(int); //信號處理程序,不接受額外數據,和signal()的參數handler一樣了 SIG_IGN 為忽略,SIG_DFL 為默認動作void (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用sigset_t sa_mask;//阻塞關鍵字的信號集(默認阻塞),可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。int sa_flags;//配置為SA_SIGINFO這個宏表示能夠接受數據}; //回調函數句柄sa_handler、sa_sigaction只能任選其一siginfo_t {int si_signo; /* Signal number */int si_errno; /* An errno value */int si_code; /* Signal code */int si_trapno; /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t si_pid; /* Sending process ID */誰發的uid_t si_uid; /* Real user ID of sending process */int si_status; /* Exit value or signal */clock_t si_utime; /* User time consumed */clock_t si_stime; /* System time consumed */sigval_t si_value; /* Signal value */接收的數據 是個聯合體 信號發送函數使用這個聯合體int si_int; /* POSIX.1b signal */數據void *si_ptr; /* POSIX.1b signal */int si_overrun; /* Timer overrun count; POSIX.1b timers */int si_timerid; /* Timer ID; POSIX.1b timers */void *si_addr; /* Memory location which caused fault */int si_band; /* Band event */int si_fd; /* File descriptor */ }

信號攜帶消息編程實現

收信號nicesignal.c

#include <stdio.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> /*struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);};*/void handler(int signum, siginfo_t *info, void *context)//info里面的參數可以通過man手冊查詢 {printf("signum=%d\n",signum);if(context != NULL){printf("get data=%d\n",info->si_int);printf("get data=%d\n",info->si_value.sival_int);//和上面一樣換了種方式printf("發送者的pid=%d\n",info->si_pid);} }int main() {struct sigaction act;printf("pid=%d\n",getpid());act.sa_sigaction=handler;act.sa_flags=SA_SIGINFO;//能夠獲取到信息信息//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);sigaction(SIGUSR1,&act,NULL);//第三個是參數是備份的這邊寫NULL SIGUSR1對應的編號為10!!!printf("22");while(1);return 0; }

發信號niceSendSig.c

#include <stdio.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> int main(int argc,char **argv) {int signum;int pid;signum=atoi(argv[1]);//atoi把字符串轉化為整形pid=atoi(argv[2]);//接收進程的pid號union sigval value;value.sival_int=100;//發送一個整型數100的消息/*union sigval {int sival_int;void *sival_ptr; 發送字符的話把地址傳進來};*///int sigqueue(pid_t pid, int sig, const union sigval value);sigqueue(pid,signum,value);printf("我的pid是:%d\n",getpid());printf("發送完畢\n");return 0; }

5.信號量(sem)

1.信號量:

信號量(Semaphore是一個計數器,用于實現進程間的互斥與同步,不用于存儲進程間的通信數據

2.特點:

(1).用于進程間同步,若要在進程間傳遞數據需要結合共享內存

(2).信號量是基于PV操作,程序對信號量是原子操作

所謂原子操作,就是“不可中斷的一個或一系列操作”,也就是不會被線程調度機制打斷的操作

(3).對信號量的操作不僅限于對信號的+1,-1,可以是任意數(視頻沒講)

P、V操作(類似信號量lock、unlock)

可以這樣理解:一間房間(臨界資源)的門前有一個盒子,盒子里有鑰匙(信號量),一個人拿了鑰匙(P操作),開了門并走進了房間,且門外還有人等著,得等進去的人出來放鑰匙(V操作),這個人才能拿鑰匙(P操作)進入房間

多道程序系統中存在許多進程,它們共享各種資源,然而有很多資源一次只能供一個進程使用。一次僅允許一個進程使用的資源稱為臨界資源。許多物理設備都屬于臨界資源,如輸入機、打印機、磁帶機等。

4.相關api
最簡單的信號量是只能取 0 和 1 的變量,這也是信號量最常見的一種形式,叫做二值信號量(Binary Semaphore)。而可以取多個正整數的信號量被稱為通用信號量。

Linux 下的信號量函數都是在通用的信號量數組上進行操作,而不是在一個單一的二值信號量上進行操作。

1 #include <sys/sem.h> 2 // 創建或獲取一個信號量組:若成功返回信號量集ID,失敗返回-1 3 int semget(key_t key, int num_sems, int sem_flags); 4 // 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1 5 int semop(int semid, struct sembuf semoparray[], size_t numops); 6 // 控制信號量的相關信息 7 int semctl(int semid, int sem_num, int cmd, ...);

例子
目前是沒有鎖的狀態,父進程想拿鎖,在這邊等待,然后子進程有鎖直接放回,父進程得到鎖后再運行。相當于不使用前面學習的wait方式保證子進程先運行,因為沒有鎖父進程卡在拿鎖那邊。

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdlib.h> #include <unistd.h>//semctl函數第三個參數cmd的宏要求后面定義一個聯合體 union semun { int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */ };void pGetKey(int semid)//p操作“拿鎖” {struct sembuf set;/*sops[0].sem_num = 0; //Operate on semaphore 0 sops[0].sem_op = 0; // Wait for value to equal 0 sops[0].sem_flg = 0;*/set.sem_num=0;//信號量編號 操作第幾個信號量(鎖) 這里就一個set.sem_op=-1;//拿鎖set.sem_flg=SEM_UNDO;//If an operation specifies SEM_UNDO, it will be automatically undone when the process terminates.//SEM_UNDO設置為當進程截止的時候,取消對鎖的操作//int semop(int semid, struct sembuf *sops, size_t nsops); 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1semop(semid,&set,1);//1代表第二個參數的個數printf("拿鎖\n"); }void vPutBackKey(int semid)//v操作“放回鎖” {struct sembuf set;/*sops[0].sem_num = 0; //Operate on semaphore 0 sops[0].sem_op = 0; // Wait for value to equal 0 sops[0].sem_flg = 0;*/set.sem_num=0;set.sem_op=1;//放回鎖set.sem_flg=SEM_UNDO;//If an operation specifies SEM_UNDO, it will be automatically undone when the process terminates.//int semop(int semid, struct sembuf semoparray[], size_t numops); 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1semop(semid,&set,1);printf("放回鎖\n"); }int main(int argc, char const *argv[]) {key_t key;int semid;key=ftok(".",2);union semun initsem;initsem.val=0;//初始是沒有鎖狀態//int semget(key_t key, int nsems, int semflg);創建或獲取一個信號量組:若成功返回信號量集ID,失敗返回-1semid=semget(key,1,IPC_CREAT|0666);//1:信號量組中有幾個信號量 這邊選擇1個,IPC_CREAT|0666:如果有就獲取沒有就創建信號量組,權限可讀可寫可執行//int semctl(int semid, int sem_num, int cmd, ...); 控制信號量的相關信息semctl(semid,0,SETVAL,initsem);//初始化信號量,int sem_num是代表操作第幾個信號量,我這邊寫0代表操作第一個信號量(和數組一樣 第一個從0開始),多個信號量要構造數組//cmd可以通過man手冊差相關的宏,SETVAL設置信號量的初值,設置為initsem(聯合體),里面有1把瑣int pid = fork();//創建子進程if(pid > 0){//去拿鎖pGetKey(semid);//拿鎖 P操作printf("這是父進程\n");vPutBackKey(semid);//放回鎖 semctl(semid,0,IPC_RMID);//銷毀鎖 }else if(pid == 0){printf("這是子進程\n");vPutBackKey(semid);//放鎖 V操作}else{printf("創建失敗\n");}return 0; }

鎖初始化為0,子進程先運行,運行結束放鎖(1),父進程拿鎖(-1),然后放鎖(1),最后銷毀鎖。

6.通信方式總結

1.管道(無名管道):速度慢,容量有限,只有父子進程能通訊

2.FIFO(有名管道):任何進程間都能通訊,但速度慢

3.消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題

4.信號量:不能傳遞數據,只能用來同步(P操作、V操作)

5.共享內存區:能夠很容易控制容量,速度快,但要保持同步(和信號量結合使用),比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當于線程中的線程安全。當然,共享內存區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一進程內的一塊內存

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的Linux进程间通信(管道、消息队列、共享内存、信号、信号量)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。