【Linux-Day10-信号量,共享内存,消息队列】

news2024/10/6 22:33:58

信号量

信号量描述

信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源 时,需要对信号量的值进行原子减一,该操作被称为 P 操作。

当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。

释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。

信号量主要用来同步进程。

信号量的值如果只取 0,1,将其称为二值信号量。

如果信 号量的值大于 1,则称之为计数信号量。

**临界资源:同一时刻,只允许被一个进程或线程访问的资源 **

**临界区:访问临界资源的代码段 **

信号量使用

semget(); 创建或者获取已存在的信号量

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

semget()成功返回信号量的 ID, 失败返回-1

key:两个进程使用相同的 key 值,就可以使用同一个信号量

nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号 量的个数

semflg 可选: IPC_CREAT IPC_EXCL

semop()对信号量进行改变,做 P 操作或者 V 操作

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

semop()成功返回 0,失败返回-1

struct sembuf

{

unsigned short sem_num; //指定信号量集中的信号量下标

short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作

short sem_flg; //SEM_UNDO

};

semctl()控制信号量

int semctl( int semid, int semnum, int cmd, …);
semctl()成功返回 0,失败返回-1

semid:信号量的ID

cmd 选项: SETVAL IPC_RMID

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

封装一个c文件实现创建一个信号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>

union semun
{
    int val;
};
static int semid = -1;
void sem_init()
{
    semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建信号量,如果存在就失败
    if ( semid == -1 )//失败,表示该(key_t)1234)信号已存在
    {
        semid = semget((key_t)1234,1,0600);//获取已存在的信号量id
        if ( semid == -1)
        {
            printf("semget err\n");
        }
    }
    else//全新创建成功,那么要进行初始化
    {
        union semun a;
        a.val = 1;//信号量的初始值
        if ( semctl(semid,0,SETVAL,a) == -1)//设置初始值
        {
            printf("semctl err\n");
        }
    }
}
void sem_p()
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = -1; //p操作
    buf.sem_flg = SEM_UNDO;

    if ( semop(semid,&buf,1) == -1)
    {
        printf("semop p err\n");
    }

}
void sem_v()
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = 1; //v操作
    buf.sem_flg = SEM_UNDO;

    if ( semop(semid,&buf,1) == -1)
    {
        printf("semop v err\n");
    }
}
void sem_destroy()
{
    if ( semctl(semid,0,IPC_RMID) == -1)
    {
        printf("semctl destroy err\n");
    }

}

假设资源只有一份,每轮a进程使用2次 ,b进程使用3次,如何解决。

我们可以使用信号量解决临界资源问题

a进程代码

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "sem.c"
int main()
{
    sem_init();//
    for(int i = 0; i < 5; i++)
    {
        //p
         sem_p();
        printf("a");
        fflush(stdout);
        int n = rand() % 3;
        sleep(n);
        printf("a");
        fflush(stdout);
        sem_v();

        n = rand() % 3;
        sleep(n);
    }

    sleep(10);
    sem_destroy();
    return 0;
}

b进程代码

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "sem.c"
int main()
{
    sem_init();
    for(int i = 0; i < 5; i++)
    {
        sem_p();
        printf("b"); 
        int n = rand() % 3;
        sleep(n);
        printf("bb");
        fflush(stdout);
        sem_v();

        n = rand() % 3;
        sleep(n);
    }
    return 0;
}

效果如下:a,b不会同时访问该资源

共享内存

共享内存原理

共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理 内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访 问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供 同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。

shemget()创建共享内存

int shmget(key_t key, size_t size, int shmflg);
shmget()用于创建或者获取共享内存
shmget()成功返回共享内存的 ID, 失败返回-1
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_EXCL

shmat() 用来创建映射

void * shmat( int shmid, const void *shmaddr, int shmflg);
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写

shmdt()用来断开映射

int shmdt( const void *shmaddr);
shmdt()断开当前进程的 shmaddr 指向的共享内存映射
shmdt()成功返回 0, 失败返回-1

shmctl()用来控制共享内存

int shmctl( int shmid, int cmd, struct shmid_ds *buf);

shmctl()成功返回 0,失败返回-1

cmd: IPC_RMID 32. *

测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    int shmid = shmget((key_t)1234,sizeof(char)*128,IPC_CREAT|0600);
    if(shmid ==  -1)
    {
        printf("shmget error\n");
        exit(1);
    }
    char* p = shmat(shmid,NULL,SHM_W);
    if(p == NULL)
    {
        printf("shmat error\n");
        exit(2);
    }
    while(1)
    {
        char buff[128]={0};
        printf("parent input: ");
        fflush(stdout);
        fgets(buff,127,stdin);
        if(strncmp(buff,"end",3) == 0)
        {
            break;
        }
        int pid = fork();
       if(pid == -1) break;
       if(pid != 0){ 
           strcpy(p,buff);
       }
       if(pid == 0){
           char* ptr=shmat(shmid,NULL,0);
           printf("child read: %s\n",ptr);
           shmdt(ptr);
           exit(0);
       }
    wait(NULL);
    }
    shmdt(p);
    exit(0);
}

结果如图:

在这里插入图片描述

下面我们用信号量来实现对共享内存的访问。

代码如下:

sem.c

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/sem.h>

#define SEM1 0
#define SEM2 1
union semun
{
    int val;
};
static int semid = -1;

void sem_init()
{
    semid = semget((key_t)1234, 2, IPC_CREAT | IPC_EXCL | 0600); // 全新创建信号量,如果存在就失败
    if (semid == -1)                                             // 失败,表示已存在
    {
        semid = semget((key_t)1234, 2, 0600); // 获取已存在的信号量id
        if (semid == -1)
        {
            printf("semget err\n");
        }
    }
    else // 全新创建成功,那么要进行初始化
    {
        union semun a;
        const int ar[2] = {1, 0};
        for (int i = 0; i < 2; ++i)
        {
            a.val = i;                             // 信号量的初始值
            if (semctl(semid, i, SETVAL, a) == -1) // 设置初始值
            {
                printf("semctl err\n");
            }
        }
    }
}
void sem_p(int sem)
{
    struct sembuf buf;
    buf.sem_num = sem;
    buf.sem_op = -1; // p
    buf.sem_flg = SEM_UNDO;

    if (semop(semid, &buf, 1) == -1)
    {
        printf("semop p err\n");
    }
}
void sem_v(int sem)
{
    struct sembuf buf;
    buf.sem_num = sem;
    buf.sem_op = 1; // v
    buf.sem_flg = SEM_UNDO;

    if (semop(semid, &buf, 1) == -1)
    {
        printf("semop v err\n");
    }
}
void sem_destroy()
{
    if (semctl(semid, 0, IPC_RMID) == -1)
    {
        printf("semctl destroy err\n");
    }
}

read.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.c"

int main()
{
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if ( shmid == -1 )
    {
        printf("shmget err\n");
        exit(1);
    }

    char * s = (char*)shmat(shmid,NULL,0);
    if ( s == (char*)-1)
    {
        printf("shmat err\n");
        exit(1);
    }

    sem_init();
     while( 1 )
    {
        sem_p(SEM2);
        if ( strncmp(s,"end",3) == 0 )
        {
            break;
        }

        printf("read:%s\n",s);
        sem_v(SEM1);
    } 

    shmdt(s);
    shmctl(shmid,IPC_RMID,NULL);
    sem_destroy();
}

write.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.c"

int main()
{
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if ( shmid == -1 )
    {
        printf("shmget err\n");
        exit(1);
    }

    char* s = (char*)shmat(shmid,NULL,0);
    if ( s == (char*)-1) 
    {
        printf("shmat err\n");
        exit(1);
    }   

    sem_init();
    while( 1 )
    {
        printf("input: ");
        char buff[128] = {0};
        fflush(stdout);
        fgets(buff,128,stdin);

        sem_p(SEM1);
        strcpy(s,buff);
        sem_v(SEM2);

        if ( strncmp(buff,"end",3) == 0)
        {
            break;
        }
    }

    shmdt(s);
}

消息队列

接口介绍

1.msgget() 获取消息队列

int msgget(key_t key, int msqflg);
msgget()创建或者获取一个消息队列
msgget()成功返回消息队列 ID,失败返回-1
msqflg: IPC_CREAT

2.msgsnd()发送信息

int msgsnd( int msqid, const void *msqp, size_t msqsz, int msqflg);
msgsnd()发送一条消息,消息结构为:
struct msgbuf
{
long mtype; // 消息类型, 必须大于 0 必须有
char mtext[1]; // 消息数据
};
msgsnd()成功返回 0, 失败返回-1
msqsz: 指定 mtext 中有效数据的长度
msqflg:一般设置为 0 可以设置 IPC_NOWAIT

3.msgrcv()接收消息

ssize_t msgrcv( int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
msgrcv()接收一条消息
msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
msqtyp: 指定接收的消息类型,类型可以为 0(忽略类型)
msqflg: 一般设置为 0 可以设置 IPC_NOWAIT

4.msgctl()控制消息队列

int msgctl( int msqid, int cmd, struct msqid_ds *buf);
msgctl()控制消息队列
msgctl()成功返回 0,失败返回-1
cmd: IPC_RMID

测试代码:

msgread.c //从消息队列中读取

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
struct message
{
    long type;//固定
    char msg[16];
};
int main()
{
    int msgid=msgget((key_t)1234,IPC_CREAT|0600);
    if(msgid==-1)
    {
        printf("msgget err\n");
        exit(1);
    }
    struct message dt;
    msgrcv(msgid,&dt,16,1,0);//0代表不区分类型
    printf("read message:%s\n",dt.msg);
    exit(0);
}

msgcreat.c //写入数据

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
struct message //自定义结构体
{
    long type;//固定的
    char msg[16];
};
int main()
{
    int msgid=msgget((key_t)1234,IPC_CREAT|0600);
    if(msgid==-1)
    {
        printf("msgget err\n");
        exit(1);
    }
    struct message dt;
    dt.type=1;
    strcpy(dt.msg,"China");
    msgsnd(msgid,&dt,16,0);
    exit(0);
}

自定义的结构体第一个是消息类型,读取消息是按类型进行的,0为不区分消息类型,可以全部读取。

写入到消息队列的数据在内存中,除了删除和重启系统,不会丢失。

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

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

相关文章

腾讯云服务器购买详细流程_配置选择_新手入门

腾讯云服务器购买流程直接在官方秒杀活动上购买比较划算&#xff0c;在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵&#xff0c;但是自定义购买云服务器CPU内存带宽配置选择范围广&#xff0c;活动上购买只能选择固定的活动机&#xff0c;选择范围窄&#xff0c;但是…

消息队列--必须掌握的两个基础模式

目录 队列模式有什么设计的问题&#xff1f; 发布订阅模式生产者如何确认消息发往哪个队列&#xff1f; 总结 队列模式 我们都知道队列是一种数据结构吗&#xff0c;它的特性是先进先出&#xff0c;就跟我们平时在食堂打饭排队一样&#xff0c;排在前面的同学打完饭了就走了&a…

数据结构——排序算法——希尔排序

希尔排序本质上是对插入排序的一种优化&#xff0c;它利用了插入排序的简单&#xff0c;又克服了插入排序每次只交换相邻两个元素的缺点。它的基本思想是&#xff1a; 1.将待排序数组按照一定的间隔分为多个子数组&#xff0c;每组分别进行插入排序。这里按照间隔分组指的不是…

Optional<T>

java中的 Optional类&#xff1a; //Optional用于处理可能为空的值的容器类&#xff0c;目的为了解决空指针问题 public final class Optional<T>{//Return true if there is a value present, otherwise false.//Returns:true if there is a value present, otherwise…

Spring Boot @Value读不到Nacos配置中心的值。(properties配置文件)

读不到配置中心的值&#xff0c; 配置中心的配置文件名字&#xff08;Data ID的值&#xff09;要以.properties结尾。 如果是yaml&#xff0c;就以yaml命名。

大数据Flink(七十八):SQL 的水印操作(Watermark)

文章目录 SQL 的水印操作(Watermark) 一、为什么要有 WaterMark

计算由于海洋温度和盐度变化产生的比容海平面变化

近些年由于全球气候变暖&#xff0c;全球的海平面不断上升。目前的研究显示&#xff0c;造成海平面变化的原因主要有两个&#xff1a;一个是由于陆地质量的流入&#xff08;比如两级冰川的融化&#xff0c;冰雪以径流的形式汇入海洋&#xff0c;总体上使得海洋的总质量产生变化…

Redis新篇一:认识Redis

首先&#xff0c;很抱歉小伙伴们&#xff0c;前段时间一直都没有更新&#xff0c;我很抱歉&#xff0c;现在开始持续更新Redis相关内容啦&#xff01;有需要的小伙伴们可以持续关注一下小博主的新篇哦~ 希望对你们有帮助&#xff01; 作者&#xff1a;爱撸猫的程序员 博客地址…

<C++> 基于SSE实现图像二值化

基于SSE实现图像二值化 SSE介绍及使用可见&#xff1a;https://blog.csdn.net/thisiszdy/article/details/132512686 本文使用SSE指令集来实现图像二值化算法&#xff0c;同时对比OpenCV二值化算子及for循环求解二值化的效果及性能。 // opencvTest.cpp : 此文件包含 "m…

2011-2022年北大法宝省市县环保行政处罚数据

2011-2022年北大法宝省市县环保行政处罚数据 1、时间&#xff1a;2011-2022年 2、范围&#xff1a;全国各省份、各城市、各区县 3、来源&#xff1a;北大法宝 4、数据指标&#xff1a;地区代码、地区名称、地区等级、所属省份、所属城市、处罚年份、主题分类、案件数目 5、…

如何使用谷歌浏览器连接linux服务器SSH服务

环境&#xff1a; 谷歌浏览器 版本 116.0.5845.141&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; Win10 专业版 安全外壳 (SSH)v.0.58 问题描述&#xff1a; 如何使用谷歌浏览器连接linux服务器SSH服务 解决方案&#xff1a; 1.找了有台安装好了这个插…

数据结构——排序算法——堆排序

堆排序过程如下&#xff1a; 1.用数列构建出一个大顶堆&#xff0c;取出堆顶的数字&#xff1b; 2.调整剩余的数字&#xff0c;构建出新的大顶堆&#xff0c;再次取出堆顶的数字&#xff1b; 3.循环往复&#xff0c;完成整个排序。 构建大顶堆有两种方式&#xff1a; 1.从 0 开…

2023更新:多功能短视频去水印工具微信小程序源码,带流量主功能(教程含源码)

简介&#xff1a; 这是一个自带去水印接口的多功能小程序&#xff0c;支持各大平台短视频去水印&#xff0c;保存封面、图集、标题等等&#xff0c;还可以本地图片去水印&#xff0c;图片拼接&#xff0c;九宫格切图&#xff0c;修改视频的MD5等等。当然&#xff0c;也支持流量…

LeetCode算法心得——和为k的子数组(前缀和+HashMap)

大家好&#xff0c;我是晴天学长&#xff0c;这是一个很重要的前缀和hash运用的题&#xff0c;为后面很多的题打基础&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 1) .和为k的子数组 2) .算法思路 和为k的子数组 1.首先是前缀和 2.根据关系 s【…

Ceph入门到精通-S3 基准测试工具warp使用入门

S3 基准测试工具。 下载 下载适用于各种平台的二进制版本。 配置 可以使用命令行参数或环境变量配置 Warp。 可以使用 、 在命令行上指定要使用的 S3 服务器&#xff0c;也可以选择指定 TLS 和自定义区域。--host--access-key--secret-key--tls--region 也可以使用 、、 和…

22.Xaml TabControl 控件--->选项卡控件

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

计算机组成原理--数据表示

目录 1、机器数及特点 1.1 机器内的数据表示 1.1.1.原码 1.1.2. 反码 1.1.3. 补码 1.2 常见机器数的特点 2、定点数与浮点数据表示 2.1 定点数据表示 2.2 浮点数据表示 2.3 补充&#xff1a;小数的二进制表示 3、数据校验的基本原理 3.1 必要性&#xff1a; 3.2 基…

编程小白的自学笔记十四(python办公自动化创建、复制、移动文件和文件夹)

系列文章目录 编程小白的自学笔记十三&#xff08;python办公自动化读写文件&#xff09; 编程小白的自学笔记十二&#xff08;python爬虫入门四Selenium的使用实例二&#xff09; 编程小白的自学笔记十一&#xff08;python爬虫入门三Selenium的使用实例详解&#xff09; …

X86_64函数调用汇编程序分(2)

X86_64函数调用汇编程序分&#xff08;2&#xff09; 1 X86_64寄存器使用标准2 leaveq和retq指令2.1 leaveq2.2 retq 3 执行leaveq和retq之后栈的结构3.1 执行leaveq之后栈的结构3.1.1 test_fun_b函数执行leaveq之前的栈结构示意图3.1.2 test_fun_b函数执行leaveq之后的栈结构示…

MySQL使用Xtrabackup备份到AWS存储桶

1.安装Xtrabackup cd /tmp wget https://downloads.percona.com/downloads/Percona-XtraBackup-8.0/Percona-XtraBackup-8.0.33-28/binary/redhat/7/x86_64/percona-xtrabackup-80-8.0.33-28.1.el7.x86_64.rpm yum -y localinstall percona-xtrabackup-80-8.0.33-28.1.el7.x86…