【嵌入式Linux】<总览> 进程间通信(更新中)

文章目录

前言

一、管道

1. 概念

2. 匿名管道

3. 有名管道

二、内存映射区

1. 概念

2. mmap函数

3. 进程间通信(有血缘关系)

4. 进程间通信(没有血缘关系)

5. 拷贝文件


前言

在文章【嵌入式Linux】<总览> 多进程中已经介绍了进程的相关概念和创建多个进程的方法。本篇聚焦于进程间通信的方式,若涉及版权问题请联系本人删除。


一、管道

1. 概念

  • 管道是进程间通信的方式之一,其数据的流动是单向的。
  • 管道本质上是内核中的一块内存,即内存缓冲区。这块内存中的数据存储在环形队列中,其默认空间为4K。这个环形队列的队头就是读指针,队尾就是写指针。
  • 由于管道是内核中的内存,需要使用系统调用的文件IO函数read和write函数来读写。每次读完后,数据相当于出队了!读写默认是阻塞:①读管道:管道中没有数据就会阻塞,直到有数据到来;②写管道:管道空间满了就会阻塞,直到有数据出队。通过fcntl函数能修改为非阻塞。
  • 管道是独立于任何进程的,并且充当了两个进程用于数据通信的载体,只要两个进程能够得到同一个管道的入口和出口(读端和写端的文件描述符),那么他们之间就可以通过管道进行数据的交互。
  • 当读端关闭了,管道破裂,写端进程直接退出;当写端关闭了,读端就会将管道中剩余内容读取完,最后返回0。

2. 匿名管道

【1】介绍:匿名管道是管道的一种,其没有具体的名字,只能实现拥有血缘关系的进程进行通信。

【2】创建匿名管道:pipe函数

#include <unistd.h>int pipe(int pipefd[2]);//参数说明:pipefd是传出的参数//pipefd[0]对应读端的文件描述符//pipefd[1]对应写端的文件描述符//返回值:0表示成功,-1表示失败

【3】进程通信的注意事项:在创建子进程之后,父进程中的读端和写端文件描述符都会被复制到子进程中。若子进程只有写的需求,那么可以将读端的文件描述符给close。若父进程只有读的需求,那么可以将写端的文件描述符给close。

程序示例:子进程执行"ps"命令,并将结果写入管道中;父进程读管道,将结果显示在终端上。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <string.h>int main(int argc, char **argv)
{/* 1.创建匿名管道 */int fd[2];int pipeRet = pipe(fd);if (pipeRet < 0) {perror("创建管道失败");return -1;}/* 2.创建子进程 */pid_t pid = fork();if (pid < 0) {perror("创建子进程失败");return -1;}/* 3.子进程操作 */if (pid == 0) {close(fd[0]);//关闭管道的读端dup2(fd[1], STDOUT_FILENO);//重定向标准输出execlp("ps", "ps", NULL);//执行ps程序perror("execlp错误");}/* 4.父进程操作 */if (pid > 0) {close(fd[1]);//关闭管道的写端//循环读取管道中的数据char buf[4096];while (1) {memset(buf, 0, sizeof(buf));//清空缓存int len = read(fd[0], buf, sizeof(buf));//读取管道if (len <= 0) {//异常或读完break;}printf("%s", buf);//输出内容至终端}close(fd[0]);//等待子进程wait(NULL);}return 0;
}

3. 有名管道

【1】介绍:有名管道在磁盘上有实体文件,文件类型为p(不存储真实数据)。

  • 有名管道可以称为fifo.
  • 有名管道的磁盘大小永远是0,因为有名管道依旧是将数据存储到内存缓冲区。打开这个磁盘文件,就能获取对应的文件描述符。
  • 任意两个进程都能利用有名管道来进行通信。

【2】创建有名管道:

  • 方式一:终端命令"mkfifo  有名管道名字"
  • 方式二:调用函数mkfifo,其细节如下:
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);//参数说明://pathname: 保存的文件名//mode: 创建的管道文件权限
//返回值:成功返回0,失败返回-1

【3】进程间通信注意事项:有名管道的读写首先需要调用open函数来打开管道文件,若管道文件中只有读端或只有写端被打开,那么就会阻塞在open函数,直到两端都被打开。

程序示例:写两个程序,一个作为写管道程序,一个作为读管道程序。①写管道:创建有名管道文件,打开该文件,向有名管道中写入数据,关闭文件。②读管道:打开有名管道文件,读取数据,并显示到终端上,关闭文件。

写管道程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include <sys/stat.h>//写管道程序
int main(int argc, char **argv)
{/* 1.创建有名管道 */int mkRet = mkfifo("./pipefile", 0664);if (mkRet < 0) {perror("创建有名管道失败");return -1;}/* 2.打开有名管道 */int fd = open("./pipefile", O_WRONLY);if (fd < 0) {perror("管道文件打开失败");return -1;}/* 3.循环写入数据 */for (int i = 0; i < 5; ++i) {char writeBuf[200] = {0};sprintf(writeBuf, "Hello, Can! 我在努力学习中... %d\n", i);int writeRet = write(fd, writeBuf, sizeof(writeBuf));if (writeRet < sizeof(writeBuf)) {printf("写入错误");break;}sleep(1);//保证能够写入}/* 4.关闭管道文件 */close(fd);return 0;
}

读管道程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>//读管道程序
int main(int argc, char **argv)
{/* 1.打开有名管道 */int fd = open("./pipefile", O_RDONLY);if (fd < 0) {perror("管道文件打开失败");return -1;}/* 2.循环读取数据并打印 */while (1) {char readBuf[1024] = {0};int readRet = read(fd, readBuf, sizeof(readBuf));if (readRet <= 0) {//异常或读完break;}printf("%s", readBuf);//打印读取的内容}/* 3.关闭管道文件 */close(fd);return 0;
}


二、内存映射区

1. 概念

  • 通过mmap函数创建内存映射区是实现进程间通信的方法之一。
  • 内存映射区位于每个进程的用户区(用于加载动态库的那个区域)。
  • 内存映射区的读写是非阻塞的。
  • 内存映射区创建成功之后,得到映射区内存的起始地址,使用内存操作函数读写数据。
  • 机制:多个进程将内存映射区和同一个磁盘文件进行映射。当其中一个进程修改了它的内存映射区,数据会自动同步到磁盘文件。同时,和该磁盘文件建立映射关系的进程中的内存映射区的数据也会实时变化,因此能够实现进程间的通信。
  • 注意:内存映射区使用完后通过munmap函数释放。
#include <sys/mman.h>int munmap(void *addr, size_t length)//参数说明://addr: 内存映射区的起始地址,就是mmap函数的返回值//length: 内存映射区的大小,与mmap函数的第二个参数相同//返回值:成功返回0,失败返回-1

2. mmap函数

【1】头文件:#include <sys/mman.h>

【2】函数原型:void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

【3】参数说明:

  • addr:从动态库加载区的什么位置开始创建内存映射区,一般为NULL来委托内核分配
  • length:创建的内存映射区的大小(单位:字节),实际大小是按4000的整数倍去分配
  • prot:对内存映射区的操作权限
    • PROT_READ: 读内存映射区
    • PROT_WRITE: 写内存映射区
    • PROT_READ | PROT_WRITE:读写内存映射区
  • flags:用于确定是否共享内存映射区
    • MAP_SHARED: 多个进程可以共享数据,进行映射区的数据同步
    • MAP_PRIVATE: 映射区数据是私有的,不能同步给其他进程
  • fd:对应打开的磁盘文件的文件描述符。内存映射区通过此与硬盘文件建立联系。
  • offset:要求>=0并且是4000的倍数,表示硬盘文件从偏移到的位置进行映射。

【4】返回值:成功返回内存映射区的首地址,失败返回MAP_FAILED其实就是(void *) -1

【5】注意事项:

  • length必须要大于0。
  • prot一般都是PROT_READ | PROT_WRITE。
  • 内存映射区创建成功之后, 关闭参数中的文件描述符fd不会影响进程间通信。

3. 进程间通信(有血缘关系)

父进程fork子进程,子进程就会将父进程的虚拟地址空间进行复制。因此,对于有血缘关系的进程来说,通过内存映射区的方式来进行通信是简单的。

【程序实例】如下代码,父进程创建子进程,父进程向内存映射区中写数据,子进程从内存映射区中读数据。注意:磁盘文件的大小不能为0,否则会报错误:Bus error (core dumped)。

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>int main(int argc, char **argv)
{/* 1.打开磁盘文件 */int fd = open("./test.txt", O_RDWR);if (fd < 0) {perror("磁盘文件打开错误");return -1;}/* 2.创建内存映射区 */void * ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap失败");close(fd);return -1;}/* 3.创建子进程,复制父进程的内存映射区 */pid_t pid = fork();if (pid < 0) {perror("fork失败");close(fd);return -1;}/* 4.父进程:写数据到内存映射区 */if (pid > 0) {const char *content = "Hello, Can!";memcpy(ptr, content, strlen(content)+1);wait(NULL);}/* 5.子进程:从内存映射区中读数据 */if (pid == 0) {sleep(1);//由于读写非阻塞,这里保证父进程先行printf("从内存映射区读取的数据:%s\n", (char*)ptr);}/* 6.关闭硬盘文件,释放内存映射区 */close(fd);munmap(ptr, 4000);return 0;
}

4. 进程间通信(没有血缘关系)

进程间没有血缘关系,需要各自创建内存映射区,并且对应的磁盘文件必须是同一个。

【程序实例】两个进程打开同一个磁盘文件,然后各自构建内存映射区。一个进程向内存映射区写数据,另一个进程读数据。

写数据的进程代码:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>//写数据的进程
int main(int argc, char **argv)
{/* 1.打开磁盘文件 */int fd = open("./test.txt", O_RDWR);if (fd < 0) {perror("磁盘文件打开错误");return -1;}/* 2.创建内存映射区 */void * ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap失败");close(fd);return -1;}/* 3.向内存映射区中写数据 */const char *content = "------Hello, Can!------";memcpy(ptr, content, strlen(content)+1);sleep(1);/* 4.关闭硬盘文件,释放内存映射区 */close(fd);munmap(ptr, 4000);return 0;
}

读数据的进程代码:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>//读数据的进程
int main(int argc, char **argv)
{/* 1.打开磁盘文件 */int fd = open("./test.txt", O_RDWR);if (fd < 0) {perror("磁盘文件打开错误");return -1;}/* 2.创建内存映射区 */void * ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap失败");close(fd);return -1;}/* 3.从内存映射区中读数据 */printf("从内存映射区中读数据:%s\n", (char*)ptr);/* 4.关闭硬盘文件,释放内存映射区 */close(fd);munmap(ptr, 4000);return 0;
}

5. 拷贝文件

鉴于内存映射区和磁盘文件的同步关系,可以用来拷贝文件,其流程如下:

①打开原文件,计算原文件大小size,并映射到内存映射区A。

②打开目标文件,拓展大小为size,并映射到内存映射区B。

③拷贝内存映射区A的内容到内存映射区B。

④关闭所有文件,释放所有内存映射区。

注意:mmap中还是需要MAP_SHARED,否则vim和cat都无法识别内容。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdio.h>int main(int argc, char **argv)
{/* 1.打开原文件,并映射到内存映射区A */int fd1 = open("./test.txt", O_RDWR);if (fd1 < 0) {perror("打开原文件失败");return -1;}void *ptrA = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);if (ptrA == MAP_FAILED) {perror("内存映射区A构建失败");return -1;}int size = lseek(fd1, 0, SEEK_END);//原文件大小/* 2.打开目标文件,拓展大小,并映射到内存映射区B */int fd2 = open("./output.txt", O_RDWR | O_CREAT, 0664);if (fd2 < 0) {perror("打开输出文件失败");return -1;}ftruncate(fd2, size);//将输出文件拓展到size大小void *ptrB = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0);if (ptrB == MAP_FAILED) {perror("内存映射区B构建失败");return -1;}/* 3.拷贝A的空间到B */memcpy(ptrB, ptrA, size);/* 4.关闭文件,释放所有内存映射区 */munmap(ptrA, 4000);munmap(ptrB, 4000);close(fd1);close(fd2);return 0;
}

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

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

相关文章

浏览器断点调试(用图说话)

浏览器断点调试&#xff08;用图说话&#xff09; 1、开发者工具2、添加断点3、查看变量值 浏览器断点调试 有时候我们需要在浏览器中查看 html页面的js中的变量值。1、开发者工具 打开浏览器的开发者工具 按F12 &#xff0c;没反应的话按FnF12 2、添加断点 3、查看变量值

清理占道经营商贩自砸西瓜?智慧城管AI视频方案助力城市街道管理

一、背景分析 近日有新闻报道&#xff0c;在山西太原&#xff0c;城管凌晨3时许查处商贩占道经营&#xff0c;商贩将西瓜砸碎一地&#xff0c;引起热议。据悉&#xff0c;事件发生的五龙口街系当地主要街道&#xff0c;来往车辆众多。该商贩长期在该地段占道经营&#xff0c;影…

昇思25天学习打卡营第2天|快速入门

快速入门 操作步骤1.引入依赖包2.下载Mnist数据集3.划分训练集和测试集4.数据预处理5.网络构建6.模型训练7.保存模型8.加载模型9.模型预测 今天通过昇思大模型平台AI实验室提供的在线Jupyter工具&#xff0c;快速入门MindSpore。 目标&#xff1a;通过MindSpore的API快速实现一…

云计算 | 期末梳理(下)

1.模运算 2. 拓展欧几里得算法 3.扩散和混淆、攻击的分类 香农的贡献:定义了理论安全性,提出扩散和混淆原则,奠定了密码学的理论基础。扩散:将每一位明文尽可能地散布到多个输出密文中去,以更隐蔽明文数字的统计特性。混淆:使密文的统计特性与明文密钥之间的关系尽量复杂…

深入解析直播带货系统源码:短视频商城APP开发全攻略

本篇文章&#xff0c;小编将深入解析直播带货系统的源码&#xff0c;并为开发短视频商城APP提供全攻略&#xff0c;助力开发者打造高效、稳定的带货平台。 一、直播带货系统概述 直播带货系统主要由直播模块、商品管理模块、订单处理模块、用户管理模块、以及支付模块等组成。…

Ubuntu20.04使用Samba

目录 一、Samba介绍 Samba 的主要功能 二、启动samba 三、主机操作 四、Ubuntu与windows系统中文件互联 五、修改samba路径 一、Samba介绍 Samba 是一个开源软件套件&#xff0c;用于在 Linux 和 Unix 系统上实现 SMB&#xff08;Server Message Block&#xff09;协议…

速卖通自养号测评:安全高效的推广手段

在速卖通平台上&#xff0c;卖家们常常寻求各种方法来提升商品的曝光、转化率和店铺权重。其中&#xff0c;自养号测评作为一种低成本、高回报的推广方式&#xff0c;备受关注。然而&#xff0c;若操作不当&#xff0c;也可能带来风险。以下是如何安全有效地进行自养号测评的指…

VS Code 使用 Makefile 运行 CPP项目

Installing the MinGW-w64 toolchainCMake Toolsmakelist.txt报错 1报错 2报错 3生成了 Makefile &#xff0c;如何使用 make 命令 Installing the MinGW-w64 toolchain 参见文档 将 GCC 与 MinGW 结合使用 CMake Tools 参见文档 Linux 上的 CMake 工具入门 CMake 的使用 …

关于Pycharm右下角不显示解释器interpreter的问题解决

关于Pycharm右下角不显示解释器interpreter的问题 在安装新的Pycharm后&#xff0c;发现右下角的 interpreter 的选型消失了&#xff1a; 觉得还挺不习惯的&#xff0c;于是网上找解决办法&#xff0c;无果。 自己摸索了一番后&#xff0c;发现解决办法如下&#xff1a; 勾…

37岁,被裁员,失业三个月,被面试官嫌弃“太水”:就这也叫10年以上工作经验?

今年部门要招两个自动化测试&#xff0c;这几个月我面试了几十位候选人。发现一个很奇怪的现象&#xff0c;面试中一问到元素定位、框架api、脚本编写之类的&#xff0c;很多候选人都对答如流。但是一问到实际项目&#xff0c;比如“项目中UI自动化和接口自动化如何搭配使用&am…

电商平台家电以旧换新销售额增长超80%

记者近日从国家发展和改革委员会举办的新闻发布会上获悉&#xff0c;今年1—5月份&#xff0c;主要电商平台家电以旧换新销售额增长超过80%&#xff0c;以旧换新成为推动家电消费增长的重要因素。 今年3月&#xff0c;国务院印发了《推动大规模设备更新和消费品以旧换新行动方…

天花板国际幼儿园是怎样的?一起来听听天津惠灵顿幼儿园园长分享

上周&#xff0c;天津惠灵顿幼儿园举行了精彩的毕业典礼。一如往常&#xff0c;这是一个回顾过去、展望未来的机会。这届毕业班有一些孩子是四年前园长加入惠灵顿学校的时入园的。他们从小小班启航&#xff0c;在这所天津国际幼儿园开始了他们的惠灵顿之旅。四年来&#xff0c;…

java基于ssm+jsp 班级同学录网站

1前台首页功能模块 班级同学录网站&#xff0c;在前台首页可以查看首页、公告信息、校友风采、论坛信息、我的、跳转到后台、客服等内容&#xff0c;如图1所示。 图1前台首页功能界面图 用户注册&#xff0c;在用户注册页面可以填写用户名、姓名、头像、性别、手机号码、邮箱等…

[leetcode]unique-paths 不同路径

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> f(m, vector<int>(n));for (int i 0; i < m; i) {f[i][0] 1;}for (int j 0; j < n; j) {f[0][j] 1;}for (int i 1; i &l…

站在巨人的肩膀上 C语言理解和简单练习(包含指针前的简单内容)

1.格式化的输入/输出 1.1printf函数 printf函数你需要了解的就是转换说明&#xff0c;转换说明的作用是将内存中的二进制转换成你所需要的格式入%d就是将内存中存储的变量的二进制转化为十进制并打印出来&#xff0c;同时我们可以在%X的转换说明对精度和最小字段宽度的指定&a…

ORA-6544[pevm_peruws_callback-1][604] is caused (Doc ID 2638095.1)

ORA-6544[pevm_peruws_callback-1][604] is caused (Doc ID 2638095.1)​编辑To Bottom In this Document Symptoms Cause Solution References Applies to: Oracle Database - Enterprise Edition - Version 12.2.0.1 and later Information in this document applies to an…

C++并发之环形队列(ring,queue)

目录 1 概述2 实现3 测试4 运行 1 概述 最近研究了C11的并发编程的线程/互斥/锁/条件变量&#xff0c;利用互斥/锁/条件变量实现一个支持多线程并发的环形队列&#xff0c;队列大小通过模板参数传递。 环形队列是一个模板类&#xff0c;有两个模块参数&#xff0c;参数1是元素…

[学习笔记] 禹神:一小时快速上手Electron笔记,附代码

课程地址 禹神&#xff1a;一小时快速上手Electron&#xff0c;前端Electron开发教程_哔哩哔哩_bilibili 笔记地址 https://github.com/sui5yue6/my-electron-app 进程通信 桌面软件 跨平台的桌面应用程序 chromium nodejs native api 流程模型 main主进程 .js文件 node…

Verilog HDL语法入门系列(二):Verilog的语言文字规则

目录 1 空白符和注释2 整数常量和实数常量3 整数常量和实数常量4 字符串&#xff08;string)5 格式符与转义符6 标识符(identifiers) 微信公众号获取更多FPGA相关源码&#xff1a; 1 空白符和注释 2 整数常量和实数常量 Verilog中&#xff0c;常量(literals)可是整数也可以是…

照片放大工具Topaz Gigapixel AI for Mac v7.1.2

Topaz Gigapixel AI软件是一款相当高效的PC端图像大小调整工具&#xff0c;更是一款能够为摄影师、设计师以及图像处理爱好者带来革命性体验的强大软件。它凭借先进的深度学习技术&#xff0c;打破了传统图像大小调整的限制&#xff0c;实现了真正意义上的无损放大和图像恢复。…