管道
在Linux中,管道是一種進程間通信方式,它允許一個進程(寫入端)將其輸出直接連接到另一個進程(讀取端)的輸入。從本質上說,管道也是一種文件,但它又和一般的文件有所不同。
具體來說,管道有兩種類型:無名管道和有名管道。無名管道是指以匿名管道的方式連接兩個進程,只能在具有親緣關系的進程之間使用,例如父子進程。有名管道則是一種文件類型,允許無關進程之間的通信,即可以在非親緣關系的進程之間使用。
無名管道通過文件描述符創建,其中文件描述符0(stdin)用于讀取管道的讀取端,文件描述符1(stdout)用于寫入管道的寫入端。有名管道則通過文件系統中的某一個文件名來建立,其它進程(不論是父子進程還是非親緣關系進程)可以通過打開該文件來進行通信。
使用管道時,需要注意以下幾點:
管道是單向的,只能從寫入端向讀取端傳遞數據。
管道的大小是有限的,當管道被填滿時,寫入端將無法繼續寫入數據。
讀取端不能比寫入端更快地讀取數據,否則會導致讀取端阻塞。
有名管道可以跨越不同進程號和主機之間的通信,但需要先用 mkfifo 命令創建管道文件。
使用完畢后,需要手動刪除管道文件。
讓我們更深入地理解一下無名管道的工作方式。在Linux中,每個進程都有一個標準輸入(stdin),一個標準輸出(stdout)和一個標準錯誤(stderr)。無名管道就是利用了進程的stdin和stdout來實現的。
具體來說,當一個進程需要將輸出發送到另一個進程的輸入時,可以使用管道。管道的寫入端與發送進程的stdout相連接,而管道的讀取端與接收進程的stdin相連接。這樣,發送進程的輸出就可以直接被接收進程讀取。
例如,在shell中可以通過“|”符號來實現無名管道的功能。例如:“ls -al | grep .txt”命令中,管道連接了“ls -al”和“grep .txt”兩個命令,將“ls -al”命令的輸出作為“grep .txt”命令的輸入,從而實現文件名搜索的功能。
接下來是有名管道。有名管道是一種在文件系統中存在的特殊文件,它允許無關的進程之間進行通信。與無名管道只能在具有親緣關系的進程之間使用不同,有名管道可以在任何進程之間使用,不論它們是否有親緣關系。
有名管道通過文件系統中的文件名來建立,然后其他進程可以通過打開這個文件來進行通信。使用有名管道時,需要先用“mkfifo”命令創建管道文件,然后通過文件I/O操作來進行數據的讀寫。
這就是Linux中的管道概念的基本解釋。它是一種非常強大的工具,可以用于在不同的進程之間傳遞數據,使得這些進程可以協同工作。
匿名管道
匿名管道通過pipe()函數實現:
在Linux中,pipe()函數是用于創建無名管道的函數。它允許將一個進程的輸出直接連接到另一個進程的輸入,從而實現在不同進程之間進行數據傳遞。
pipe()函數在頭文件unistd.h中聲明,其原型如下:
int pipe(int pipefd[2]);
pipefd是一個包含兩個整數的數組,用于存儲管道的文件描述符。pipefd[0]是管道的讀取端,pipefd[1]`是管道的寫入端。
當pipe()函數成功時,返回值為0;如果出現錯誤,返回值為-1,并設置errno來表示錯誤原因。
注意,在父進程和子進程中要分別關閉管道的讀取端和寫入端,以避免資源泄漏。
實例
這個實例的功能:建立一個父子進程關系,讓父進程往管道當中寫,子進程從管道當中讀出來數據:
運行結果:
命名管道
命名管道通過mkfifo()函數實現:
mkfifo是一個在Linux系統中用于創建命名管道的函數,也被稱為FIFO文件。命名管道是一種特殊類型的文件,它被設計為在進程間通信(IPC)中臨時存儲數據。FIFO文件通常在shell中使用,例如mkfifo /tmp/myfifo。
mkfifo函數的原型如下:
#include <sys/types.h>
#include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
mkfifo`函數需要兩個參數:
pathname:這是要創建的FIFO文件的路徑名。
mode:這是文件的權限位。這通常包括讀、寫和執行權限,就像在chmod函數中使用的一樣。
這個函數會創建一個具有指定權限的FIFO文件,如果文件已經存在,那么它將被打開并返回一個文件描述符。如果文件不存在,那么一個新的文件將被創建。
當mkfifo成功時,返回值為0。如果失敗,返回值為-1,并設置全局變量errno以指示錯誤原因。可能的錯誤包括權限問題、磁盤空間不足、文件系統錯誤等。
實例
以下是一個簡單的使用mkfifo的例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h> int main() { int result = mkfifo("/tmp/myfifo", 0666); if (result == -1) { perror("mkfifo"); return 1; } return 0;
}
在這個例子中,我們嘗試在"/tmp"目錄下創建一個名為"myfifo"的FIFO文件,文件的權限設置為0666(也就是用戶、組和其他所有人都可以讀和寫)。如果創建失敗,我們打印出錯誤信息并返回1。
XSI(也就是SysV系列進程通信手段)
XSI是X/Open公司制定用于進程通信的系統接口,是UNIX系統V版本中定義的一組系統調用和信號的集合。在Linux系統中,XSI提供了一種用于進程間通信(IPC)的機制。
XSI進程間通信需要借助系統內核,需要創建內核對象,內核對象會以整數形式返回給用戶態,相當于文件描述符,也叫IPC標識符。
XSI包括信號量、共享內存、消息隊列和信號等進程間通信方式。其中,信號量是一個計數器,可以對計數器進行加操作和減操作。在加操作時,計數器立即返回;在減操作時,如果計數器的當前值足夠進行減操作,則減操作不會被阻塞,如果計數器的當前值不夠進行減操作,則阻塞等待直到計數器的值足夠進行減操作。
此外,XSI還包括一些創建和管理共享內存的函數,例如shmget用于創建或獲取共享內存,shmat用于將共享內存映射到進程的虛擬地址空間等。
在Linux中使用 ipcs 命令可以查看當前系統中存在的各種IPC(Inner-Process Communication進程間通信)機制情況:
這三種方式都可以用于有親緣關系的進程間通信,也可以用于沒有親緣關系的進程間通信。
從上圖中可以看到,,每一種通信機制都有一個key,這個key的作用就是用來確保沒有親緣關系的兩個進程之間通信時拿到的是同一個通信機制(有親緣關系的就很簡單了,不需要額外的操作,因為是fork出來的)。
通過ftok函數我們可以拿到通信機制的key。
ftok()
ftok是Linux系統下的一個函數,主要用于創建唯一的鍵值(稱為令牌)以用于標識文件系統對象,比如共享內存、消息隊列和信號量等System V IPC對象。
函數原型如下:
#include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
ftok`函數的參數包括:
pathname:一個已經存在的文件或目錄的路徑。該函數將通過這個路徑來生成一個唯一的鍵值。
proj_id:這是一個用戶定義的整數,用于在多個進程之間區分不同的鍵值。通常,如果兩個進程使用相同的pathname和proj_id調用ftok,那么它們將得到相同的鍵值。
函數的返回值是一個key_t類型的鍵值,該鍵值可以被用作System V IPC對象的標識符。如果函數執行失敗,將返回-1。
值得注意的是,雖然ftok函數生成的鍵值在大多數情況下都是唯一的,但在某些情況下,兩個不同的路徑可能會生成相同的鍵值。此外,不同的文件系統或操作系統可能會以不同的方式實現ftok函數,因此在不同的系統之間不能保證鍵值的唯一性。這就是為什么在使用ftok函數時,最好選擇一個唯一的proj_id,并在可能的情況下避免在不同的系統之間共享鍵值。
IPC機制系列函數
對于每一種IPC機制,基本都有 XXXget()、XXXop()、XXXctl()。
其中XXX是每一種IPC機制的縮寫,消息隊列的縮寫是msg,信號量數組的縮寫是sem,共享內存是shm:
接下來將分別介紹這些函數。
消息隊列
Linux下的消息隊列機制是System V IPC(進程間通信)的一種實現方式,允許進程之間以消息的形式傳遞數據。與管道、信號和共享內存等其他IPC機制相比,消息隊列具有一些獨特的優點,例如可以跨多個進程傳遞數據、可以實現異步通信、可以避免忙輪詢等。
消息隊列的實現原理可以簡單地描述為:在內存中創建一個隊列數據結構,進程可以向該隊列中寫入消息,也可以從該隊列中讀取消息。每個消息都有一個類型和長度,類型用于區分不同的消息,長度則用于限制消息的大小。進程可以通過指定消息類型來讀取或寫入特定類型的消息,也可以通過指定消息的優先級來控制消息的讀寫順序。
在Linux系統中,消息隊列是通過消息隊列標識符(也稱為消息隊列ID)來訪問的。進程可以使用msgget函數來獲取或創建一個消息隊列,該函數會根據指定的鍵值生成一個唯一的消息隊列標識符。一旦獲取到消息隊列標識符,進程就可以使用msgsnd函數向隊列中發送消息,或者使用msgrcv函數從隊列中接收消息。同時,進程還可以使用msgctl函數來控制消息隊列的屬性,例如設置權限、刪除隊列等。
需要注意的是,Linux系統對消息隊列的數量和大小都有限制,因此在使用消息隊列時需要注意這些限制,并根據實際需求進行合理的設計和使用。此外,由于消息隊列是保存在內存中的,因此在系統重啟后,所有的消息隊列都會被清空。如果需要長期保存數據,可以考慮使用文件或其他持久化存儲方式來實現進程間通信。
msgget()
msgget是Linux系統下的一個函數,用于獲取或創建一個消息隊列。它是System V消息隊列子系統的一部分,通常用于進程間的通信(IPC)。
函數原型如下:
#include <sys/ipc.h> int msgget(key_t key, int flags);
msgget`函數的參數包括:
key:一個由ftok函數生成的鍵值,用于標識消息隊列。如果這個鍵值是0,那么msgget將創建一個新的消息隊列。
flags:這是一個標志位,用于控制msgget的行為。最常用的標志是IPC_CRE,它告訴msgget如果消息隊列不存在則創建一個新的消息隊列。
函數的返回值是一個指向消息隊列的描述符(即消息隊列的標識符),如果函數執行失敗,將返回-1。
在使用msgget函數時,需要注意以下幾點:
消息隊列是進程間通信的一種方式,因此需要謹慎地使用,避免在多個進程之間造成混亂。
消息隊列的創建和使用都需要使用適當的權限(即flags參數需要按位或上一個權限值)。一般來說,只有具有適當權限的用戶或進程才能創建和使用消息隊列。
消息隊列是有限制的,因此在使用時需要了解其限制,例如消息的最大長度和隊列的最大數量等。
消息隊列在使用完畢后需要使用msgctl函數來釋放和刪除。
msgop()
msgop并不是Linux下的一個具體函數,指的是涉及消息隊列的一系列操作。是和消息隊列相關的函數,比如msgsnd、msgrcv和msgctl等。
msgsnd: 此函數用于將消息添加到消息隊列中。它接受四個參數:消息隊列的標識符、指向要發送的消息的指針、消息的大小以及一個標志位。這個函數在成功時返回0,在失敗時返回-1。
msgrcv: 此函數用于從消息隊列中接收消息。它接受五個參數:消息隊列的標識符、指向接收消息的緩沖區的指針、緩沖區的大小、要接收的消息的類型以及一個標志位。這個函數在成功時返回接收到的消息的大小,在失敗或沒有消息可接收時返回-1。
msgctl: 此函數用于控制消息隊列,包括設置權限、獲取狀態以及刪除消息隊列等操作。它接受三個參數:消息隊列的標識符、要執行的操作以及一個可選的參數,該參數根據要執行的操作而有所不同。這個函數在成功時返回0,在失敗時返回-1。
在使用這些函數時,你需要注意一些事項:
首先,你需要使用msgget函數來獲取或創建一個消息隊列。這個函數會返回一個唯一的消息隊列標識符,你可以使用這個標識符來進行后續的消息發送和接收操作。
其次,你需要注意消息的大小和類型。每個消息都有一個大小和一個類型,大小用于限制消息的長度,類型用于區分不同的消息。你可以在發送和接收消息時指定消息的大小和類型。
最后,你需要注意權限和錯誤處理。只有具有適當權限的進程才能創建和使用消息隊列。同時,你需要檢查函數的返回值以處理可能的錯誤。
實例
建立三個文件,proto就是協議,用來約定雙方的對話格式,sender為發送方發送一個學生信息,rcver為接收方接收該學生信息:
proto.h:
#ifndef PROTO_H__#define PROTO_H__//對話雙方需要拿到同一個msg id,那么就必須要得到同一個key值
//所以我們在協議中約定好ftok的兩個參數,以確保它們拿到的是同一個key
//KEYPATH就是ftok函數的第一個參數
#define KEYPATH "/etc/services"
//KEYPROJ 是第二個參數,用字母是因為這樣就能知道其為一個整形,有單位比較規范
#define KEYPROJ 'g'
#define NAMESIZE 32//需要發送的學生信息格式的封裝
struct msg_st{long mtype;//這個是由msgrcv函數的第二個參數消息格式必須要的,表示消息類型,本例用不到但必須存在char name[NAMESIZE];int math;int chinese;
};#endif
rcver.c:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>#include "proto.h"int main(){key_t key;struct msg_st rbuf;int msgid;//先拿到keykey = ftok(KEYPATH,KEYPROJ);if(key < 0){perror("ftok()");exit(1);}//根據拿到的key創建消息隊列實例,注意要按位或上權限信息嗷msgid = msgget(key,IPC_CREAT|0600);if(msgid < 0){perror("msgget()");exit(1);}//從消息隊列中拿到數據//第一個參數是消息隊列實例的id值//第二個參數是接收到的消息數據所要存放的地方//第三個參數表示接收來的數據類型大小(自己需要的數據的大小,所以-long,其實不減也行)//第四個參數指定要接收的消息的類型,為0就是不挑//第五個參數指定一些特殊要求,為0就是沒有while(1){//循環接收打印數據if(msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0) < 0){perror("msgrcv()");exit(1);}printf("NAME = %s\n",rbuf.name);printf("MATH = %d\n",rbuf.math);printf("CHINESE = %d\n",rbuf.chinese);}//銷毀消息隊列實例//第一個參數是要進行控制的消息隊列實例//第二個參數是要進行的操作命令//第三個參數是第二個參數操作的傳參,這里沒有,寫NULLmsgctl(msgid,IPC_RMID,NULL);exit(0);
}
sender.c:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#include "proto.h"int main(){key_t key;struct msg_st sbuf;//先拿到keykey = ftok(KEYPATH,KEYPROJ);if(key < 0){perror("ftok()");exit(1);}//根據拿到的key獲得消息隊列實例即可//因為通信的雙方只要有一方創建了消息隊列即可//本程序中已經由接收方創建了,所以這里寫0表示沒有特殊操作int msgid = msgget(key,0);if(msgid < 0){perror("msgget()");exit(1);}//發送數據//先構造數據sbuf.mtype = 1; //這個值大于0即可//這里注意C語言的語法要求//name是一個數組名,是一塊連續空間的起始位置//如果沒有在一開始定義的時候就進行初始化賦值//那么后面再進行賦值時其將成為常量,常量不能在賦值號的左邊出現嗷//所以只能使用strcpy進行賦值strcpy(sbuf.name,"Alan");sbuf.math = rand()%100;sbuf.chinese = rand()%100;if(msgsnd(msgid,&sbuf,sizeof(sbuf)-sizeof(long),0) < 0){perror("msgsnd()");exit(1);}puts("ok!");exit(0);
}
先運行接收方:
再運行發送方:
再查看接收方:
可以看見已經接收到了發送過來的數據。
我們也可以不先運行接收方,直接讓發送方連發三次數據,當接收方運行的時候就會一下子接收到三條信息,因為消息隊列中緩存的有數據,只要去相同的消息隊列實例中拿取數據即可。
信號量數組
信號量數組是一個由多個信號量組成的數據結構,每個信號量都可以用來表示一個資源的使用情況。在Linux系統中,信號量數組被廣泛用于進程間通信和同步,以及保護共享資源。
每個信號量都是一個計數器,可以用來表示資源的可用數量。當一個進程需要使用共享資源時,它需要先讀取信號量的值。如果信號量的值大于0,表示資源可用,進程可以使用該資源。如果信號量的值為0,表示資源不可用,進程需要等待,直到資源可用。
在Linux中,信號量數組通常用于管理多個共享資源的情況。例如,當多個進程需要訪問同一個文件時,可以使用信號量數組來保護文件的訪問權限。當一個進程正在訪問文件時,其他進程需要等待該進程釋放資源后才能訪問文件。這樣可以避免多個進程同時訪問同一個文件,從而防止文件損壞或數據不一致的問題。
需要注意的是,在Linux中,信號量數組的操作是原子操作,即它們是線程安全的。這意味著在多個進程同時訪問信號量數組時,不會出現競爭條件或死鎖的情況。
semget()
semget函數是Linux系統調用的一部分,用于獲取或創建一個信號量。信號量是一種同步機制,用于解決多進程之間的同步和協調問題。
函數原型如下:
#include <sys/ipc.h> int semget(key_t key, int nsems, int semflg);
semget`函數的參數包括:
key:一個由ftok函數生成的鍵值,用于標識信號量集。如果這個鍵值是0,那么semget將創建一個新的信號量集。
nsems:指定信號量集中的信號量數量。通常,這個值被設置為1,表示只創建一個單一的信號量。
semflg:標志位,用于控制semget的行為。最常用的標志是IPC_CRE,它告訴semget如果信號量集不存在則創建一個新的信號量集。
函數的返回值是一個指向信號量集的標識符(即信號量集的ID),如果函數執行失敗,將返回-1。
在使用semget函數時,需要注意以下幾點:
信號量是進程間通信的一種方式,因此需要謹慎地使用,避免在多個進程之間造成混亂。
信號量集的創建和使用都需要使用適當的權限。一般來說,只有具有適當權限的用戶或進程才能創建和使用信號量集。
信號量是有限制的,因此在使用時需要了解其限制,例如最大數量和最大值等。
信號量集在使用完畢后需要使用semctl函數來釋放和刪除。
總之,semget函數用于獲取或創建信號量集,它是Linux下進程間通信的一種機制,可以用于解決多進程之間的同步和協調問題。
semop()
semop函數是Linux系統下用于操作信號量的函數之一。信號量是一種用于進程間同步的機制,通過它可以協調多個進程之間的訪問和修改共享資源。
semop函數的原型如下:
#include <sys/ipc.h>
#include <sys/sem.h> int semop(int semid, struct sembuf *sops, size_t nsops);
參數說明:
semid:信號量集的唯一標識符,通常通過semget函數獲取。
sops:指向一個sembuf結構體的指針,該結構體定義了要執行的操作。sembuf結構體包含三個成員:
sem_num:指定要操作的信號量在信號量集中的編號(從0開始)。
sem_op:指定要執行的操作。正值表示要增加信號量的值,負值表示要減少信號量的值。如果信號量的當前值小于要減少的值,則操作將阻塞,直到信號量的值足夠大。
sem_flg:指定操作的標志位。常用的標志包括IPC_NOWAIT(非阻塞操作)和SEM_UNDO(在操作完成后自動撤銷對信號量的改變)。
nsops:指定要執行的操作的數量,即sops數組中結構體的數量。
返回值:如果函數執行成功,返回0;如果執行失敗,返回-1并設置相應的錯誤碼。
使用semop函數時需要注意以下幾點:
在調用semop之前,需要先通過semget函數獲取信號量集的標識符。
要確保對信號量的操作是原子的,即在一個進程執行操作期間,其他進程不能對該信號量進行操作。這可以通過設置合適的標志位或使用信號量相關的函數(如sem_wait和sem_post)來實現。
要注意信號量的初始化和釋放。在使用完信號量后,應該使用semctl函數釋放相關的資源。
要注意錯誤處理。在調用semop函數后,應該檢查返回值以確定操作是否成功,并根據需要處理錯誤情況。
總之,semop函數是Linux系統下用于操作信號量的重要函數之一,它可以用于實現進程間的同步和協調。通過合理地使用信號量,可以有效地解決并發訪問共享資源時的競爭條件問題。
semctl()
semctl函數是Linux系統下的一個系統調用函數,用于控制信號量集。信號量集是一種用于管理進程間同步和互斥的數據結構。
函數原型如下:
#include <sys/ipc.h>
#include <sys/sem.h> int semctl(int semid, int semnum, int cmd, union semun arg);
參數說明:
semid:信號量集的唯一標識符,通常通過semget函數獲取。
semnum:指定要操作的信號量在信號量集中的編號(從0開始)。
cmd:指定要執行的操作命令。常見的命令包括:
——GETVAL:獲取信號量的當前值。
——SETVAL:將信號量的值設置為指定值。
——GETPID:獲取前一個對此信號進行操作的進程的標識符。
——GETNCNT:等待信號值增加的進程的總數。
——GETZCNT:等待信號值變為0的進程的總數。
——SETALL:將所有semun結構中的值設定到信號集中。
arg:根據命令的不同,可以是一個不同的參數結構體。例如,當命令為SETVAL時,需要傳遞一個包含要設置的值的長整型參數結構體。當命令為GETALL時,需要傳遞一個用于存儲所有信號值的數組的指針。
返回值:如果函數執行成功,返回0;如果執行失敗,返回-1并設置相應的錯誤碼。
使用semctl函數時需要注意以下幾點:
在調用semctl之前,需要先通過semget函數獲取信號量集的標識符。
要確保對信號量的操作是原子的,即在一個進程執行操作期間,其他進程不能對該信號量進行操作。這可以通過設置合適的標志位或使用信號量相關的函數(如sem_wait和sem_post)來實現。
要注意信號量的初始化和釋放。在使用完信號量后,應該使用semctl函數釋放相關的資源。
要注意錯誤處理。在調用semctl函數后,應該檢查返回值以確定操作是否成功,并根據需要處理錯誤情況。
總之,semctl函數是Linux系統下用于控制信號量的函數之一,它可以用于獲取和設置信號量的值、獲取和設置進程信息等操作。通過合理地使用信號量,可以有效地管理進程間的同步和互斥行為。
共享內存
共享內存(Shared Memory)是Linux下進程間通信(Inter-process Communication, IPC)的一種方式,它允許多個進程共享一段物理內存,以實現快速的數據交換和協同工作。
在Linux系統中,每個進程都有自己的虛擬地址空間,通過地址映射機制,將虛擬地址轉換為物理內存的地址。共享內存就是將一段物理內存區域定義為共享區域,多個進程可以將其虛擬地址空間映射到這個共享區域,從而可以訪問和修改共享區域的數據。
共享內存的創建和管理由操作系統完成。當一個進程需要使用共享內存時,它會向操作系統請求并獲得一段共享內存,然后將其映射到自己的虛擬地址空間中。其他進程也可以將共享內存映射到自己的虛擬地址空間,從而可以訪問和修改共享內存中的數據。
共享內存的優點在于其訪問速度非常快,因為通信時可以直接訪問內存。相較于管道等其他IPC方式,共享內存避免了數據在用戶空間和內核空間之間的復制和拷貝,提高了數據傳輸的效率。但是,共享內存也存在一些缺點,例如不支持阻塞等待操作,可能會導致死鎖等問題。此外,共享內存的使用也需要謹慎,因為多個進程可以同時訪問和修改共享區域的數據,需要有一定的數據同步和互斥機制來保證數據的正確性和一致性。
總之,共享內存是一種高效的進程間通信方式,適用于需要快速、高效地交換大量數據的場景,如數據庫、消息隊列等應用中。在使用共享內存時,需要注意數據的同步和互斥問題,避免出現死鎖和數據不一致等問題。
shmget()
shmget函數是Linux系統下用于創建或獲取共享內存段的函數。共享內存是一種進程間通信機制,它允許多個進程共享同一段物理內存,以實現快速的數據交換和協同工作。
函數原型如下:
#include <sys/ipc.h>
#include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
參數說明:
key:指定共享內存段的鍵值,通常由ftok函數生成。
size:指定共享內存段的大小,以字節為單位。
shmflg:指定共享內存段的標志位,包括如下選項:
——IPC_CREAT:如果指定的鍵值不存在,則創建共享內存段。
——IPC_EXCL:如果指定了IPC_CREAT,并且指定的鍵值已經存在,則返回錯誤。
——0:指定共享內存段的權限位,例如0600表示只有所有者可以讀寫。
返回值:如果函數執行成功,返回共享內存段的標識符(即共享內存段的ID);如果執行失敗,返回-1并設置相應的錯誤碼。
使用shmget函數時需要注意以下幾點:
共享內存段的生命周期與進程無關,當最后一個引用該共享內存段的進程退出時,共享內存段將被自動刪除。因此,在使用完共享內存段后,應該通過shmdt函數將其從當前進程的地址空間中解除映射。
共享內存段的訪問權限由其標志位指定,可以通過修改標志位來控制對共享內存段的訪問權限。例如,可以通過設置0600來限制只有所有者可以讀寫共享內存段。
在使用共享內存段時,需要確保多個進程之間的同步和互斥,以避免出現競爭條件和死鎖等問題。可以通過信號量或其他同步機制來實現進程之間的同步和互斥。
共享內存段的大小受限于系統的限制和可用內存資源。因此,在使用時需要注意合理地分配和管理內存資源。
總之,shmget函數是Linux系統下用于創建或獲取共享內存段的函數,它提供了高效的進程間通信機制,適用于需要快速、高效地交換大量數據的場景,如數據庫、消息隊列等應用中。在使用時需要注意同步和互斥問題,以及合理地分配和管理內存資源。
shmop()
注意這個使用man手冊來查的時候,必須用man shmop的方式來查詢,用man shmat是沒有用的。
Linux系統下并沒有shmop函數,而是使用shmat函數來實現共享內存的映射。shmat函數用于將共享內存段映射到調用進程的地址空間中,使得進程可以通過指針訪問共享內存中的數據。
shmat函數的原型如下:
#include <sys/ipc.h>
#include <sys/shm.h> int shmat(int shmid, const void *shmaddr, int shmflg);
參數說明:
shmid:指定共享內存段的標識符,即通過shmget函數獲取的標識符。
shmaddr:指定共享內存段的地址,如果為NULL,則由系統自動分配地址。
shmflg:指定共享內存的訪問權限和附加標志。
返回值:如果函數執行成功,返回一個指向映射區域的指針;如果執行失敗,返回-1并設置errno來指示具體的錯誤原因。
使用shmat函數時需要注意以下幾點:
調用進程必須具有足夠的權限才能訪問共享內存段。
映射區域的大小必須與共享內存段的大小相匹配,否則會導致訪問越界。
當進程不再需要訪問共享內存時,需要使用shmdt函數將映射區域解除映射,以釋放資源。
在多進程環境下,需要注意同步和互斥問題,以避免數據競爭和不一致性的情況發生。
總之,shmat函數是Linux系統下用于將共享內存映射到進程地址空間的函數之一,通過映射操作,進程可以通過指針訪問共享內存中的數據,實現進程間的數據共享和通信。
除了上述這個函數,還有一個函數shmdt;
shmdt函數是Linux系統下用于將共享內存段從當前進程的地址空間中解除映射的函數。它的原型如下:
#include <sys/types.h>
#include <sys/shm.h> int shmdt(const void *shmaddr);
參數說明:
shmaddr:一個指向共享內存段的指針,該指針通常是由shmat函數返回的地址。
返回值:如果函數執行成功,返回0;如果執行失敗,返回-1并設置errno來指示具體的錯誤原因。
shmdt函數的作用是將共享內存段從當前進程的地址空間中分離,相當于shmat函數的反操作。當進程不再需要訪問共享內存段時,可以調用shmdt函數將其解除映射,以釋放相應的資源。
需要注意的是,shmdt函數僅僅是將共享內存段從當前進程的地址空間中分離,并不會銷毀共享內存段本身。共享內存段仍然存在于系統中,直到顯式刪除或系統重啟。
在使用shmdt函數時,需要注意確保進程不再需要訪問共享內存段,以免出現意外的問題。同時,在多進程環境下,需要注意同步和互斥問題,以避免數據競爭和不一致性的情況發生。
shmctl()
shmctl函數是Linux系統下用于控制共享內存段的函數。通過shmctl函數,我們可以執行各種操作來控制共享內存段,包括刪除共享內存段、修改共享內存段的屬性等。
函數原型如下:
#include <sys/ipc.h>
#include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
參數說明:
shmid:指定共享內存段的ID,即通過shmget函數獲取的標識符。
cmd:指定要執行的控制命令,用于指定要執行的操作,比如刪除共享內存段、修改權限等。常用的命令包括IPC_RMID(刪除共享內存段)和IPC_SET(修改共享內存段的屬性)等。
buf:指向一個shmid_ds結構體的指針,用于存儲共享內存段的信息。可以為NULL,表示不獲取共享內存段的信息。
返回值:如果函數執行成功,返回值為0;如果出現錯誤,返回值為-1,并設置errno來指示具體的錯誤原因。
shmctl函數可以執行的控制命令包括:
IPC_RMID:刪除共享內存段。當使用完共享內存段后,應該通過shmctl函數將其刪除,以釋放內存資源。執行該命令時,需要將buf參數設置為NULL。
IPC_SET:修改共享內存段的屬性。可以通過該命令來修改共享內存段的標志位、擁有者和權限等屬性。執行該命令時,需要將buf參數指向一個shmid_ds結構體,并設置相應的字段來修改共享內存段的屬性。
IPC_STAT:獲取共享內存段的屬性。可以通過該命令來獲取共享內存段的詳細信息,包括創建時間、最后一次修改時間、訪問時間等。執行該命令時,需要將buf參數指向一個shmid_ds結構體,并獲取共享內存段的信息。
使用shmctl函數時需要注意以下幾點:
在刪除共享內存段時,需要注意確保沒有任何進程正在使用該共享內存段,否則會導致刪除失敗。
在修改共享內存段的屬性時,需要注意權限問題。只有具有相應權限的進程才能修改共享內存段的屬性。
在使用完共享內存段后,需要通過shmdt函數將其從當前進程的地址空間中解除映射,以釋放共享內存段所占用的資源。
在多進程環境下,需要注意同步和互斥問題,以避免出現競爭條件和死鎖等問題。可以通過信號量或其他同步機制來實現進程之間的同步和互斥。
實例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>#define MEMSIZE 1024int main(){pid_t pid;int shmid;//拿到相同的共享內存的key值//但這里因為是父子進程之間//所以不需要使用ftok來保證多個進程之間使用的是相同//的共享內存實例//ftok();//首先創建一個共享內存的實例//第一個參數表示共享內存實例的key值,這里沒有,所以我們用IPC_PRIVATE表示是匿名實例//第二個參數是要設置的共享內存大小//第三個參數表示設置共享內存實例的權限shmid = shmget(IPC_PRIVATE,MEMSIZE,0600);if(shmid < 0){perror("shmget()");exit(1);}//使用父子進程來實現共享內存的使用pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid == 0){ //子進程寫 共享內存數據//使用shmat來映射這塊共享內存實例到程序中來//第一個參數是共享內存實例的id號//第二個參數是映射到哪兒去,為NULL表示由系統自動分配//第三個參數是設置一些特殊要求,為0表示沒有void* ptr = shmat(shmid,NULL,0);if(ptr == (void *)-1){//手冊上是這么寫的,就按照手冊寫perror("shmat()");exit(1);}//映射上來之后就可以往映射到的共享內存實例內開始寫數據strcpy(ptr,"Hello!");//用完就解除映射shmdt(ptr);exit(0);}else{ //父進程讀 共享內存數據//收尸wait(NULL);void* ptr = shmat(shmid,NULL,0);if(ptr == (void *)-1){perror("shmat()");exit(1);}puts(ptr);//解除映射shmdt(ptr);//銷毀共享內存實例shmctl(shmid,IPC_RMID,NULL);exit(0);}exit(0);
}
編譯運行: