信号量
- 信号量实际上是一个计数器,作用是统计临界资源的多少,保护临界资源。
- 信号量本身也是临界资源
进程互斥
- 各进程要求共享资源,而有些资源必须互斥使用,因此进程就会竞争这些资源,称为互斥。
- 一次只允许一个进程使用的资源叫做临界资源。
- 互斥资源的的程序段叫做临界区。
进程同步
- 多个进程需要相互配合完成一项任务。
信号量和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] 来删除