1、信号量:
基本概念:
- 1、多个进程或者线程可能同时访问的资源(变量、链表、文件等)称为共享资源也叫临界资源
- 2、访问这些资源的代码称为临界代码,这些代码的区域称为临界区
- 3、程序进入临界区之前必须要对资源进行申请,这个动作称为P操作(申请资源),就像汽车进入停车场,先要申请一张停车卡,P操作就是申请资源。如果申请成功,资源的数量就会减少。如果申请失败,要不就等待,要不就取消
- 4、程序离开临界区之后,要释放响应的资源,这个动作被称为V操作,就像汽车离开停车场之后,会把停车卡还回去,V操作就是释放资源,释放资源就是让资源数增加
- 信号量的P、V操作最核心的特征:他们是原子性的,他们确保在同一时刻只用一个进程或者线程可以进入临界区,从而避免竞争条件或则数据不一致的问题。保证程序正常、高效的运行
2、 获取信号量的ID
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/892b008f50144161c0da6f418f833f0a.png)
创建信号量时,还会受到以下系统信息的影响:
- 1、SEMMNI:系统中信号量的总数最大值
- 2、SEMMSL:每个信号量中信号量元素的最大个数
- 3、SEMMNS:系统中所有信号量中的信号元素的总数最大值
3、对信号量进行PV操作
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6914d8612d9e3e5e974b86a6681c96cb.png)
注意:
-
1、信号量操作的结构体如下:
struct sembuf { unsigned short sem_num;//信号量元素的序号 short sem_op;//操作参数,PV操作 short sem_flag;//操作选项 }; 注意:信号量元素的序号是从0开始的,实际上就是数组下标
-
2、根据sem_op的值,信号量的操作分为三种情况:
- ①、当sem_op大于0时:进行V操作,信号量元素的值(semval)会被加上sem_op的值(这意味着V操作会增加信号量的值,从而允许其他正在等待信号量资源的进程或则线程继续执行)。如果SEM_UNDO被设置了,那么该V操作会被系统记录。V操作永远不会被进程阻塞
- ②、当sem_op等于0时:进行等零操作,如果此时semval正好为0,则semop( )立即成功返回,否则如果IPC_NOWAIT被设置,则立刻出错返回,并将error设置为EAGAIN,否则将使进程进入睡眠,直到以下情况发生:
- semval变为0
- 信号量被删除(将导致semop( )出错退出,返回EIDRM)
- 收到信号(将导致semop( )出错,返回EINTR)
- ③、当sem_op小于0时:进行P操作,信号量元素的值semval将会被减去sem_op的绝对值,如果 semval 大于或等于 sem_op 的绝对值,则 semop( )立即成功返回,semval 的值将减去 sem_op 的绝对值,并且如果 SEM_UNDO 被设置了,那么该 P 操作将会被系统记录。如果 semval 小于sem_op 的绝对值并且设置了IPC_NOWAIT,那么 semop( )将会出错返回且将错误码置为 EAGAIN否则将使得进程进入睡眠,直到以下情况发生:
- semval 的值变得大于或者等于 sem_op 的绝对值。
- 信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM)
- 收到信号。(将导致 semop( )出错退出,错误码为 EINTR)
4、获取或者设置信号量的属性
练习:jack.c和rose.c进行通信
//jack.c 用于写入信息(申请空间资源)
#include "stdio.h"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun
{
int val;//当cmd为SETVAL时使用
struct semid_ds *buf;//当cmd为IPC_STAT或IPC_SET时使用
unsigned short *array;//当cmd为GETALL或SETALL时使用
struct seminfo *_buf;//当cmd为IPC_INFO时使用
};
char* shm_Init(void)
{
//获取key值
int key = ftok("./", 'X');
//获取ID
int ID = shmget(key, 4096, IPC_CREAT | 0666);
//映射共享内存到该进程
char* shm_msg = shmat(ID, NULL, 0);
if(shm_msg == (void *)-1)
{
printf("映射失败\n");
exit(1);//直接退出程序
}
return shm_msg;
}
int sem_Init(void)
{
//获取键值
int key = ftok("./", 'V');
//获取信号量的ID(信号元素为2个)
int ID = semget(key, 2, IPC_CREAT | 0666);
//初始化信号量的内容,主要是初始化他们的资源数
//初始化没有数据,只有一个空间
union semun set;
set.val = 0;
semctl(ID, 0, SETVAL, set);//初始化数据资源为0
set.val = 1;
semctl(ID, 1, SETVAL, set);//初始化空间资源为1
//返回信号量的ID
return ID;
}
int main()
{
//申请共享内存并初始化
char* shm_map = shm_Init();
//初始化信号量
int ID = sem_Init();
// struct sembuf
// {
// unsigned short sem_num;//信号量元素序号,数组下标
// short sem_op;//操作参数
// short sem_flg;//操作选项
// };
//在写入共享内存之前需要先申请一个空间资源
//对于jack来说,需要申请的资源是空间资源
struct sembuf space = {
.sem_num = 1,//需要设置的空间资源的元素下标为1
.sem_flg = 0,//设置标记为0,啥也不选
.sem_op = -1,//(信号量操作的标志)表示资源量将减1,这是P操作,用于申请资源
};
struct sembuf data = {
.sem_num = 0,//需要设置的数据资源的元素下标为0
.sem_flg = 0,//设置标记为0,不选
.sem_op = 1,//(信号量操作的标志)表示资源量将加1,这是V操作,用于释放资源
};
/*在使用信号量来控制共享资源时,
会先执行P操作申请资源,然后在操作完成后执行V操作释放资源,
以确保共享资源在同一时刻只有一个进程或线程在访问。
*/
while(1)
{
//等待空间资源 如果资源暂时不能得到则会阻塞等待(睡眠)
printf("正在等待空间进行写入数据\n");
semop(ID, &space, 1);
printf("已经得到空间,正在写入数据\n");
printf("请输入需要发送的数据\n");
fgets(shm_map, 4096, stdin);
//设置数据资源为1
printf("设置数据为1\n");
semop(ID, &data, 1);
}
return 0;
}
在jack.c中,space结构体用于申请空间资源(P操作),释放数据资源(V操作)。这是因为它需要确保在写入共享内存之前有足够的空间资源可用,同时写入完成后释放数据资源,允许rose.c进程读取。
//rose.c 用于读取信息(申请数据资源)
#include "stdio.h"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun
{
int val;//当cmd为SETVAL时使用
struct semid_ds *buf;//当cmd为IPC_STAT或IPC_SET时使用
unsigned short *array;//当cmd为GETALL或SETALL时使用
struct seminfo *_buf;//当cmd为IPC_INFO时使用
};
// struct sembuf
// {
// unsigned short sem_num;//信号量元素序号,数组下标
// short sem_op;//操作参数
// short sem_flg;//操作选项
// };
char* shm_Init(void)
{
//获取key值
int key = ftok("./", 'X');
//获取ID
int ID = shmget(key, 4096, IPC_CREAT | 0666);
//映射共享内存到该进程
char* shm_msg = shmat(ID, NULL, 0);
if(shm_msg == (void *)-1)
{
printf("映射失败\n");
exit(1);//直接退出程序
}
return shm_msg;
}
int sem_Init(void)
{
//获取键值
int key = ftok("./", 'V');
//获取信号量的ID(信号元素为2个)
int ID = semget(key, 2, IPC_CREAT | 0666);
//初始化信号量的内容,主要是初始化他们的资源数
//初始化没有数据,只有一个空间
union semun set;
set.val = 0;
semctl(ID, 0, SETVAL, set);//初始化数据资源为0
set.val = 1;
semctl(ID, 1, SETVAL, set);//初始化空间资源为1
//返回信号量的ID
return ID;
}
int main()
{
//申请共享内存并初始化
char* shm_map = shm_Init();
//初始化信号量
int ID = sem_Init();
//在写入共享内存之前需要先申请一个空间资源
//对于rose来说,与要==释放==的资源是空间资源
struct sembuf space = {
.sem_num = 1,//需要设置的空间资源的元素下标为1
.sem_flg = 0,//设置标记为0,啥也不选
.sem_op = 1,//(表示空间资源量加1)V操作,释放空间资源
};
//对于rose来说,需要==申请==的资源为数据资源
//在写入共享内存之前需要先申请一个数据资源
struct sembuf data = {
.sem_num = 0,//需要设置的数据资源的元素下标为0
.sem_flg = 0,//设置标记为0,不选
.sem_op = -1,//(表示数据资源量减1)P操作,申请数据资源
};
while(1)
{
//等待数据资源 如果资源暂时不能得到则会阻塞等待(睡眠)
printf("正在等待数据到达\n");
semop(ID, &data, 1);
printf("数据已经到达\n");
printf("数据为:%s\n", shm_map);
//设置空间资源设置为1
printf("数据读取完毕,设置空间资源为1\n");
semop(ID, &space, 1);
}
return 0;
}
在rose.c中,data结构体用于申请数据资源(P操作),释放空间资源(V操作)。这是因为它需要确保在读取共享内存之前有可用的数据资源,同时读取完成后释放空间资源,允许jack.c进程继续写入。
注意:
- 注意:需要分清当前进程 需要申请的是什么资源。通过sem_op来设置;