[TOC] 1. System V IPC 概述 以下三種類型的IPC合稱為System V IPC: System V信號量 System V消息隊列 System V共用記憶體 System V IPC在訪問它們的函數和內核為它們維護的信息上有一些類似點,主要包括: IPC鍵和ftok函數 ipc ...
目錄
1. System V IPC
概述
以下三種類型的IPC合稱為System V IPC:
- System V信號量
- System V消息隊列
- System V共用記憶體
System V IPC在訪問它們的函數和內核為它們維護的信息上有一些類似點,主要包括:
- IPC鍵和ftok函數
- ipc_perm結構
- 創建或打開時指定的用戶訪問許可權
- ipcs和ipcrm命令
下表彙總了所有System V IPC函數。
信號量 | 消息隊列 | 共用記憶體 | |
---|---|---|---|
頭文件 | sys/sem.h | sys/msg.h | sys/shm.h |
創建或打開IPC的函數 | semget | msgget | shmget |
控制IPC操作的函數 | semctl | msgctl | shmctl |
IPC操作函數 | semop | msgsnd msgrcv |
shmat shmdt |
IPC鍵和ftok函數
三種類型的System V IPC都使用IPC鍵作為它們的標識,IPC鍵是一個key_t類型的整數,該類型在sys/types.h
中定義。
IPC鍵通常是由ftok
函數賦予的,該函數把一個已存在的路徑名pathname和一個非0整數id組合轉換成一個key_t值,即IPC鍵。
#include <sys/ipc.h>
//成功返回IPC鍵,失敗返回-1
key_t ftok(const char *pathname, int id);
參數說明:
- pathname在是程式運行期間必須穩定存在,不能反覆創建與刪除
- id不能為0,可以是正數或者負數
ipc_perm結構
內核給每個IPC對象維護一個信息結構,即struct ipc_perm
結構,該結構及System V IPC函數經常使用的常值定義在sys/ipc.h
頭文件中。
struct ipc_perm
{
uid_t uid; //owner's user id
gid_t gid; //owner's group id
uid_t cuid; //creator's group id
gid_t cgid; //creator's group id
mode_t mode; //read-write permissions
ulong_t seq; //slot usage sequence number
key_t key; //IPC key
};
創建與打開IPC對象
創建或打開一個IPC對象使用相應的xxxget函數,它們都有兩個共同的參數:
- 參數key,key_t類型的IPC鍵
- 參數oflag,用於指定IPC對象的讀寫許可權(ipc_perm.mode),並選擇是創建一個新的IPC對象還是打開一個已存在的IPC對象
對於參數key,應用程式有兩種選擇:
- 調用ftok,給它傳pathname和id
- 指定key為
IPC_PRIVATE
,這將保證會創建一個新的、唯一的IPC對象,但該標誌不能用於打開已存在的IPC對象,只能是新建
對於參數oflag,如上所述,它包含讀寫許可權、創建或打開這兩方面信息:
- 可以指定
IPC_CREAT
標誌,其含義和Posix IPC的O_CREAT一樣 - 還可以設置為下表所示的常值來指定讀寫許可權
ipcs和ipcrm命令
- 由於System V IPC的三種類型不是以文件系統路徑名標識的,因此無法使用ls和rm命令查看與刪除它們
- ipcs和ipcrm分別用於查看與刪除系統中的System V IPC
usage : ipcs -asmq -tclup
ipcs [-s -m -q] -i id
ipcs -h for help.
usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
[-Q msgkey] [-M shmkey] [-S semkey] ... ]
2. System V信號量
計數信號量集
我們已經知道了Posix信號量採用計數信號量,System V信號量在此基礎上增加了一級複雜度,它採用計數信號量集,計數信號量集是由一個或多個計數信號量構成的集合。
對於系統中的每個信號量集,內核都維護一個struct semid_ds
信息結構,它定義在sys/sem.h
頭文件中。
struct semid_ds
{
struct ipc_perm sem_perm;
struct sem *sem_base; //指向信號量集的指針
ushort sem_nsems; //信號量集中的信號量個數
time_t sem_otime; //上一次調用semop的時間
time_t sem_ctime; //創建時間或上一次以IPC_SET調用semctl的時間
};
其中,sem_base是指向信號量集的指針,信號量集中的每個成員都對應一個struct sem結構:
struct sem
{
ushort_t semval; //信號量的值
short sempid; //上一次成功調用semop,或以SETVAL、SETALL調用semctl的進程ID
ushort_t semncnt; //等待semval變為大於當前值的線程數
ushort_t semzcnt; //等待semval變為0的線程數
};
semget
semget用於創建一個新的信號量集或打開一個已存在的信號量集。
- nsems參數指定集合中的信號量個數,如果是打開一個已存在的信號量集,就把該參數設為0
- oflag參數可設置為IPC_CREAT,以及SEM_R和SEM_A指定的訪問許可權,如果是打開一個已存在的信號量集,就把該參數設為0
- 成功時返回一個稱為信號量標識符的非負整數,semop和semctrl函數將使用它
//成功返回信號量標識符,失敗返回-1
int semget(key_t key, int nsems, int oflag);
當實際操作為創建一個新的信號量集時,相應semid_ds結構中與集合中每個信號量關聯的struct sem結構並不初始化,而是在以SETVAL或SETALL命令調用semctrl時初始化的。
也就是說,創建一個新的System V信號量集(semget)並將它初始化(semctl)需要兩次函數調用,這是System V信號量的一個致命缺陷。
semop
使用semget打開一個信號量集後,對其中一個或多個信號量值的操作就使用semop函數。
//成功返回0,失敗返回-1
int semop(int semid, struct sembuf *ops, size_t nops);
- semid指定待操作的信號量集
- nops為集合中的信號量個數
- ops指向一個struct sembuf類型的結構數組,該數組中的每個元素給目標信號量集中某個特定的信號量指定sem_op操作,這個特定的信號量由sem_num指定
我們只保證sembuf含有以下三個成員,不保證這三個成員的順序,也不保證還有其他成員,因此sembuf數組元素必須採用如ops[0].sem_num = 0所示的方法進行初始化。
struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_op指定的操作有三類:
- sem_op > 0:操作內容為semval += sem_op,這對應於釋放某個信號量控制的資源
- sem_op = 0:調用者阻塞等待直到semval變為0
- sem_op < 0:調用者阻塞等待直到semval >= abs(sem_op),調用者阻塞等待直到semval>這對應於分配資源
sem_flg可設置的值有:0、IPC_NOWAIT、SEM_UNDO,一般使用0,對於其餘兩個值:
- IPC_NOWAIT用於給信號量集中某個特定信號量設置非阻塞標誌
- SEM_UNDO為System V信號量特有的複舊機制
semctl
semctl函數對一個信號量集執行各種控制操作。
//成功根據cmd返回相應的非負值,失敗返回-1
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
- semid指定待控制的信號量集
- semnum指定信號量集內的某個成員,僅在cmd為GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID時使用
- cmd指定控制命令
- arg是可選的,取決於cmd的具體值
第四個參數arg是可選的,取決於cmd的值,當需要用到該參數時,應用程式必須按如下結構定義union semun:
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) */
};
定義如下術語用於後續cmd說明:
- semval:信號量的當前值
- sempid:上一次成功調用semop,或以SETVAL、SETALL調用semctl的進程ID
- semncnt:等待semval變為大於當前值的線程數
- semzcnt:等待semval變為0的線程數
System V支持下列cmd值,除非特殊說明,否則成功時返回值均為0。
cmd | 說 明 |
---|---|
GETVAL | 把semval的當前值作為函數返回值返回,它是一個unsigned short類型的整數 |
SETVAL | 把semval的值設為arg.val |
GETPID | 把sempid的當前值作為函數返回值返回 |
GETNCNT | 把semncnt的當前值作為函數返回值返回 |
GETZCNT | 把semzcnt的當前值作為函數返回值返回 |
GETALL | 返回信號量集內每個成員的semval值,這些值通過arg.array返回,arg.array需由調用者分配記憶體 |
SETALL | 設置信號量集內每個成員的semval值,這些值通過arg.array指定,此時第二個參數semnum設為0即可 |
IPC_STAT | 返回信號量集當前的semid_ds結構,該結構通過arg.buf返回,arg.buf需由調用者分配記憶體,可以用該命令獲得信號量集中的信號量個數 |
IPC_SET | 設置信號量集對應semid_ds結構中的sem_perm.uid、sem_perm.gid和sem_perm.mode,設置的值來自arg.buf |
IPC_RMID | 刪除由semid指定的信號量集,此時第二個參數semnum設為0即可 |
其中,前五個命令針對的都是信號量集semid中由semnum指定的信號量。
3. 測試程式
代碼實現
semcreate.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define FTOK_FILE "/home/delphi/ftok.file"
#define FTOK_ID 1
#define SEM_MODE 0666
/*
#define SEM_MODE_OWNER SEM_R | SEM_A
#define SEM_MODE_GROUP (SEM_R >> 3) | (SEM_A >> 3)
#define SEM_MODE_OTHER (SEM_R >> 6) | (SEM_A >> 6)
#define SEM_MODE (SEM_MODE_OWNER | SEM_MODE_GROUP | SEM_MODE_OTHER)
*/
int main()
{
int nsems = 3;
int oflag = IPC_CREAT | SEM_MODE;
key_t key = ftok(FTOK_FILE, FTOK_ID);
int semid = semget(key, nsems, oflag);
if (semid >= 0)
{
printf("semcreate success, semid = %d\n", semid);
}
return 0;
}
這裡遇到個問題,SEM_MODE一開始是按註釋部分定義的,但man semget給出的三個頭文件都已經包含了,編譯時卻報錯,提示SEM_R和SEM_A未定義,不知道為什麼,只能用0666定義了。
semrmid.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define FTOK_FILE "/home/delphi/ftok.file"
#define FTOK_ID 1
int main()
{
key_t key = ftok(FTOK_FILE, FTOK_ID);
int semid = semget(key, 0, 0);
semctl(semid, 0, IPC_RMID);
return 0;
}
semsetvalues.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define FTOK_FILE "/home/delphi/ftok.file"
#define FTOK_ID 1
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) */
};
int main(int argc, char **argv)
{
key_t key;
int semid;
int nsems;
unsigned short *semvals;
union semun arg;
struct semid_ds seminfo;
int i;
/* 打開semcreate創建的信號量集 */
key = ftok(FTOK_FILE, FTOK_ID);
semid = semget(key, 0, 0);
/* 獲得信號量集中的信號量個數 */
arg.buf = &seminfo;
semctl(semid, 0, IPC_STAT, arg);
nsems = arg.buf->sem_nsems;
/* 設置信號量集中每個信號量的值 */
semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));
for (i = 0; i < nsems; i++)
{
semvals[i] = atoi(argv[i + 1]); //通過命令行參數分別指定集合中每個信號量的值
}
arg.array = semvals;
semctl(semid, 0, SETALL, arg);
return 0;
}
semgetvalues.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define FTOK_FILE "/home/delphi/ftok.file"
#define FTOK_ID 1
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) */
};
int main(int argc, char **argv)
{
key_t key;
int semid;
int nsems;
unsigned short *semvals;
union semun arg;
struct semid_ds seminfo;
int i;
/* 打開semcreate創建的信號量集 */
key = ftok(FTOK_FILE, FTOK_ID);
semid = semget(key, 0, 0);
/* 獲得信號量集中的信號量個數 */
arg.buf = &seminfo;
semctl(semid, 0, IPC_STAT, arg);
nsems = arg.buf->sem_nsems;
/* 獲得信號量集中每個信號量的值 */
semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));
arg.array = semvals;
semctl(semid, 0, GETALL, arg);
for (i = 0; i < nsems; i++)
{
printf("semvals[%d] = %d\n", i, semvals[i]);
}
return 0;
}
semops.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define FTOK_FILE "/home/delphi/ftok.file"
#define FTOK_ID 1
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) */
};
int main(int argc, char **argv)
{
key_t key;
int semid;
int nsems;
union semun arg;
struct semid_ds seminfo;
struct sembuf *semops;
int i;
/* 打開semcreate創建的信號量集 */
key = ftok(FTOK_FILE, FTOK_ID);
semid = semget(key, 0, 0);
/* 獲得信號量集中的信號量個數 */
arg.buf = &seminfo;
semctl(semid, 0, IPC_STAT, arg);
nsems = arg.buf->sem_nsems;
/* 對信號量集中的所有信號量進行相同的semop操作 */
semops = (struct sembuf *)calloc(nsems, sizeof(struct sembuf));
for (i = 0; i < nsems; i++)
{
semops[i].sem_num = i;
semops[i].sem_op = atoi(argv[1]); //操作類型由命令行參數指定,>0, 0, <0
semops[i].sem_flg = 0;
}
semop(semid, semops, nsems);
return 0;
}
運行測試
- 運行semcreate,通過運行前後的ipcs -s,可以確認信號量集創建成功
- 運行semsetvalues,將三個信號量的值分別設為1、2、3
- 然後運行semgetvalues,列印信息和我們設置的值一致,符合預期
- 運行semops,並指定sem_op > 0
- 然後運行semgetvalues,列印信息顯示每個信號量的值都增加了1,符合預期
- 運行semrmid,通過運行前後的ipcs -s,可以確認信號量集已經從系統刪除