說明:只供學習交流,轉載請註明出處
七,semctl函數
在使用信號量之前,需要對信號量集中的每個元素進行初始化操作。semctl函數提供了該項功能。該函數的具體信息如下表:
semctl函數
頭文件 |
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
||
函數原型 |
int semctl(int semid, int semnum, int cmd, …); |
||
返回值 |
成功 |
失敗 |
是否設置errno |
依賴於cmd參數 |
-1 |
是 |
說明:semctl函數提供了對信號量集的控制操作。參數semid爲要修改的信號量集的標識符。參數semnum用於指定修改信號量集中的semnum個信號量。cmd參數用於指定semctl函數的操作類型,可以取如下值:
IPC_STAT:將內核中的相關數據複製給指向semid_ds結構體中的arg.buf指針。semnum參數將被忽略。調用semctl函數的進程需要有訪問信號量集的權限。
IPC_SET:設置來自arg.buf的權限。
IPC_RMID:刪除semid參數中指定的信號量集。
IPC_INFO(Linux特有的參數):獲得系統的信號量限制和arg.__buf中的信號量集信息,arg.__buf指向了seminfo結構體(sys/msg.h中定義,同時必須定義_GNU_SOURCE宏),seminfo結構體具體定義如下:
struct seminfo {
int semmap; //信號量層映射裏的記錄數量
int semmni;//最大信號量集
int semmns;//所有信號量集的最大信號量集
int semmnu;//系統最大信號量撤銷數;未使用
int semmsl;//在信號量集中最多可容納的信號量數
int semopm;//semop函數最大操作次數
int semume;//每個進程最大信號量撤銷數;未使用
int semusz;//結構體sem_undo的大小
int semvmx; //最大信號量值
int semaem; //可被記錄用於信號量的調整值。
};
SEM_INFO(Linux特有的參數):將獲得與使用IPC_INFO一樣的信息。除此之外,semusz將獲得系統當前擁有的信號量集的數量,而semaem將獲得系統中所有信號量集中的信號量的個數。
SEM_STAT(Linux特有的參數):返回的參數與使用IPC_STAT相同。調用參數semid爲信號量集標識符,而是Linux內核內部用於維護系統信號量集的數組索引。
GETALL:在arg.array中返回信號量集的值。
GETNCNT:返回等待信號量增加的進程數。
GETPID:返回最後一個調用semop函數操作信號量集中信號量的進程號。
GETVAL:返回指定信號量集中信號量的值。
GETZCNT:返回等待信號量變爲0的進程數。
SETALL:用arg.array來設置信號量集的值。
SETVAL:將指定信號量集中的信號量設置爲arg.val。
cmd參數爲上述的某些值時,需要使用arg參數來讀取或存儲返回的結果。參數arg的類型爲semun的聯合類型。使用該參數必須定義該類型,具體定義如下:
union semun {
int val; /*value for SETVAL */
struct semid_ds __user *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short __user *array; /* array for GETALL & SETALL */
struct seminfo __user *__buf; /* buffer for IPC_INFO */
void __user *__pad;
};
semctl函數的返回值依賴於cmd參數的取值。除了以下的參數,semctl函數執行成功都返回0。
GETNCNT:返回semncnt的取值。
GETPID:返回sempid的取值。
GETVAL:返回semval的取值。
GETZCNT:返回semzcnt的取值。
IPC_INFO:返回內核中信號量集數組的最高索引值。
SEM_INFO:等同於IPC_INFO。
SEM_STAT:返回semid指定的信號量集標識符。
錯誤信息:
EACCES:進程函數調用失敗。
EFAULT:arg.array指向非法地址空間。
EIDRM:信號量集被刪除。
EINVAL:參數cmd或semid的取值無效,或cmd取SEM_STAT時semid指向的信號量集索引未被使用。
EPERM:參數cmd取值爲IPC_SET或IPC_RMID,但是調用進程的有效ID不是信號量的所有者或創建者。
ERANGE:cmd爲SETALL或SETVAL時,給出的semval超出範圍(0~SEMVMX)。
實例:
程序爲使用semctl獲得創建的信號量的實例。程序首先通過semget函數產生信號量,然後使用semctl函數獲得創建的信號量信息。
程序中使用了兩種參數來調用semctl函數:一種是IPC_STAT,該參數是POSIX標準中的參數,對應所有遵循POSIX標準的操作系統而言,使用該參數不會存在任何的問題;還有一種是Linux系統特有的參數,爲了保證程序的通用性,在代碼中通過宏開關來控制是否編譯使用Linux特有的參數。
由於在cmd參數中使用SEM_INFO,buffer獲得的實際上是指向seminfo結構體的指針。因此,在使用前必須做強制類型轉換。而seminfo結構體的定義只有在定義了_GNU_SOURCE後纔可見,代碼中還必須先定義該宏。
具體代碼如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define LINUX_ENV
#ifdef LINUX_ENV
#define _GNU_SOURCE
#endif
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
int main(void)
{
key_t key;
int proj_id;
int semid;
int nsems;
union semun arg;
struct semid_ds buffer;
proj_id = 2;
key = ftok("./program", 0666);
if (key == -1)
{
perror("Cannot generate the IPC key");
return (1);
}
nsems = 2;
semid = semget(key, nsems, IPC_CREAT|0666);
if (semid == -1)
{
perror("Cannot create semaphore set resource");
return (1);
}
arg.buf = &buffer;
if (semctl(semid, 0, IPC_STAT, arg) == -1)
{
perror("Cannot get semaphore set info");
return (1);
}
printf("======semphore set info======\n");
printf("effective user id : %d\n", buffer.sem_perm.uid);
printf("effective group is : %d\n", buffer.sem_perm.gid);
printf("semaphore set't creator user id : %d\n", buffer.sem_perm.cuid);
printf("semaphore set't creator group id : %d\n", buffer.sem_perm.cgid);
printf("access mode : %x\n", buffer.sem_perm.mode);
#ifdef LINUX_ENV
if(semctl(semid, 0, SEM_INFO, arg) == -1)
{
perror("Cannot get semphore set info");
return (1);
}
struct seminfo *sem_info;
sem_info = (struct seminfo*)(&buffer);
printf("Max. # of semaphore sets: %d \n", sem_info->semmni);
printf("Max. # of semaphores in all semaphore sets : %d\n", sem_info->semmns);
printf("Max. # of semaphores in a set : %d\n", sem_info->semmsl);
printf("Max. # of operations for semop(): %d\n", sem_info->semopm);
printf("size of struct sem_undo : %d\n", sem_info->semusz);
printf("Max. value that can be recorded for semaphore adjustment (SEM_UNDO):%d\n",
sem_info->semaem);
#endif
return (0);
}
運行結果:
[root@localhost test]# ./semctl
======semphore set info======
effective user id : 0
effective group is : 0
semaphore set't creator user id : 0
semaphore set't creator group id : 0
access mode : 1b6
Max. # of semaphore sets: 128
Max. # of semaphores in all semaphore sets : 32000
Max. # of semaphores in a set : 250
Max. # of operations for semop(): 32
size of struct sem_undo : 3
Max. value that can be recorded for semaphore adjustment (SEM_UNDO):6
八,信號量集的操作
POSIX IPC提供了semop函數對信號量集中的信號量進行操作。semop函數的具體信息如下表所示:
semop函數
頭文件 |
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
||
函數原型 |
int semop(int semid, struct sembuf *sops, unsigned nsops); |
||
返回值 |
成功 |
失敗 |
是否設置errno |
0 |
-1 |
是 |
說明:semop函數用於在單個信號量集上執行sops參數指定的操作。在信號量集中的每個信號量都與下面的值相關。
unsigned short semval; //信號量的值
unsigned short semzcnt;//等待信號量變爲0的進程數
unsigned short semncnt; //等待信號量增加的進程數
pid_t sempid; //最後對信號量進行操作的進程號。
參數semid用於指定要進行操作的信號量集。參數sops指向了用於指定信號量操作的結構體。參數nsops給出了sops中要操作的信號量是哪個。
結構體sembuf定義如下:
struct sembuf {
unsigned short sem_num; //信號量數值
short sem_op; //要執行的操作
short sem_flg; //操作指定的標誌位
};
sembuf中的成員sem_op表示信號量要執行的操作。如果是正數,表示讓信號量增加(即釋放資源);如果爲負數表示減小信號量的值(試圖獲取資源);爲0表示查看當前信號量是否爲0(所有資源都已經分配,處於使用狀態)。sem_flg可以取下面兩種值:
IPC_NOWAIT:如果信號量操作沒有完成(例如要減少信號量的值或測試其是否爲0),函數調用立即返回。
SEM_UNDO:如果沒有指定參數IPC_NOWAIT,SEM_UNDO將充許在阻塞操作調用失敗的情況下,撤銷操作。
下表爲sem_op取值與semop函數執行的操作之間的關係:
sem_op取值與semop函數執行的操作
sem_op取值 |
條件 |
Flag參數 |
semop函數執行的操作 |
sem_op<0 |
semval>|sem_op| |
無 |
semval-|sem_op| |
semval>=|sem_op| |
SEM_UNDO |
semval-|sem_op|,同時更新undo信號量的計數器 |
|
semval<|sem_op| |
無 |
semncnt加1,同時阻塞操作直到:semval>=|sem_op|,執行對應的操作。信號量集被刪除,返回出錯信息 |
|
sem_op>0 |
無 |
無 |
semval+sem_op |
無 |
SEM_UNDO |
semval+sem_op,同時更新undo信號量的計數器 |
錯誤信息:
E2BIG:參數nsops超過系統調用充許的值(SEMOPM)。
EACCES:調用進程沒有訪問特定信號量的權限。
EAGAIN:操作被阻塞,同時msg_flg中設置了IPC_NOWAIT或超過指定的等待時間。
EFAULT:參數sops指向非法的地址空間。
EFBIG:某個sops中的sem_num值過小,或超出信號量集中的信號量的數目。
EIDRM:信號量集被刪除。
EINTR:當系統調用被阻塞時,進程捕獲到了信號。
EINVAL:信號量集不存在,或semid小於0,或nsops爲非正數。
ENOMEM:sem_flg某些操作指定了SEM_UNDO,系統用於保存這些undo結構的內存不足。
ERANGE:操作使得(sem_op+semval)的值大於系統最大信號量值(SEMVMX)。
實例:
程序使用信號量集實現進程互斥,以保護臨界區資源。程序首先創建信號集。這裏,在信號量集中只設置一個信號量。使用fork產生子進程,調用semget函數讓子進程獲得訪問信號量集的權限。在子進程中設置for循環,讓每個進程都進入兩次臨界區。完成者兩次進入後,子進程退出。父進程一直處於等待狀態,等待所有子進程結束。在所有子進程結束後,調用semctl函數刪除產生的信號量集。具體代碼如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
int main(int argc, char *argv[])
{
int semid;
pid_t pid;
int proj_id;
key_t key;
int num;
int i;
int j;
union semun arg;
static struct sembuf acquire = {0, -1, SEM_UNDO};
static struct sembuf release = {0, 1, SEM_UNDO};
//獲取要產生的子進程數
if ( argc != 2 )
{
printf("Usage: %s num\n", argv[0]);
return (1);
}
num = atoi(argv[1]);
proj_id = 3;
key = ftok("./program", proj_id);
if (key == -1)
{
perror("Cannot generate the IPC key");
return (-1);
}
//生成進程互斥的信號量集,信號量集中設置一個信號量
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if (semid == -1)
{
perror("Cannot create semaphore set");
return (-1);
}
//設置信號量集中的信號量的初始值爲1
//static unsigned short start_var = 1;
arg.array = (unsigned short*)1;
if (semctl(semid, 0, SETVAL, arg) == -1)
{
perror("Cannot set semaphore set");
return (-1);
}
for ( i = 0; i < num; i++ )
{
pid = fork();
if ( pid < 0 )
{
perror("Cannot create new process");
return (-1);
}
else if (pid == 0)
{
//讓子進程獲得訪問信號量的權限
semid = semget(key, 1, 0);
if (semid == -1)
{
perror("Cannot let the process get the access right");
_exit(-1);
}
for (j = 0; j < 2; j++)
{
sleep(i);
//acquire中的sem_op爲負數,表示獲取資源
if (semop(semid, &acquire, 1) == -1)
{
perror("Cannot acquire the resource");
_exit(-1);
}
//顯示進程進入臨界區的信息
printf("======enter the critical section======\n");
printf("-----pid : %ld ---\n", (long)getpid());
sleep(1);
printf("======leave the critical section======\n");
//release中的sem_op爲正數,表示釋放資源
if (semop(semid, &release, 1) == -1)
{
perror("Cannot release the resource");
_exit(-1);
}
//_exit(0);
}//end for{}
_exit(0);
}//end else if{}
}
//等待子進程結束
for (i = 0; i < num; i++)
{
wait(NULL);
}
//刪除產生的信號量集
if (semctl(semid, 0, IPC_RMID, 0) == -1)
{
perror("Cannot remove the semaphore set");
return (-1);
}
return (0);
}
運行結果:
[root@localhost test]# ./semop 3
======enter the critical section======
-----pid : 12937 ---
======leave the critical section======
======enter the critical section======
-----pid : 12937 ---
======leave the critical section======
======enter the critical section======
-----pid : 12938 ---
======leave the critical section======
======enter the critical section======
-----pid : 12939 ---
======leave the critical section======
======enter the critical section======
-----pid : 12938 ---
======leave the critical section======
======enter the critical section======
-----pid : 12939 ---
======leave the critical section======