信号量是不同进程间或一个给定进程内部不同线程间同步的机制。System V信号量是一个或多个信号量的集合,其中的每一个都是氮素的计数信号量。System V信号量由内核维护,主要函数有:semget,semop,semctl。
我们重点来讨论semop函数,该函数的主要功能是对信号进行PV操作。
P操作负责把当前进程由运行状态转换为阻塞状态,知道另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出,若失败,则该进程被阻塞。
V操作负责把被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息,操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒。
semop函数原型:
int semop(int semid,struct sembuf *sops,unsigned nsops)
semop操作中:sembuf结构的sem_flg成员可以为0,IPC_NOWAIT,SEM_UNDO,为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。
下面我们用一段程序来看看SEM_UNDO的作用:
#include "comm.c"文件:
static int comm_creat_sem_set(int _sem_nums,int flag)
{
key_t _key=ftok(_PATH_,_PROJ_ID_);
if(_key<0)
{
perror("ftok");
return -1;
}
int sem_id=semget(_key,_sem_nums,flag);
if(sem_id<0)
{
return -1;
}
return sem_id;
}
int creat_sem_set(int _sem_nums)
{
umask(0);
int flag=IPC_CREAT|IPC_EXCL|0666;
return comm_creat_sem_set(_sem_nums,flag);
}
int get_sem_set(int _sem_nums)
{
int flag=IPC_CREAT;
return comm_creat_sem_set(_sem_nums,flag);
}
int init_sem_set(int _sem_id,int _sem_num,int _init_val)
{
union semun _un;
_un.val=_init_val;
if(semctl(_sem_id,_sem_num,SETVAL,_un)<0)
{
perror("semctl");
return -1;
}
return 0;
}
int p_sem(int _sem_id,int _seq_num)
{
struct sembuf _sem_buf[1];
_sem_buf[0].sem_num=_seq_num;
_sem_buf[0].sem_op=-1;
// _sem_buf[0].sem_flg=SEM_UNDO;
_sem_buf[0].sem_flg=0;
if(semop(_sem_id,_sem_buf,1)<0)
{
perror("semop");
return -1;
}
return 0;
}
int v_sem(int _sem_id,int _seq_num)
{
struct sembuf _sem_buf[1];
_sem_buf[0].sem_num=_seq_num;
_sem_buf[0].sem_op=1;
_sem_buf[0].sem_flg=0;
// _sem_buf[0].sem_flg=SEM_UNDO;
if(semop(_sem_id,_sem_buf,1)<0)
{
perror("semop");
return -1;
}
return 0;
}
comm.h文件:
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define _PATH_ "."
#define _PROJ_ID_ 0x6666
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*/
};
int creat_sem_set(int _sem_nums);
int get_sem_set(int _sem_nums);
int init_sem_set(int _sem_id,int _sem_nums,int _init_val);
int p_sem(int _sem_id,int _seq_num);
int v_sem(int _sem_id,int _seq_num);
int destroy_sem_set(int _sem_id);
sem.c文件:
int main()
{
int sem_id=creat_sem_set(1);
init_sem_set(sem_id,0,1);
pid_t id=fork();
if(id <0)
{
perror("fork");
exit(1);
}
else if(id==0)
{
int childid=getpid();
int fatherid=getppid();
printf("childid:%d,fatherid:%d\n",childid,fatherid);
int sem_id=get_sem_set(0);
while(1)
{
p_sem(sem_id,0);
printf("child wrinting\n");
sleep(1);
fflush(stdout);
printf("child finish post\n");
sleep(10);
fflush(stdout);
v_sem(sem_id,0);
}
}
else
{
while(1)
{
p_sem(sem_id,0);
printf("father wrinting\n");
sleep(1);
fflush(stdout);
printf("father finish post\n");
sleep(1);
fflush(stdout);
v_sem(sem_id,0);
}
}
destroy_sem_set(sem_id);
return 0;
}
在没用SEM_UNDO参数前,运行结果:
从代码中我们看到,在子进程P操作之后,我们让子进程sleep了10秒,在这之前我们把子进程傻掉了,也就是子进程没有进行V操作,所以没有释放占用的信号量,我们会看到,把子进程杀掉之后,父进程阻塞了。
而我们用了SEM_UNDO这个参数后,运行结果如下:
我们可以看到,在把子进程kill后,父进程照样可以运行,因为使用了SEM_UNDO后,操作系统自动释放该进程持有的信号量,从而可以使得另一个进程可以继续工作。否则,另外一个进程将永远阻塞。