一、信号量相关函数
1. 创建信号量集
本次实验使用信号量机制来实现进程的同步与互斥,因此首先需要创建信号量集。
使用函数semget可以创建信号量集,它的原型如下:
int semget(key_t key, int nsems, int semflg)
其中,key是创建信号量集的键,nsems是信号量集中信号量量的数量;
semflg是指定的选项及其权限位,包含IPC_CREAT(创建新的信号量集)、IPC_EXCEL(如果信号量集已经存在,则返回错误)等。
这个函数创建的是一个信号量集,其中包含多个信号量,可以通过函数semop来访问信号量集中的某个信号量,或者使用semctl函数来对信号量进行操作,一般创建数据集后都要首先使用semctl对每个信号量设置初始值。semop和semctl函数的介绍在下面。
2. 获取信号量集
一个进程创建号信号量集后,另一个进程想要访问这个信号量集,可以使用semget函数传入KEY值来获取该信号量集的id,该函数原型如下:
int semid = semget(KEY, 0, 0);
上述代码只为获取一个已经存在的信号量集,不需要nsems和semflg,全部为0即可。获取成功会返回信号量集id,如果获取失败的话会返回-1 。
3. 等待、通知信号量集
使用semop函数可以访问一个信号量集,进行获取(P操作)和释放(V操作),其原型如下:
int semop(int semid,struct sembuf *sops,unsigned nsops)
semid是使用semget函数获取到的信号量集的id;
sops是一个sembuf类型的结构,用于描述信号量的操作:等待、通知等,其定义如下:
struct sembuf{
short sem_num; // 要访问的信号量在信号量集中的索引
short sem_op; // 对信号量的操作,为负数是P操作,正数是V操作
short sem_flg; // 操作标志,可以是0或IPC_NOWAIT(非阻塞方式)
}
nsops是指定信号量集中操作的信号量个数。
4. 控制信号量集
要对整个信号量集进行操作可以使用semctl函数,其原型如下:
int semctl(int semid, int semnum, int cmd, union semun arg)
semid是由semget函数返回的信号量集id
semnum是信号量在信号量集中的索引
cmd是控制命令,用于对信号量执行指定的操作,命令包括:
IPC_STAT:获取信号量集合的属性信息,将结果写入指定的结构体中。
IPC_SET:设置信号量集合的属性信息,使用指定的结构体中的值进行设置。
GETVAL:获取指定信号量的值。
SETVAL:设置指定信号量的值。
GETALL: 获取信号量集合中所有信号量的值
SETALL: 设置所有信号量的值
GETPID:获取最后一个执行 semop() 操作的进程 ID。
GETNCNT:获取等待该信号量值增加的进程数。
GETZCNT:获取等待该信号量值变为 0 的进程数。
IPC_RMID:删除信号量集合。
二、简单进程同步
现在使用上面给出的函数编写几个程序实现进程同步。
信号量机制实现进程同步需要使用一些简单的信号量集操作,包括创建信号量集,P操作,V操作,删除信号量集。
1. 创建信号量集
createSem.cpp
#include<iostream>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h>
using namespace std;
#define KEY 2002
int main(){
int semid = semget(KEY, 1, IPC_CREAT);
semctl(semid, 0, SETVAL, 0);
}
上面代码使用semid函数创建一个只包含一个信号量的信号量集,随后使用semctl将该信号量的值初始化为0 。
2. P操作
p1.cpp
#include<iostream>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h>
#define KEY 2002
using namespace std;
int main(){
int semid = semget(KEY, 0, 0);
struct sembuf *sops = new sembuf;
sops->sem_num = 0;
sops->sem_op = -1;
sops->sem_flg = 0;
if(semop(semid, sops, 1) == -1){
char err[] = "semop";
perror(err);
exit(1);
}
cout << "获取成功" << endl;
return 0;
}
上一个程序createSem创建完信号量集后,信号量集就保存在缓冲区中,此时另一个程序可以使用semget函数访问该信号量集,使用同样的键值KEY就能访问到上一个进程创建的信号量集。
上述代码首先获取已经创建好的信号量集,随后定义sembuf类型结构指针sops,设置访问信号量索引sem_num为0,信号量操作sem_op为-1(p操作,信号量减一),操作标志为0 。接着就使用semop函数传入sops对信号量进行操作,如果操作失败(如信号量集不存在或信号量索引非法等)就输出错误信息,结束程序。如果获取成功则会执行下面的语句打印“获取成功”,如果进行P操作时该信号量为0,此时进行会阻塞,等待其他进程释放信号,为了简化问题,一开始设置信号量为0,此时执行p1程序一定会阻塞。
3. V操作
p2.cpp
#include<iostream>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h>
#define KEY 2002
using namespace std;
int main(){
int semid = semget(KEY, 0, 0);
struct sembuf *sops = new sembuf;
sops->sem_num = 0;
sops->sem_op = 1;
sops->sem_flg = 0;
if(semop(semid, sops, 1) == -1){
char err[] = "semop";
perror(err);
exit(1);
}
return 0;
}
上面代码与p1基本时一样的,只是修改了sem_op,改为1(V操作,信号量加1),执行该程序后,对应信号量会加一,原本阻塞的程序就能结束等待,继续执行下去。
4. 删除信号量集
deletSem.cpp
信号量集使用完毕后需要删除信号量集,使用semctl函数实现,控制命令选择IPC_RMID。
#include<iostream>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h>
using namespace std;
#define KEY 2002
int main(){
int semid = semget(KEY, 0, 0);
semctl(semid, 0, IPC_RMID, 0);
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70001647/viewspace-2998725/ ,如需转载,请注明出处,否则将追究法律责任。