目录
- 1. 信号量和P、V原语
- 2. 信号量操作接口
- 3. 封装Sem
1. 信号量和 P、V 原语
信号量和P、V原语由Dijkstra(迪杰斯特拉)提出。
1.1 信号量常规理解
- 执行流间互斥与同步。
1.2 信号量值含义
S > 0:S表示可用资源的个数。S = 0:表示无可用资源、无等待进程。S < 0:|S|表示等待队列中的进程个数。
1.3 信号量结构体伪代码
// 信号量本质上是一个计数器 struct semaphore { int value; pointer_PCB queue; };1.4 P 原语
P(s) { s.value--; if (s.value < 0) { // 将该进程状态置为等待状态 // 将该进程的 PCB 插入相应等待队列 s.queue 的末尾 } }1.5 V 原语
V(s) { s.value++; if (s.value <= 0) { // 唤醒相应等待队列 s.queue 中等待的一个进程 // 改变其状态为就绪态 // 将其插入 OS 就绪队列 } }1.6 信号量集结构体
Thesemid_dsdata structure is defined in<sys/sem.h>as follows:
structsemid_ds{structipc_permsem_perm;/* Ownership and permissions */time_tsem_otime;/* Last semop time */time_tsem_ctime;/* Last change time */unsignedlongsem_nsems;/* No. of semaphores in set */};Theipc_permstructure is defined as follows (the fieldsuid,gid, andmodeare settable usingIPC_SET):
structipc_perm{key_t__key;/* Key supplied to semget(2) */uid_tuid;/* Effective UID of owner */gid_tgid;/* Effective GID of owner */uid_tcuid;/* Effective UID of creator */gid_tcgid;/* Effective GID of creator */unsignedshortmode;/* Permissions */unsignedshort__seq;/* Sequence number */};2. 信号量操作接口
2.1 semget
NAME
semget- get a System V semaphore set identifier
SYNOPSIS
#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>intsemget(key_tkey,intnsems,intsemflg);RETURN VALUE
If successful, the return value will be thesemaphore set identifier(a nonnegative integer), otherwise,
-1is returned, witherrnoindicating the error.
参数介绍
key:信号量集的键值,同消息队列和共享内存。nsems:信号量集中信号量的个数。semflg:同消息队列和共享内存。
2.2 semctl
NAME
semctl- System V semaphore control operations
SYNOPSIS
#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>intsemctl(intsemid,intsemnum,intcmd,...);This function has three or four arguments, depending on
cmd. When there are four, the fourth has the typeunion semun.The calling program must define this union as follows:
unionsemun{intval;/* Value for SETVAL */structsemid_ds*buf;/* Buffer for IPC_STAT, IPC_SET */unsignedshort*array;/* Array for GETALL, SETALL */structseminfo*__buf;/* Buffer for IPC_INFO (Linux-specific) */};RETURN VALUE
On failure,
semctl()returns-1witherrnoindicating the error.Otherwise, the system call returns a nonnegative value depending on
cmdas follows:
GETNCNT: the value ofsemncnt.GETPID: the value ofsempid.GETVAL: the value ofsemval.GETZCNT: the value ofsemzcnt.IPC_INFO: the index of the highest used entry in the kernel’s internal array recording information about all semaphore sets. This information can be used with repeatedSEM_STATorSEM_STAT_ANYoperations to obtain information about all semaphore sets on the system.SEM_INFO: as forIPC_INFO.SEM_STAT: the identifier of the semaphore set whose index was given insemid.SEM_STAT_ANY: as forSEM_STAT.
All othercmdvalues return0on success.
参数介绍
semid:由semget返回的信号集标识码。semnum:信号集中信号量的序号。
semctl()performs the control operation specified bycmdon the System V semaphore set identified bysemid, or onthesemnum-th semaphore of that set. The semaphores in a set are numberedstarting at0.
cmd:将要采取的动作,具体动作查看man手册。
2.3 semop
NAME
semop,semtimedop- System V semaphore operations
SYNOPSIS
#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>intsemop(intsemid,structsembuf*sops,size_tnsops);
semop()performs operations on selected semaphores in the set indicated bysemid. Each of thensopselements in the array pointed to bysopsis a structure that specifies an operation to be performed on a single semaphore.The elements of this structure are of type
struct sembuf, containing the following members:
unsignedshortsem_num;/* semaphore number */shortsem_op;/* semaphore operation: -1, P 操作; 1, V 操作 */shortsem_flg;/* operation flags */Flags recognized in
sem_flgareIPC_NOWAITandSEM_UNDO.If an operation specifiesSEM_UNDO, it will be automatically undone when the process terminates.
RETURN VALUE
If successful,
semop()andsemtimedop()return0; otherwise they return-1witherrnoindicating the error.
参数介绍
semid:该信号量的标识码,也就是semget函数的返回值。sops:指向一个sembuf结构的指针。nsops:sops数组中的操作项数量,可在一次semop调用中提交多个操作。
3. 封装 Sem
- 我们使用信号量,简化信号量使用,测试使用二元信号量对显示输出进行互斥打印。
3.1 Sem.hpp
#pragmaonce#include<iostream>#include<string>#include<memory>#include<cstdlib>#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>#include<unistd.h>conststd::string pathname="/tmp";intproj_id=0x77;#defineGET_SEMIPC_CREAT// 不给权限#defineBUILD_SEM(IPC_CREAT|IPC_EXCL|0666)// 只关注使用和删除classSemaphore{public:Semaphore(intsemid,intflag):_semid(semid),_flag(flag){}voidP(){structsembufsb;// 该结构系统提供sb.sem_num=0;sb.sem_op=-1;sb.sem_flg=SEM_UNDO;intn=::semop(_semid,&sb,1);(void)n;}voidV(){structsembufsb;sb.sem_num=0;sb.sem_op=1;sb.sem_flg=SEM_UNDO;intn=::semop(_semid,&sb,1);(void)n;}~Semaphore(){if(_flag==GET_SEM)return;// 让信号量自动销毁// 如果销毁信号量集合:The argument semnum is ignoredintn=::semctl(_semid,0,IPC_RMID);(void)n;std::cout<<"sem set destroy!"<<std::endl;}private:int_semid;int_flag;};usingsem_sptr=std::shared_ptr<Semaphore>;// 使用简单的建造者模式,用它来构建单 semclassSemaphoreBuilder{public:SemaphoreBuilder():_val(-1){}SemaphoreBuilder&SetVal(intval){_val=val;return*this;// 支持连续访问}sem_sptrBuild(intflag){// 0. 先做简单的合法性判断if(_val<0){std::cerr<<"you must init first!"<<std::endl;returnnullptr;}// 1. 申请 key 值key_t k=::ftok(pathname.c_str(),proj_id);if(k<0)std::exit(1);// 2. 根据初始值创建信号量集合intsemid=::semget(k,1,flag);// 只创建一个信号量就够用了if(semid<0)std::exit(2);if(BUILD_SEM==flag){// 3. 初始化信号量unionsemun// 该联合体系统不提供,需要我们自己定义{intval;/* Value for SETVAL */structsemid_ds*buf;/* Buffer for IPC_STAT, IPC_SET */unsignedshort*array;/* Array for GETALL, SETALL */structseminfo*__buf;/* Buffer for IPC_INFO (Linux-specific) */}un;un.val=_val;// 设置为初始值intn=::semctl(semid,0,SETVAL,un);if(n<0)std::exit(3);}// 4. 创建并返回信号量集合returnstd::make_shared<Semaphore>(semid,flag);}~SemaphoreBuilder(){}private:int_val;// 所有信号量的初始值};3.2 Writer.cc:测试信号量接口
#include"Sem.hpp"#include<cstdio>#include<cstdlib>#include<time.h>#include<unistd.h>intmain(){SemaphoreBuilder sb;autofsem=sb.SetVal(1).Build(BUILD_SEM);// 创建一个信号量并初始化为 1,当作锁使用if(fork()==0){autocsem=sb.Build(GET_SEM);intcnt=10;while(cnt--){csem->P();printf("C");fflush(stdout);usleep(std::rand()%95270);printf("C ");usleep(std::rand()%43990);fflush(stdout);csem->V();}std::exit(0);}intcnt=50;while(cnt--){fsem->P();printf("F");fflush(stdout);usleep(std::rand()%95270);printf("F ");usleep(std::rand()%43990);fflush(stdout);fsem->V();}return0;}3.3 结论
System V信号量生命周期也是随内核的。ipcs -s && ipcrm -s semid