【嵌入式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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server数据库安装

原文&#xff1a;https://blog.c12th.cn/archives/26.html SQL Server数据库安装 测试&#xff1a;笔记本原装操作系统&#xff1a;Windows 10 家庭中文版 资源分享链接&#xff1a;提取码&#xff1a;qbt2 注意事项&#xff1a; 请严格按照步骤安装&#xff0c;SQL软件安装较…

DDMA信号处理以及数据处理的流程---doa估计

Hello,大家好,我是Xiaojie,好久不见,欢迎大家能够和Xiaojie一起学习毫米波雷达知识,Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程,本系列文章将从目标生成、信号仿真、测距、测速、cfar检测、测角、目标聚类、目标跟踪这几个模块逐步介绍,这个系列的…

鼠标与键盘交互设计

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在海龟绘图中&#xff0c;也支持与鼠标或键盘的交互操作。它提供了监听键盘按键事件、鼠标事件以及定时器等方法&#xff0c;下面分别进行介绍。 1键…

tensorflow学习:错误 InternalError: Dst tensor is not initialized

tensorflow学习&#xff1a;错误 InternalError: Dst tensor is not initialized_dst tensor is not initialized.-CSDN博客https://blog.csdn.net/wanglitao588/article/details/77033659

基于51单片机的RFID门禁系统-LCD12864显示

一.硬件方案 本RFID系统设计可分为硬件部分和软件部分。硬件部分以MFRC522射频识别模块为核心&#xff0c;结合主控模块STC89C52设计系统的外围硬件电路&#xff0c;实现对射频卡的控制与MCU之间的互通。软件部分采用C语言进行系统的下位机程序的开发&#xff0c;完成与IC卡之…

后端学习笔记:Python基础

后端学习笔记&#xff1a;Python基础 数据类型&#xff1a; Python中主要有以下几种常用的基本数据类型&#xff1a; String 字符串类型&#xff0c;用单引号或者双引号引用Number 数字类型&#xff0c;包括浮点数&#xff0c;整数&#xff0c;长整数和复数List 列表项&…

【Linux】常用基本命令

wget网址用于直接从网上下载某个文件到服务器&#xff0c;当然也可以直接从网上先把东西下到本地然后用filezilla这个软件来传输到服务器上。 当遇到不会的命令时候&#xff0c;可以使用man “不会的命令”来查看这个命令的详细信息。比如我想要看看ls这个命令的详细用法&…

一个土木工程专业背景的开发者,讲述开源带给他的力量

在前段时间我们举办的“TDengine Open Day”第一季技术沙龙中&#xff0c;TDengine 应用研发高级工程师谭雪峰进行的“开源之路&#xff1a;程序员的成长与探索”主题分享获得了众多参会者的好评。谭雪峰从自身独特的职业发展经历出发&#xff0c;分享了自己在开源领域的种种收…

替换或重写Tomcat内置的404页面

替换或重写Tomcat内置的404页面 准备一个Tomcat隐藏Tomcat的相关信息纯净版的Tomcat解决Tomcat启动乱码的问题 替换或重写Tomcat内置的404页面创建新的首页和错误页面页面代码如下所示&#xff1a;创建首页index.html创建error_404.html页面创建其他错误页面创建编写web.xml&am…

工业软件的分类与选择策略:针对中小企业的实际应用考量

工业软件是现代工业体系的“大脑”&#xff0c;已经渗透到几乎所有工业领域的核心环节&#xff0c;是现代产业之“魂”&#xff0c;是制造强国之重器。工业软件通过优化生产流程、实时监控设备状态、实现自动化控制等功能&#xff0c;可以帮助企业显著提升生产效率和质量&#…

让工厂像手机一样更“聪明”

手机&#xff0c;作为我们日常生活中不可或缺的一部分&#xff0c;以其智能、便捷、高效的特点&#xff0c;彻底改变了我们的沟通、娱乐和工作方式。那么&#xff0c;想象一下&#xff0c;如果工厂能像手机一样便捷&#xff0c;那么生产过程中的每一个环节都将变得触手可及。通…

jstack的火焰图使用说明

1、jstack的官方文档说明 How to use Flame Graph? - Fast thread 2、jstack的文件分析网站&#xff0c;可以关注cpu消耗比较高的线程和火焰图 GC log analysis error

[word] Word如何删除所有的空行? #职场发展#学习方法

Word如何删除所有的空行&#xff1f; 很多网友从网页复制文字粘贴到word文档后发现段落之间有空行&#xff0c;如果文字不多&#xff0c;手动删除这些空行也没有多少工作量&#xff0c;但是如果文字的字数达到成千上万&#xff0c;一个个手动删除这些空行还是很繁琐的。那么&a…

Linux-笔记 高级I/O操作

前言 I/O&#xff08;Input/Output&#xff0c;输入/输出&#xff09;是计算机系统中的一个重要组成部分&#xff0c;它是指计算机与 外部世界之间的信息交流过程。I/O 操作是计算机系统中的一种基本操作&#xff0c;用于向外部设备&#xff08;如 硬盘、键盘、鼠标、网络等&am…

Spring Cloud - 开发环境搭建

1、JDK环境安装 1、下载jdk17&#xff1a;下载地址&#xff0c;在下图中红色框部分进行下载 2、双击安装&#xff0c;基本都是下一步直到完成。 3、设置系统环境变量&#xff1a;参考 4、设置JAVA_HOME环境变量 5、在PATH中添加%JAVA_HOME%/bin 6、在命令行中执行&#xff1a;j…

Python学习笔记17:进阶篇(六)代码测试

代码测试 代码测试是软件开发过程中的关键环节&#xff0c;旨在确保代码质量、功能正确性以及性能符合预期。 在开发过程中&#xff0c;进行代码测试有很多好处&#xff1a; 提高软件质量&#xff1a;通过发现并修复错误&#xff0c;测试有助于提升软件的功能性、可靠性和稳…

黑马程序员-瑞吉外卖-前六章

黑马程序员瑞吉外卖 文章目录 1、开发环境搭建1.1 数据库环境1.2 maven项目搭建1.2.1 新建项目1.2.2 整合MyBatisPlus1.2.3 整合Druid1.2.4 配置文件application.yml1.2.5 尝试启动项目1.2.6 将前端静态页面引入到项目中 2、 后台系统登录功能2.1 需求分析2.2 代码开发2.2.1 创…

解析网页数据并且处理网页正则表达式与re模块

目录 一、解析网页数据的技术 1&#xff0e;正则表达式 2&#xff0e;XPath 3&#xff0e;Beautiful Soup 4&#xff0e;JSONPath 二、正则表达式的语法 1&#xff0e;元字符 2&#xff0e;预定义字符集 三、 re 模块的使用 1&#xff0e;创建 Pattern 对象 2&#…

libreoffice报: error while loading shared libraries: libcups.so.2: cannot

切换到cd /opt/libreoffice7.1/programlibreoffice查看版本的时候报:/opt/libreoffice7.1/program/soffice.bin: error while loading shared libraries: libcups.so.2: cannot open shared object file: No such file or directory ./soffice --version解决办法&#xff1a; …

MySQL之复制(六)

复制 复制拓扑 拥有备库的主-主结构 另外一种相关的配置是为每个主库增加一个备库&#xff0c;如图所示。这种配置的优点是增加了冗余&#xff0c;对于不同地理位置的复制拓扑&#xff0c;能够消除站点单点失效的问题。你也可以像平常一样&#xff0c;将读查询分配到备库上。…