说明:只供学习交流,转载请注明出处
七,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======