信号量就是具有原子性的计数器,就相当于一把锁,在每个进程要访问临界资源时,必须要向信号量拿个锁”,它才能进去临界资源这个“房间”,并锁上门,不让其他进程进来,此时信号量执行P()操作,锁的数目减少了一个,所以计数器减1,;当它访问完成时,它出来,将锁还给信号量,执行V()操作,计数器加1;
1.semget函数:创建信号量
该函数用来创建一个新信号量,其定义为:int semget(key_t key, int num, int sem_flags)
Key是整数值,程序对所有信号量的访问都是间接的,先提供一个键,再由系统生成一个信号量标识符。num_sem参数指定需要的信号量数目,一般取1;sem_flags参数是一组标志。
semget函数在成功时返回一个正数,也就是其他信号量函数用到的信号量标识符,失败时返回-1.
2.semop函数:改变信号量的值
定义为:int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops)
sem_id 表示信号量标识符,semops指向一个结构数组的指针,每个数组元素至少包含以下几个成员:
struct sembuf {
short sem_num; //信号量编号,一般取0
short sem_op;//信号量需要改变的值,-1/+1
short sem_flg;//设置为SEM_UNDO
}
3.semctl函数:控制信号量的信息
定义为:int semctl(int sem_id, int sem_num, int command,…)
sem_id:表示信号量标识符,sem_num表示信号量编号一般取0,command参数是将要采取的行动,如:SETVAL:用来把信号量初始化为一个已知的值,作用就是在信号量第一次使用前对它进行设置。IPC_RMID用于删除一个不再继续使用的信号量标识符。如果还有第四个参数,它是一个union semun结构(该联合结构可能需自己定义,可通过查阅semctl的手册查看是否给出了该定义)
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}
4.程序源码
让两个进程分别向显示器(linux下一切皆文件,临界资源)打印AA和BB,当没有信号量进行保护时,会出现数据混乱,例如:“AABBABAAAB…”,为了解决这一问题,我们创建信号量进行保护。打印“AA”或“BB”
vi sem.c
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
//struct seminfo *buff;
};
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
int main(int argc, char *argv[])
{
int i;
int pause_time;
char op_char = 'O';
srand((unsigned int)getpid());
sem_id =semget((key_t)1234, 1, 0666 | IPC_CREAT);
/* 如果程序第一个被调用,也就是调用时含有一个参数,使得argc>1,此时就调用set_semvalue初始化信号量,并将op_char设置为x*/
if (argc > 1){
if(!set_semvalue()){
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
op_char = 'X';
sleep(2);
}
/*进入和离开临界区10次,每次循环开始的时候首先调用semaphore_p函数,它在程序将进入临界区域时设置信号量以等待进入*/
for(i=0; i<10; i++){
if(!semaphore_p()) exit(EXIT_FAILURE);
printf("%c", op_char);fflush(stdout);
pause_time = rand() % 3;
sleep(pause_time);
printf("%c", op_char);fflush(stdout);
/*进入临界区域后,调用semaphore_v将信号量设置为可用,然后等待一段随机的时间,再进入下一次循环*/
if(!semaphore_v()) exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n%d - finished\n", getpid());
if (argc > 1)
{
sleep(10);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
/*该函数用来将semctl调用的command参数设置为SETVAL来初始化信号量*/
static int set_semvalue(void)
{
union semun sem_union;
sem_union.val = 1;
if (semctl(sem_id, 0, SETVAL, sem_union)==-1) return 0;
return (1);
}
/*通过调用semctl调用的command设置为IPC_RMID来删除信号量ID*/
static void del_semvalue(void)
{
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union)==-1)
fprintf(stderr, "Failed to delete semaphore");
}
/*对信号量执行减1操作*/
static int semaphore_p(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1){
fprintf(stderr,"semaphore_p failed\n");
return (0);
}
return(1);
}
/*对信号量执行加1操作*/
static int semaphore_v(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1){
fprintf(stderr,"semaphore_v failed\n");
return (0);
}
return(1);
}
编译运行
5.附代码2:父子进程间的信号量
与上述代码几乎相同,只不过变为了父子进程间的信号量机制。
vi sem2.c
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#include<sys/types.h>
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
//struct seminfo *buff;
};
int main(int argc, char *argv[])
{
int i;
int pause_time;
char op_char = 'O';
srand((unsigned int)getpid());
sem_id =semget((key_t)1234, 1, 0666 | IPC_CREAT);
int id = fork();
if(id<0)
{
perror("fork failed\n");
return -1;
}
else if (id>0){
if(!set_semvalue()){
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
op_char = 'X';
sleep(2);
}
for(i=0; i<10; i++){
if(!semaphore_p()) exit(EXIT_FAILURE);
printf("%c", op_char);fflush(stdout);
pause_time = rand() % 3;
sleep(pause_time);
printf("%c", op_char);fflush(stdout);
if(!semaphore_v()) exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n%d - finished\n", getpid());
if (id> 0)
{
sleep(10);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
static int set_semvalue(void)
{
union semun sem_union;
sem_union.val = 1;
if (semctl(sem_id, 0, SETVAL, sem_union)==-1) return 0;
return (1);
}
static void del_semvalue(void)
{
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union)==-1)
fprintf(stderr, "Failed to delete semaphore");
}
static int semaphore_p(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1){
fprintf(stderr,"semaphore_p failed\n");
return (0);
}
return(1);
}
static int semaphore_v(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1){
fprintf(stderr,"semaphore_v failed\n");
return (0);
}
return(1);
}
编译运行结果:
注意:在这里我们可以看到子进程的pid为父进程的pid+1,注意的是fork创建子进程的返回值为0,和这里的pid是不同的。
参考:Linux程序设计第四版