目录
1、信号量
2、信号量接口函数
2.1、semget函数
2.2、semctl函数
2.3、semop函数
3、接口封装
4、实例运行
5、ipcs 可以查看消息队列、共享内存、信号量的使用情况,使用 ipcrm 可以进行删除操作。
在多进程、多线程系统,程序中通常存在着一部分临界区,我们需要确保只有一个进程或执行线程可以进入这个临界区,并拥有对资源独占式的访问。
临界区:访问临界资源的代码段。
临界资源:同一时间只允许被一个进程或线程访问的资源。比如,打印机同时只能被一个人使用;多线程对一个全局变量进行累加(在多处理器,线程并行的情况下,如果不加控制,两个线程同时对同一全局变量进行累加,某个累加操作会被覆盖)。
我们可以通过信号量同步多进程、线程对资源的访问。
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的一类资源的数量。信号量与其控制访问的资源之间并无实际代码将其关联,而是在逻辑上一直抽象的,用一个信号量代表一类资源的数量,信号量的拥有的数量值与一类资源的数量相对应。
P 操作:获取(使用)资源时对信号量的值进行原子减一,代表有一份资源被占用 。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。
V 操作:释放资源时,对信号量的值进行原子加一。
信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。
信号量函数均在
Linux系统中的信号量接口提供了比通常所需的更多机制,所有信号量函数都是针对成组的信号量(一组信号量组成信号量集)进行操作。因为实际情况中可能并不只有单一资源需要被控制访问,而是有多个资源需要被同时控制。当然,接口函数中有选项可以选择进行单信号量的控制。
多个信号量按顺序如数组一般保存在信号量集中,操作时可以通过下标来指定信号量。
函数原型:
int semget(key_t key,int num_sems,int sem_flags);
semget函数用于创建一个新的信号量集,或者获取一个已有的信号量集的标识符。函数返回信号量集的标识符。其他信号量接口都通过该标识符对信号量进行操作。
key:一个整数值。多个进程、线程可以通过相同的key值来获取同一个信号量集的标识符。
num_sems:指定所需信号量的数目(要控制访问的资源类型的数目) 。num_sems==1,代表只对一类资源进行控制访问。
sem_flags:其中二进制低位9位定义了信号量集的权限(所有者、组和其他人)。可与IPC_CREAT按位或来创建一个信号量集。若信号量集已存在,则为通过key值获取该信号量集的标识符。也可以使用IPC_CREAT与IPC_EXCL来确保创建出的是一个新的、唯一的信号量集,若信号量集已存在(key值已被使用过),则失败,返回-1。
用于控制信号量的信息。常用于对信号量集中的一个信号量进行初始化值,或者删除整个信号量集。
函数原型:
int semctl(int sem_id,int sem_num,int command,...);
sem_id:semget函数返回的信号量集的标识符。
sem_num:信号量在信号量集中的编号(下标位置)。若只有一个信号量,则这个值取值为0,表示这是第一个也是唯一的一个信号量。
command:将要进行的操作。常用的两个值:
(1)IPC_RMID:删除一个不用的信号量集。
(2)SETVAL:在信号量第一次使用前对其进行值的设置。若使用SETVAL,则函数需要第四个参数 union semun。
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}
信号量的值由 union semun 中的val设置。
用于改变信号量的值:进行P(信号量值-1)、V(信号量值+1)操作。
函数原型:
int semop(int sem_id,struct sembuf *sem_ops,size_t num_sem_ops);
sem_id:semget函数返回的信号量集的标识符。
sem_ops:指向一个结构体数组的指针。结构体中保存对指定信号量的操作。数组中可保存多个结构体,可指定同时对多个信号量进行操作。
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
}
(1)sem_num:信号量在信号量集中的编号(下标位置)。只有一个信号量时取值为0。
(2)sem_op:信号量在一次操作中需要改变的数值。通常使用两个值: -1,P操作,占用信号量对应资源的一份;1,V操作,释放一份资源。
(3)sem_flg:通常被设置为SEM_UNDO。它使得操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。
num_sem_ops:结构体数组中元素的数量(一次操作信号量的个数)。
因为信号量与资源之间并未实际关联,在信号量较少,P/V操作简单时,我们可以将信号量接口函数进行简单封装,使其使用起来更简单、便捷。
假设目前我们只需要对一类资源进行访问控制,且该资源数量为1。
sem.h:
#include
#include
#include
#include
union semun{
int value;
};
void sem_init();//信号量初始化
void sem_p(); //P操作
void sem_v(); //V操作
void sem_destroy();//信号量释放
sem.c:
#include"sem.h"
static int semid;//静态全局变量 .data区 本文件有效
void sem_init(){
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
if(semid==-1){//创建失败(已存在,直接获取)
semid=semget((key_t)1234,1,0600);//已存在,获取
if(semid==-1) //仍然失败则退出
exit(1);
}
else{//创建信号量集成功,对唯一的信号量进行赋值初始化
union semun a;
a.value=1;
if(semctl(semid,0,SETVAL,a)==-1){//设置值
perror("semctl err");
exit(1);
}
}
}
void sem_p(){//封装P操作
struct sembuf buf;
buf.sem_num=0;//信号量编号为0
buf.sem_op=-1;//信号量值-1
buf.sem_flg=SEM_UNDO;//系统帮忙维护
if(semop(semid,&buf,1)==-1){
perror("semop err");
exit(1);
}
}
void sem_v(){//封装V操作
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=1;//信号量值+1
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1){
perror("semop err");
exit(1);
}
}
void sem_destroy(){//删除信号量集
if(semctl(semid,0,IPC_RMID)==-1){
perror("semctl err");
exit(1);
}
}
假设,当前有两个进程要使用打印机,打印机只有一个,一个人使用打印机时不能被打断。
a.c: 通过信号量进行进程同步,使得a进程与b进程交替打印"aabbaabb......"。
#include
#include
#include
#include"sem.h"
int main(){
sem_init();
for(int i=0;i<5;i++){
sem_p();//P操作获取打印机资源
printf("A");
fflush(stdout);
int n=rand()%3;//n可能取值0,1,2
sleep(n);//模拟打印操作的持续时间
//在持续时间内由于信号量的存在,
//其他进程不能进行printf
printf("A");
fflush(stdout);
sem_v();//V操作释放打印机资源
n=rand()%3;
sleep(n);
//使当前进程暂停一下,
//给其他进程获取到资源的机会
}
sleep(5);
sem_destroy();//删除信号量集
exit(0);
}
b.c:
#include
#include
#include
#include"sem.h"
int main(){
sem_init();
for(int i=0;i<5;i++){
sem_p();//P操作,资源被占用时,阻塞
printf("B");
fflush(stdout);
int n=rand()%3;//0,1,2
sleep(n);
printf("B");
fflush(stdout);
sem_v();//释放资源
n=rand()%3;
sleep(n);
}
exit(0);
}
通过信号量进行进程同步,使即使某一进程在暂停时,其他进程也不能进行打印。实现了交替按序打印。