uc_12_进程间通信IPC_有名管道_无名管道

2023-12-01 13:45

本文主要是介绍uc_12_进程间通信IPC_有名管道_无名管道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1  内存壁垒

       进程间天然存在内存壁垒,无法通过交换虚拟地址直接进行数据交换:

        每个进程的用户空间都是0~3G-1(32位系统),但它们所对应的物理内存却是各自独立的。系统为每个进程的用户空间维护一张专属于该进程的内存映射表,记录虚拟内存到物理内存的对应关系,因此在不同进程之间交换虚拟内存地址是毫无意义的。

        所有进程的内核空间都是3G~4G-1,它们所对应的物理内存只有一份,系统为所有进程的内核空间维护一张内存映射表init_mm.pgd,记录虚拟内存到物理内存的对应关系,因此不同进程通过系统调用所访问的内核代码和数据是同一份。

        用户空间的内存映射表会随着进程的切换而切换,内核空间的内存映射表不变:

        

        Unix/Linux系统(32位)中的每个进程都拥有4G字节大小的专属于自己的虚拟内存空间,出去内核空间的1G,每个进程都有一张独立的内存映射表(内存分页表)记录着虚拟内存页和物理内存页之间的映射关系。

        同一个虚拟内存地址,在不同的进程中,会被映射到完全不同的物理内存区域,因此在多个进程之间以交换虚拟内存地址的方式交换数据是不可能的。

        鉴于进程之间天然存在的内存壁垒,要想实现多个进程间的数据交换,就必须提供一种专门的机制,这就是所谓的进程间通信(InterProcessCommunication,IPC

2  进程间通信(IPC)的种类

2.1  命令行参数

        在通过exec ()函数创建新进程时,可以为其指定命令行参数——借助命令行参数,可将创建者进程的某些数据传入新进程

        execl ("./login", "login", "username", "password", NULL);

2.2  环境变量

        类似地,也可在调用exec ()函数时为新进程提供环境变量:

        sprintf (envp[0], "USERNAME=%s", username);

        sprintf (envp[1], "PASSWORD=%s", password);

        execl ("./login", "login", NULL, envp);

2.3  内存映射文件

        通信双方分别将自己的一段虚拟内存映射到同一个文件中:mmap()

2.4  管道

        管道是Unix系统中最古老的进程间通信方式,并且所有的Unix系统和包括Linux系统在内的各种类Unix系统也都提供这种进程间通信机制。管道有2种限制:

        1  管道都是半双工的,数据只能沿着一个方向流动,类似对讲机,而非手机。

        2  管道只能在具有公共祖先的进程之间使用。通常一个管道由一个进程创建,然后该进程通

            过fork()函数创建子进程,父子进程之间通过管道交换数据。

        大多数Unix/Linux系统出了提供传统意义上的无名管道以外,还提供有名管道,对后者而言,第2中限制已不复存在。

2.5  共享内存

        共享内存允许两个或两个以上的进程共享同一块给定的内存区域。因为数据不需要在通信诸方之间来回复制,所以这是速度最块的一种进程间通信方式。

2.6  消息队列

        消息队列是由系统内核负责维护并可在多个进程之间共享存取的消息链表。优点是:

        传输可靠、流量受控、面向有结构的记录、支持按类型过滤。

2.7  信号量

        与共享内存和消息队列不同,信号量并不是为了解决进程间的数据交换问题。

        信号量关注的是有限的资源如何在无限的用户间合理分配,即资源竞争问题。

2.8  本地套接字

        BSD版本的有名管道。编程模型和网络通信统一。

3  有名管道(FIFO)

        有名管道是一种特殊的文件,它的路径名存在于文件系统中。

        有名管道文件在磁盘上只有i节点,没有数据块,也不保存数据。数据由内核操作。

3.1  mkfifo 命令

         通过shell命令mkfifo,基于有名管道实现进程间通信的逻辑模型:

        

        通过mkfifo命令可以创建有名管道文件:

                $ mkfifo myfifo

        即使是毫无亲缘关系的进程,也可以通过有名管道文件通信:

                $ echo 'Hello, FIFO!' > myfifo

                $ cat myfifo

                Hello, FIFO!

3.2  mkfifo()函数

        通过mkfifo()函数,基于有名管道实现进程间通信的编程模型:

        

        #include <sys/stat.h>

        int mkfifo (char const* pathname,  mode_t mode);

                功能:创建有名管道(文件)

                pathname:有名管道文件的路径

                mode:权限模式

                返回值:成0-1 

//wfifo.c  写入有名管道文件
#include<stdio.h>
#include<string.h>
#include<unistd.h>// write() close()
#include<fcntl.h>// open()
#include<sys/stat.h>// mkfifo()int main(void){//创建有名管道printf("%d进程:创建有名管道\n",getpid());if(mkfifo("./fifo",0664) == -1){perror("mkfifo");return -1;}//打开有名管道printf("%d进程:打开有名管道\n",getpid());int fd = open("./fifo",O_WRONLY);if(fd == -1){perror("open");return -1;}//写入有名管道printf("%d进程:发送数据\n",getpid());for(;;){//通过键盘获取数据 scanf fgets read fread fscanf char buf[64] = {};fgets(buf,sizeof(buf),stdin); //这里不用减1,fgets()会自动减!!//当输入!时退出循环if(strcmp(buf,"!\n") == 0){break;}//写入管道文件if(write(fd,buf,strlen(buf)) == -1){perror("write");return -1;}}//关闭有名管道printf("%d进程:关闭有名管道\n",getpid());close(fd);//删除有名管道printf("%d进程:删除有名管道\n",getpid());unlink("./fifo");printf("%d进程:大功告成\n",getpid());return 0;
}//编译后,开两终端,一个执行wfifo,一个执行rfifo
//rfifo.c  读取有名管道文件
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>int main(void){//打开有名管道printf("%d进程:打开有名管道\n",getpid());int fd = open("./fifo",O_RDONLY);if(fd == -1){perror("open");return -1;}//读取有名管道printf("%d进程:接收数据\n",getpid());for(;;){//读取有名管道char buf[64] = {};ssize_t size = read(fd,buf,sizeof(buf) - 1);if(size == -1){perror("read");return -1;}if(size == 0){printf("%d进程:对方关闭管道文件\n",getpid());break;}//显示printf("%s",buf);}//关闭有名管道printf("%d进程:关闭有名管道\n",getpid());close(fd);printf("%d进程:大功告成\n",getpid());return 0;
}//编译后,开两终端,一个执行wfifo,一个执行rfifo

 

4  无名管道(PIPE)

        通过pipe()函数,基于无名管道实现进程间通信的编程模型(5步):

        1)父进程调用pipe()函数在系统内核中创建无名管道对象,

             并通过该函数的输出参数pipefd,

             获得分别用于写该管道的两个 文件描述符pipefd[0]和pipefd[1]。

                

        2) 父进程调用fork()函数,创建子进程。

               子进程复制父进程的文件描述符表,因此子进程同样持有pipefd[0]和pipefd[1]  。

                

        3) 负责写数据的进程关闭无名管道对象的读端描述符pipefd[0],

               复测读数据的进程关闭无名管道对象的写端描述符pipefd[1]。

                

        4)父子进程通过无名管道对象以半双工的方式传输数据。 

              如果需要在父子进程间双向通信,一般会创建两个管道,一个从父流向子,一个相反。

                

        5)父子进程分别关闭自己所持有的写端或读端文件描述符。

              在相关联的所有文件描述符都被关闭后,该无名管道对象即从内核中被销毁。

                

//pipe.c  无名管道演示
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>int main(void){//父进程创建无名管道printf("%d进程:创建无名管道\n",getpid());int fd[2];//用来输出管道读端写端描述符if(pipe(fd) == -1){perror("pipe");return -1;}printf("fd[0] = %d\n",fd[0]);printf("fd[1] = %d\n",fd[1]);//父进程创建子进程pid_t pid = fork();if(pid == -1){perror("fork");return -1;}//子进程代码,从管道中读取数据if(pid == 0){printf("%d进程:接受数据\n",getpid());printf("%d进程:关闭写端\n",getpid());close(fd[1]);for(;;){//通过读端描述符读取数据char buf[64] = {};ssize_t size = read(fd[0],buf,sizeof(buf)-1);if(size == -1){perror("read");return -1;}if(size == 0){printf("%d进程:对方关闭写端描述符\n",getpid());break;}//显示printf("--->%s",buf);}printf("%d进程:关闭读端\n",getpid());close(fd[0]);printf("%d进程:大功告成\n",getpid());return 0;//!!!!}//父进程代码,向管道中写入数据printf("%d进程:发送数据\n",getpid());printf("%d进程:关闭读端\n",getpid());close(fd[0]);for(;;){//通过键盘获取数据char buf[64] = {};fgets(buf,sizeof(buf),stdin);//!退出if(strcmp(buf,"!\n") == 0){break;}//通过管道写端写入if(write(fd[1],buf,strlen(buf)) == -1){perror("write");return -1;}}printf("%d进程:关闭写端\n",getpid());close(fd[1]);//父进程收尸if(wait(NULL) == -1){perror("wait");return -1;}printf("%d进程:大功告成\n",getpid());return 0;
}//编译执行

5  管道须知

         1)从写端已被关闭的管道读取,只要管道中还有数据,依然可以被正常读取,一致到管道中没有数据了,这时read()函数会返回0(不是返回-1,也不是阻塞),指示读到文件尾。

        2)向读端已被关闭的管道写入,会直接出发SIGPIPE(13)信号。该信号的默认操作是终止执行写入动作的进程。但如果执行写入动作的进程事先13信号的处理设置为忽略或捕获,则write()函数会返回-1,并置errno为EPIPE。

        3)系统内核通常为每个管道维护一个4096字节的内存缓冲区(新系统更大)。如果写管道时发现缓冲区的空闲空间不足以容纳此次write()函数所要写入的字节,则write()函数阻塞,直到缓冲区的空闲空间变得足够大为止。

        4)读取一个写段处于开放状态的空管道,直接导致read()函数阻塞

6  管道符 | 的原理

        1)Unix/Linux系统中的多数shell环境都支持,

              通过管道符号 "|" 将前一个命令的输出作为后一个命令的输入:

                $ ls -l /etc | more           实现按空格键翻页

                $ ifconfig | grep inet      过滤得到ip地址

        2)系统管理员通常用这种方法,把多个简单的命令连接成一条工具链,解决复杂问题:

                $ 命令1  |  命令2  |  命令3

        3)假设用户输入以下命令:a | b,管道符工作原理如下:

                Shell进程调用fork()函数创建子进程A

                子进程A调用pipe()函数创建无名管道,而后执行:

                                dup2 (pipefd[1], STDOUT_FILENO);

                子进程A调用fork()函数创建孙进程B,孙进程B执行:

                                dup2 (pipefd[0], STDOUT_FILENO);

                子进程A和孙进程B分别调用exec ()函数创建a、b进程。

                a进程所有的输出都通过写段进入管道,而b进程所有的输入则来自管道的读端。

                

这篇关于uc_12_进程间通信IPC_有名管道_无名管道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/441381

相关文章

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

java 进程 返回值

实现 Callable 接口 与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。 public class MyCallable implements Callable<Integer> {public Integer call() {return 123;}} public static void main(String[] args

C#关闭指定时间段的Excel进程的方法

private DateTime beforeTime;            //Excel启动之前时间          private DateTime afterTime;               //Excel启动之后时间          //举例          beforeTime = DateTime.Now;          Excel.Applicat

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的

Golang进程权限调度包runtime

关于 runtime 包几个方法: Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行GOMAXPROCS:设置最大的可同时使用的 CPU 核数Goexit:退出当前 goroutine(但是defer语句会照常执行)NumGoroutine:返回正在执行和排队的任务总数GOOS:目标操作系统NumCPU:返回当前系统的 CPU 核数量 p

如何保证android程序进程不到万不得已的情况下,不会被结束

最近,做一个调用系统自带相机的那么一个功能,遇到的坑,在此记录一下。 设备:红米note4 问题起因 因为自定义的相机,很难满足客户的所有需要,比如:自拍杆的支持,优化方面等等。这些方面自定义的相机都不比系统自带的好,因为有些系统都是商家定制的,难免会出现一个奇葩的问题。比如:你在这款手机上运行,无任何问题,然而你换一款手机后,问题就出现了。 比如:小米的红米系列,你启用系统自带拍照功能后

flume系列之:记录一次flume agent进程被异常oom kill -9的原因定位

flume系列之:记录一次flume agent进程被异常oom kill -9的原因定位 一、背景二、定位问题三、解决方法 一、背景 flume系列之:定位flume没有关闭某个时间点生成的tmp文件的原因,并制定解决方案在博主上面这篇文章的基础上,在机器内存、cpu资源、flume agent资源都足够的情况下,flume agent又出现了tmp文件无法关闭的情况 二、

C++及AIDL服务间通信相关

只做细节展示,没有什么逻辑,陆续补充中

C++编程:ZeroMQ进程间(订阅-发布)通信配置优化

文章目录 0. 概述1. 发布者同步发送(pub)与订阅者异步接收(sub)示例代码可能的副作用: 2. 适度增加缓存和队列示例代码副作用: 3. 动态的IPC通道管理示例代码副作用: 4. 接收消息的超时设置示例代码副作用: 5. 增加I/O线程数量示例代码副作用: 6. 异步消息发送(使用`dontwait`标志)示例代码副作用: 7. 其他可以考虑的优化项7.1 立即发送(ZMQ_IM