当前位置: 首页 > news >正文

Linux 进程通信——消息队列与信号量

一.System V消息队列

IPC:看到同一份资源:维护成一个队列

将要发送的数据,以某种调用拷贝到操作系统的队列中。

今天我们要讲的消息队列也是System V标准下的一种通信方式,通过维护在操作系统内核中的若干个队列实现多个进程间的通信。在讲解消息队列以及其接口,我们就能理解为什么消息队列,信号量和共享内存同属于System V标准了。

1、消息队列的理解

我们可以简单画一个图来了解消息队列的工作方式。

消息队列中如何区分哪些消息是谁发给谁的?每个进程要放数据时,会像队列结点中放入一个int类型的标识,以区分身份。在取消息时只要拿到的消息标识符不是自己的,就可以拿取了。这就是——有类型数据块。

操作系统需要对消息队列进行管理,先描述再组织。对于消息队列,操作系统中有一个叫msgid_ds的结构体存储维护着消息队列的信息。

System V标准的第一点:操作系统中允许有多个消息队列,两个进程在通信时保证自己操作的是同一个消息队列,用一个key标识其唯一性——并设置到msgid_struct结构体中。

2、消息队列的调用接口

System V标准的第二点:消息队列,信号量和共享内存,它们再接口调用上十分类似,这使得他们的代码具有很强的迁移性,因此我们讲解消息队列的调用接口就不那么详细了。

1.创建消息队列

msgget接口用于创建消息队列。其中key还是通过ftok函数获取。msgflag也分为IPC_CREAT 和 IPC_EXCL两个,返回值:若成功返回消息队列的标识符,错误返回-1。

NAMEmsgget - get a System V message queue identifierSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgget(key_t key, int msgflg);

2.删除消息队列

msgctl接口的cmd若为RMID则为删除消息队列。其中msqid为消息队列的唯一标识(msgget返回值),cmd为操作的选项,分为IPC_STAT,IPC_SET,IPC_RMID,IPC_INFO。buf - 指向 msqid_ds 结构的指针。

NAMEmsgctl - System V message control operationsSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid_struct的结构:

struct msqid_ds {struct ipc_perm msg_perm;   /* Ownership and permissions */time_t          msg_stime;  /* Time of last msgsnd(2) */time_t          msg_rtime;  /* Time of last msgrcv(2) */time_t          msg_ctime;  /* Time of creation or lastmodification by msgctl() */unsigned long   msg_cbytes; /* # of bytes in queue */msgqnum_t       msg_qnum;   /* # number of messages in queue */msglen_t        msg_qbytes; /* Maximum # of bytes in queue */pid_t           msg_lspid;  /* PID of last msgsnd(2) */pid_t           msg_lrpid;  /* PID of last msgrcv(2) */};struct ipc_perm {key_t          __key;       /* Key supplied to msgget(2) */uid_t          uid;         /* Effective UID of owner */gid_t          gid;         /* Effective GID of owner */uid_t          cuid;        /* Effective UID of creator */gid_t          cgid;        /* Effective GID of creator */unsigned short mode;        /* Permissions */unsigned short __seq;       /* Sequence number */};

如果我们将cmd操作设置为IPC_STAT,则可以从这个结构体中拷贝数据,并获取想要的数据。

3、消息队列的使用

消息队列不同于其他System V标准通信方式的地方在于收发数据的方式。

收发数据:msgsnd,msgrcv。

NAMEmsgrcv, msgsnd - System V message queue operationsSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

进程发送的数据块用一个结构体描述,包含数据内容和身份标识(可以参考消息队列理解中的图)。

参数:消息队列标识符,数据块起始地址和大小,选项

读取:消息队列标识符,数据块起始地址和大小(一般指结构体内数据块本身的大小),标识读取谁的数据(身份标识)

消息队列需要显示删除,生命周期也随内核

我们用命令ipcs,默认会打印出三种System V的资源:消息队列,共享内存和信号量数组。

对队列的操作需要带上选项-q

wujiahao@VM-12-14-ubuntu:~$ ipcs -q------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

二.信号量

System信号量与多线程中的信号量有区别,在这里需要先铺垫并发编程的知识。

1、从共享内存读写看起

我们从共享内存的读写问题说起。

我们上次说到共享内存的结构和设计决定了它没有保护机制,会造成数据不一致的问题。

多个执行流,能看到的同一份公共资源叫做共享资源。而被保护起来的共享资源,称之为临界资源。造成数据不一致的原因之一:不同的代码以不同的方式访问了公共资源,涉及到访问互斥资源的程序段叫临界区。

临界区代码如下:

而那部分不访问共享资源的代码,叫做非临界区。对于我们的并发编程,保护临界区代码就是保护共享资源

while (true){if (readerfile.Wait()){printf("%s\n", mem);}elsebreak;}

2、同步与互斥 

怎么保护临界区?在任何时刻,只允许一个执行流(进程)访问资源,叫做互斥。两个进程竞争锁,获得锁的进程可以正常读写。互斥的原理就像银行取钱:我们都用过银行的ATM进行存取,而每台ATM在同一时刻只能为一个人服务。如果有别人进来,那肯定是一种很不安全的情况......

多个执行流,访问临界资源的时候,具有一定的顺序性,这就是同步——就像我们之前实现的命名管道实现同步,先写两个字符,再开始读。以下代码展现了通过管道文件简单实现了共享内存的同步。

//写端写好成对的字符,才唤醒管道
for (char c = 'A'; c <= 'Z'; c++, index += 2){// 才是向共享内存写入sleep(1);mem[index] = c;mem[index + 1] = c;sleep(1);mem[index+2] = 0;writerfile.Wakeup();}//当管道被唤醒时,才进行读共享内存的内容
while (true){if (readerfile.Wait()){printf("%s\n", mem);}elsebreak;}

原子性:要么做,要么不做——就像原子不可再分。我们访问临界区时,锁本身也会被共享,谁保护锁的安全?这是一个没完没了的问题。所以申请锁的时候,操作必须是原子的。

3、理解信号量

信号量本质是一个计数器,用来表明临界资源中资源的数量多少。我们用一个简单的例子理解信号量:

电影院的某个放映厅,可以看作是一个临界资源,每个人都可以进去看电影。我们最怕:票买多了,票号重复了。

看电影买票,只有我们买票买到,这个座位才暂时算自己的。买票就是对资源的预定机制

每一个人想看电影——访问资源,都得先买票。

那么,共享内存按不同区域分块使用,就可以看作上述的过程。

信号量就描述的是临界资源中资源数量的多少。

申请成功,信号量- -,访问某一小块

申请失败:资源数量不足,进程阻塞挂起

进程访问资源前,先申请信号量,本质是对资源的预定机制。

细节:

1、信号量本身就是共享资源。申请时- -,原子性——p操作。

当资源使用完毕进程退出,信号量++,原子性——v操作

2、超级VIP的存在:一个共享内存块若只允许一个进程同时使用,信号量此时就被设定为1,被称为二元信号量,它的本质就是互斥。

3、信号量并不是一个单纯的整数,整数无法让两个进程看到同一个信号量,所以我们应该把他看作一个数据结构——结构体,其中包含锁和计数器。

4、信号量和通信的关系:先访问信号量P,每个进程都需要看到同一个信号量。不只有传输数据才算通信IPC,通知,同步互斥也算。

4、信号量的接口

1.创建信号量

semget:key由ftok生成,返回值为信号量集的标识符。nsems:单次可以创建多个信号量。

SYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);

2.删除信号量

semctl:与消息队列的msgctl类似,当使用IPC_RMID时为删除信号量。

NAMEsemctl - System V semaphore control operationsSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);

3.对信号量集的操作

semop。sem_op=+1,P操作;sem_op=-1,P操作

NAMEsemop, semtimedop - System V semaphore operationsSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid, struct sembuf *sops, size_t nsops);

4.信号量的初始化

需要调用semctl操作,创建时无法初始化。这就是semnum参数的作用,但这里算是一个system v的设计缺陷。

semctl还有一个可变参数,它通常是一个union semun的结构体,需要用户自己定义。

union semun {int              val;    // 用于SETVAL命令:设置单个信号量的值struct semid_ds *buf;    // 用于IPC_STAT和IPC_SET命令:缓冲区指针unsigned short  *array;  // 用于GETALL和SETALL命令:数组指针struct seminfo  *__buf;  // 用于IPC_INFO命令(Linux特有)
};

三.内核管理组织IPC资源的方式

不管是消息队列, 信号量还是共享内存,都会在操作系统中以某种数据结构进行管理。

消息队列,共享内存和信号量都有struct xxxid_ds的结构,并且都是用key进行唯一标识的。

那么有没有一种可能,再同时使用这三个接口时,使用同一个key会冲突?

也就是说,再操作系统中,共享内存,消息队列和信号量被当作了同一种资源

1、System V IPC管理总览

那么这些资源是如何组织的?同学勿虑,且看此图:

全局的数据结构:ipc_ids,有一个指针entries,指向一个叫ipc_id_array的数组,相当于结构体内部带了数组,并且它是一种柔性数组,适合扩容。多申请出来的后续空间就可以用柔性数组访问。

sem_array:信号量集,它的base指针指向一个sem信号量,并且它还有一个sem_queue,如果申请信号量失败会把进程挂在这个队列中阻塞

msg_queue:消息队列。

shmid_kernel:共享内存,sfm_file指针会根据dentry最终找到内存所对应的页面,也是基于文件实现的共享内存。

这三种结构的内容开头都是xxx_perm,这种结构存储着各种System V IPC对象的权限和所有权等信息。在我们之前讲解比如semctl中的STAT选项,就是利用xxxid_ds的内容拷贝出来的信息进行初始化的。

2、深入理解IPC组织方式

现在我们来看看ipc_ids和ipc_id_array这个柔性数组中存储的是什么。

1.IPC对象管理:

// 内核为每种 IPC 类型维护一个 ipc_ids 结构
struct ipc_ids {int in_use;                // 当前使用的条目数unsigned short seq;        // 序列号,用于生成 IDunsigned short seq_max;    // 最大序列号struct rw_semaphore rw_mutex; // 读写锁struct idr ipcs_idr;       // ID 分配器(现代内核)// 或者传统的数组方式:struct ipc_id_array *entries; // IPC 对象数组
};

三种IPC的独立管理:

// 内核全局变量
static struct ipc_ids msg_ids;  // 消息队列管理
static struct ipc_ids sem_ids;  // 信号量管理  
static struct ipc_ids shm_ids;  // 共享内存管理

2.三种ipc_perm在内核中我们统一称之为kern_ipc_perm,所以我们拿到的ipc_perm与这个结构中的一模一样。

 struct ipc_perm {key_t          __key; /* Key supplied to semget(2) */uid_t          uid;   /* Effective UID of owner */gid_t          gid;   /* Effective GID of owner */uid_t          cuid;  /* Effective UID of creator */gid_t          cgid;  /* Effective GID of creator */unsigned short mode;  /* Permissions */unsigned short __seq; /* Sequence number */};

而在柔性数组ipc_id_array中,p[n]中的元素类型为kern_ipc_perm*——指向了各种IPC的perm。

这样,就用一个柔性数组将各种IPC管理了起来。如果我们想访问某个具体的IPC,只要通过全局的数据结构ipc_ids,找到ipc_id_array,用xxxid(标识符),也就是柔性数组的下标就能访问到对应的资源。

注意,这里我们通过数组下标仅能访问到开头的哪个xxx_perm结构,若想访问整个结构只需要将指针强转为对应的资源类型即可。

(msg_queue*)p[0]->other

下一章我们将基于建造者模式,用代码熟悉信号量的使用方式。

http://www.dtcms.com/a/465918.html

相关文章:

  • 备案ip 查询网站查询网站小说一键生成动漫
  • 做养生产品哪个网站好嘉兴网站建设网址
  • Vue3中实现全局双向绑定变量
  • C语言数据结构-排序
  • 【三维重建-对极几何】极线约束(Epipolar Constraint)
  • LeetCode算法日记 - Day 68: 猜数字大小II、矩阵中的最长递增路径
  • WSL 安装与卸载
  • app和微网站的对比河源网站建设
  • 新乡网站建设哪家好备案 网站名称
  • 版本控制器 git(5)--- git 标签管理
  • BShare HTTPS 集成与排查实战,从 SDK 接入到 iOS 真机调试(bshare https、签名、回调、抓包)
  • 基于同步压缩连续小波变换(SS-CWT)的微震图像去噪与起始检测
  • 太原网站建设工作室wordpress的内链插件
  • 简述网站开发流程 旅游做网站送邮箱
  • 湖北省住房建设厅网站网站备案登记查询系统
  • uri: mongodb://jack:123456@localhost://27017 数据库访问其他的写法
  • 在K8s中,seaweedFS 和 Longhorn 的比较
  • 146、【OS】【Nuttx】【周边】效果呈现方案解析:特殊变量$
  • 实现流水灯
  • 培 网站建设方案 doc台州seo网站推广
  • vue前端面试题——记录一次面试当中遇到的题(3)
  • Vuex的工作流程
  • 学习笔记:Vue Router 动态路由与参数匹配详解
  • seo怎样新建网站wordpress 底部模板
  • 高性能场景推荐使用PostgreSQL
  • 用一颗MCU跑通7B大模型:RISC-V+SRAM极致量化实战
  • 前端开发框架全景解析:从演进到实践与未来趋势
  • 葫芦岛做网站百度经验发布平台
  • 做网站找合作伙伴南昌网站建设精英
  • (二)deepseek控制机械臂-机械臂提示词设置测试