目录
(1) semget():创建信号量(由内核维护)或获取一个已存在的信号量
1. 信号量的基本概念
(1)信号量的定义
信号量sem为一个特殊变量(通常取正值),代表可用资源数目,由内核直接管理。信号量是用来控制同步进程的。
【信号量的pv操作】:
信号量减一代表获取资源 可能阻塞(信号量为0时) (p操作)
信号量加一代表释放资源(v操作)
信号量作用:同步进程(对程序的执行进行控制)。
信号量只取0和1:二值信号量;信号量的取值>1:计数信号量(资源信号量)。
如下为用信号量控制两进程对临界资源打印机的使用示例:
(2)临界区、临界资源
临界资源:同一时刻只允许一个进程访问(操作)的资源。
临界区:访问临界资源的代码段。
控制对临界资源的访问:控制对临界区代码的执行即可。
2. 操作信号量的接口
引入头文件:#include<sys/sem.h>
(1) semget():创建信号量(由内核维护)或获取一个已存在的信号量
创建或获取:获取已存在的信号量是指另一个进程创建信号量供两个进程共同使用,该进程只需要获取信号量即可(信号量控制两进程同步执行)。函数成功返回信号量的ID,失败返回-1。
两程序都要根据使用相同的key值去获取同一个信号量(一个创建,一个获取)
- 参数key_t key:两个进程使用相同的key值就可以使用同一个信号量。
- nsems:内核维护的是一个信号量集,在新建信号量时,指定信号量集中的信号量的个数(创建几个信号量)。
- semflg:两个参数结合IPC_CREAT | IPC_EXCL代表全新创建!(IPC_CREAT | IPC_EXCL | 0600:规定信号量的权限为0600)
- 举例:semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
(2) semop():对信号量进行改变,实现p,v操作。
参数sops为一个结构体指针,用其来描述要对信号量做怎样的改变。
参数nsops:结构体指针指向元素的个数(定义的结构体变量数)
struct sembuf
{
unsigned short sem_num; //信号量下标(从0开始)
short sem_op; //其值为-1代表 P 操作,其值为1代表 V 操作
short sem_flg; //标志位 SEM_UNDO 避免程序异常崩溃:执行p操作后进程崩溃会导致v操作未执行
};
(3) semctl():初始化、销毁信号量。
![](https://img-blog.csdnimg.cn/0c92c37e0dac442cad20e66a50e45968.png)
![](https://img-blog.csdnimg.cn/2864eb57a58d49a6a2d8c6d3080900fe.png)
对id为semid的信号量,对其中第semnum个信号量进行初始化(下标从0开始),cmd命令:SETVAL实现信号量的初始化(将提前初始化过的联合体成员val的值赋给信号量),最后一个参数将联合体传入(需要自己定义联合体 union semun{})。
自己创建的信号量用完以后必须删除!cmd选项: IPC_RMID 会删除所有创建的信号量(统一移除)
(4)信号量的创建、初始化、销毁代码示例
自定义头文件sem.h:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
union semun//信号量初始化semctl传参用到的结构体
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
sem.c:
#include"sem.h"
static int semid=-1;//信号量
//信号量的创建、初始化
void sem_init()
{
semid = semget((key_t)1234, 1, IPC_CREAT | IPC_EXCL | 0600);//参数1为任意值(只要创建和获取信号量使用的相同即可) 参数3设置全新创建
if(semid==-1)//全新创建失败~信号量已存在?~直接获取
{
semid = semget((key_t)1234, 1, 0600); // 只要参数1相同即可获得同一信号量
if(semid==-1)
{
printf("semget error\n");
}
}
else//全新创建成功~初始化
{
union semun a;
a.val = 1;//规定信号量初值
if(semctl(semid,0,SETVAL,a)==-1)//SETVAL标志可以将信号量初值设置为联合体的val 对第0个信号量操作(下标0开始)
{
printf("semctl setval error\n");
}
}
}
//信号量p操作
void sem_p()
{
struct sembuf a;//semop传参需要
a.sem_num = 0;//信号量下标:从0开始
a.sem_op = -1;//-1代表p操作~信号量-1;1代表v操作~信号量+1
a.sem_flg = SEM_UNDO;//标志位 SEM_UNDO 避免程序异常崩溃:执行p操作后进程崩溃->未执行v操作
if(semop(semid,&a,1)==-1)
{
printf("semop p error\n");
}
}
//信号量v操作
void sem_v()
{
struct sembuf a;
a.sem_num = 0;
a.sem_op = 1;//-1代表p操作~信号量-1;1代表v操作~信号量+1
a.sem_flg = SEM_UNDO;//标志位 SEM_UNDO 避免程序异常崩溃:执行p操作后进程崩溃->未执行v操作
if(semop(semid,&a,1)==-1)
{
printf("semop v error\n");
}
}
//信号量销毁(销毁一组信号量)
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID)==-1)
{
printf("semctl destroy err\n");
}
}
3. 信号量应用示例~互斥访问打印机
题目:进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符'A'表示开始使用打印机,输出第二个字符'A'表示结束使用,b 进程操作与 a 进程相同,输出'B'。(由于打印机同一时刻只能被一个进程使用,所以输出结果不应该出现 ABAB)
![](https://img-blog.csdnimg.cn/57866426c05e44b19e38c4435c43220b.png)
(1)知识准备
- 两进程使用的是同一个信号量,且只需其中一个程序销毁信号量!
- 编译时别忘记编译sem.c,由于a.c和b.c中引用了sem.h的头文件并调用了sem.c中的方法:gcc -o a a.c sem.c(多文件编译)
- ipcs可以看系统中存在的消息队列、共享内存和信号量信息。
- 由于两个程序共用同一个信号量,只在a.c中编辑了删除信号量的语句,但当只执行b程序时,自己创建的信号量未被移除->需要手动移除! ipcrm -s 信号量id:手动删除信号量
(2)代码实现
源文件a.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<time.h>
#include"sem.h"
int main()
{
srand(time(NULL));
sem_init();//若先执行a程序,则信号量会被全新创建。另一个进程获取信号量。
for(int i=0;i<5;i++)
{
sem_p();//使用临界资源(打印机)前,执行p操作,信号量-1。若此时信号量值为0,则a进程阻塞等待。
printf("A");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();//使用结束后,执行v操作,信号量+1
n=rand()%3;
sleep(n);
}
sleep(10);//等待两进程都结束后销毁信号量
sem_destroy();//ab进程共享同一个信号量,因此只在一个进程中移除信号量即可。
exit(0);
}
源文件b.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<time.h>
#include"sem.h"
int main()
{
srand(time(NULL));
sem_init();//先执行的进程全新创建信号量,另一个进程直接获取信号量
for(int i=0;i<5;i++)
{
sem_p();
printf("B");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n=rand()%3;
sleep(n);
}
exit(0);
}
4. 信号量示例 顺序输出ABC
sem.h:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
#define SEMNUM 3 //创建3个信号量(信号量下标0 1 2)
#define SEM1 0
#define SEM2 1
#define SEM3 2
union semun
{
int val;
};
void sem_init();
void sem_p(int index);//根据下标对应信号量执行pv操作
void sem_v(int index);
void sem_destroy();
sem.c:
#include"sem.h"
static int semid=-1;
void sem_init()
{
semid=semget((key_t)1234,SEMNUM,IPC_CREAT|IPC_EXCL|0600);//创建SEMNUM个信号量
if(semid==-1)
{
semid=semget((key_t)1234,SEMNUM,0600);
if(semid==-1)
{
printf("semget error!\n");
}
}
else
{
union semun a;
int arr[SEMNUM]={1,0,0};//sem1 sem2 sem3初始化为1 0 0
for(int i=0;i<SEMNUM;i++)
{
a.val=arr[i];
if(semctl(semid,i,SETVAL,a)==-1)
{
printf("semctl init err\n");
}
}
}
}
void sem_p(int index)
{
if(index<0||index>=SEMNUM)
{
return;
}
struct sembuf buf;
buf.sem_num=index;//对下标为index的信号量进行p操作
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
printf("semop p err\n");
}
}
void sem_v(int index)
{
if(index<0||index>=SEMNUM)
{
return;
}
struct sembuf buf;
buf.sem_num=index;//对下标为index的信号量进行v操作
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
printf("semop v err\n");
}
}
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID)==-1)
{
printf("semctl destroy err\n");
}
}
a.c:
#include"sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5;++i)
{
sem_p(0);//p sem0
printf("A");
fflush(stdout);
sem_v(1);//v sem1供b进程运行
int n = rand() % 3;
sleep(n);
}
exit(0);
}
b.c:
#include"sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5;++i)
{
sem_p(1);
printf("B");
fflush(stdout);
sem_v(2);
int n = rand() % 3;
sleep(n);
}
exit(0);
}
c.c:
#include"sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5;++i)
{
sem_p(2);
printf("C");
fflush(stdout);
sem_v(0);
int n = rand() % 3;
sleep(n);
}
sem_destroy();//abc进程使用的为同一信号量~只需一个进程对信号量移除即可
exit(0);
}