演示案例:
- 初始化时二进制信号量SV的值为1
- 此时进程A执行了P(SV)操作将SV减1,则进程B若再执行P(SV)则会被挂起
- 当A执行完离开关键代码段之后,并执行V(SV)操作将SV加1,则会唤醒进程B开始执行
POSIX信号量
- POSIX信号量详情点击===>:https://blog.csdn.net/qq_41453285/article/details/90612787
为了获得共享资源,进程需要执行下列操作:
以下3种特性造成了XSI信号量的复杂性:
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};
- struct semid_ds代表一个信号量集合,这个集合中包含很多的信号量
- 不同的信号量有自己对应的ID和信号量的值(还有其他成员省略未画出)
struct{
unsigned short semval; /* 信号值 */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /*最后一个操作该信号量的进程号*/
};
#include
#include
#include
int semget(key_t key, int nsems, int flag);
//返回值:成功返回信号量ID;出错返回-1
参数:
- key参数:该信号量集合对应的key值
- nsems参数:如果是创建新信号量集合,那么nsems代表新信号量集合中的信号量的数目。如果是引用一个现有的信号量集合,那么此参数指定为0
- flag参数:创建/使用信号量集合时的标志
key参数与flag参数如何设置,详情见文章==>:https://blog.csdn.net/qq_41453285/article/details/90552522
如果此函数用来创建一个信号量集合:
如果semget函数用来创建一个新集合,那么内核会自动把新信号量集合的struct semid_ds结构体做以下初始化:
- 初始化ipc_perm结构体(该结构体中的mode成员按semget函数的flag参数中的相应权限位设置)
- sem_otime设置为0
- sem_ctime设置为当前时间
- sem_qnsems设置为semget函数的nsems参数的值
#include
#include
#include
int semctl(int semid, int semnum, int cmd, .../*union semun arg*/);
//返回值:见下
semid参数
- 信号量集合ID
semnum参数:
- 此参数用来指定该信号量集合中的某一特定信号量成员,也就是信号量对应的ID。(semid值在0~nsems-1之间,包括0和nsems-1(nsems就是创建信号量集合semget函数的参数2))
cmd参数:
- 此参数指定下列10种命令中的一种。其中有5种命令是针对一个特定的信号量值的(特定的信号量值由semnum参数指定)
arg参数:
- 此参数是可选的,是否使用取决于所请求的命令,如果使用该参数,则其类型是union semun,它是多个命令特定参数的联合
- 重点:如果应用程序想使用此联合体,必须自己定义。(但是在FreeBSD8.0中,此联合已经由
定义) 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 (Linux-specific) */ };
返回值:
- cmd参数为GET_XXX类型的(除去GETALL命令,因为其将值存储在参数arg中,而不是返回):
- 函数执行成功:函数的返回值为对应的值(见上图)
- 函数执行失败:返回-1
- cmd参数为其他类型的(包含GETALL命令):
- 函数执行成功:返回0
- 函数执行失败:返回-1
#include
#include
#include
int semop(int semid, struct sembuf *sops, unsigned nsops);
//返回值:成功返回0;出错返回-1
semop函数具有原子性,它或者执行sops中的所有操作,或者一个也不执行
semid参数:
- 信号量集合ID
sops参数:
- 此参数是一个指针,指向一个由struct sembuf结构表示的信号量操作数组
- 数组中的每个sembuf结构体对应一个信号量ID,以及对该信号量ID进行操作的标志
struct sembuf{ unsigned short sem_num; /*信号量ID*/ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ };
sem_flg成员如下:
- 默认值填0
- SEM_UNDO :进程退出后,该进程对sem进行的操作将被撤销(例如对信号量值进行加1或减1操作,则进程退出后这些操作都撤销)
nsops参数:
- 对应于参数2的sops数组的元素个数
如何使用:
- 对信号量集合中每个信号成员的操作由struct sembuf的sem_op成员的值决定。此值可以是负值、0、正值
- 备注:下面提到的“undo”标志对应于struct sembuf的sem_flg成员的SEM_UNDO位
一、如果sem_op是正值
- 作用:释放sem_num对应的信号量的资源(所以信号量的值增加)。sem_op的值增加到sem_num对应的信号量上
- 如果指定了undo标志:则也从该进程的此信号量调整值中减去sem_op
二、如果sem_op是负值
- 作用:获取sem_num对应的信号量的资源(所以信号量的值减少)
- ①如果该信号量的值>=sem_op的绝对值:从信号量值中减去sem_op的绝对值。这能保证信号量的结果值>=0。(如果指定了undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上)
- ②如果该信号量的值
则适用下列条件: 三、如果sem_op是0
- 作用:这表示调用进程希望“等待”到该信号量值变为0
- 如果信号量值当前是0:则此函数立即返回
- 如果信号量值非0:则使用下列条件:
程序实现的功能:
- shm_a程序创建信号量集合,集合中只有一个信号量,该信号量的值为1,然后阻塞等待该信号量的值变为0
- 紧接着shm_b程序引用shm_a程序创建的信号量集合,将信号量的值变为0
- 信号量的值变为0之后,shm_a程序阻塞返回,销毁该信号量集合
代码实现
//shm_a.c #include
#include #include #include #define FLAG IPC_CREAT|0777 #define SEMID 100 #define FILENAME "./sem.txt" key_t createKey(char *name,int id); union semun{ int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; int main() { key_t key; int semId; key=createKey(FILENAME,SEMID); //创建信号量集合,集合中只有1个信号量(ID为0) if((semId=semget(key,1,FLAG))==-1){ printf("A semget error\n"); exit(EXIT_FAILURE); } //设置信号量集合中ID为0的信号量的值为1 union semun arg; arg.val=1; if(semctl(semId,0,SETVAL,arg)==-1){ printf("A semctl error\n"); exit(EXIT_FAILURE); } //获得当前信号量集合中ID为0的信号量值 int semval; if((semval=semctl(semId,0,GETVAL,NULL))==-1){ printf("A semctl error\n"); exit(EXIT_FAILURE); } printf("Current semval:%d\n",semval); printf("Wait for the semval to change to 0...\n"); //等待信号量集合中ID为0的信号量值变为0 struct sembuf buff; buff.sem_num=0; buff.sem_op=0; buff.sem_flg=0; if(semop(semId,&buff,1)==-1){ printf("A semop error\n"); exit(EXIT_FAILURE); } //销毁信号量集合 if(semctl(semId,0,IPC_RMID,NULL)==-1){ printf("A semctl error\n"); exit(EXIT_FAILURE); } printf("End...\n"); exit(EXIT_SUCCESS); } key_t createKey(char *name,int id) { key_t key; if((key=ftok(name,id))==-1){ printf("A ftok error\n"); exit(EXIT_FAILURE); } return key; }
//shm_b.c #include
#include #include #include #include #define FLAG 0777 #define FILENAME "./sem.txt" #define SEMID 100 key_t createKey(char* filename,int id); int main() { int semId; key_t key; key=createKey(FILENAME,SEMID); //引用信号量集合 if((semId=semget(key,0,FLAG))==-1){ printf("B semget error\n"); exit(EXIT_FAILURE); } printf("Change semval to 0...\n"); //将信号量集合中信号量ID为0的信号量值变为0 struct sembuf buff; buff.sem_num=0; buff.sem_op=-1; buff.sem_flg=0; if(semop(semId,&buff,1)==-1){ printf("B semop error\n"); exit(EXIT_FAILURE); } printf("End...\n"); exit(EXIT_SUCCESS); } key_t createKey(char* filename,int id) { key_t key; if((key=ftok(filename,id))==-1){ printf("B ftok error\n"); exit(EXIT_FAILURE); } return key; } 演示结果:
- 运行shm_a之后,程序创建了一个信号量集合,集合中只有一个信号量,信号量的值为0。程序中打印了信号量的值并且阻塞等待信号量的值变为0
- 紧接着运行程序shm_b,该程序将引用到shm_a程序创建的信号量集合中,并且将集合中的信号量的值变为0。可以看到左侧的程序返回了
#include
#include
#include
#include
#include
#include
#include
#define FILENAME "."
#define PROJID 0x6666
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
//设置一个key值并返回
static key_t get_Key(const char* filename,int proj_id);
//创建或引用一个信号量集合
static int get_semid(key_t key,int nsems,int flag);
//设置信号量ID为semnum的semval为value
static void set_semvalue(int semid,int semnum,int value);
//得到信号量ID为semnum的semval
static int get_semvalue(int semid,int semnum);
//释放一个信号量ID为semnum的semval(semval+1)
static void semaphore_v(int semid,int semnum);
//销毁信号量集合
static void destory_sem(int semid);
int main()
{
int sem_id,sem_value;
key_t key;
pid_t pid;
key=get_Key(FILENAME,PROJID);
if((pid=fork())<0){
perror("fork");
exit(EXIT_FAILURE);
}else if(pid==0){
printf("I am child:\n");
//创建一个信号量集合
sem_id=get_semid(key,1,IPC_CREAT|0777);
printf(" create sem\n");
//设置信号量ID为0的semval为5
set_semvalue(sem_id,0,5);
printf(" set semval is 5\n");
//得到信号量ID为0的semval
sem_value=get_semvalue(sem_id,0);
printf(" current semval is %d\n",sem_value);
//将信号量ID为0的semval加1
semaphore_v(sem_id,0);
printf(" add 1 to semval\n");
//得到信号量ID为0的semval
sem_value=get_semvalue(sem_id,0);
printf(" current semval is %d\n",sem_value);
}else{
wait(NULL);
printf("I am father:\n");
//引用子进程的信号量集合
sem_id=get_semid(key,1,0);
printf(" get child sem\n");
//得到信号量ID为0的semval
sem_value=get_semvalue(sem_id,0);
printf(" current semval is %d\n",sem_value);
//释放信号量集合
destory_sem(sem_id);
printf(" destory sem\n");
}
exit(0);
}
static key_t get_Key(const char* filename,int proj_id)
{
key_t key;
if((key=ftok(filename,proj_id))==-1){
perror("ftok");
exit(EXIT_FAILURE);
}
return key;
}
static int get_semid(key_t key,int nsems,int flag)
{
int sem_id;
if((sem_id=semget(key,nsems,flag))==-1){
perror("semget");
exit(EXIT_FAILURE);
}
return sem_id;
}
static void set_semvalue(int semid,int semnum,int value)
{
union semun arg;
arg.val=5;
if(semctl(semid,semnum,SETVAL,arg)==-1){
perror("semctl");
exit(EXIT_FAILURE);
}
}
static int get_semvalue(int semid,int semnum)
{
int sem_value;
if((sem_value=semctl(semid,semnum,GETVAL))==-1){
perror("semctl");
exit(EXIT_FAILURE);
}
return sem_value;
}
static void semaphore_v(int semid,int semnum)
{
struct sembuf sem_temp;
sem_temp.sem_num=semnum;
sem_temp.sem_op=1;
sem_temp.sem_flg=0;
if(semop(semid,&sem_temp,1)==-1){
perror("semop");
exit(EXIT_FAILURE);
}
}
static void destory_sem(int semid)
{
if(semctl(semid,0,IPC_RMID)==-1){
perror("semctl");
exit(EXIT_FAILURE);
}
}
演示结果
- 上面的代码是:子进程使用semop函数对信号量ID为0的信号量值进行加1操作,父进程使用信号时,信号量的值是被子进程加1后的值(因为struct sembuf结构体的sem_flg成员没有指定SEM_UNDO参数)
- 如果修改上面的代码,使用semop函数时,struct sembuf结构体的sem_flg成员指定了SEM_UNDO参数,则会产生下面这种情况(因为如果使用SEM_UNDO,进程对该信号量的所有操作都会撤销,所以父进程得到的信号量的值是没有变的)