信号量
- 信号量实际上是一个计数器,作用是统计临界资源的多少,保护临界资源。
- 信号量本身也是临界资源
进程互斥
- 各进程要求共享资源,而有些资源必须互斥使用,因此进程就会竞争这些资源,称为互斥。
- 一次只允许一个进程使用的资源叫做临界资源。
- 互斥资源的的程序段叫做临界区。
进程同步
- 多个进程需要相互配合完成一项任务。
信号量和P、V原语
- 信号量
同步 :P、V在同一进程中
互斥 :P、V在不同进程中
- 信号量值的含义
S > 0 :S表示可用资源的个数。
S = 0 :表示当前无可用资源,也无进程等待。
S < 0 :表示当前无可用资源,有 |S| 个进程在等待。 // S < 0 是通用信号量的概念,在system V信号量中,S不会小于0
- P原语 用于获取临界资源,如果当前没有临界资源,进程的PCB就会插入相应的等待队列
- V原语 用于释放临界资源,唤醒等待的进程,将其插入就绪队列
信号量集结构
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 short sem_nsems; /* No. of semaphores in set */
};
信号量集函数 (信号量集是一组信号的集合)
shmget - 创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集的名字
nsems:信号集中信号量的总数
semflg: 由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该信号集的标识码;失败返回-1
shmctl - 控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
参数
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值)
最后⼀个变长参数根据命令不同⽽不同
返回值:成功返回0;失败返回-1
cmd参数值 | 说明 |
SETVAL | 设置信号量集中信号量的计数值(也就是表示当前信号量集中有多少个信号),需要通过一个联合体中来设置,需要左后一个变长参数,就是当前联合体。 |
GETVAL | 获取信号量集中有多少个信号量。不需要最后一个变长参数,并且函数的返回值表示了当前可用的信号数量。 |
IPC_STAT | 把semid_ds结构体中的数据设置为信号集当前关联值。 |
IPC_SET | 在进程有足够的权限下,吧信号集当前的关联值设置为semid_ds结构体中的值。 |
IPC_RMID | 删除信号即,用法同消息队列和共享内存。 |
semop - 创建和访问一个信号量集 (原子操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向⼀个结构数值的指针
nsops:信号量的个数 返回值:
成功返回0;失败返回-1
sops所指向的结构体 - sembuf结构体
struct sembuf{
short sem_num; //信号量的编号
short sem_op; //试一次P、V操作对应的加减数值,P操作设置为 -1,表示消耗一个临界资源,V操作设置为 1,表示释放了一个临界资源
short sem_flg; // 有两个取值,IPC_NOWAIT,或 SEM_UNDO(将已申请的信号量还原为初识状态,即刚开始获得这个信号量时的状态)
}
代码演示
两个进程(这里用父子进程演示),父进程在屏幕上输出 A,间隔一段时间在输出A,子进程在屏幕上输出B,间隔一段时间在屏幕上再次输出B。
非原子操作:
原子操作:
代码演示原子操作:
//com.h
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> union semun { int val; //SETVAL的值 struct semid_ds *buf; unsigned short *array; struct seminfo *_buf; }; //nums 表示当前信号集中有多少个信号量 //这里我们为二元信号量(互斥锁),所以num = 0 int CreateSem(int nums); //semid 是CreateSem的返回值 //index 是设置当前信号集中第几个信号量,从0开始 //initval 是将当前信号量设置为initval值,表示临界资源的可用数目。 //这里我们为二元信号量(互斥锁),所以index = 0,initcval = 1 int InitSem(int semid,int index,int initval); //获取当前信号量集的第index个信号量 //这里我们为二元信号量(互斥锁),所以index = 0 int GetSem(int index); //对信号量集中的哪一个信号进行P操作 //这里我们为二元信号量(互斥锁),所以who = 0 int P(int semid,int who); //对信号量集中的哪一个信号进行V操作 //这里我们为二元信号量(互斥锁),所以who = 0 int V(int semid,int who); int DestorySem(int semid);
//com.c
#include "com.h" int Command(int nums,int flags) { key_t key = ftok(".",0666); if(key == -1) { perror("ftok"); exit(1); } int semid = semget(key,nums,flags); if(semid < 0) { perror("semget"); exit(1); } return semid; } int CreateSem(int nums) { return Command(nums,IPC_CREAT | IPC_EXCL | 0666); } int InitSem(int semid,int index,int initval) { union semun _un; _un.val = initval; if(semctl(semid,index,SETVAL,_un) < 0) { perror("semctl initsem"); exit(1); } return 0; } int GetSem(int index) { return Command(index,IPC_CREAT); } int CommandPV(int semid,int who,int val) { struct sembuf buf; buf.sem_num = who; buf.sem_op = val; buf.sem_flg = 0; //忽略这个操作 if(semop(semid,&buf,1) < 0) { perror("semop"); exit(1); } return 0; } int P(int semid,int who) { return CommandPV(semid,who,-1); } int V(int semid,int who) { return CommandPV(semid,who,1); } int DestorySem(int semid) { if(semctl(semid,0,IPC_RMID) < 0) { perror("semctl destrysem"); exit(1); } return 0; }
//Print.c
#include "com.h" #include <unistd.h> int main() { int semid = CreateSem(1); InitSem(semid,0,1); int pid = fork(); if(pid > 0) //parent { while(1) { P(semid,0); printf("A"); usleep(654321); printf("A "); usleep(123456); fflush(stdout); V(semid,0); } } else if(pid == 0) //child { while(1) { P(semid,0); printf("B"); usleep(123456); printf("B "); fflush(stdout); usleep(321456); V(semid,0); } } else { perror("fork"); exit(1); } DestorySem(semid); return 0; }
结果演示:没有出现单个A或者B交错出现的情况,符合预期
信号量也可以使用 ipcs -s 查看
可以使用 ipcrm -s [semid] 来删除