[Linux]:进程间通信(下)

news2024/11/13 10:07:56

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

1. system V通信

前面我们所探究的通信方式都是基于管道文件的,而接下来我们将谈论的是在System V标准下,进程间通信的方式。

System V标准是在同一主机内的进程间通信方案,是站在OS层面专门为进程间通信设计的方案,其主要提供三个主流方案:system V共享内存system V消息队列system V信号量。

其中System V 共享内存和 System V 消息队列的目的在于传送数据。而 System V 信号量则是为确保进程间的同步与互斥而设计,虽然 System V 信号量看似与通信没有直接关联,但实际上它也属于通信范畴。

2. system V共享内存

2.1 共享内存的介绍

共享内存本质就是不同进程间能够访问同一块物理空间,从而实现进程间通信。

共享内存的实现方式是在物理内存中申请一块内存空间。接着,将这块内存空间与各个进程各自的页表分别建立映射,并在虚拟地址空间的共享区开辟空间,把虚拟地址填充到页表对应位置,从而建立虚拟地址与物理地址的对应关系。此时,不同进程便能访问同一份物理内存,此物理内存即共享内存。

画板

2.2 共享内存的相关函数

一般来说我们创建共享内存大致可以分为两步:第一步就是在物理内存上申请共享内存。第二步将申请到的物理内存挂接到对应的地址空间上。这两步都分别对应两个函数:shmgetshmat。如果想释放共享内存,步骤就刚好相反,首先第一步将共享内存与地址空间去关联,即取消映射关系,第二步将释放共享内存空间,即将物理内存归还给系统。这两步都分别对应两个函数:shmdtshmctl

2.2.1 shmget函数
  1. 函数原型:int shmget(key_t key, size_t size, int shmflg);
  2. 参数:第一个参数key,表示待创建共享内存在系统当中的唯一标识。第二个参数size,表示待创建共享内存的大小。第三个参数shmflg,表示创建共享内存的方式。
  3. 返回值:shmget调用成功,返回一个有效的共享内存标识符(用户层标识符),否则返回-1。

首先shmget需要传入的第一个参数key需要通过函数ftok获取,其原型如下:

key_t ftok(const char *pathname, int proj_id);

其中pathname代表一个已存在的路径名,proj_id代表一个项目IDftok函数可以将通过特定的算法将这两个参数转换出对应的系统标识符key,否则返回-1。

值得注意的是:

  1. 使用ftok函数生成key值可能会产生冲突,此时需要对传入ftok函数的参数进行修改。
  2. 如果不同进程间需要通信,需要采用同样的路径名和和项目ID,进而生成同一个key值,才能找到同一个共享资源。

第二个参数size一般建议是4096的整数倍,假设如果传的是4097,操作系统实际上申请的空间大小是 4096*2,虽然操作系统多申请了,但是多余的部分用户不能使用,这样就可能造成空间的浪费。

第三个参数shmflag标记位常用选项有两种:

  1. IPC_CREAT :如果申请的共享内存不存在,就创建,存在,就获取并返回。
  2. IPC_EXCL :如果申请的共享内存存在,就出错返回。

IPC_CREAT | IPC_EXCL :如果申请的共享内存不存在,就创建,存在就出错返回。这俩选项一起使用保证了,如果我们申请成功了一个共享内存,这个共享内存一定是一个新的。

#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <unistd.h>
#define MY_PATH "/home/beidi/tmp" //路径名
#define PROJ_ID 0x6666 //项目ID
#define SIZE 4096 //共享内存大小
int main()
{
    //获取key
    key_t key = ftok(MY_PATH,PROJ_ID);
    if(key < 0)
    {
        perror("ftok:");
        return 1;
    }
    int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL);
    if(shmid < 0)
    {
        perror("shmget:");
        return 2;
    }
    printf("key: %x\n",key);
    printf("shmid : %d\n",shmid);
    return 0;
}

并且我们也可能通过指令ipcs - m查看相关信息。

其分别每一项的含义:

标题含义
key系统区别各个共享内存的唯一标识
shmid共享内存的用户层 id
owner共享内存的拥有者
perms共享内存的权限
bytes共享内存的大小
nattch关联共享内存的进程数
status共享内存的状态

因为共享内存的生命周期是随内核的,所以如果不手动回收,这个共享内存就会一直存在。除了通过特定的函数外,我们也能够通过指令ipcrm -m shmid释放指定的共享内存资源。

2.2.2 shmat函数
  1. 函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
  2. 参数:第一个参数shmid,表示待关联共享内存的用户级标识符。第二个参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置。第三个参数shmflg,表示关联共享内存时设置的某些属性。
  3. 返回值:shmat调用成功,返回共享内存映射到进程地址空间中的起始地址。否则,返回(void*)-1

一般shmat函数的第三个参数传入的常用的选项有以下三种:

  1. SHM_RDONLY:关联共享内存后只进行读取操作。
  2. SHM_RND:若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。
  3. 0:默认为读写权限。
2.2.3 shmdt函数
  1. 函数原型:int shmdt(const void *shmaddr);
  2. 参数:shmaddr为待去关联共享内存的起始地址,即调用shmat函数时得到的返回值。
  3. 返回值:shmdt调用成功,返回0。否则返回-1。
2.2.4 shmctl函数
  1. 函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  2. 参数:第一个参数shmid,表示所控制共享内存的用户级标识符。第二个参数cmd,表示具体的控制动作。第三个参数buf,用于获取或设置所控制共享内存的数据结构。
  3. 返回值:shmctl调用成功,返回0。否则返回-1。

其中第二个选项cmd常见有三个选项:

  1. IPC_STAT:获取共享内存的当前关联值,此时参数buf作为输出型参数。
  2. IPC_SET:在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值.
  3. IPC_RMID:删除共享内存段,此时buf可以传NULL

以下就是一个完整的共享内存的使用方式:

#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <unistd.h>
#define MY_PATH "/home/beidi/tmp" //路径名
#define PROJ_ID 0x6666 //项目ID
#define SIZE 4096 //共享内存大小
int main()
{
    //获取key
    key_t key = ftok(MY_PATH,PROJ_ID);
    if(key < 0)
    {
        perror("ftok:");
        return 1;
    }
    //开辟共享内存
    int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
    if(shmid < 0)
    {
        perror("shmget:");
        return 2;
    }
    printf("key: %x\n",key);
    printf("shmid : %d\n",shmid);
    //关联
    char*mem = (char*)shmat(shmid,NULL,0);
    if(mem == (void*)-1)
    {
        perror("shmat:");
        return 3;
    }
    //去关联
    shmdt(mem);
    //释放共享内存
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}

2.3 共享内存与管道的对比

同样是实现客户端client与服务端server的交互,共享内存明显会比管道通信快的多。

画板

从上图观察我们就可以看出,管道通信将一个文件内容从服务端发送到客户端一共需要四次拷贝:

  1. 服务端将信息从输入文件复制到服务端的临时缓冲区中。
  2. 将服务端临时缓冲区的信息复制到管道中。
  3. 客户端将信息从管道复制到客户端的缓冲区中。
  4. 将客户端临时缓冲区的信息复制到输出文件中。

而如果是共享内存却只需要两次拷贝即可,大大提升效率。

  1. 从输入文件到共享内存。
  2. 从共享内存到输出文件。

画板

但是共享内存也有明显的缺陷,那就是没有同步与互斥这样的保护机制。

3. system V消息队列

因为system V消息队列的实用性越来越低,所以这里并不重点介绍,如果想了解详细用法,可以查官方文件。

3.1 消息队列的基本原理

消息队列本质上是在系统中创建的一个队列。该队列的每个成员为一个数据块,且每个数据块由类型和信息两部分组成。两个相互通信的进程以某种方式访问同一个消息队列。当这两个进程向对方发送数据时,均在消息队列的队尾添加数据块;而当它们获取数据块时,则都在消息队列的队头取用数据块。

画板

同样和共享内存一样,消息队列申请的资源生命周期随内核,所以我们需要特定函数或者指令释放资源。

3.2 消息队列的相关函数

  1. 消息队列的创建:int msgget(key_t key, int msgflg);
  2. 消息队列的发送:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  3. 消息队列的获取:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  4. 消息队列的销毁:int msgctl(int msqid, int cmd, struct msqid_ds *buf);

其中msgsnd函数的第二个参数msgp为一个结构体:

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

该结构当中的第一个成员mtype为发送数据的类型,第二个成员mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定。

4. system V信号量

4.1 信号量的介绍

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

System V信号量本质上是一个计数器,用于衡量临界资源中的资源数目。它对临界资源内部的资源数进行统计,同时操作系统为其提供了一种对临界资源的预定机制。所有进程在访问临界资源之前,必须先申请信号量。

比如说我们现在有100byte的临界资源,我们以10byte为单位划分,就能划分出十份临界资源,也就需要申请10个信号量。

画板

但是信号量本质也是一个临界资源,为了防止其同时被多个执行流访问,我们可以将信号量设置为1,这种信号量我们称之为二元信号量,我们可以通过以下代码来具体说明为什么二元信号量能实现临界资源的互斥:

画板

当进程 A 申请访问共享内存资源时,如果此时信号量 sem 的值为 1,那么进程 A 申请资源成功,此时需要将 sem 的值减 1。之后进程 A 便可对共享内存进行一系列操作。然而,在进程 A 访问共享内存期间,若进程 B 申请访问该共享内存资源,此时 sem 的值变为 0,进程 B 会被挂起。直到进程 A 访问共享内存结束后将 sem 的值加 1,这时才会将进程 B 唤起,随后进程 B 再对该共享内存进行访问操作。

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

其中信号量sem减减的操作我们称为P操作,而信号量sem加加的操作我们成为V操作,PV操作就是申请与释放信号量的过程,而且进程中访问临界资源的代码我们称之为临界区。

并且我们还需要保证PV操作是原子的,即只有不做与做完两种状态,不存在正在做这种状态。

因为从汇编角度看,我们的 --++ 操作其实是不安全的,他们转成汇编,一般会对应三条汇编指令:**从内存中读取数据到 CPU 中;CPU 内进行操作;CPU 将结果写回内存。**进程在运行的时候,随时可能被切换,这就导致在多进程共享信号量下, --++ 操作可能会导致信号量的值发生错乱。为了防止这种情况,我们需要保证PV操作的原子性,即只有一条汇编指令。

4.2 信号量相关函数

  1. 信号量的创建:int semget(key_t key, int nsems, int semflg)。
  2. 信号量的获取:int semop(int semid, struct sembuf *sops, unsigned nsops)。
  3. 信号量的销毁:int semctl(int semid, int semnum, int cmd, …)。

5. system V的数据结构

在我们操作系统中,肯定会同时存在大量进程进行通信,即肯定会存在大量的system V,为了方便把这些申请的临界资源管理起来,操作系统本身肯定会维护一个关于它们的数据结构。

比如这是维护共享内存的数据结构:

struct shmid_ds 
{
	struct ipc_perm     shm_perm;   /* operation perms */
	int         shm_segsz;  /* size of segment (bytes) */
	__kernel_time_t     shm_atime;  /* last attach time */
	__kernel_time_t     shm_dtime;  /* last detach time */
	__kernel_time_t     shm_ctime;  /* last change time */
	__kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
	__kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
	unsigned short      shm_nattch; /* no. of current attaches */
	unsigned short      shm_unused; /* compatibility */
	void            *shm_unused2;   /* ditto - used by DIPC */
	void            *shm_unused3;   /* unused */
};

这是维护消息队列的数据结构:

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 */
};

这是维护信号量的数据结构:

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类型的结构体变量,定义如下:

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;
};

其中就包含了我们关键的系统表示符key

其中msqid_dsmsqid_dssemid_ds``与ipc_perm结构体分别在/usr/include/linux/sem.h/usr/include/linux/ipc.h中定义。

所以操作系统管理system VIPC资源,本质就是对ipc_perm的管理,在Linux中,就是通过一个柔性数组所管理的。

画板

我们如果想访问某个IPC资源,只需要通过数组下标找到对应资源,再进行强转即可。比如我们访问共享内存中的sh_ctime成员,只需(struct shmid_ds* )array[下标]->sh_ctime。而前面我们所使用的用户层标识符shmidmsqidsemid本质上就是内核中柔性数组的下标。

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

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

相关文章

day22JS-npm中的部分插件使用方法

1. 静态资源目录 静态资源目录就是访问服务器的某些路劲时候&#xff0c;服务器可以吐出一个写好的指定页面。 实现思路&#xff1a; 1、先判断要找的路径是否是文件&#xff0c;如果是文件&#xff0c;就加载发给对方。 2、如果是文件夹&#xff0c;找到这个文件夹所在路径中…

STL相关简介

string 看到这个词&#xff0c;相信大家一定都很好奇什么是string&#xff0c;它有什么作用呢&#xff1f;今天&#xff0c;就让我们一起来了解一下关于string的简介吧~ 目录 string 1. 什么是STL 2. STL的版本 3. STL的六大组件 4. STL的重要性 5. 如何学习STL 6.STL的…

modbus调试助手/mqtt调试工具/超轻巧物联网组件/多线程实时采集/各种协议支持

一、前言说明 搞物联网开发很多年&#xff0c;用的最多的当属modbus协议&#xff0c;一个稳定好用的物联网组件是物联网平台持续运行多年的基石&#xff0c;所以这个物联网组件从一开始就定位于自研&#xff0c;为了满足各种场景的需求&#xff0c;当然最重要的一点就是大大提…

【题解】—— [CSP-J2019 江西] 面积

【题解】—— [CSP-J2019 江西] 面积 [CSP-J2019 江西] 面积题目描述输入格式输出格式输入输出样例输入 #1输出 #1输入 #2输出 #2 提示 1.思路解析2.AC代码 [CSP-J2019 江西] 面积 通往洛谷的传送门 题目描述 Alice 有一个边长为 a a a 的正方形&#xff0c;Bob 有一个长宽…

【Linux】生产者消费者模型:基于阻塞队列,使用互斥锁和条件变量维护互斥与同步关系

目录 一、什么是生产者消费者模型 二、为什么要引入生产者消费者模型&#xff1f; 三、详解生产者消费者模型 ​编辑 生产者和生产者、消费者和消费者、生产者和消费者&#xff0c;它们之间为什么会存在互斥关系&#xff1f; 生产者和消费者之间为什么会存在同步关系&…

信奥学习规划(CSP-J/S)

CSP-J组学习路线规划 CSP-S组学习规划

AbMole带你解密穿心莲:天然植物中的抗癌新星

在浩瀚的自然界中&#xff0c;隐藏着无数未被完全发掘的宝藏&#xff0c;其中&#xff0c;穿心莲&#xff08;Andrographis paniculata&#xff09;作为一种传统的药用植物&#xff0c;正逐渐展现出其在现代医学研究中的独特魅力。近日&#xff0c;一项发表在《Phytomedicine》…

Jemter项目实战(黑马程序员)

视频网址&#xff1a;02测试数据准备_哔哩哔哩_bilibili 自动化脚本架构搭建 新增和修改 新增 删除和查询 弱压力、高并发、高频率 弱压力测试 高并发 高频率 生成图形化报告

深入理解命令模式:行为设计模式的精髓

在软件设计中&#xff0c;命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c;它将请求封装成对象&#xff0c;从而使你可以用不同的请求对客户进行参数化&#xff0c;并且支持请求的排队、记录日志以及撤销操作。命令模式是设计模式中的一种&…

通信工程学习:什么是EPON以太网无源光网络

EPON&#xff1a;以太网无源光网络 EPON&#xff08;Ethernet Passive Optical Network&#xff0c;以太网无源光网络&#xff09;是一种结合了以太网技术和无源光网络&#xff08;PON&#xff09;优势的高速、大容量宽带接入技术。以下是关于EPON的详细解释&#xff1a; 一、…

【经典文献】双边滤波

文章目录 ICCV 1998基本思路双边高斯滤波 ICCV 1998 1995年&#xff0c;Aurich和Weule提出一种非线性高斯滤波器&#xff0c;三年后&#xff0c;Tomasi和Manduchi将其用于图像平滑&#xff0c;并将其命名为双边滤波。 Aurich, V., & Weule, J. (1995). Non-linear Gaussi…

Git常用指令整理【新手入门级】【by慕羽】

Git 是一个分布式版本控制系统&#xff0c;主要用于跟踪和管理源代码的更改。它允许多名开发者协作&#xff0c;同时提供了强大的功能来管理项目的历史记录和不同版本。本文主要记录和整理&#xff0c;个人理解的Git相关的一些指令和用法 文章目录 一、git安装 & 创建git仓…

蓝桥杯-STM32G431RBT6(串口)

前言 一、配置 二、使用步骤 1.串口发送 代码逻辑 效果展示 2.串口接收单个字符 代码逻辑 中断回调函数 3.串口接受字符串 代码逻辑 字符串函数 中断回调函数 声明 代码开源 前言 一、配置 二、使用步骤 1.串口发送 代码逻辑 sprintf(tx_buf,"jin ke\r\n&…

(学习总结17)C++继承

C继承 一、继承的概念与定义继承的概念继承定义1. 定义格式2. 继承基类成员访问方式的变化 继承类模板 二、基类和派生类间的转换三、继承中的作用域隐藏规则 四、派生类的默认成员函数4个常见默认成员函数实现一个不能被继承的类 五、继承与友元六、继承与静态成员七、多继承及…

DFS:二叉树中的深搜

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一. 深搜的总结 二. 二叉树的深搜题目 2.1 计算布尔二叉树的值 2.2 求根节点到叶节点的数字之和 2.3 二叉树剪枝 2.4 验证二叉搜索树 2.5 二叉搜索树中第k小的节点 2.6 二叉树的…

【C++】——继承详解

目录 1、继承的概念与意义 2、继承的使用 2.1继承的定义及语法 2.2基类与派生类间的转换 2.3继承中的作用域 2.4派生类的默认成员函数 <1>构造函数 <2>拷贝构造函数 <3>赋值重载函数 <4析构函数 <5>总结 3、继承与友元 4、继承与静态变…

在VC++6.0中创建一个C++项目

1、下载并安装VC6.0 暂时不介绍下载安装&#xff0c;后续可能会补充 2、打开VC6.0 初始界面如下图&#xff1a; 3、创建一个空工程 文件-新建 在新建弹框中选择&#xff1a;工程-win32 console Application-填写工程名、选择保存路劲-确定 在新的弹框中&#xff0c;选择&…

BIT小学期-电话号码问题

Output 输出包括两个部分&#xff0c;第一个部分是错误的电话号码&#xff0c;对于这些号码应当按照输入的顺序以原始的形式输出。在输出错误电话号码前输出Error:&#xff0c;随后输出这些号码&#xff0c;如果没有错误的电话号码&#xff0c;则输出Not found. 第二部分是重…

[C++进阶]AVL树

前面我们说了二叉搜索树在极端条件下时间复杂度为O(n),本篇我们将介绍一种对二叉搜索树进行改进的树——AVL树 一、AVL 树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找效率低下。因此&#xff0c;两位…

6个Python小游戏项目源码【免费】

6个Python小游戏项目源码 源码下载地址&#xff1a; 6个Python小游戏项目源码 提取码: bfh3