站在巨人的肩膀上——Linux信号量操作

2023-05-16

感谢那些让我度过此学海的无名勇士


信号量简介:

在对于临界区资源管理的过程中,多个程序同时访问一个共享资源经常容易引发一系列问题:如死锁,结果不唯一等等,

在1965年,由荷兰科学家E.W.Dijkstra提出了一种新的进程同步工具,信号量及其PV操作。

对于信号量的定义是这样的:

                让多个进程通过特殊变量展开交互,一个进程在某一个关键点上被迫停止执行直至接收到对应的特殊变量值,通过这一措施,任何复杂的进程交互要求均可得到满足,这种特殊的变量就是信号量。

信号量的种类分为以下2种:

一般信号量:

               设s为一个记录型数据结构,其中value为整型变量,系统初始化时为其赋值,PV操作的原语描述如下:

               P(s):将信号量value值减1,若结果小于0,则执行P操作的进程被阻塞,若结果大于等于0,则执行P操作的进程将继续执行。

               V(s):将信号量的值加1,若结果不大于0,则执行V操作的进程从信号量s有关的list所知队列中释放一个进程,使其转化为就绪态,自己则继续执行,若结果大于0,则执行V操作的进程继续执行。

二值信号量:

              设s为一个记录型数据结构,其中分量value仅能取值0或1,二值信号量的PV操作的原语描述和一般信号量相同,虽然二值信号量仅能取值0或1,但可以证明他有着与其他信号量相同的表达能力

            

当然以上都是创造者所给出的定义,对于实现方式在不同的平台下接口会有不同,以下的实现部分均是建立在Linux平台下使用C语言编码实现,主要介绍的也是对应的C接口

上面那些定义总结起来可以这样说:通过使用信号量生成令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的一种手段。


函数操作:

对于与信号量操作有关的接口,Linux下主要提供了以下几个函数,值得注意的是,在Linux下的C接口中,这些函数的操作对象都是信号量值组,也就是一个信号量值的链表


int semget(key_t key, int num_sems, int sem_flags);

该函数的作用是创建一个新信号量或取得一个已有信号量。

第一个参数key是整数值(唯一非零),就是Linux线程操作中经常用到的键值,可以通过ftok函数得到,不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1



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

该函数的作用是改变信号量的值,其实就是为了信号量的PV操作而准备的,这个函数可以讲的地方比较多,下面会详细介绍:

函数的第一个参数 semid 为信号量集的标识符;

第2个参数 sops 指向进行操作的结构体数组的首地址,在 semop 的第二个参数 sops 指向的结构体数组中,每个 sembuf 结构体对应一个特定信号的操作。因此对信号量进行操作必须熟悉该数据结构,该结构定义在 linux/sem.h,如下所示:

struct sembuf{
unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号
short sem_op; //操作类型
short sem_flg; //操作标志
};

struct sembuf{
unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号
short sem_op; //操作类型
short sem_flg; //操作标志
};

对于该结构中各个成员都具有特殊的含义,具体含义的介绍如下:


sem_op 参数:

sem_op > 0
信号加上 sem_op 的值,表示进程释放控制的资源;

sem_op = 0 如果sem_flg没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为0;否则进程不会睡眠,直接返回 EAGAIN

sem_op < 0 信号加上 sem_op 的值。若没有设置 IPC_NOWAIT ,则调用进程阻
塞,直到资源可用;否则进程直接返回EAGAIN

sem_flg 参数:


该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,当该进程终止时它将会自动撤消。

第3个参数 nsops 指出将要进行操作的信号的个数。semop 函数调用成功返回 0,失败返回 -1。

该函数所做的对于信号量的操作都是原子操作,即整个行为是一个整体,是不可打断的。所有操作是否可以立即执行取决于在个人sem_flg领域的IPC_NOWAIT标志的存在。


int semctl(int sem_id, int sem_num, int command, ...);

函数的第一个参数 semid 为信号量集的标识符;

函数的第二个参数sem_num则是表示即将要进行操作的信号量的编号,即信号量集合的索引值,其中第一个信号量的索引值为0。

函数的第3个参数command代表将要在集合上执行的命令,其取值含义如下,通常用特定的宏代替:

IPC_STAT:获取某个信号量集合的semid_ds结构,并将其储存在semun联合体的buf参数所指的地址之中

IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值,该命令所取的值是从semun联合体的buf参数中取到的

IPC_RMID:从内核删除该信号量集合

GETALL:用于获取集合中所有信号量的值,整数值存放在无符号短整数的一个数组中,该数组有联合体的array成员所指定

GETNCNT:返回当前正在等待资源的进程的数目

GETPID:返回最后一次执行PV操作(semop函数调用)的进程的PID

GETVAL:返回集合中某个信号量的值

GETZCNT:返回正在等待资源利用率达到百分之百的进程的数目

SETALL:把集合中所有信号量的值,设置为联合体的array成员所包含的对应值

SETVAL:将集合中单个信号量的值设置为联合体的val成员的值

其中semun联合体的结构如下:

union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *array;  
    struct seminfo *__buf;
}; 

对于该函数,只有当command取某些特定的值的时候,才会使用到第4个参数,第4个参数它通常是一个union semum结构,定义如下:

union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  
对于第4个参数arg,

当执行SETVAL命令时用到这个成员,他用于指定要把信号量设置成什么值,涉及成员:val

在命令IPC_STAT/IPC_SET中使用,它代表内核中所使用内部信号量数据结构的一个复制 ,涉及成员:buf

在命令GETALL/SETALL命令中使用时,他代表指向整数值一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,涉及成员:array

剩下的还有一些用法都将在系统内核中的信号量代码使用,应用程序开发中使用很少,这里也就不介绍了。


实现样例:

这里列举一个别人的样例,主要是为了展示信号量控制进程的操作代码如下:

#include<iostream>
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <sys/sem.h>
using namespace std;

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
};

static int sem_id = 0;  
  
static int set_semvalue();  
static void del_semvalue();  
static int semaphore_p();  
static int semaphore_v();  
  
int main(int argc, char *argv[])  
{  
    char message = 'S';  
    int i = 0;  

    

    //创建信号量  
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);  
  
    if(argc > 1)  
    {  
        
        //程序第一次被调用,初始化信号量  
        if(!set_semvalue())  
        {  
            fprintf(stderr, "Failed to initialize semaphore\n");  
            exit(EXIT_FAILURE);  
        }  
        //设置要输出到屏幕中的信息,即其参数的第一个字符  
        message = argv[1][0];  
        sleep(2);  
    }  
    cout<<argc<<message<<endl;

    for(i = 0; i < 10; ++i)  
    {  
        //进入临界区  
        if(!semaphore_p())  
            exit(EXIT_FAILURE);  
        //向屏幕中输出数据  
        printf("进入%c", message);  
        //清理缓冲区,然后休眠随机时间  
        fflush(stdout);  
        sleep(rand() % 3);  
        //离开临界区前再一次向屏幕输出数据  
        printf("离开%c\n", message);  
        fflush(stdout);  
        //离开临界区,休眠随机时间后继续循环  
        if(!semaphore_v())  
            exit(EXIT_FAILURE);  
        sleep(rand() % 2);  
    }  
  
    sleep(10);  
    printf("\n%d - finished\n", getpid());  
  
    if(argc > 1)  
    {  
        //如果程序是第一次被调用,则在退出前删除信号量  
        sleep(3);  
        del_semvalue();  
    }  
    exit(EXIT_SUCCESS);  
}  
  
static int set_semvalue()  
{  
    //用于初始化信号量,在使用信号量前必须这样做  
    union semun sem_union;  
  
    sem_union.val = 1;  
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)  
        return 0;  
    return 1;  
}  
  
static void del_semvalue()  
{  
    //删除信号量  
    union semun sem_union;  
  
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)  
        fprintf(stderr, "Failed to delete semaphore\n"); 
    else
        fprintf(stdout, "已经删除信号量\n"); 

}  
  
static int semaphore_p()  
{  
    //对信号量做减1操作,即等待P(sv)  
    struct sembuf sem_b;  
    sem_b.sem_num = 0;  
    sem_b.sem_op = -1;//P()  
    sem_b.sem_flg = SEM_UNDO;  
    if(semop(sem_id, &sem_b, 1) == -1)  
    {  
        fprintf(stderr, "semaphore_p failed\n");  
        return 0;  
    }  
    return 1;  
}  
  
static int semaphore_v()  
{  
    //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)  
    struct sembuf sem_b;  
    sem_b.sem_num = 0;  
    sem_b.sem_op = 1;//V()  
    sem_b.sem_flg = SEM_UNDO;  
    if(semop(sem_id, &sem_b, 1) == -1)  
    {  
        fprintf(stderr, "semaphore_v failed\n");  
        return 0;  
    }  
    return 1;  
}  
编译及运行指令为:

g++ -o 可执行文件名 代码文件名   //编译
./可执行文件名 1 2 & ./可执行文件名   //运行
运行效果图如下:


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

站在巨人的肩膀上——Linux信号量操作 的相关文章

  • PX4之启动脚本

    PX4通过rcS脚本来设定需要启动的程序 xff0c 比如设备驱动 控制模块 数据通讯等 rcS脚本在项目中的文件位置 ROMFS px4fmu common rcS 对应硬件平台固件上的位置 etc init d rcS 启动脚本流程如下
  • pip 使用阿里源

    pip 使用阿里源 使用pip install 的时候默认会去国外服务器下载 所以经常断开或者速度很慢 只需要在原来的命令后加上 i https mirrors aliyun com pypi simple即可直接从阿里源上安装 pip s
  • npm安装vue报错:npm ERR! code ETIMEDOUT

    npm安装vue报错 信息如下 C span class token punctuation span Users span class token punctuation span Q span class token operator
  • 将element-plus分页组件由默认英文,改为中文

    1 现象 分页组件默认显示为英文 但实际页面中大多都是中文 弄个英文显得比较突兀 2 配置 在main js中添加以下两句语句 span class token function import span locale from span c
  • Nginx第三方模块nginx_upstream_check_module实现http检测

    1 故障现象 架构如下 当时servera的容器oom了 开始不停重启 造成80端口是活着的 但没法正常提供http服务 原先的配置如下 upstream aaa span class token punctuation span serv
  • 【云原生 | Kubernetes 系列】---Prometheus监控mysql

    1 Prometheus监控mysql 对mysql的版本要求 MySQL gt 61 5 6 MariaDB gt 61 10 3 1 1 测试环境准备 span class token comment apt install maria
  • [Gitops--2]Argocd和Gitlab-runner安装配置

    ArgoCd Argo是一组k8s原生工具集 用于运行和管理k8s上的作业和应用程序 Argo提供了一种在k8s上创建工作和应用的三种计算模式 服务模式 工作流模式和基于事件模式 所有的Argo工具都实现为了创建控制器和自定义资源 为什么选
  • Windows update 0x8024401c 0x80244019

    Windows 更新失败 报错 0x8024401c 0x80244019 以系统管理员身份运行 net stop wuauserv reg delete f HKEY LOCAL MACHINE span class token punc
  • K8s常见面试题20问

    K8s常见面试题19问 收集了一些K8s常见问题和同学们面试常被问到的问题 如果有新的面试题私聊或者留言给我 1 Docker和虚拟机有那些不同 虚拟化环境下每个 VM 是一台完整的计算机 xff0c 在虚拟化硬件之上运行所有组件 xff0
  • Dockerfile常用命令

    Dockerfile常用命令 1 Dockerfile Dockerfile是一个文本文件 用一组指令来完成镜像的构建 每一条指令构建一层镜像 所有尽量将相同的命令合并成一行以减少中间镜像的层数 2 From 必须 指定基础镜像即我从哪里可
  • Kubesphere流水线实现蓝绿发布

    Kubesphere流水线实现蓝绿发布 1 Gitlab仓库准备 1 1 创建仓库 新建空白项目 名字随便取 greenweb 复制克隆地址 http 192 168 31 199 deploy greenweb git 1 2 初始化并上
  • PX4之代码结构

    PX4开源飞控是目前主流的开源飞控项目 xff0c 被很多公司作为飞控开发的参考 也广泛被用于现在流行的evtol验证机的飞控 xff0c 进行初步的飞行验证 可能大多数AAM以及UAM都离不开PX4 项目代码可以从github下载 git
  • docker更改默认仓库地址

    docker更改默认仓库地址 复制代码 zq 64 ubuntu docker pull h Flag shorthand h has been deprecated please use help Usage docker pull OP
  • 关于Proteus仿真stm32时出现电源与地对接错误的解决方案

    最近开始了stm32的学习 xff0c 但发现在Proteus仿真时总是出现 PROTEUS显示 电路图如下 xff1a 经过一段时间的研究 xff0c 除了之前的要将Design gt Configure Power Rails中的VSS
  • 【ssh】ssh密钥配置无效(如git@github.com: Permission denied (publickey).)

    使用ssh keygen创建密钥后 xff0c 默认会将公司钥保存至 ssh目录下 xff0c 文件名分别为id rsa和id rsa pub xff08 如果你使用的是rsa加密 xff09 这种情况下 xff0c 一般不需要配置其他内容
  • 【Python】NameError: name ‘self‘ is not defined

    NameError name 39 self 39 is not defined 最近开发Python包 xff0c 遇到一个 NameError name self is not defined 问题 在执行 span class tok
  • C/C++关于strcpy、strcat函数使用

    char p1 15 61 34 abcd 34 p2 61 34 ABCD 34 str 50 61 34 xyz 34 strcpy str 43 2 strcat p1 43 2 p2 43 1 printf 34 s 34 str
  • intel RealSense摄像头比较

    1 D415 xff0c D435和D435i xff08 1 xff09 总体对比 xff08 2 xff09 具体对比 相机细节 三个深度相机大小相同 xff08 在毫米内 xff09 xff0c 它们都使用相同的视觉处理器通过USB
  • shell中的#!/bin/bash

    bin bash是指此脚本使用 bin bash来解释执行 其中 xff0c 是一个特殊的表示符 xff0c 其后 xff0c 跟着解释此脚本的shell路径
  • 嵌入式控制器EC是如何运行起来的

    EC的作用在本文中就不谈了 xff0c 百度里面可以找到很多 xff0c 反正像笔记本这样的移动设备 xff0c 都需要EC来做相关的控制 xff0c 具体可以自己去百度 这里主要介绍EC是如何运行起来的 xff0c 其实和CPU是如何运行

随机推荐

  • 如何在C/C++中利用变量来创建变化长度的数组

    在C C 43 43 中可以直接使用 xff1a int a n 创建长度为n的整型数组 xff0c 这种定义数组的方法需要事先确定好数组的长度 xff0c 即 n 必须为常量 xff0c 这意味着 xff0c 如果在实际应用中无法确定数组
  • Host是如何与EC通信的(BIOS通过ACPI协议对EC RAM进行读写)

    文章目录 一 EC RAM是什么 二 使用步骤 1 高级配置和电源接口 ACPI 规范 2 EC RAM读写过程 总结 一 EC RAM是什么 EC提供256字节的可被系统读写的RAM空间 EC的资源 包括电池信息 EC版本等信息 在该RA
  • PX4之飞行控制框架

    PX4的飞行控制程序通过模块来实现 xff0c 与飞控相关的模块主要有commander xff0c navigator xff0c pos control xff0c att control这几个 xff0c 分别可以在src modul
  • 使用树莓派学习Linux驱动开发-02 面向对象/分层/分离驱动设计思想编写LED驱动程序

    系列文章目录 此博客内容根据韦东山嵌入式Linux驱动开发课程书写而来 将课程中用到的代码移植到树莓派4B板子 文章目录 系列文章目录 前言 一 驱动设计思想 分离 二 示例代码 三 操作步骤如下 前言 在上一篇内容中 书写了一个设备驱动程
  • 0.嵌入式控制器EC实战 Embedded Controller开发概述

    文章目录 1 嵌入式控制器EC概述2 EC芯片框图 xff08 IT8502为例 xff09 3 General Purpose I O Port GPIO 4 SMBus xff08 System Management Bus xff0c
  • 12.嵌入式控制器EC实战 SMBus概述

    文章目录 SMBus概述 ACPI规范中的函数返回值含义 SMBus h中的宏定义含义 EC中SMBus各个读写函数分析 bRWSMBus读写函数 在嵌入式控制器EC中 SMBus的起到的作用有两个 第一个是通过SMBus读取智能电池中的相
  • Win10下开机自动启动运行bat脚本并打开cmd运行命令

    场景 xff1a 本菜鸟有一台工作站安装了windows10操作系统 xff0c 机器设置的是开机自动启动 xff0c 但是维护人员无法一直在机房 xff0c 一旦机房断电重启就会导致工作站中运行的程序无法重新启动 xff0c 需要维护人员
  • 银河麒麟V10系统 syslog和kern.log文件过大问题解决,定时清理日志文件

    文章目录 1 新建clear log sh脚本文件 2 设置cron任务 3 解决不执行的方法 需求 在使用银河麒麟V10系统时 var log kern log 和 var log syslog两个文件随着使用的时间增长会一直增大 最后可
  • 2.龙芯2k1000 linux3.10内核编译过程

    龙芯2k1000 linux3 10内核编译过程 文章目录 龙芯2k1000 linux3 10内核编译过程 xff08 一 xff09 在Ubuntu环境下载并配置交叉编译链 xff08 二 xff09 下载linux3 10内核源码 x
  • Vulkan实战之逻辑设备和队列

    文章目录 介绍指定要创建的队列指定使用的设备特性创建逻辑设备检索队列句柄最终代码 介绍 在选择要使用的物理设备之后 xff0c 我们需要设置一个逻辑设备来与它接口 逻辑设备创建过程类似于实例创建过程 xff0c 并描述了我们想要使用的特性
  • Vulkan实战之Window surface

    文章目录 创建window surface查询演示支持创建surface队列最终代码 由于Vulkan是一个平台无关的API xff0c 因此它不能自己直接与窗口系统接口交互 为了在Vulkan和窗口系统之间建立连接并将结果显示到屏幕上 x
  • 飞腾UEFI配置GPIO—飞腾FT2000/4 (D2000/8) GPIO引脚配置及使用

    文章目录 一 FT2000 4 GPIO介绍 GPIO 接口信号说明 专用GPIO GPIO0 A1 GPIO0 A7 SCI 介绍 二 UEFI下配置GPIO相关寄存器 GPIO相关相关引脚功能配置说明 UEFI配置GPIO相关代码 UE
  • stm32-sbus数据接收,并通过CAN转发给车辆控制

    1 xff09 串口程序 代码如下 xff1a span class token macro property span class token directive hash span span class token directive
  • 基于stm32的无线多点温度采集系统设计

    本科时候做过关于ds18b20温度传感器的课程设计 xff0c 当时好像是先用单片机A采集温度 xff0c 其中用矩阵键盘设置报警值 xff0c 然后通过232串口将温度值传给单片机B xff0c 单片机B上的数码管显示 xff0c 同时单
  • 无刷电机和桨叶的选择

    无刷电机和桨叶的选择 无刷电机的kv值越高 就要配越小的螺旋桨 简单说 高kv配小桨 低kv配大桨 无刷电机KV值定义为 转速 V xff0c 意思为输入电压增加1伏特 xff0c 无刷电机空转转速增加的转速值 由此无刷电机电压的输入与电机
  • Intel RealSense Win10+QT+Cmaker 开发环境搭建

    文章目录 一 Intel RealSense SDK开发工具下载安装二 QT 43 CMaker配置Intel RealSense SDK2 0开发环境 一 Intel RealSense SDK开发工具下载安装 从官方github上下载S
  • NVIDIA Jetson不同系列对比

    文章目录 一 NVIDIA Jetson介绍二 NVIDIA Jetson模组比较 一 NVIDIA Jetson介绍 NVIDIA Jetson 是世界领先的平台 xff0c 适用于自主机器和其他嵌入式应用程序 该平台包括 Jetson
  • SLAM笔记五——EKF-SLAM

    上一节主要讲解了EKF的基本原理 xff0c 这一次主要关注如何将EKF算法应用在SLAM上 EKF SLAM 现在的问题就是解决下面这个概率分布的估计问题 xff1a 阴影部分为未知 这里我们需要确定均值和方差到底是什么 xff1f 假设
  • HTTP协议-报文解析

    概述 HTTP xff08 超文本传输协议 xff09 是一个基于请求与响应模式的 无状态的 应用层的协议 xff0c 常基于TCP的连接方式 HTTP消息由客户端到服务器的请求和服务器到客户端的响应组成 请求消息和响应消息的组成 xff1
  • 站在巨人的肩膀上——Linux信号量操作

    感谢那些让我度过此学海的无名勇士 信号量简介 xff1a 在对于临界区资源管理的过程中 xff0c 多个程序同时访问一个共享资源经常容易引发一系列问题 xff1a 如死锁 xff0c 结果不唯一等等 xff0c 在1965年 xff0c 由