Linux 学习笔记16 信号量Semaphore
信号量概念
- 信号量(或信号灯)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。
- 信号量是控制进程(或线程)同步(谁先执行,谁后执行)的一种方式。如:有时候我们需要保护一段代码,使它每次只能被一个执行进程运行。(如:抢火车票,每次只能一个用户买,买完再轮到下一个用户)。此时就需要二进制开关(加解锁)。
信号量分类:
- System V 信号量,在内核中维护,可用于进程或线程间的同步,常用于进程的同步。
- Posix 有名信号量,一种来源于 POSIX 技术规范的实时扩展方案(POSIX Realtime Extension),可用于进程或线程间的同步,常用于线程。(不常用)
- Posix 基于内存的信号量,存放在共享内存区中,可用于进程或线程间的同步
获取共享资源(如共享内存shm,代码)进程
操作步骤
step1:测试控制该资源的信号量(类比火车进站,是否是绿灯)
step2:若信号量的值为正,则进程可以使用该资源。进程信号量值减 1(红灯), 表示它使用了一个 资源单位。此进程使用完共享资源后对应的信号量会加 1。以便其他进程使用
step3:若信号量的值为 0,则进程进入休息状态(等待), 直至信号量值大于 0。进程被唤醒,返回step1。
system V IPC机制 信号量函数学习
-
int semget(key_t key, int nsems, int semflg); //创建一个信号量"集合" ,名字:semget - get a System V semaphore set identifier 获取一个system V的信号量集合ID
-
int semop(int semid, struct sembuf *sops, size_t nsops);//semop接口是一个原子操作
-
int semctl(int semid, int semnum, int cmd, ...)
- semget参数解析:
semget(key_t key, int nsems, int semflg);- key:类似于shm的key值,key 是唯一标识一个信号量的关键字,如果为 IPC_PRIVATE(值为 0,创建一 个只有创建者进程才可以访问的信号量,通常用于父子进程之间;非 0 值的 key(可以通过ftok 函数获得)表示创建一个可以被多个进程共享的信号量
- nsems:信号量的个数
- semflg:信号量权限,可设置为IPC_CREATE|0600
示例代码如下:
#include <func.h>
int main(int argc,char * argv[])
{
int sems_id;//semset id
sems_id=semget(1000,1,IPC_CREAT|0600);//一旦创建,数目不可修改,修改要使用接口semctl
ERROR_CHECK(sems_id,-1,"semget");
return 0;
}
执行效果如下:
注意,信号量第一次被设定之后,不能再更改信号量的权限、参数等,只能使用 semctl 修改
-
semctl(int semid, int semnum, int cmd, ...)参数解析
-
semid:信号量ID
-
semnum:参数 semnum 为集合中信号量的编号,当要用到成组的信号量时,从 0 开始。一 般取值为0,表示这是第一个也是唯一的一个信号量
-
cmd:参数 cmd 为执行的操作。
常用cmd为:IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程)
GETVAL(根据 semnum 返回信号量的值,从 0 开始,第一个信号量编号为 0)
SETVAL(根据 semnum 设定信号的值,从 0 开始,第一个信号量编号为 0)
GETALL(获取所有信号量的值,第二个参数为 0,将所有信号的值存入 semnum.array 中)、SETALL(将所有 semnum.array 的值设定到信号集中,第二个参数为 0)等
代码如下:
-
#include <func.h>
int main(int argc,char * argv[])
{
int sems_id;//semset id
sems_id=semget(1000,1,IPC_CREAT|0600);//一旦创建,数目不可修改,修改要使用接口semctl
ERROR_CHECK(sems_id,-1,"semget");
int ret=semctl(sems_id,0,SETVAL,1);//0代表只有一个信号量,1代表将信号量的值初始化为1
ERROR_CHECK(ret,-1,"semctl");
return 0;
}
setval之后看不出变化,此时采用getval 查看信号量的变化,代码如下:
#include <func.h>
int main(int argc,char * argv[])
{
int sems_id;//semset id
sems_id=semget(1000,1,IPC_CREAT|0600);//一旦创建,数目不可修改,修改要使用接口semctl
ERROR_CHECK(sems_id,-1,"semget");
int ret=semctl(sems_id,0,SETVAL,1);//0代表只有一个信号量,1代表将0号信号量的值初始化为1
ERROR_CHECK(ret,-1,"semctl");
ret=semctl(sems_id,0,GETVAL);//GETVAL取0号信号量的值
ERROR_CHECK(ret,-1,"semctl");
printf("value=%d\n",ret);//打印setval设置的0号信号量的值
return 0;
}
执行效果如下:
删除信号量集合代码如下:
#include <func.h>
//删除信号量集合
? int main(int argc,char * argv[])
{
int sems_id;//semset id
sems_id=semget(1000,1,IPC_CREAT|0600);//一旦创建,数目不可修改,修改要使用接口semctl
ERROR_CHECK(sems_id,-1,"semget");
int ret=semctl(sems_id,0,IPC_RMID);//0代表只有一个信号量,1代表信号量的值初始化为1
ERROR_CHECK(ret,-1,"semctl");
return 0;
}
执行效果如下:
- PV操作
- semop参数解析:
-
semop(int semid, struct sembuf *sops, size_t nsops);//semop接口是一个原子操作
-
semid:由 semget 设定的semID
-
sops :是指向一个结构体数组的指针,即一个结构体数组的起始地址
-
nsops:结构体的数量,一般都设置为1
-
- sops的结构体数组指针
-
struct sembuf { unsigned short sem_num; /* semaphore number */ 信号量ID,第一个信号量ID为 0 short sem_op; /* semaphore operation */ 信号量操作 p代表-1操作,v代表+1操作 short sem_flg; /* operation flags */ 通常设为:SEM_UNDO }
例:两个进程同时对共享内存(shm)进行写操作,使用PV操作,代码如下:
#include <func.h>
#define N 10000000
//父子进程同时对共享内存做加法(写)
//测试是否能够成功等于20000000,pv操作
int main(int argc,char * argv[])
{
int shmid;//存储创建的共享内存id
shmid=shmget(1000,4096,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
printf("shmid=%d\n",shmid);
int *p=(int*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(int *)-1,"shmat");
p[0]=0;//初始化共享内存值为0
int i;
int sems_id;//设置信号量
sems_id=semget(1000,1,IPC_CREAT|0600);//一旦创建,数目不可修改,修改要使用接口semctl
ERROR_CHECK(sems_id,-1,"semget");
int ret=semctl(sems_id,0,SETVAL,1);//0代表只有一个信号量,1代表信号量的值初始化为1
ERROR_CHECK(ret,-1,"semctl");
struct sembuf sopp,sopv;//p对信号量减1操作,v加1操作
sopp.sem_num=0;
sopp.sem_op=-1;//等待信号量变为可用,p操作
sopp.sem_flg=SEM_UNDO;//一般都写SEM_UNDO,防止程序崩溃
//SEM_UNDO能够使程序崩溃时进程还原成原来的状态
sopv.sem_num=0;//0号信号量
sopv.sem_op=1;//发送信号通知信号量可用,v操作
sopv.sem_flg=SEM_UNDO;
if(!fork())
{//子进程写
for(i=0;i<N;i++)
{
semop(sems_id,&sopp,1);//p操作,加锁
p[0]=p[0]+1;
semop(sems_id,&sopv,1);//v操作,解锁
}
exit(0);
}else
{//父进程写
for(i=0;i<N;i++)
{
semop(sems_id,&sopp,1);
p[0]=p[0]+1;
semop(sems_id,&sopv,1);
}
wait(NULL);
printf("ret=%d\n",p[0]);
}
return 0;
}
执行效果如下:
- 多个信号量操作
- 获取多个信号量,代码如下:
#include <func.h>
//一个信号量集合中创建多个信号量
//
int main(int argc,char * argv[])
{
int sems_id;
sems_id=semget(1000,2,IPC_CREAT|0600);
ERROR_CHECK(sems_id,-1,"semet");
return 0;
}
执行效果如下:
- 设置多个信号量 SETALL 代码如下:
#include <func.h>
int main(int argc,char * argv[])
{
int sems_id;//semset id
sems_id=semget(1000,1,IPC_CREAT|0600);//一旦创建,数目不可修改,修改要使用接口semctl
ERROR_CHECK(sems_id,-1,"semget");
unsigned short arr[2]={2,5};//两个信号量的值设置为非负数
int ret=semctl(sems_id,0,SETALL,arr);//当设置setall时,第二个参数就不起作用,因此一般填0
ERROR_CHECK(ret,-1,"semctl");
bzero(arr,sizeof(arr));//清空arr,采用bzero接口
ret=semctl(sems_id,0,GETALL,arr);//获取刚设置的信号量的值
ERROR_CHECK(ret,-1,"semctl");
printf("arr[0]=%d,arr[1]=%d\n",arr[0],arr[1]);//打印设置的信号量的值
return 0;
}
执行效果如下: