Linux之进程间通信——管道(上)

简介: Linux之进程间通信——管道(上)

前言

管道是Linux中最古老的进程间通信的方式,本文介绍了进程间通信的相关概念,主要介绍了匿名管道和命名管道。


一、进程间通信

1.概念

什么是进程间通信?

进程具有独立性,每个进程之间是互不干扰的状态,但是一个大的项目,不会只让一个进程独立完成所有工作,所以进程间是一定会有通信的情况,同时进程间通信的成本一定不低(通信的本质:OS需要直接或间接给通信双方的进程提供“内存空间”,而要通信的进程,必须看到一份公共的资源)。

成本不低的原因:我们需要让不同的进程看到同一份资源。

2.目的

进程间通信的目的是:

  • 数据传输:一个进程需要将它的数据发送给另一个进程;
  • 资源共享:多个进程之间共享同一个资源;
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某件事(例如:子进程终止时,要通知父进程);
  • 进程控制:有的进程希望完全控制另一个进程大的执行(例如:Debug进程)。

为什么需要进程间通信?

有时候我们需要多进程协同,共同完成某种业务内容。例如:管道。

3.进程间通信分类

我们所说的不同通信种类本质上是按照:上面所说的资源是OS中的哪一个模块提供的来划分的。如:文件系统提供的叫做管道通信;OS对应的System V模块提供的……

  1. 采用标准的做法:System V进程间通信(聚焦在本地通信,如共享内存)、POSIX进程间通信(让通信过程可以跨主机);
  2. 采用文件的做法:管道——基于文件系统(匿名管道、命名管道)。

本文主要介绍的是管道。

二、管道

1.管道介绍

  1. 管道是Unix中最古老的进程间通信的方式,我们把连接两个进程的数据流称为“管道”。
  2. 任何一个文件包括两套资源:1.file的操作方法;2.属于自己的内核缓冲区。所以父子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以在对应的文件缓冲区读取,这样就可以完成进程间通信。这种方式中被子进程写入和父进程读取的文件,我们称为管道文件。管道文件本质就是内存级文件(不需要IO)。
  3. 两个进程如何看到同一个管道文件?
    fork创建子进程,管道创建时,要分别以读和写的方式打开同一个文件(如果父进程是以只读或只写的方式(其中一种方式)打开文件,子进程也只会继承只读或者只写,父子双方打开文件的方式一样,导致无法完成单向通信)
    父子进程正确进行管道通信的方式:
  4. 父进程创建管道,同时具有读写权限。
  5. 父进程创建子进程,子进程继承管道以及对管道的读写
  6. 父进程关闭读取端,子进程关闭写入端。自此父进程只能向管道中写入,子进程只能从管道中读取,完成了父子进程的单向通讯。

2.管道分类

管道根据是否具有文件名,分为匿名管道和有名管道。

1.匿名管道

通过父进程创建子进程,子进程继承文件地址的方式,让父子进程看到同一个内存级文件,该内存级文件没有名称,则就称为匿名管道。匿名管道可以用来进行父进程和子进程之间的进程间通信。

pipi

pipi创建一个管道,只需要调用pipe系统调用。

它的头文件是unistd.h;调用成功就返回0,调用失败就返回-1;其参数是输出型参数。

创建管道文件,打开读写端

1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 int main()
  5 {
  6         int fds[2];
  7         int n = pipe(fds);
  8         assert(n == 0);
  9         printf("fds[0]:%d\n",fds[0]);
 10         printf("fds[1]:%d\n",fds[1]);
 11         return 0;
 12 }

因此,fds[0]:3代表读取,fds[1]:4代表写入;

fork子进程

1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 int main()
  5 {
  6         int fds[2];
  7         int n = pipe(fds);
  8         assert(n == 0);
  9         pid_t id = fork();
 10         assert(id >= 0);
 11         if(id == 0)//子进程
 12         {
 13                 //子进程通信
 14                 exit(0);
 15         } 
 16         //父进程
 17         n = waitpid(id, NULL, 0);
 18         assert(n == id);
 19         return 0;
 20 }

关闭父进程的写入端,关闭子进程的读取端

1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         sleep(1);//每一秒写一次
 30                 }
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 exit(0);
 33         }
 34         //父进程
 35         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 36         while(1)
 37         {
 38                 char buffer[1024];
 39                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 40                 if(s > 0) buffer[s] = 0;
 41                 printf("Get Message : %s | mypid = %d\n", buffer, getpid());
 42         }
 43         n = waitpid(id, NULL, 0);
 44         assert(n == id);
 45         close(fds[0]);//退出程序前,关闭读取端
 46         return 0;
 47 }

读写特征

  1. 读快,写慢
    子进程休眠时,不再写入,父进程仍在读取(如果管道内没有数据,而读端在读取,则会默认直接阻塞当前正在读取的进程);
1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         sleep(50);//每一秒写一次
 30                 }
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 exit(0);
 33         }
 34         //父进程
 35         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 36         while(1)
 37         {
 38                 char buffer[1024];
 39                 printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
 40                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 41                 printf("888888888888888888888888888888888888!!\n");
 42                 if(s > 0) buffer[s] = 0;
  43                 printf("Get Message : %s | mypid = %d\n", buffer, getpid());
 44         }
 45         n = waitpid(id, NULL, 0);
 46         assert(n == id);
 47         close(fds[0]);//退出程序前,关闭读取端
 48         return 0;
 49 }

2.读慢写快

读取管道的进程一直不进行读取,而写端一直在写入。写端可以向管道内写入,但是管道是固定大小的缓冲区,不断的只写不读管道会被写满。满了以后就不能再写入了,此时写端会处于阻塞状态。

文件test.c

1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         printf("count: %d\n",cnt);
 30                 }
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 exit(0);
 33         }
 34         //父进程
 35         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 36         while(1)
 37         {
 38                 sleep(50);//父进程不读
 39                 char buffer[1024];
 40                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 41                 if(s > 0) buffer[s] = 0;
 42                 printf("Get Message : %s | mypid = %d\n", buffer, getpid());
  43         }
 44         n = waitpid(id, NULL, 0);
 45         assert(n == id);
 46         close(fds[0]);//退出程序前,关闭读取端
 47         return 0;
 48 }

如果,让父进程只sleep(2)的话,读取的速度稍微比较慢:

这种情况,写端是将数据塞到管道内,管道读取是安装指定大小读取(并非一行一行的读取,最初安装一行来读取是因为写入的慢,一次只写一行数据,数据就被读取了)。

相关文章
|
13天前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
26 0
|
8天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
12 1
|
13天前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
16 0
Linux c/c++之IPC进程间通信
|
13天前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
15 0
Linux c/c++进程间通信(1)
|
13天前
|
Linux C++
Linux c/c++之进程的创建
这篇文章介绍了在Linux环境下使用C/C++创建进程的三种方式:system函数、fork函数以及exec族函数,并展示了它们的代码示例和运行结果。
17 0
Linux c/c++之进程的创建
|
1月前
|
Linux Shell
6-9|linux查询现在运行的进程
6-9|linux查询现在运行的进程
|
13天前
|
Linux C++
Linux c/c++进程之僵尸进程和守护进程
这篇文章介绍了Linux系统中僵尸进程和守护进程的概念、产生原因、解决方法以及如何创建守护进程。
15 0
|
1月前
|
SQL 网络协议 数据库连接
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
|
1月前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
37 0
|
3月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能