采用信号量处理多进程互斥同步
信号量与消息类似,也是进程间通信的一种方法。我们在这里讲的信号量,实际上是一个包含信号量元素数组的信号量集。信号量元素与E.W.Dijkstra提出的整数信号量相对应。在一个单系统调用中,进程可在完整的信号量集上操作。
信号量集的内部表示和各自信号量元素不是直接可以访问的,但每个信号量元素必须包括下列各项:
l 一个标识信号量元素的非负整数
l 最后操作信号量元素的进程ID
l 等待信号量元素值加1的进程数
l 等待信号量元素值等于0的进程数
信号量操作允许一个进程阻塞直到信号量元素值为0或者直到它变为正数。每个元素具有两个相关联的队列:一个是等待信号量元素值加1的进程队列,另一个是等待信号量元素值等于0的进程队列。
1、 semget函数
系统调用semget用来创建一个信号量集并将每个元素初始化为0
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key, int nsems, int semflg );
函数semget有3个参数。第1个参数为标识信号量集的关键字,程序指定关键字的方法有三种:使用IPC_PRIVATE让系统产生一个关键字、挑选一个随机数,或者是使用ftok从文件路径名中产生一个关键字。第2个参数为信号量集中的元素个数。第3个参数为信号量存取权标志与建立标志,该参数的低9位是信号量的存取权标志;建立标志有2个,IPC_CTEAT和IPC_EXCL。IPC_CREAT表示如果信号量集不存在,则创建它; IPC_EXCL表示只有在信号量集不存在的情况下,新的信号量集才会被创建,否则semget返回-1,并设定errno。如果这两个标志联合使用,则功能如同O_EXCL标志于open。
函数semget调用成功时返回一个用于以后semctl等操作的整数句柄,失败时返回-1。
在指定信号量集的关键字时,我们可以使用ftok根据文件路径名产生一个关键字。
#include<sys/ipc.h>
key_t ftok(const char * pathname, int proj_id);
函数ftok的第1个参数为文件路径名,该文件必须存在且进程对该文件有访问权。第2个参数为程序员指定的一个整数ID。该函数成功调用时返回一个关键字,如调用失败,则返回-1。
2、 semctl函数
系统调用semctl可以对信号量进行很多控制。
#include<sys/ipc.h>
#include<sem.h>
int semctl(int semid, int semnum, int cmd, /*union senum arg*/…);
函数的第1个参数为信号量集的句柄。第2个参数指定信号量集中的元素。第3个参数为执行的控制命令,常见的命令如下表:
命令名称
表示的含义
GETVAL
获得一个指定信号量的值
GETPID
获得操作此元素的最后进程的ID
GETNCNT
获得等待元素增1的进程数
GETZCNT
获得等待元素变为0的进程数
SETVAL
设置一个指定信号量元素的值为arg.val
IPC_RMID
删除一个信号量
IPC_SET
设置信号量的许可权
函数semctl在出现错误时返回-1并设置erro。当调用成功时,其返回值决定于参数cmd,当cmd为GETVAL、GETPID、GETNCNT或GETZCNT时,函数返回相应的值,当cmd为别的值时,返回0。
union senum定义可直接包含在程序中,因为系统的头文件中并没有定义它。它的定义如下:
union senum
{
int val;
struct semid_ds *buf;
ushort *array;
};
union senum中提到的semid_ds结构定义如下:
struct semid_ds
{
struct ipc_perm sem_perm; /*operation permission struct*/
time_t sem_otime; /*last semop() time*/
time_t sem_ctime; /*last time changed by semctl()*/
struct sem*sembase; /*ptr to first semaphore in array*/
struct sem_queue *sem_pending; /*pending operations*/
struct sem_queue *sem_pending_last; /*last pending operation*/
struct sem_undo *undo; /*undo requests on this array*/
unsigned short int sem_nsems; /*number of semaphores in set*/
};
3、 semop函数
系统调用semop可以对信号量增1、减1或测试其是否为0。
#include <sys/ipc.h>
#include <sys/sem.h>
int sempo(int semid, struct sembuf *sops, unsigned int nsops);
函数semop的第1个参数为semget返回的句柄,第2个参数指向元素操作数组,第3个参数指定在数组中元素操作的个数。
函数成功调用时返回0,失败时返回-1。如果时被信号中断,则返回-1,同时设置errno为EINTR。
结构体sembuf的定义如下:
struct sembuf
{
short int sem_num; //信号量元素个数
short int sem_op; //信号量元素上的操作
short int sem_flg; //操作选项
}
在结构sembuf中,sem_num表示信号量元素的个数,sem_op表示在信号量元素上执行的特别操作,sem_flg表示操作选项的标志。如果sem_op为正数,semop函数将该值加到相应的信号量元素中,并唤醒所有等待元素增1的进程。如果sem_op为0而信号量的值不为0, semop将阻塞调用进程并增加那个元素的等零进程个数。如果sem_op为负数,semop将该值加到相应的信号量元素中(只要结果不为负数),如结果为负数,semop将阻塞进程等待信号量元素值增加;如值为0,semop将唤醒等零进程。
上面的描述假设sem_flg为0。如果sem_flg&IPC_NOWAIT为真,调用不会阻塞,而是返回-1并设置error为 EAGAIN。如果sem_flg&SEM_UNDO为真,函数也将为进程修改信号量的调整值。这个调整值允许进程在退出时恢复它在信号量上的作用。
下面的程序给出了关于信号量集的系统调用semget,semctl和semop的基本用法。
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
main()
{
int semid,pid,I,j;
static struct sembuf lock={0,-1,SEM_UNDO};
static struct sembuf unlock={0,1,SEM_UNDO|IPC_NOWAIT};
if((semid=semget(998,1,IPC_CREAT|IPC_EXCL|0666))==-1)
{
printf(“error:semget!/n”);
exit(1);
}
if(semctl(semid,0,SETVAL,1)==-1)
{
printf(“error:semctl!/n”);
exit(1);
}
setbuf(stdout,(char *)NULL);
for(i=0;i<3;i++)
{
if(fork()==0)
{
if((semid=semget(998,1,0))==-1)
{
printf(“error:semget!/n”);
exit(1);
}
for(j=0;j<3;j++)
{
sleep(i);
if(semop(semid,&lock,1)==-1)
{
printf(“error:semop!/n”);
exit(1);
}//if lock
printf(“process %d get into critical section!/n”,getpid());
sleep(1);
printf(“process %d left critical section!/n”,getpid());
if(semop(semid,&unlock,1)==-1)
{
printf(“error:semop!/n”);
exit(1);
}
}//for j
exit(0);
}//if fork
}//for i
for(i=0;i<3;i++)
wait(NULL);
if(semctl(semid,0,IPC_RMID,0)==-1)
{
printf(“error:semctl!/n”);
exit(1);
}
exit(0);
}
信号量与消息类似,也是进程间通信的一种方法。我们在这里讲的信号量,实际上是一个包含信号量元素数组的信号量集。信号量元素与E.W.Dijkstra提出的整数信号量相对应。在一个单系统调用中,进程可在完整的信号量集上操作。
信号量集的内部表示和各自信号量元素不是直接可以访问的,但每个信号量元素必须包括下列各项:
l 一个标识信号量元素的非负整数
l 最后操作信号量元素的进程ID
l 等待信号量元素值加1的进程数
l 等待信号量元素值等于0的进程数
信号量操作允许一个进程阻塞直到信号量元素值为0或者直到它变为正数。每个元素具有两个相关联的队列:一个是等待信号量元素值加1的进程队列,另一个是等待信号量元素值等于0的进程队列。
1、 semget函数
系统调用semget用来创建一个信号量集并将每个元素初始化为0
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key, int nsems, int semflg );
函数semget有3个参数。第1个参数为标识信号量集的关键字,程序指定关键字的方法有三种:使用IPC_PRIVATE让系统产生一个关键字、挑选一个随机数,或者是使用ftok从文件路径名中产生一个关键字。第2个参数为信号量集中的元素个数。第3个参数为信号量存取权标志与建立标志,该参数的低9位是信号量的存取权标志;建立标志有2个,IPC_CTEAT和IPC_EXCL。IPC_CREAT表示如果信号量集不存在,则创建它; IPC_EXCL表示只有在信号量集不存在的情况下,新的信号量集才会被创建,否则semget返回-1,并设定errno。如果这两个标志联合使用,则功能如同O_EXCL标志于open。
函数semget调用成功时返回一个用于以后semctl等操作的整数句柄,失败时返回-1。
在指定信号量集的关键字时,我们可以使用ftok根据文件路径名产生一个关键字。
#include<sys/ipc.h>
key_t ftok(const char * pathname, int proj_id);
函数ftok的第1个参数为文件路径名,该文件必须存在且进程对该文件有访问权。第2个参数为程序员指定的一个整数ID。该函数成功调用时返回一个关键字,如调用失败,则返回-1。
2、 semctl函数
系统调用semctl可以对信号量进行很多控制。
#include<sys/ipc.h>
#include<sem.h>
int semctl(int semid, int semnum, int cmd, /*union senum arg*/…);
函数的第1个参数为信号量集的句柄。第2个参数指定信号量集中的元素。第3个参数为执行的控制命令,常见的命令如下表:
命令名称
表示的含义
GETVAL
获得一个指定信号量的值
GETPID
获得操作此元素的最后进程的ID
GETNCNT
获得等待元素增1的进程数
GETZCNT
获得等待元素变为0的进程数
SETVAL
设置一个指定信号量元素的值为arg.val
IPC_RMID
删除一个信号量
IPC_SET
设置信号量的许可权
函数semctl在出现错误时返回-1并设置erro。当调用成功时,其返回值决定于参数cmd,当cmd为GETVAL、GETPID、GETNCNT或GETZCNT时,函数返回相应的值,当cmd为别的值时,返回0。
union senum定义可直接包含在程序中,因为系统的头文件中并没有定义它。它的定义如下:
union senum
{
int val;
struct semid_ds *buf;
ushort *array;
};
union senum中提到的semid_ds结构定义如下:
struct semid_ds
{
struct ipc_perm sem_perm; /*operation permission struct*/
time_t sem_otime; /*last semop() time*/
time_t sem_ctime; /*last time changed by semctl()*/
struct sem*sembase; /*ptr to first semaphore in array*/
struct sem_queue *sem_pending; /*pending operations*/
struct sem_queue *sem_pending_last; /*last pending operation*/
struct sem_undo *undo; /*undo requests on this array*/
unsigned short int sem_nsems; /*number of semaphores in set*/
};
3、 semop函数
系统调用semop可以对信号量增1、减1或测试其是否为0。
#include <sys/ipc.h>
#include <sys/sem.h>
int sempo(int semid, struct sembuf *sops, unsigned int nsops);
函数semop的第1个参数为semget返回的句柄,第2个参数指向元素操作数组,第3个参数指定在数组中元素操作的个数。
函数成功调用时返回0,失败时返回-1。如果时被信号中断,则返回-1,同时设置errno为EINTR。
结构体sembuf的定义如下:
struct sembuf
{
short int sem_num; //信号量元素个数
short int sem_op; //信号量元素上的操作
short int sem_flg; //操作选项
}
在结构sembuf中,sem_num表示信号量元素的个数,sem_op表示在信号量元素上执行的特别操作,sem_flg表示操作选项的标志。如果sem_op为正数,semop函数将该值加到相应的信号量元素中,并唤醒所有等待元素增1的进程。如果sem_op为0而信号量的值不为0, semop将阻塞调用进程并增加那个元素的等零进程个数。如果sem_op为负数,semop将该值加到相应的信号量元素中(只要结果不为负数),如结果为负数,semop将阻塞进程等待信号量元素值增加;如值为0,semop将唤醒等零进程。
上面的描述假设sem_flg为0。如果sem_flg&IPC_NOWAIT为真,调用不会阻塞,而是返回-1并设置error为 EAGAIN。如果sem_flg&SEM_UNDO为真,函数也将为进程修改信号量的调整值。这个调整值允许进程在退出时恢复它在信号量上的作用。
下面的程序给出了关于信号量集的系统调用semget,semctl和semop的基本用法。
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
main()
{
int semid,pid,I,j;
static struct sembuf lock={0,-1,SEM_UNDO};
static struct sembuf unlock={0,1,SEM_UNDO|IPC_NOWAIT};
if((semid=semget(998,1,IPC_CREAT|IPC_EXCL|0666))==-1)
{
printf(“error:semget!/n”);
exit(1);
}
if(semctl(semid,0,SETVAL,1)==-1)
{
printf(“error:semctl!/n”);
exit(1);
}
setbuf(stdout,(char *)NULL);
for(i=0;i<3;i++)
{
if(fork()==0)
{
if((semid=semget(998,1,0))==-1)
{
printf(“error:semget!/n”);
exit(1);
}
for(j=0;j<3;j++)
{
sleep(i);
if(semop(semid,&lock,1)==-1)
{
printf(“error:semop!/n”);
exit(1);
}//if lock
printf(“process %d get into critical section!/n”,getpid());
sleep(1);
printf(“process %d left critical section!/n”,getpid());
if(semop(semid,&unlock,1)==-1)
{
printf(“error:semop!/n”);
exit(1);
}
}//for j
exit(0);
}//if fork
}//for i
for(i=0;i<3;i++)
wait(NULL);
if(semctl(semid,0,IPC_RMID,0)==-1)
{
printf(“error:semctl!/n”);
exit(1);
}
exit(0);
}