> 文档中心 > 【Linux】IPC通信——信号量的使用

【Linux】IPC通信——信号量的使用


目录

👉一、前言

👉二、认识信号量

1、信号量的概念

2、信号量的作用

👉三、信号量相关函数

1、semget()函数

2、semctl()函数

3、semop()函数

👉四、主函数测试

1、工程一

2、工程二

3、先执行工程一立马执行工程二

🔔Tip


👉一、前言

        回顾一下关于进程间的通信(IPC,InterProcess Communication)我们学习了哪些

  • 1️⃣信号 :信号的使用
  • 2️⃣管道:管道的使用
  • 3️⃣共享内存:共享内存的使用
  • 4️⃣消息队列:消息队列的使用
  • 5️⃣套接字(socket):socket的使用

        今天来学习一个新的IPC通信: 信号量 


 

👉二、认识信号量

1、信号量的概念

  • 信号量是一种变量,它只能取正整数值,对这些正整数只能进行两种操作:等待信号
  • 最简单的信号量是一个只能取“0”和“1”值的变量,也就是人们常说的“二进制信号量

等待:P(semaphore variable)

        如果信号量为1,则进行 -1 操作,若为 0 ,则挂起该进程。

信号:V(semaphore variable)

        如果有其他进程因等待信号量而被挂起,就让它恢复执行;如果没有,则进行 +1 操作。

  • 假设进程一和进程二共享同一个信号量,有如下两个场景

场景一

        初始sv=1,进程一先访问信号量,进行P操作后sv=0,此时进程二也访问了信号量并想要执行P操作,但是由于此时sv=0,所以进程二只能先挂起,直至进程一执行完业务,进行V操作后,即sv+1=1后,进程二恢复执行

 

场景二

        进程一业务做完,执行V操作,sv+1=1,进程二恢复执行P操作,sv-1=0。


2、信号量的作用

  • 信号量可以解决进程同步问题

        看完上述两个场景,可以发现信号量被一个进程占用的时候,另一个进程无法访问只能暂时挂起,是不是有点类似于线程锁?

        因此我们可以发现信号量可以解决进程的同步问题,即当两个进程访问同一块内存区域时(假设同时访问同一个共享内存),如果两个进程同时操作里面的数据时,这时候就会出现数据不安全的问题,使用信号量可以让一个进程先执行,另一个挂起,直至第一个进程执行完业务,第二个进程再执行业务。

 

下面我们来学习一下信号量的相关函数


 

👉三、信号量相关函数

1、semget()函数

  • 函数功能:创建一个新的信号量或者取得一个现有信号量的键字

头文件👇

#include
#include
#include

函数原型👇

int semget(key_t key, int nsems, int semflg);

参数👇

key:一个整数值,不相关的进程将通过这个值去访问同一个信号量。

nsems:需要使用的信号量个数,一般为1

semflg:权限位,类似于文件的权限位,一般为(IPC_CREAT | 0777)。

返回值👇

✔️成功:返回一个正数值 ,即 semid

❌失败:returns -1

  • 代码示例
int sem_create(key_t key, int nsems){int res = semget(key, nsems, IPC_CREAT | 0777);if (res < 0){perror("semget error");}return res;}

2、semctl()函数

  • 函数功能:允许我们直接控制信号量的信息

头文件👇

#include
#include
#include

函数原型👇

int semctl(int semid, int semnum, int cmd, ...);

参数👇

semid:semget () 函数的返回值。

semnum:信号量的编号,一般取值为 0,表示这是第一个也是唯一的信号量。

cmd:将要采取的操作动作。

...:如果还有第四个参数,那它将是一个“union semun”复合结构

返回值👇

✔️成功:returns 0

❌失败:returns -1

  • 补充
  • semctl函数里的command可以有许多不同的值,下面这两个是比较常用的:

        SETVAL:用来把信号量初始化为一个已知的值,这个值在semun结构里是以val成员的面目传递的。

        IPC_RMID:删除一个已经没有人继续使用的信号量标识码

  • union semun复合结构如下

union semun

{
    int              val;    /* Value for SETVAL */ //设置具体值
    struct semid_ds* buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short* array;  /* Array for GETALL, SETALL */
    struct seminfo* __buf;  /* Buffer for IPC_INFO  (Linux-specific) */
};

一般通过设置val的值,来设置信号量具体值。

  • 代码示例
union semun {intval;    /* Value for SETVAL */ //设置具体值struct semid_ds* buf;    /* Buffer for IPC_STAT, IPC_SET */unsigned short* array;  /* Array for GETALL, SETALL */struct seminfo* __buf;  /* Buffer for IPC_INFO  (Linux-specific) */};//信号量设置值int sem_setval(int semid, int senindex, int val){//senindex:信号量的编号,​​​​​​​一般取值为 0union semun arg;arg.val = val;//设置具体值int res = semctl(semid, senindex, SETVAL, arg);if (res < 0){perror("semctl error");}return res;}

3、semop()函数

  • 函数功能:改变信号量的键值

头文件👇

#include
#include
#include

函数原型👇

int semop(int semid, struct sembuf *sops, size_t nsops);

参数👇

semid:semget () 函数的返回值。

sops:是个指向一个结构体 struct sembuf 的指针。

nsops:一般为1。

返回值👇

✔️成功:returns 0

❌失败:returns -1

  • struct sembuf 结构体如下

struct sembuf

{         

         //信号量的编号,一般为0

        short sem_num; 

        //一般只会用到两个值,P操作 -1,V操作 1

        short sem_op; 

        //sem_flg通常被设置为SEM_UNDO

        short sem_flg;

};

  • 代码示例
//信号量p操作 -1int sem_p(int semid, int semindex){//senindex:信号量的编号,​​​​​​​一般取值为 0struct sembuf buf = { semindex, -1, SEM_UNDO };int res = semop(semid, &buf, 1);if (res < 0){perror("semop error");}return res;}//信号量v操作 +1int sem_v(int semid, int semindex){struct sembuf buf = { semindex, 1, SEM_UNDO };int res = semop(semid, &buf, 1);if (res < 0){perror("semop error");}return res;}

👉四、主函数测试

  • 创建两个工程,访问同一个信号量,并执行P、V操作。
  • 调用的函数为上面示例代码所封装的

1、工程一

  • 工程一为创建信号量,并赋初值为1
int main(){//如果1001信号量存在则访问 不存在则创建int semid = sem_create((key_t)1001, 1);//将信号量数组下标为0的数据设置为1sem_setval(semid, 0, 1);//加锁 信号量数组下标为0 执行P操作后信号量的值为0sem_p(semid, 0);for (int i = 0; i < 5; i++){cout << "第一个进程正在运行…… i = " << i << endl;sleep(1);}//解锁,执行V操作后 信号量的值为1sem_v(semid, 0);return 0;}

2、工程二

  • 工程二为访问上一个工程创建的信号量,不需要再赋初值。
int main(){//如果1001信号量存在则访问 不存在则创建int semid = sem_create((key_t)1001, 1);//加锁 -1sem_p(semid, 0);//阻塞无法继续执行  只有上一个工程结束后才执行for (int i = 0; i < 5; i++){cout << "第二个进程正在运行…… i = " << i << endl;sleep(1);}//解锁 +1sem_v(semid, 0);return 0;}

3、先执行工程一立马执行工程二

  • 测试效果如下

  • 可以发现执行第一个工程后,立马执行第二个工程(此时第二个工程被挂起),只有当第一个工程结束,第二个工程才恢复执行。
  • 类似于线程“加锁”,解决进程同步问题

 

🔔Tip

在linux终端

  • 输入ipcs, 可以查看创建的信号量

  • 输入 ipcrm -a,可以删除创建的信号量

 

 

😘The end ……🔚

原创不易,转载请标明出处。

对您有帮助的话可以一键三连,会持续更新的(嘻嘻)。