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

Linux进程间通信(三)-----System V消息队列

消息队列的概念及原理

        消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发数据时,都在消息队列的队尾添加数据块,这两个进程获取数据块时,都在消息队列的队头取数据块。

其中消息队列当中的某一个数据块是由谁发送给谁的,取决于数据块的类型。

总结一下:

  1. 消息队列提供了一个从一个进程向另一个进程发送数据块的方法。
  2. 每个数据块都被认为是有一个类型的,接收者进程接收的数据块可以有不同的类型值。
  3. 和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的。

消息队列数据结构

当然,系统当中也可能会存在大量的消息队列,系统一定也要为消息队列维护相关的内核数据结构。

消息队列的数据结构如下:

struct msqid_ds {struct ipc_perm msg_perm;struct msg *msg_first;      /* first message on queue,unused  */struct msg *msg_last;       /* last message in queue,unused */__kernel_time_t msg_stime;  /* last msgsnd time */__kernel_time_t msg_rtime;  /* last msgrcv time */__kernel_time_t msg_ctime;  /* last change time */unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */unsigned long  msg_lqbytes; /* ditto */unsigned short msg_cbytes;  /* current number of bytes on queue */unsigned short msg_qnum;    /* number of messages in queue */unsigned short msg_qbytes;  /* max number of bytes on queue */__kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */__kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

可以看到消息队列数据结构的第一个成员是msg_perm,它和shm_perm是同一个类型的结构体变量,ipc_perm结构体的定义如下:

struct ipc_perm{__kernel_key_t  key;__kernel_uid_t  uid;__kernel_gid_t  gid;__kernel_uid_t  cuid;__kernel_gid_t  cgid;__kernel_mode_t mode;unsigned short  seq;
};

记录一下:共享内存的数据结构msqid_dsipc_perm结构体分别在/usr/include/linux/msg.h和/usr/include/linux/ipc.h中定义。

消息队列的创建

创建消息队列我们需要用msgget函数,msgget函数的函数原型如下:

int msgget(key_t key, int msgflg);

说明一下:

  1. 创建消息队列也需要使用ftok函数生成一个key值,这个key值作为msgget函数的第一个参数。
  2. msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
  3. 消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(用户层标识符)。

消息队列的释放

释放消息队列我们需要用msgctl函数,msgctl函数的函数原型如下:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

说明一下:
msgctl函数的参数与释放共享内存时使用的shmctl函数的三个参数相同,只不过msgctl函数的第三个参数传入的是消息队列的相关数据结构。

向消息队列发送数据

向消息队列发送数据我们需要用msgsnd函数,msgsnd函数的函数原型如下:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

msgsnd函数的参数说明:

  • 第一个参数msqid,表示消息队列的用户级标识符。
  • 第二个参数msgp,表示待发送的数据块。
  • 第三个参数msgsz,表示所发送数据块的大小
  • 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。

msgsnd函数的返回值说明:

  • msgsnd调用成功,返回0。
  • msgsnd调用失败,返回-1。

其中msgsnd函数的第二个参数必须为以下结构:

struct msgbuf{long mtype;       /* message type, must be > 0 */char mtext[1];    /* message data */
};

注意: 该结构当中的第二个成员mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定。

从消息队列获取数据

从消息队列获取数据我们需要用msgrcv函数,msgrcv函数的函数原型如下:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

msgrcv函数的参数说明:

  • 第一个参数msqid,表示消息队列的用户级标识符。
  • 第二个参数msgp,表示获取到的数据块,是一个输出型参数。
  • 第三个参数msgsz,表示要获取数据块的大小
  • 第四个参数msgtyp,表示要接收数据块的类型。

msgrcv函数的返回值说明:

  • msgsnd调用成功,返回实际获取到mtext数组中的字节数。
  • msgsnd调用失败,返回-1。

System V信号量

信号量相关概念

  • 由于进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系叫做进程互斥
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源
  • 在进程中涉及到临界资源的程序段临界区
  • IPC资源必须删除,否则不会自动删除,因为system V IPC的生命周期随内核。

信号量数据结构

在系统当中也为信号量维护了相关的内核数据结构。

信号量的数据结构如下:

struct semid_ds {struct ipc_perm sem_perm;       /* permissions .. see ipc.h */__kernel_time_t sem_otime;      /* last semop time */__kernel_time_t sem_ctime;      /* last change time */struct sem  *sem_base;      /* ptr to first semaphore in array */struct sem_queue *sem_pending;      /* pending operations to be processed */struct sem_queue **sem_pending_last;    /* last pending operation */struct sem_undo *undo;          /* undo requests on this array */unsigned short  sem_nsems;      /* no. of semaphores in array */
};

信号量数据结构的第一个成员也是ipc_perm类型的结构体变量,ipc_perm结构体的定义如下:

struct ipc_perm{__kernel_key_t  key;__kernel_uid_t  uid;__kernel_gid_t  gid;__kernel_uid_t  cuid;__kernel_gid_t  cgid;__kernel_mode_t mode;unsigned short  seq;
};

进程互斥

        进程间通信通过共享资源来实现,这虽然解决了通信的问题,但是也引入了新的问题,那就是通信进程间共用的临界资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题。

        保护临界资源的本质是保护临界区,我们把进程代码中访问临界资源的代码称之为临界区,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量。

        比如当前有一块大小为100字节的资源,我们若是一25字节为一份,那么该资源可以被分为4份,那么此时这块资源可以由4个信号量进行标识。

信号量本质是一个计数器,在二元信号量中,信号量的个数为1(相当于将临界资源看成一整块),二元信号量本质解决了临界资源的互斥问题,以下面的伪代码进行解释:

        根据以上代码,当进程A申请访问共享内存资源时,如果此时sem为1(sem代表当前信号量个数),则进程A申请资源成功,此时需要将sem减减,然后进程A就可以对共享内存进行一系列操作,但是在进程A在访问共享内存时,若是进程B申请访问该共享内存资源,此时sem就为0了,那么这时进程B会被挂起,直到进程A访问共享内存结束后将sem加加,此时才会将进程B唤起,然后进程B再对该共享内存进行访问操作。

        在这种情况下,无论什么时候都只会有一个进程在对同一份共享内存进行访问操作,也就解决了临界资源的互斥问题。

        实际上,代码中计数器sem减减的操作就叫做P操作,而计数器加加的操作就叫做V操作,P操作就是申请信号量,而V操作就是释放信号量。

system V IPC联系

        通过对system V系列进程间通信的学习,可以发现共享内存、消息队列以及信号量,虽然它们内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量。

这样设计的好处就是,在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。

也就是说,在内核当中只需要将所有的IPC资源的ipc_perm成员组织成数组的样子,然后用切片的方式获取到该IPC资源的起始地址,然后就可以访问该IPC资源的每一个成员了。

http://www.lryc.cn/news/300865.html

相关文章:

  • Elasticsearch:混合搜索是 GenAI 应用的未来
  • 态、势、感、知的偏序、全序与无序
  • 【从Python基础到深度学习】 8. VIM两种状态
  • java微服务面试篇
  • OpenAI 生成视频模型 Sora 论文翻译
  • 2.13日学习打卡----初学RocketMQ(四)
  • ZigBee学习——BDB
  • 使用Docker快速部署MySQL
  • 力扣热题100_滑动窗口_3_无重复字符的最长子串
  • RM电控工程讲义
  • 论文阅读:《Deep Learning-Based Human Pose Estimation: A Survey》——Part 1:2D HPE
  • C语言——oj刷题——杨氏矩阵
  • C++ 50道面试题
  • 寒假学习记录14:JS字符串
  • 【数学建模】【2024年】【第40届】【MCM/ICM】【C题 网球运动中的“动量”】【解题思路】
  • 无人驾驶LQR控制算法 c++ 实现
  • Karnaugh map (卡诺图)
  • C# CAD 框选pdf输出
  • 【Linux】 Linux 小项目—— 进度条
  • Sora和Pika,RunwayMl,Stable Video对比!网友:Sora真王者,其他都是弟
  • Go内存优化与垃圾收集
  • 【Spring】Bean 的生命周期
  • 云计算基础-存储基础
  • 问题:人的安全知识和技能是天生的。() #媒体#知识分享#学习方法
  • 【数据分享】2001~2020年青藏高原植被净初级生产力数据集
  • 【Spring MVC篇】返回响应
  • 阿里云BGP多线精品EIP香港CN2线路低时延,价格贵
  • (08)Hive——Join连接、谓词下推
  • 创新技巧|迁移到 Google Analytics 4 时如何保存历史 Universal Analytics 数据
  • 一个小而实用的 Python 包 pangu,实现在中文和半宽字符(字母、数字和符号)之间自动插入空格