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系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

一文解密Python进行监控进程的黑科技

《一文解密Python进行监控进程的黑科技》在计算机系统管理和应用性能优化中,监控进程的CPU、内存和IO使用率是非常重要的任务,下面我们就来讲讲如何Python写一个简单使用的监控进程的工具吧... 目录准备工作监控CPU使用率监控内存使用率监控IO使用率小工具代码整合在计算机系统管理和应用性能优化中,监

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Windows的CMD窗口如何查看并杀死nginx进程

《Windows的CMD窗口如何查看并杀死nginx进程》:本文主要介绍Windows的CMD窗口如何查看并杀死nginx进程问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows的CMD窗口查看并杀死nginx进程开启nginx查看nginx进程停止nginx服务

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析