linux-二元信号量和计数信号量-生产者消费者模型以及用二元信号量实现-死锁(2)-侠义消息队列(fifo)-proc文件系统

news2024/9/20 8:58:46

二元信号量和计数信号量的区别:

二元信号量和计数信号量在嵌入式系统和多任务环境中都是重要的同步机制,用于控制对共享资源的访问。它们之间的主要区别体现在以下几个方面:

1. 状态表示

  • 二元信号量(Binary Semaphore):二元信号量只有两种状态,通常用于表示某个共享资源是否被占用。当资源被占用时,二元信号量的值为0;当资源未被占用时,二元信号量的值为1。这种信号量适用于只允许一个任务或线程访问的共享资源。
  • 计数信号量(Counting Semaphore):计数信号量可以有多个状态,用于表示某个共享资源的可用数量。当资源被占用时,计数信号量的值会减少;当资源被释放时,计数信号量的值会增加。计数信号量允许多个任务或线程并发访问共享资源,但数量受到信号量初始值的限制。

2. 应用场景

  • 二元信号量:由于其简单性,二元信号量特别适用于那些只能被一个任务或线程独占访问的资源。例如,在嵌入式系统中,用于控制对硬件设备的独占访问。
  • 计数信号量:计数信号量更适用于允许多个任务或线程并发访问的资源。例如,在操作系统中,用于控制对共享内存区域、信号量集或其他类型共享资源的访问。

3. 实现复杂度

  • 二元信号量:由于只有两种状态,二元信号量的实现相对简单。在硬件层面,可以利用简单的寄存器或位操作来实现。
  • 计数信号量:计数信号量需要维护一个计数器,因此实现上相对复杂一些。在软件层面,需要额外的逻辑来处理计数器的增减和线程间的同步。

4. 同步机制

  • 两者都是通过信号量的获取(wait/take)和释放(signal/give)操作来实现对共享资源的同步访问。然而,由于计数信号量允许多个任务或线程并发访问,因此在同步机制上需要更复杂的逻辑来确保资源的正确分配和释放。

5. 示例

  • 二元信号量:假设有一个打印机资源,在任何时候只能被一个任务使用。此时可以使用二元信号量来控制对打印机的访问。当任务需要打印时,首先尝试获取二元信号量;如果成功获取(信号量值为1),则进行打印操作;打印完成后释放信号量(将信号量值设置为0)。
  • 计数信号量:假设有一个包含多个座位的会议室,允许一定数量的任务同时进入。此时可以使用计数信号量来控制对会议室的访问。信号量的初始值可以设置为会议室的座位数。当任务需要进入会议室时,首先尝试获取计数信号量;如果信号量的值大于0,则允许任务进入并减少信号量的值;任务离开会议室时释放信号量(增加信号量的值)。

PV操作对二元信号量和计数信号量的影响

PV操作是信号量机制中的两个基本操作,用于实现对共享资源的同步访问。

P操作(Wait/Semaphore Decrement)

  • 功能:P操作主要用于申请资源。当一个进程或线程需要访问某个共享资源时,它会执行P操作来尝试获取信号量。
  • 实现:在P操作中,信号量的值会减1。如果减1后的信号量值仍然大于等于0,则表示还有可用资源,进程或线程可以继续执行;如果减1后的信号量值小于0,则表示资源已被全部占用,进程或线程将被阻塞,并放入等待队列中等待资源释放。

V操作(Signal/Semaphore Increment)

  • 功能:V操作主要用于释放资源。当一个进程或线程完成了对共享资源的访问后,它会执行V操作来释放信号量。
  • 实现:在V操作中,信号量的值会加1。如果加1前的信号量值小于0,则表示有等待该资源的进程或线程被阻塞在等待队列中,此时操作系统会从等待队列中唤醒一个或多个进程或线程,让它们有机会继续执行。

应用于二元信号量和计数信号量

  • 二元信号量:在二元信号量中,由于信号量的取值只有0和1,P操作实际上是在检查资源是否可用(值为1表示可用,值为0表示不可用),而V操作则是在释放资源(将值从0变为1),从而允许等待的进程或线程继续执行。
  • 计数信号量:在计数信号量中,信号量的取值可以是任意非负整数,表示可用资源的数量。P操作会尝试减少资源数量,如果减少后资源数量仍然大于等于0,则进程或线程可以继续执行;如果小于0,则进程或线程被阻塞。V操作则会增加资源数量,并可能唤醒等待队列中的进程或线程。

生产者消费者模型:

任何并发问题都可以用二元信号量解决;

用二元信号量解决生产者消费者问题:

错误示例:

内核为单核时,PV有效,produce和space加起来等于10;

(多个生产者或多个消费者)多核情况下,p[0]>0未被信号量保护起来;

子程序可以同时进入临界区,当第一个子进程解锁后把p[0]减少1,另一个子进程立刻加锁,又把p[0]减少1;最终导致仓库出现负数;

正确实例:

把PV操作移入外面,先加锁再判断资源是否大于0;n'g

二元信号量实现生产者消费者;

#include <43func.h>
int main()
{
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    ERROR_CHECK(shmid, -1, "shmget");
    int *p = shmat(shmid, NULL, 0);
    ERROR_CHECK(p, (void *)-1, "shmat");
    // p[0]->仓库,p[1]->goods
    p[0] = 10;
    p[1] = 0;
    int semid = semget(1000, 1, IPC_CREAT | 0600);
    ERROR_CHECK(semid, -1, "semget");
    int semc_val = semctl(semid, 0, SETVAL, 1);
    ERROR_CHECK(semc_val, -1, "semctl SETVAL");
    semc_val = semctl(semid, 0, GETVAL);
    ERROR_CHECK(semc_val, -1, "semctl GETVAL")
    printf("semc_val = %d\n", semc_val);
    struct sembuf P, V;
    P.sem_num = 0;
    P.sem_op = -1;
    P.sem_flg = SEM_UNDO; // 在进程终止时把减去的资源加回,避免出现死锁
    V.sem_num = 0;
    V.sem_op = 1;
    V.sem_flg = SEM_UNDO;
    if (fork() == 0)
    {
        while (1)
        {
            semop(semid, &P, 1);
            if (p[1] > 0)
            {
                printf("before consumer space = %d,goods = %d\n", p[0], p[1]);
                --p[1];
                ++p[0];
                printf("after consumer space = %d,goods = %d\n", p[0], p[1]);
            }
            semop(semid, &V, 1);
        }
    }else if(fork() == 0){
           while (1)
        {
            semop(semid, &P, 1);
            if (p[1] > 0)
            {
                printf("before consumer space = %d,goods = %d\n", p[0], p[1]);
                --p[1];
                ++p[0];
                printf("after consumer space = %d,goods = %d\n", p[0], p[1]);
            }
            semop(semid, &V, 1);
        }
    }
    else
    {
        while (1)
        {
            semop(semid, &P, 1);
            if (p[0] > 0)
            {
                printf("before produce space = %d,goods = %d\n", p[0], p[1]);
                ++p[1];
                --p[0];
                printf("after produce space = %d,goods = %d\n", p[0], p[1]);
            }
            semop(semid, &V, 1);
            
        }
        wait(NULL);
    }

    shmdt(p);
    shmctl(shmid, IPC_RMID, NULL);
}

死锁:

(1)pipe死锁,若果一个行为需要多种资源,获取资源顺序不对导致死锁;

(2)处于加锁状态的进程异常终止:

SEM_UNDO:避免异常终止的处于加锁状态的进程出现死锁;在进程终止时,把减去的资源加回来;


侠义的消息队列(本机通信):

侠义上讲,消息队列(Message Queue)是进程间通信(IPC,Inter-Process Communication)的一种方式,特指在UNIX/Linux系统中,用于在不同进程之间传递消息的队列型数据结构。消息队列是一种先进先出的数据结构,能够确保消息的顺序性,同时允许消息按照类型进行分类和接收。

消息队列在本机中通信的函数

在UNIX/Linux系统中,消息队列的通信主要依赖于几个关键的系统调用函数,这些函数允许进程创建、发送、接收和控制消息队列。以下是这些函数的基本介绍和使用方法:

  1. msgget() - 创建或打开消息队列

    • 功能:打开或创建一个消息队列。
    • 原型int msgget(key_t key, int msgflg);
    • 参数
      • key:消息队列的键值,用于唯一标识一个消息队列。
      • msgflg:控制消息队列的创建和打开行为的标志位,如IPC_CREAT表示创建新队列,IPC_EXCLIPC_CREAT结合使用表示如果队列已存在则不创建。
    • 返回值:成功时返回消息队列的标识符(ID),失败时返回-1。
  2. msgsnd() - 发送消息到消息队列

    • 功能:向指定的消息队列发送一条消息。
    • 原型int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    • 参数
      • msqid:消息队列的标识符。
      • msgp:指向要发送的消息的指针。
      • msgsz:消息的大小(不包括消息类型字段)。
      • msgflg:控制消息发送行为的标志位。
    • 返回值:成功时返回0,失败时返回-1。
  3. msgrcv() - 从消息队列接收消息

    • 功能:从指定的消息队列接收一条消息。
    • 原型ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    • 参数
      • msqid:消息队列的标识符。
      • msgp:指向接收消息的缓冲区的指针。
      • msgsz:接收缓冲区的大小。
      • msgtyp:指定接收消息的类型,0表示接收队列中的第一条消息。
      • msgflg:控制消息接收行为的标志位。
    • 返回值:成功时返回接收到的消息的实际字节数(不包括消息类型字段),失败时返回-1。
  4. msgctl() - 控制消息队列

    • 功能:对消息队列执行各种控制操作,如删除队列、获取或设置队列的属性等。
    • 原型int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    • 参数
      • msqid:消息队列的标识符。
      • cmd:指定要执行的操作,如IPC_RMID表示删除队列。
      • buf:指向msqid_ds结构的指针,用于获取或设置队列的属性。
    • 返回值:成功时返回0,失败时返回-1。

广义的的消息队列:网络中间件;网络通信,kcfka;

IPC:只能在本机通信;可以保留消息的边界;

流式消息:管道消息没有边界;TCP

消息队列以数据包的形式发送:UDP

FIFO(先进先出):System V版本

msgget:创建消息队列;

msgsnd:发送消息

#include <43func.h>
typedef struct MsgBuf
{
    long mtype;
    char mtext[256];
} MyMsg;
int main()
{
    int msgid = msgget(1004, IPC_CREAT | 0600);
    ERROR_CHECK(msgid, -1, "msgget");
    MyMsg msg1 = {1, "hello I am msg1"};
    MyMsg msg2 = {2, "haha I am msg2"};
    MyMsg msg3 = {3, "nihao I am msg3"};
    MyMsg msg4;
    msg4.mtype = 4;
    strcpy(msg4.mtext, "msg4 msg4 msg4");
    int msg_snd1 = msgsnd(msgid, &msg1, strlen(msg1.mtext), 0);
    int msg_snd2 = msgsnd(msgid, &msg2, sizeof(MyMsg), 0);
    int msg_snd3 = msgsnd(msgid, &msg3, sizeof(MyMsg), 0);
    int msg_snd4 = msgsnd(msgid, &msg4, sizeof(MyMsg), 0);
    puts("send over!");
   // msgctl(msgid, IPC_RMID, NULL);
}

msgflg的作用

msgflgmsgsnd函数中主要用于指定发送消息时的行为,包括是否阻塞、是否立即返回等。具体来说,它可以包含以下标志位(这些标志位通常是通过位或操作组合使用的):

  • IPC_NOWAIT:如果消息队列已满,且设置了此标志位,则msgsnd调用将不会阻塞等待空间可用,而是立即返回,并设置errnoEAGAIN。这允许程序在无法立即发送消息时继续执行其他操作,而不是无限期地等待。

  • 0(或其他未指定IPC_NOWAIT的情况):如果消息队列已满,且未设置IPC_NOWAIT标志位,则msgsnd调用将阻塞,直到队列中有足够的空间来容纳待发送的消息为止。这是默认行为,确保消息能够可靠地发送到队列中,但可能会导致发送者在队列满时暂停执行。

msgrcv:接收消息;

#include <43func.h>
typedef struct MsgBuf
{
    long mtype;
    char mtext[256];
} MyMsg;
int main()
{
    int msgid = msgget(1004, IPC_CREAT | 0600);
    ERROR_CHECK(msgid, -1, "msgget");
    long type;
    printf("msg1,2,3,4\n");
    scanf("%ld",&type);
    MyMsg msg;
    memset(&msg,0,sizeof(msg));
   int ret =  msgrcv(msgid,&msg,sizeof(msg.mtext),type,0);
    ERROR_CHECK(ret,-1,"msgrcv");
    printf("%ld,msg = %s\n",type,msg.mtext);

}

IPC_NOWAIT:防止等待阻塞;

ssize_t nbytes;  
MyMsg msg;  
nbytes = msgrcv(msgid, &msg, sizeof(msg.mtext), 0, IPC_NOWAIT);  
if (nbytes == -1) {  
    if (errno == EAGAIN || errno == ENOMSG) {  
        // 没有消息可接收,且设置了 IPC_NOWAIT  
        // 处理错误或继续执行其他操作  
    } else {  
        // 处理其他可能的错误  
        perror("msgrcv");  
        // 可能需要退出或进行其他恢复操作  
    }  
}  
// 处理接收到的消息

IPC_NOWAIT 标志通常与 msgrcv(接收消息)函数一起使用。当您调用 msgrcv 并传递 IPC_NOWAIT 标志时,如果消息队列中没有符合条件的消息可供接收,msgrcv 会立即返回一个错误(通常是 EAGAIN 或 ENOMSG),而不是阻塞等待消息的到来。


proc文件系统:

伪文件系统:操作系统的运行状态在文件系统的映射;

process‘;

可以像修改文件一样,修改操作系统的属性;

cat:读

cat /proc/cpuinfo:查看cpu信息;

echo:写;

数字标识进程;

在sys目录下改操作系统设置:

net:网络

kernel:内核(消息队列,共享内存)

fs:文件

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

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

相关文章

[计算机基础]一、计算机组成原理

计算机组成原理的考察目标为&#xff1a; 1. 掌握单处理器计算机系统中主要部件的工作原理、组成结构以及相互连接方式。 2. 掌握指令集体系结构的基本知识和基本实现方法&#xff0c;对计算机硬件相关问题进行分析&#xff0c;并能够对相关部件进行设计。 3. 理解计算机系统的…

HTML5-canvas1

1、canvas&#xff1a;创建画布 <canvas id"canvas"></canvas>2、画一条直线 var canvasdocument.getElementById(cancas&#xff09;; canvas.width800; canvas.height800; var contextcanvas.getContext(2d); //获得2d绘图上下文环境 //画一条直线 c…

算法 - 图论Dijkstra(原理、思路代码实现、以东南大学真题为例讲解手算方法)

Dijkstra 算法原理&#xff1a; Dijkstra算法是一种经典的用于计算单源最短路径的算法。它可以在带权重的图中找到从源节点到所有其他节点的最短路径。Dijkstra算法通过贪心策略&#xff0c;不断选择当前已知最短路径最小的节点&#xff0c;更新其邻接节点的路径长度&#xff…

Linux之旅:常用的指令,热键和权限管理

目录 前言 1. Linux指令 &#xff08;1&#xff09; ls &#xff08;2&#xff09; pwd 和 cd &#xff08;3&#xff09;touch 和 mkdir &#xff08;4&#xff09; rmdir 和 rm &#xff08;5&#xff09;cp &#xff08;6&#xff09;mv &#xff08;7&#xff09;…

Qt窗口介绍

Qt窗口 一、Qt窗口二、菜单栏创建菜单栏在菜单栏中添加菜单创建菜单项在菜单项之间添加分割线综合练习 三、工具栏创建工具栏设置停靠位置设置浮动属性设置移动属性综合练习 四、状态栏状态栏的创建在状态栏中显示实时消息在状态栏显示永久的消息 五、浮动窗口浮动窗口的创建设…

达梦数据库系列—29. DTS迁移ORACLE到DM

目录 1.ORACLE源端信息 2.DM目的端信息 3.DTS 迁移评估 4.数据库迁移 4.1 Oracle 源端数据库准备 4.2 目的端达梦数据库准备 初始化参数设置 兼容性参数设置 表空间规划 用户规划 创建迁移用户和表空间 4.3迁移步骤 创建迁移 配置数据源 配置迁移对象及策略 开…

Vue3 --- 路由

路由就是一组key-value的对应关系&#xff1b;多个路由&#xff0c;需要经过路由器的管理。 1. 基本切换效果 安装路由器 npm i vue-router /router/index.ts // import { createRouter, createWebHistory } from vue-router import Home from /components/Home.vue import…

[Armbian] 部署Docker版Home Assistent,安装HACS并连接米家设备

title: [Armbian] 部署Docker版Home Assistent&#xff0c;安装HACS并连接米家设备 date: 2024-07-21T10:51:23Z lastmod: 2024-07-21T11:40:39Z [Armbian] 部署Docker版Home Assistent&#xff0c;安装HACS并连接米家设备 官网&#xff1a;Home Assistant (home-assistant.i…

算法——滑动窗口(day6)

1004.最大连续1的个数 ||| 1004. 最大连续1的个数 III - 力扣&#xff08;LeetCode&#xff09; 题目解析&#xff1a; 这道题如果能转化为滑动窗口的话就会很简单&#xff0c;因为我们如果尝试去把0翻转为1再计数的话等到第2轮又得重新翻转回来&#xff0c;费时费力~ 那么我…

gihub导入gitee仓库实现仓库同步

昨天在GitHub里导入了gitee仓库&#xff0c;但是在仓库同步这里卡了很久&#xff0c;因为网上大多数都是从github导入gitee&#xff0c;然后github生成token放入实现同步&#xff0c;但是我找到一种更为方便的&#xff01; 1.首先找到项目文件下的.git文件里的config文件 2.在…

linux 下将文件安装的mysql.server服务添加到systemctl中管理

1、找到mysql 的 mysql.server/ 文件位置 find / -name mysql.server 2、编辑mysql.server脚本&#xff0c;并将mysql.server脚本添加到linux服务 3、将mysql.server 复制到etc/init.d/mysqld 目录 cp /app/db/mysql/support-files/mysql.server /etc/init.d/mysqld 4、授权…

08 B端产品业务调研的流程(1)

业务调研是掌握业务情况的有效方法。一般核心流程如下图&#xff1a; 明确调研目标&#xff1a;即调研的目标&#xff0c;解决为什么要做业务调研&#xff0c;期望解决什么问题&#xff0c;达到什么样效果&#xff0c;定向。 选取调研对象&#xff1a;涉及多角色&#xff0c;一…

【吊打面试官系列-ZooKeeper面试题】zookeeper 负载均衡和 nginx 负载均衡区别?

大家好&#xff0c;我是锋哥。今天分享关于 【zookeeper 负载均衡和 nginx 负载均衡区别? 】面试题&#xff0c;希望对大家有帮助&#xff1b; zookeeper 负载均衡和 nginx 负载均衡区别? zk 的负载均衡是可以调控&#xff0c;nginx 只是能调权重&#xff0c;其他需要可控的都…

heic文件怎么转换成jpg?上百份文件转换3秒就能搞定(办公必备)

heic和jpg是两种不同的图片格式&#xff0c;平时整理图片素材时&#xff0c;如果需要将heic转为jpg格式&#xff0c;那么可以使用相关的heic图片转换工具。 ​ 为什么要将heic文件转换成jpg&#xff1f;虽然HEIC格式具有很多优点&#xff0c;但是目前并不是所有设备和应用程序…

IAR环境下STM32+IAP方案的实现

--基于STM32F103ZET6的UART通讯实现 一、什么是IAP&#xff0c;为什么要IAP IAP即为In Application Programming(在应用中编程)&#xff0c;一般情况下&#xff0c;以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了&#xff0c;如果在设备使…

【Qt】按钮的属性相关API

目录 一. QPushButton 二. QRadioButton 按钮组 三. QCheckBox Qt中按钮的继承体系如下图 QAbstractButton是一个抽象类&#xff0c;集成了按钮的核心属性和API 按钮说明QPushButton&#xff08;普通按钮&#xff09;最常见的按钮&#xff0c;用于触发操作或者事件。可以设…

【Git命令】git rebase之合并提交记录

使用场景 在本地提交了两个commit&#xff0c;但是发现根本没有没必要分为两次&#xff0c;需要想办法把两次提交合并成一个提交&#xff1b;这个时候可以使用如下命令启动交互式变基会话&#xff1a; git rebase -i HEAD~N这里 N 是你想要重新调整的最近的提交数。 如下在本地…

27.js实现鼠标拖拽

e.offsetX是鼠标距离准确事件源的左上角距离 e.clientX是鼠标距离浏览器可视窗口左上角的距离 e.pageX是鼠标距离文档左上角的距离 /* 当鼠标点击div时开始挪动&#xff0c;当鼠标抬起&#xff0c;div静止——事件源是div 当鼠标点击后,鼠标在移动——事件源…

视频汇聚,GB28181,rtsp,rtmp,sip,webrtc,视频点播等多元异构视频融合,视频通话,视频会议交互方案

现在视频汇聚&#xff0c;视频融合和视频互动&#xff0c;是视频技术的应用方向&#xff0c;目前客户一般有很多视频的业务系统&#xff0c;如已有GB28181的监控&#xff08;GB现在是国内主流&#xff0c;大量开源接入和商用方案&#xff09;&#xff0c;rtsp设备&#xff0c;音…

Spring Boot 笔记1(启动类与控制器)

采用Spring Tool Suit 4 java jdk 17 学习启动类与控制器 创建Spring Starter Project 如果https://start.spring.io链接无效&#xff0c;可以选择https://start.aliyun.com 项目结构 启动类 启动类是程序的执行入口 例子1 Demo1Application.java package com.example.dem…