信号量

news2024/9/22 7:26:17

信号量(semaphore)和信号只有一字之差,却是不同的概念,信号量与之前介绍的IPC不同,它是一个计数器,用于实现进程间的互斥于同步

本文参考:

Linux 的信号量_linux 信号量_行孤、的博客-CSDN博客

【Linux】Linux的信号量集_Yngz_Miao的博客-CSDN博客

Linux进程间通信(九)——信号量_linux 信号量_天山老妖的博客-CSDN博客

信号量函数(semget、semop、semctl)及其范例_semget函数_guoping16的博客-CSDN博客

概念

信号量本质上是一个计数器,用于协调多个进程(但不包括父子进程)对共享数据对象的读/写。它不以传输数据为目的,主要是用来保护临界资源(一次仅允许一个进程使用的资源称为临界资源,很多物理设备都属于临界资源,比如打印机,磁带机),保证临界资源在一个时刻只有一个进程独享。

信号量是描述某一种资源是否可用的变量,信号量的值表示当前可用的资源的数量,若信号量的值等于0则意味着目前没有可用的资源

信号量是一个特殊的变量,只允许进程对它进行等待信号(P)和发送信号(V)操作

  • P操作(拿):等待。如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行
  • V操作(放):发送信号。如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv

最简单的信号量是只能取0,1的信号量,这也是信号量最常见的一种形式,叫做二值信号量,而可以取多个正整数的信号量称为通用信号量。 

信号量集

就是由多个信号量组成的一个数组。作为一个整体信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定,当进程的一个操作需要多个共享资源时,如果只成功获得了其中的部分资源,那么这个请求即告失败,进程必须立即释放所有已获得资源,以防止形成死锁。

 

相关API

Linux下的信号量函数都是在通用的信号量数组(信号量集)上进行操作,而不是在一个单一的二值信号上进行操作。

semget函数

获取或创建信号量组

需要添加的库

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

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

函数参数

  • key:IPC键值
  • nsems:信号量集中信号量的个数
  • semflag:取以下值中的一个,若不取0,还要或上新建信号量集的权限
  • 0(取信号量集标识符,若不存在则函数会报错)
  • IPC_CREAT:不存在则创建信号量集
  • IPC_CREAT|IPC_EXCL:不存在则创建信号量集,若存在会报错
  • 返回值:成功返回信号量集ID,失败返回-1

semop函数

对信号量组进行操作,改变信号量的值,也就是对信号量进行P或V操作

需要添加的库

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

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

函数参数

  • semid:信号量集ID,semget的返回值
  • sops:一个结构体:
struct sembuf
{
  short sem_num;   // 信号量集的个数,单个信号量设置为0。
  short sem_op;    // 信号量在本次操作中需要改变的数据:-1-等待操作;1-发送操作。
  short sem_flg;   // 把此标志设置为SEM_UNDO,操作系统将跟踪这个信号量。如果设置为IPC_NOWAIT则会直接返回
                   // 如果当前进程退出时没有释放信号量,操作系统将释放信号量,避免资源被死锁。
};
  • nsops:操作信号量的个数,即上一个参数sops结构变量的个数(只有一个就写1)
  • 返回值:成功返回0,失败返回-1

使用举例

封装P操作:
void pGet(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = -1;
        sops.sem_flg = SEM_UNDO;

        if(semop(semid,&sops,1) == -1){
                printf("pGet error!\n");
        }else{
                printf("pGet success\n");
        }

}
封装V操作:
void vPut(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = 1;
        sops.sem_flg = SEM_UNDO;

        if(semop(semid,&sops,1) == -1){
                printf("vPut error!\n");
        }else{
                printf("vPut success\n");
        }

}

semctl函数

控制信号量(常用于设置信号量的初始值和销毁信号量)

需要添加的库

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

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

函数参数

  • semid:信号量集ID,semget的返回值
  • semnum:要操作的目标信号量,第一个就是0,第二个就是1....(只有一个就写0)
  • cmd:对目标信号量采取的命令,取以下值中的一个:

IPC_STAT

IPC_SET

IPC_RMID:销毁信号量,此时没有第四个参数

IPC_INFO

SEM_INFO

SEM_STAT

GETALL

GETNCNT

GETPID

GETVAL

GETZCNT

SETALL

SETVAL:设置信号量的初始值,此时有第四个参数

  • 第四个参数:根据cmd设置的情况决定是否存在这个参数,如果存在,那这个参数的类型就是一个名为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) */
};

使用举例

假设我要操作semid是1234的信号量集中的第一个信号量,将这个信号量的值初始化为1:

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) */
};


int main()
{
    union semun mysemun_1;
    mysemun_1.val = 1; 
    semctl(1234,0,SETVAL,mysemun);

    return 0;
}

回顾联合体的知识:和结构体不同,联合体的成员共用存储空间,所以对多个成员的赋值可能会导致覆盖,产生问题,所以此处只需要对val进行赋值,并不用为了防止野指针而对其他成员赋值

实操演示

需求:创建并初始化一个只有一个信号量的信号量集,然后fork一下,之后通过操控信号量让子进程先打印一句话,打印完才允许父进程打印另一句话

sem1.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>

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) */
};

void pGet(int semid)
{
	struct sembuf sops;
	sops.sem_num = 0;
	sops.sem_op = -1;
	sops.sem_flg = SEM_UNDO;

	if(semop(semid,&sops,1) == -1){
		printf("pGet error!\n");
	}else{
		printf("pGet success\n");
	}
	
}


void vPut(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = 1;
        sops.sem_flg = SEM_UNDO;

        if(semop(semid,&sops,1) == -1){
                printf("vPut error!\n");
        }else{
                printf("vPut success\n");
        }

}



int main()
{
	key_t key;
	key = ftok(".",2);
	int semid;
	pid_t pid;

	semid = semget(key, 1, IPC_CREAT|0666);
	if(semid == -1){
		printf("semget error\n");
		return 1;
	}else{
		printf("get semid = %d\n",semid);
	}

	union semun mysemun_1;
	mysemun_1.val = 0; //初始化为无锁的状态
	semctl(semid,0,SETVAL,mysemun_1);

	pid = fork();
	if(pid>0){
		pGet(semid); //尝试获取锁
		printf("this is father\n");
		semctl(semid,0,IPC_RMID); //销毁信号量
	}else if(pid == 0){
		printf("this is son\n");
		vPut(semid); //放入锁
	}else{
		printf("fork error\n");
		return 1;
	}

	return 0;
}

实现效果:

由于一开始初始化信号的时候将信号量设置为了0,也就是无锁状态:

而父进程首先是想拿锁,所以必然被阻塞,只有等子进程先打印一句话然后将锁放进去之后,父进程才可以拿到锁并打印自己的那句话。

且此时父进程不需要收集子进程退出状态,因为父子进程都是打了一句话就都返回退出了。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/901987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

常见的 Python 错误及其解决方案

此文整理了一些常见的 Python 错误及其解决方案。 1、SyntaxError: invalid syntax 说明&#xff1a;无效的语法是最常见的错误之一&#xff0c;通常是由于编写代码时违反了 Python 的语法规则。可能的原因&#xff1a; 忘记在 if、while、for 等语句后写冒号&#xff0c;或者…

perl下载与安装教程【工具使用】

Perl是一个高阶程式语言&#xff0c;由 Larry Wall和其他许多人所写&#xff0c;融合了许多语言的特性。它主要是由无所不在的 C语言&#xff0c;其次由 sed、awk&#xff0c;UNIX shell 和至少十数种其他的工具和语言所演化而来。Perl对 process、档案&#xff0c;和文字有很强…

GAN!生成对抗网络GAN全维度介绍与实战

目录 一、引言1.1 生成对抗网络简介1.2 应用领域概览1.3 GAN的重要性 二、理论基础2.1 生成对抗网络的工作原理2.1.1 生成器生成过程 2.1.2 判别器判别过程 2.1.3 训练过程训练代码示例 2.1.4 平衡与收敛 2.2 数学背景2.2.1 损失函数生成器损失判别器损失 2.2.2 优化方法优化代…

windows批处理set命令:设置变量,计算表达式

文章目录 基础模式表达式模式变量输入模式环境变量 基础模式 变量是一切编程的基础&#xff0c;在批处理语言中&#xff0c;通过set设置变量&#xff0c;而在调用变量时&#xff0c;需要在变量两端加上百分号&#xff0c;例如 set a1 echo %a%运行结果为 >set a1 >ech…

离市区太远的高铁站,导致旅客舍弃高铁而转用其他交通工具

随着高铁网络的完善&#xff0c;如今旅客出行越来越多选择快速、便捷的高铁作为出行交通工具&#xff0c;不过旅客显然也对一些城市的高铁站离市区太远有所抱怨&#xff0c;导致不少消费者选择舍弃高铁而转用其他交通工具。 网上有一份高铁站离市区距离的排名&#xff0c;据悉离…

Linux Xshell常用命令

一、查看服务器信息 1.1、查看CentOS服务器版本号 执行以下命令 cat /etc/redhat-release结果如下&#xff1a; 1.2、查看服务器根目录下空间占用情况 执行以下命令 cd / du -h --max-depth1 |grep G |sort参数说明&#xff1a; –max-depth1 最大深度为1 grep ‘G’ 文…

JavaScript中的this关键字的作用,以及它如何确定其值

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ this关键字的作用⭐ this的值取决于执行上下文⭐ 示例⭐ 总结⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这…

Linux常用命令——dig命令

在线Linux命令查询工具 dig 域名查询工具 补充说明 dig命令是常用的域名查询工具&#xff0c;可以用来测试域名系统工作是否正常。 语法 dig(选项)(参数)选项 <服务器地址>&#xff1a;指定进行域名解析的域名服务器&#xff1b; -b<ip地址>&#xff1a;当主…

探秘Maven神奇力量:使用systemPath加载外部JAR包

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; 探秘Maven神奇力量&#xff1a;使用systemPath加载外部JAR包 ⏱️ 创作…

攻防世界-ics-06

原题解题思路 看着页面多&#xff0c;其实只有报表中心能够跳转&#xff0c;但是选了确定后没反应&#xff0c;应该不是注入&#xff0c;只有id会变化。 在burp中设置好负载进行爆破 有一个长度与众不同的包 打开发现flag。

攻防世界-simple_php

原题 解题思路 flag被分成了两个部分&#xff1a;flag2&#xff0c;flag2。获得flag1需要满足变量a0且变量a≠0&#xff0c;这看起来不能实现&#xff0c;但实际上当变量a的值是字符时&#xff0c;与数字比较会发生强制类型转换&#xff0c;所以a为字符型数据即可&#xff0c;变…

STP生成树总结

一、什么是STP&#xff08;802.1D&#xff09; STP协议生来就是为了冗余而存在的&#xff0c;单纯树型的网络无法提供足够的可靠性&#xff0c;由此我们引入了额外的链路&#xff0c;这才出现了环路这样的问题。但单纯是标准的802.1D STP协议并不能实现真正的冗余与负载…

Threejs学习05——球缓冲几何体背景贴图和环境贴图

实现随机多个三角形随机位置随机颜色展示效果 这是一个非常简单基础的threejs的学习应用&#xff01;本节主要学习的是球面缓冲几何体的贴图部分&#xff0c;这里有环境贴图以及背景贴图&#xff0c;这样可以有一种身临其境的效果&#xff01;这里环境贴图用的是一个.hdr的文件…

openGauss学习笔记-45 openGauss 高级数据管理-物化视图

文章目录 openGauss学习笔记-45 openGauss 高级数据管理-物化视图45.1 全量物化视图45.1.1 全量物化视图语法格式45.1.2 全量物化视图参数说明45.1.3 全量物化视图示例 45.2 增量物化视图45.2.1 增量物化视图语法格式45.2.2 增量物化视图参数说明45.2.3 增量物化视图示例 openG…

Rest学习环境搭建:服务消费者

建一个子模块 导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache…

浅析Linux SCSI子系统:调试方法

文章目录 SCSI日志调试功能scsi_logging_level调整SCSI日志等级 SCSI trace events使能SCSI trace events方式一&#xff1a;通过set_event接口方式二&#xff1a;通过enable 跟踪trace信息 相关参考 SCSI日志调试功能 SCSI子系统支持内核选项CONFIG_SCSI_LOGGING配置日志调试…

什么是原型(prototype)和原型链(prototype chain)?如何继承一个对象的属性和方法?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 原型&#xff08;Prototype&#xff09;和原型链&#xff08;Prototype Chain&#xff09;⭐ 原型&#xff08;Prototype&#xff09;⭐ 原型链&#xff08;Prototype Chain&#xff09;⭐ 继承属性和方法⭐ 写在最后 ⭐ 专栏简介 前端入…

15---脚注(footnote)

本节我们来学习markdown的脚注 一、脚注的基本使用&#xff1a; 当使用Markdown编写文档时&#xff0c;有时我们希望在文本中插入一些额外的注释或解释&#xff0c;这时可以使用脚注&#xff08;footnote&#xff09;来实现。脚注是一种注释形式&#xff0c;通常以数字或符号的…

STM32——RTC实时时钟

文章目录 Unix时间戳UTC/GMT 时间戳转换BKP简介BKP基本结构读写BKP备份寄存器电路设计关键代码 RTC简介RTC框图RTC基本结构硬件电路RTC操作注意事项读写实时时钟电路设计关键代码 Unix时间戳 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日…

如何创建和查看软链接和硬链接?这二者的区别是什么?

索引节点&#xff08;inode&#xff09;硬链接创建硬链接查看硬链接 软链接创建软链接查看软链接 inode编号妙用总结软链接和硬链接的区别感谢 &#x1f496; hello大家好&#x1f60a; 在linux中&#xff0c;文件链接可以使多个文件名引用同一个文件。有两种方式可以创建指向同…