文章目錄
- 一、共享內存
- 1.1 一些接口
- 1. shmget 函數:申請一個 system v 的共享內存塊
- 2. ftok 函數:設置唯一標識碼
- 3. shmctl 函數:控制 system v 的共享內存塊(可以刪除、查看...)
- 4. shmat 函數:將進程與共享內存塊 關聯\ 掛接(attach)
- 5. shmdt 函數:將進程與共享內存塊 去關聯(detach)
- 1.2 一些命令
- 1. ipcs - - 查看三種 ipc 資源
- 2. ipcrm -- 刪除某種 ipc 資源
- 1.3 結論
- 二、消息隊列
- 1. msgget 函數:創建消息隊列
- 2. msgctl 函數
- 3. msgsnd 和 msgrcv 函數,發送和接收消息
- 三、信號量
- 1. semget
- 2. semctl 函數:可以獲取信號量的相關屬性
- 3. semop 函數:對信號量進行操作
- 總結:
一、共享內存
我們知道,進程間通信的本質就是:讓不同的進程,看到同一份資源
這里要介紹的同一份資源就是:內存塊,即 共享內存(shared memory,簡寫為 shm)
- 共享內存的原理:
- 1.創建(key 和 共享內存)
- 2.關聯進程 和 取消關聯
- 3.釋放共享內存
內存中的每塊共享內存,會有一個 struct shm 結構體
,里面放著共享內存的全部屬性,OS 通過這個結構體建立鏈表關系來對所有的共享內存進行管理,就等于把管理 shm 的問題轉化成了管理鏈表的問題。
故:
共享內存
=
共享內存的內核數據結構 (偽代碼:struct shm)
+
真正開辟的內存空間
1.1 一些接口
1. shmget 函數:申請一個 system v 的共享內存塊
頭文件:
#include <sys/ipc.h>
#include <sys/shm.h>// umask的頭文件如下
#include <sys/types.h>
#include <sys/stat.h>
int shmget(key_t key, size_t size, int shmflg);
?
參數 key:
- 使用
ftok
函數設置的唯一標識碼,他雖由用戶設置,卻是在內核中使用的?
參數 size
- 需要申請共享內存塊的大小,單位為字節,不足 PAGE 頁(4KB)時,會向上對齊到 PAGE 頁
?
參數 shmflg:
選項 IPC_CREAT and IPC_EXCL
單獨使用 IPC_CREAT:創建一個共享內存,如果共享內存不存在,就創建,如果已經存在就獲取已經存在的共享內存并返回。
IPC_CREAT | IPC_EXCL :IPC_EXCL 必須要配合 IPC_CREAT 使用,創建一個共享內存,如果共享內存不存在,就創建,如果已經存在就出錯返回
意味著,一起使用時,如果創建成功,對應的shm,一定是最新的!IPC_CREAT | IPC_EXCL | 0666 :上面的基礎上,添加權限(可以配合函數 umask(0) 使用)
?
返回值:
- 成功會返回一個共享內存標識符,失敗返回 -1
2. ftok 函數:設置唯一標識碼
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
?
參數 pathname
- 用戶設置的路徑
?
參數 proj_id
- 用戶設置的項目 id
?
返回值:
- 根據用戶傳入的參數,結合一定的算法,返回一個沖突概率很低的值。ket_t 就是一個 32 位的整數,是對 int 的封裝
3. shmctl 函數:控制 system v 的共享內存塊(可以刪除、查看…)
頭文件
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
?
參數 shmid
- 需要的共享內存塊的 shmid
?
參數 cmd:
- 選項 IPC_STAT:把用戶傳入 shmid 的相應內核數據結構信息復制到 buf 中(在調用者有讀權限的情況下,才能成功)
- 選項 IPC_RMID:刪除 shmid 為傳入值的共享內存塊
?
輸出型參數 buf:
- 需要得到 ipc 信息時傳一個相應類型的值用來接收結果
?
返回值:
- 失敗返回 -1,成功則根據 cmd 傳入的選項返回對應的值
//The buf argument is a pointer to a shmid_ds structure, defined in <sys/shm.h> as follows:struct shmid_ds {struct ipc_perm shm_perm; /* Ownership and permissions */size_t shm_segsz; /* Size of segment (bytes) */time_t shm_atime; /* Last attach time */time_t shm_dtime; /* Last detach time */time_t shm_ctime; /* Last change time */pid_t shm_cpid; /* PID of creator */pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */shmatt_t shm_nattch; /* No. of current attaches */...};//The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):struct ipc_perm {key_t __key; /* Key supplied to shmget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions + SHM_DEST andSHM_LOCKED flags */unsigned short __seq; /* Sequence number */};
4. shmat 函數:將進程與共享內存塊 關聯\ 掛接(attach)
頭文件
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
?
參數 shmid
- 需要的共享內存塊的 shmid
?
參數 shmaddr:
- 用戶可以選擇虛擬地址作為共享內存塊的起始地址
- 用戶一般不定義,設為 nullptr 讓 OS 自主定義即可
?
參數 shmflg:
- 選項 SHM_RDONLY:只讀
- 0:可以讀寫
?
返回值:
- 掛接成功,返回共享內存塊的虛擬地址的起始地址
5. shmdt 函數:將進程與共享內存塊 去關聯(detach)
頭文件
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
?
參數 shmaddr:
- 共享內存塊的起始地址
?
返回值:
- 去關聯成功返回 0,失敗返回 -1
1.2 一些命令
1. ipcs - - 查看三種 ipc 資源
ipcs 就是進程間通信(ipc)資源
ipcs
:可以查看 消息隊列、共享內存虧塊、信號量
-m
:查看 共享內存塊(memory)
-s
:查看 信號量(semaphore)
perms:權限
nattach:當前 ipc 掛接的進程數
2. ipcrm – 刪除某種 ipc 資源
ipcrm
:刪除一個 消息隊列、共享內存虧塊、信號量
-m
:刪除一個共享內存塊,后接 shmid
1.3 結論
-
兩個進程管道通信一次,需要進行兩次復制。而共享內存間的通信,可以讓進程們直接在自己映射的地址空間中訪問,減少了拷貝次數()
-
管道單方面關閉讀寫端會有相應的保護,而共享內存沒有保護機制(同步互斥)。管道通過系統接口通信,共享內存直接通信
-
互斥:任何一個時刻,都只允許一個執行流在進行共享資源的訪問,叫做加鎖
-
我們把任何一個時刻,都只允許一個執行流在進行訪問的共享資源,叫做 臨界資源。凡是訪問臨界資源的代碼,叫做臨界區,控制進出臨界區的手段造就了臨界資源。
二、消息隊列
1. msgget 函數:創建消息隊列
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
?參數 key:
- 使用
ftok
函數設置的唯一標識碼?
參數 msgflg:
選項 IPC_CREAT and IPC_EXCL
單獨使用 IPC_CREAT:創建一個共享內存,如果共享內存不存在,就創建,如果已經存在就獲取已經存在的共享內存并返回。
IPC_CREAT | IPC_EXCL :IPC_EXCL 必須要配合 IPC_CREAT 使用,創建一個共享內存,如果共享內存不存在,就創建,如果已經存在就出錯返回
意味著,一起使用時,如果創建成功,對應的shm,一定是最新的!IPC_CREAT | IPC_EXCL | 0666 :上面的基礎上,添加權限(可以配合函數 umask(0) 使用)
?
返回值:
- 成功則返回消息隊列的標識符
2. msgctl 函數
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
?
參數 msqid
- 需要的消息隊列的 msqid
?
參數 cmd:
- 選項 IPC_STAT:把用戶傳入 msqid 的相應內核數據結構信息復制到 buf 中(在調用者有讀權限的情況下,才能成功)
- 選項 IPC_RMID:刪除 msqid 為傳入值的共享內存塊
?
輸出型參數 buf:
- 需要得到 ipc 信息時傳一個相應類型的值用來接收結果
?
返回值:
- 成功返回 >= 0 的值,失敗返回 -1
The msqid_ds data structure is defined in <sys/msg.h> as follows:struct msqid_ds {struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */msgqnum_t msg_qnum; /* Current number of messagesin queue */msglen_t msg_qbytes; /* Maximum number of bytesallowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */};The ipc_perm structure is defined as follows (the highlighted fields are settable using
IPC_SET):struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};
3. msgsnd 和 msgrcv 函數,發送和接收消息
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
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);
?參數 msqid:
- 發送和接收訪問的同一個消息隊列
?參數 msgp:
- 發送或接收的數據塊
?參數 msgsz:
- 發送或接收數據塊的大小
?參數 msgflg:
- 選項,一般填 0 即可
?參數 msgtyp:
- msgbuf 里面的 mtype
// The msgp argument is a pointer to caller-defined structure of the following general form:struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */
};
三、信號量
信號量 / 信號燈(semaphore),本質 就是一個計數器,是一個描述資源數量的計數器。
舉個例子:
-
比如我們任何一個執行流,像訪問臨界資源中的一個子資源的時候,不能直接訪問,需要 先申請信號量資源(P操作),此時 count-- 。只要申請信號量成功,未來就一定能拿到一個子資源。(類似搖號)
-
然后進入進程自己的臨界區,訪問對應的臨界資源。
-
使用完成后,進程釋放信號量資源(V操作),只要將計數器增加 count++,就表示將我們對應的資源進行了歸還。
至此,進程通過執行代碼來申請,意味著,所有進程都得先看到信號量,信號量就是一個共享資源。(信號量保護共享資源,自己卻又是一個共享資源)
故,信號量必須保證自己的 ++ - - 是原子的
也,信號量被歸類到了進程間通信
信號量部分未完待續~
1. semget
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
?
參數 key:
- 使用
ftok
函數設置的唯一標識碼,他雖由用戶設置,卻是在內核中使用的?
參數 nsems:
- 申請信號量的個數(叫做信號量集)
?
參數 semflg:
選項 IPC_CREAT and IPC_EXCL
單獨使用 IPC_CREAT:創建一個共享內存,如果共享內存不存在,就創建,如果已經存在就獲取已經存在的共享內存并返回。
IPC_CREAT | IPC_EXCL :IPC_EXCL 必須要配合 IPC_CREAT 使用,創建一個共享內存,如果共享內存不存在,就創建,如果已經存在就出錯返回
意味著,一起使用時,如果創建成功,對應的shm,一定是最新的!IPC_CREAT | IPC_EXCL | 0666 :上面的基礎上,添加權限(可以配合函數 umask(0) 使用)
?
返回值:
- 成功會返回一個信號量計數器標識符,失敗返回 -1
2. semctl 函數:可以獲取信號量的相關屬性
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
?
參數 semid:
- 需要的信號量的semid
?
參數 semnum:
- 信號量編號
?
參數 cmd:
- 選項 IPC_STAT:把用戶傳入 msqid 的相應內核數據結構信息復制到 buf 中(在調用者有讀權限的情況下,才能成功)
- 選項 IPC_RMID:刪除 msqid 為傳入值的共享內存塊
?
返回值:
- 成功返回 >= 0 的值,失敗返回 -1
//This function has three or four arguments, depending on cmd. When there are four, the
//fourth has the type union semun. The calling program must define this union as follows: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) */};//The semid_ds data structure is defined in <sys/sem.h> as follows:struct semid_ds {struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Last change time */unsigned long sem_nsems; /* No. of semaphores in set */};//The ipc_perm structure is defined as follows (the highlighted fields are settable usingIPC_SET):struct ipc_perm {key_t __key; /* Key supplied to semget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};
3. semop 函數:對信號量進行操作
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
?
參數 sops:
- 需要用戶自己定義后傳入,設置結構體內容,以此達到對信號量的 PV 操作等等
//Each semaphore in a System V semaphore set has the following associated values:unsigned short semval; /* semaphore value */unsigned short semzcnt; /* # waiting for zero */unsigned short semncnt; /* # waiting for increase */pid_t sempid; /* ID of process that did last op *///semop() performs operations on selected semaphores in the set indicated by semid.
//Each of the nsops elements in the array pointed to by sops specifies an operation to be performed on a single semaphore.
//The elements of this structure are of type struct sembuf, containing the following members:unsigned short sem_num; /* semaphore number */short sem_op; /* semaphore operation */short sem_flg; /* operation flags */
總結:
共享內存、消息隊列、信號量 這三種 ipc 都有各自的內核數據結構體,而結構體的第一個成員都是 struct ipc_perm xx_perm
。
可以理解為 OS 將這三種 ipc 都放進了一個 struct ipc_perm* ipc_id_arr[]
指針數組中進行管理(這里只是做理解解釋,實際上更復雜)。
OS 通過指針,可以找到每個結構體(同時也是每個結構的第一個成員,即 struct ipc_perm xx_perm),在其中找到 key 值就可以確定。
要訪問里面的內容時,以共享內存舉例
((struct shmid_ds*)ipc_id_arr[n])->other...
對指針進行強轉,就可以訪問到其中內容了,這也是一種多態。