废旧网站那个做的最好,称多县公司网站建设,wordpress瓶颈,帝国cms灵动标签做网站地图阅读导航 引言一、进程间通信概念二、进程间通信目的三、进程间通信分类四、管道1. 什么是管道2. 匿名管道#xff08;1#xff09;创建和关闭⭕pipe() 函数⭕创建匿名管道⭕关闭匿名管道 #xff08;2#xff09;通信方式#xff08;3#xff09;用法示例#xff08;41创建和关闭⭕pipe() 函数⭕创建匿名管道⭕关闭匿名管道 2通信方式3用法示例4匿名管道的特点 3. 运用匿名管道建立进程池4. 命名管道1创建和关闭⭕mkfifo() 函数⭕创建命名管道⭕关闭命名管道 2通信方式3用法示例4命名管道的特点 5. 匿名管道与命名管道的区别1. 匿名管道2. 命名管道 温馨提示 引言
当今计算机系统中进程间通信扮演着至关重要的角色。随着计算机系统的发展和复杂性的增加多个进程之间的协作变得更加必要和常见。进程间通信使得不同进程能够共享资源、协调工作、传输数据并实现更加复杂和强大的功能。本文将深入探讨进程间的通信以及管道的作用。它为多个进程提供了一种有效的交互方式使得系统能够更好地协同工作、共享资源并实现更高级别的功能。通过恰当地选择和使用进程间通信的方式我们可以构建出高效、可靠且高度协同的系统。下面话不多说坐稳扶好咱们要开车了
一、进程间通信概念
进程间通信IPC是操作系统中的一个重要概念它允许不同的进程在执行过程中交换数据、共享资源、协调行为等。在多道程序设计环境下多个进程可能需要相互通信以完成复杂的任务而进程间通信提供了各种机制来实现这种交互。
二、进程间通信目的
进程间通信的主要目的包括
数据交换允许进程之间传递数据比如传输文件、文本、图像等信息。资源共享多个进程可以访问和共享同一块内存区域以便协同完成某项任务。进程控制允许一个进程控制另一个进程的行为比如启动、暂停、终止等。同步与互斥确保多个进程能够按照特定的顺序执行避免竞态条件和数据冲突。通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。
三、进程间通信分类
以下是几种常见的进程间通信方式 管道Pipe管道是一种半双工的通信方式用于具有亲缘关系的进程间通信。它可以是匿名管道使用pipe系统调用或命名管道使用mkfifo命令并且数据只能在一个方向上流动。 信号Signal信号是一种异步的通信机制用于通知进程发生了某种事件。进程可以向另一个进程发送信号比如终止信号SIGTERM、中断信号SIGINT等。 消息队列Message Queue消息队列是一种消息传递机制可以在不同进程之间按队列方式传递数据。它允许一个进程向另一个进程发送消息而不需要直接的数据连接。 共享内存Shared Memory共享内存允许多个进程访问同一块物理内存因此它是最快的 IPC 方式之一。但需要开发者自行解决竞争条件和同步的问题。 信号量Semaphores信号量是一种计数器用于控制对共享资源的访问。它通常与共享内存一起使用以避免多个进程同时访问共享内存时产生的竞争条件。 套接字Socket套接字是一种进程间通信的常见方式可以用于不同主机之间的通信也可以用于同一主机上不同进程之间的通信。
四、管道
1. 什么是管道
管道Pipe是一种在UNIX和类UNIX系统中用于进程间通信的机制。管道允许一个进程将其输出直接发送到另一个进程的输入从而实现两个进程之间的数据传输。
管道的特点包括
管道是一种半双工的通信方式数据只能在一个方向上流动。管道通常用于实现父子进程之间的通信例如一个进程的输出连接到另一个进程的输入实现数据传递和处理。管道的数据是以先进先出FIFO的方式传输的保持了数据的顺序性。 2. 匿名管道
1创建和关闭
⭕pipe() 函数
在Linux系统中pipe()函数用于创建匿名管道它是一个系统调用函数位于unistd.h头文件中。该函数创建一个管道返回两个文件描述符一个用于读取数据另一个用于写入数据。
语法
#include unistd.hint pipe(int pipefd[2]);参数
pipefd: 一个整型数组用于存储管道的文件描述符。pipefd[0]用于从管道中读取数据pipefd[1]用于向管道中写入数据。
返回值
若成功返回值为0若失败返回值为-1并设置errno来指示错误类型。
⭕创建匿名管道
使用pipe()系统调用来创建匿名管道。pipe()系统调用会创建一个管道返回两个文件描述符一个用于读取数据另一个用于写入数据。在Linux系统中可以通过命令行工具或者编程语言来使用pipe()系统调用创建匿名管道。以下是使用C语言创建匿名管道的示例代码
#include unistd.hint main() {int pipefd[2];if (pipe(pipefd) -1) {// 处理创建失败的情况}// 现在pipefd[0]是用于读取的文件描述符pipefd[1]是用于写入的文件描述符
}⭕关闭匿名管道
匿名管道的关闭通常由操作系统自动处理当所有指向管道的文件描述符都关闭时操作系统会自动关闭管道。在编程中可以通过close()系统调用显式地关闭管道的读取端或写入端。以下是使用C语言关闭匿名管道的示例代码
#include unistd.hint main() {int pipefd[2];if (pipe(pipefd) -1) {// 处理创建失败的情况}// 在适当的时机关闭管道close(pipefd[0]); // 关闭读取端close(pipefd[1]); // 关闭写入端
}2通信方式
⭕父子进程之间的通信父子进程可以通过匿名管道进行通信。通常的做法是在调用fork()之后子进程继承了父进程的文件描述符包括管道。子进程可以关闭不需要的文件描述符然后使用write()函数向管道中写入数据父进程则使用read()函数从管道中读取数据。
⭕兄弟进程之间的通信兄弟进程之间也可以通过匿名管道进行通信。通常的做法是在调用pipe()和fork()之后子进程再次调用fork()创建兄弟进程。然后兄弟进程可以通过管道进行通信一个进程负责写入另一个进程负责读取。
3用法示例
在Shell脚本中可以使用管道将一个命令的输出传递给另一个命令进行处理比如command1 | command2。在C语言或其他编程语言中可以通过创建管道来实现父子进程之间的通信或者在多个兄弟进程之间进行数据交换后面进程池会细讲示例。
4匿名管道的特点 阻塞式读写 当管道读取端为空时尝试从管道中读取数据的进程将会被阻塞直到有数据可供读取为止。读取端的进程会等待直到管道中有数据可用或者直到收到信号中断。当管道写入端已满时尝试向管道中写入数据的进程将会被阻塞直到有足够的空间可以写入为止。这个时候写入端的进程会等待直到管道中有足够的空间或者直到收到信号中断。 数据顺序性 匿名管道保证数据的顺序性数据是以先进先出FIFO的方式传输的从而保持了数据的顺序性。 局限性 匿名管道通常适用于具有亲缘关系的进程间通信无法用于无亲缘关系的进程间通信。匿名管道只能在本地进程间通信无法用于远程通信。 单向通信匿名管道是一种单向通信机制数据只能在一个方向上传输。其中一个进程负责写入数据而另一个进程负责读取数据。这使得匿名管道适用于一些特定的通信场景如父子进程或者兄弟进程之间的通信。 半双工通信匿名管道是半双工的意味着它可以在两个进程之间进行双向通信但是不能同时进行读和写操作。虽然它可以实现双向通信但是在任意给定的时间点数据只能在一个方向上传输。 自动关闭当所有指向管道的文件描述符全部关闭时操作系统会自动关闭管道。这样做可以确保在程序结束时释放资源并且不会造成资源泄漏。
3. 运用匿名管道建立进程池
#include iostream
#include vector
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include ctime
#include cstdlib
#include cassert
#define PROCESS_NUM 5 // 定义常量 PROCESS_NUM 为 5using namespace std;// 从文件描述符 waitFd 中读取命令
int waitCommand(int waitFd, bool quit) {uint32_t command 0;ssize_t s read(waitFd, command, sizeof(command)); // 从文件描述符中读取命令if (s 0) { // 如果成功读取到命令quit true; // 标记为需要退出return -1;}assert(s sizeof(uint32_t)); // 断言读取的字节数与命令长度相等return command; // 返回读取到的命令
}// 向指定的进程发送命令并在标准输出中打印相关信息
void sendAndWakeup(pid_t who, int fd, uint32_t command) {write(fd, command, sizeof(command)); // 向文件描述符中写入命令cout main process: call process who execute desc[command] through fd endl; // 打印相关信息
}int main()
{load(); // 加载一些内容vectorpairpid_t, int slots; // 用于保存子进程的PID和管道写端文件描述符// 先创建多个进程for (int i 0; i PROCESS_NUM; i){int pipefd[2] {0};assert(pipe(pipefd) 0); // 创建管道并检查是否成功pid_t id fork();assert(id ! -1); // 检查fork()是否成功if (id 0) // 子进程逻辑{close(pipefd[1]); // 关闭写端while (true){bool quit false;int command waitCommand(pipefd[0], quit); // 等待命令if (quit)break; // 如果收到退出命令则退出循环if (command 0 command handlerSize()){dummyHandler(); // 执行对应的命令处理函数}else{cout 非法command: command endl;}}exit(1);}else // 父进程逻辑{close(pipefd[0]); // 关闭子进程的读端slots.push_back(pairpid_t, int(id, pipefd[1])); // 保存子进程的PID和写端管道文件描述符}}// 父进程派发任务srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 设置随机数种子while (true){int command rand() % handlerSize(); // 随机选择一个任务int choice rand() % slots.size(); // 随机选择一个子进程sendAndWakeup(slots[choice].first, slots[choice].second, command); // 向选定的子进程发送任务sleep(1); // 休眠一秒}// 关闭fd, 所有的子进程都会退出for (const auto slot : slots){close(slot.second); // 关闭所有子进程的写端}// 回收所有的子进程信息for (const auto slot : slots){waitpid(slot.first, nullptr, 0); // 回收子进程}
}这段代码是一个简单的进程调度和通信示例它创建了多个子进程并使用管道进行进程间通信父进程通过随机选择一个子进程来派发任务。 创建多个子进程 使用 fork() 函数创建子进程并使用 pipe() 函数创建管道用于进程间通信。父进程将每个子进程的 PID 和写端管道文件描述符保存在 slots 向量中。 子进程逻辑 子进程关闭写端然后进入一个无限循环不断等待命令并执行。当收到命令时执行对应的命令处理函数如果收到退出命令则退出循环并终止子进程。 父进程逻辑 通过 srand() 来初始化随机数种子使得每次运行产生的随机数不同。进入一个无限循环随机选择一个任务和一个子进程然后将任务发送给选定的子进程。每次发送完任务后休眠一秒钟。 最后父进程关闭所有子进程的写端然后回收所有子进程的信息。
4. 命名管道
1创建和关闭
⭕mkfifo() 函数
mkfifo() 函数用于创建一个FIFOFirst In First Out或者称为命名管道它允许进程之间进行通信。下面是关于 mkfifo() 函数的详细介绍
函数原型
#include sys/types.h
#include sys/stat.hint mkfifo(const char *pathname, mode_t mode);参数
pathname要创建的命名管道的路径名。mode创建命名管道时设置的权限模式通常以 8 进制表示比如 0666。
返回值
若成功返回值为 0若失败返回值为 -1并设置errno来指示错误类型。
功能 mkfifo() 函数的作用是在文件系统中创建一个特殊类型的文件该文件在外观上类似于普通文件但实际上是一个FIFO用于进程之间的通信。这种通信方式是单向的即数据写入FIFO的一端可以从另一端读取出来按照先进先出的顺序。
⭕创建命名管道 包含头文件首先需要包含相关的头文件以便使用相关函数和数据结构。 #include sys/types.h
#include sys/stat.h
#include fcntl.h调用 mkfifo() 函数使用 mkfifo() 函数创建命名管道。该函数原型如下 int mkfifo(const char *pathname, mode_t mode);pathname要创建的命名管道的路径名。mode创建命名管道时设置的权限模式通常以 8 进制表示比如 0666。 示例代码 std::string fifoPath /tmp/my_named_pipe; // 命名管道的路径名
mkfifo(fifoPath.c_str(), 0666); // 创建权限为0666的命名管道处理返回值检查 mkfifo() 的返回值若返回 0 表示成功创建若返回 -1 表示创建失败并通过 errno 来获取具体的错误信息。
注意事项
路径名确保要创建的命名管道路径名合法且没有重复。权限模式根据实际需求设置合适的权限模式确保可被需要访问该管道的进程所访问。错误处理对 mkfifo() 函数的返回值进行适当的错误处理根据具体的错误原因进行相应的处理和日志记录。
示例 下面是一个简单的创建命名管道并处理错误的示例
#include iostream
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include cerrnoint main() {std::string fifoPath /tmp/my_named_pipe; // 命名管道的路径名if (mkfifo(fifoPath.c_str(), 0666) -1) {if (errno EEXIST) {std::cerr Named pipe already exists std::endl;} else {perror(Error creating named pipe);}} else {std::cout Named pipe created successfully std::endl;}return 0;
}使用命名管道进行读写操作在打开命名管道后可以通过 read() 或 write() 函数对其进行读写操作。
⭕关闭命名管道 关闭命名管道当进程使用完毕命名管道后需要调用 close() 函数来关闭文件描述符释放相关资源。 close(fd); // 关闭命名管道注意事项 关闭顺序如果有多个文件描述符指向同一个命名管道需要依次关闭这些文件描述符直到所有相关资源都得到释放。 示例 下面是一个简单的示例演示了关闭命名管道的过程
#include iostream
#include fcntl.h
#include unistd.h
#include cerrnoint main() {int fd open(/tmp/my_named_pipe, O_RDONLY); // 以只读方式打开命名管道// 进行读取操作...if (close(fd) -1) {perror(Error closing named pipe);} else {std::cout Named pipe closed successfully std::endl;}return 0;
}总之关闭命名管道是确保在进程使用完毕后释放相关资源的重要步骤。通过调用 close() 函数可以关闭文件描述符释放命名管道相关的资源。
2通信方式 单向通信 命名管道提供了一种单向通信的方式一个进程可以向管道中写入数据而另一个进程则可以从管道中读取数据。这种通信方式适用于需要单向数据传输的场景。 持久性 命名管道与匿名管道不同之处在于它以文件的形式存在于文件系统中具有持久性。即使管道的创建进程终止命名管道仍然存在其他进程可以继续使用该管道进行通信。 阻塞和非阻塞 在进行命名管道通信时可以选择阻塞或非阻塞模式。在阻塞模式下如果读取进程尝试从空管道中读取数据它将被阻塞直到有数据可读而在非阻塞模式下读取进程将立即返回一个错误从而避免阻塞。
3用法示例
下面是一个简单的示例演示了两个进程通过命名管道进行通信的方式
进程 A 写入数据到命名管道
int fd open(/tmp/my_named_pipe, O_WRONLY); // 以只写方式打开命名管道
write(fd, Hello, named pipe!, 18); // 向管道中写入数据
close(fd); // 关闭命名管道进程 B 从命名管道读取数据
int fd open(/tmp/my_named_pipe, O_RDONLY); // 以只读方式打开命名管道
char buffer[50];
read(fd, buffer, 50); // 从管道中读取数据
close(fd); // 关闭命名管道4命名管道的特点 持久性命名管道以文件的形式存在于文件系统中并且具有持久性。即使创建了命名管道的进程终止该管道仍然存在于文件系统中其他进程可以继续使用它进行通信。 单向通信命名管道提供单向通信的能力允许一个进程向管道中写入数据而另一个进程则可以从管道中读取数据。这种单向通信模式适用于需要单向数据传输的场景。 实时数据传输命名管道允许实时的数据传输写入管道的数据会立即被读取进程获取从而实现了实时通信的能力。 阻塞和非阻塞模式对于读取和写入操作命名管道可以选择阻塞或非阻塞模式。在阻塞模式下读取进程将被阻塞直到有数据可读而在非阻塞模式下读取进程将立即返回错误避免阻塞。 简单易用使用命名管道进行进程间通信相对简单只需通过类似文件操作的方式打开、读取和关闭管道即可完成通信过程。 适用范围广泛命名管道适用于各种场景例如实现多个进程之间的数据共享、进程之间的控制和协调等。
5. 匿名管道与命名管道的区别
匿名管道和命名管道分别适用于不同的通信需求。 ⭕匿名管道适用于有亲缘关系的父子进程间的通信。 ⭕命名管道更适合不相关进程间的通信且具有持久性和更灵活的应用方式。
1. 匿名管道
单向通信匿名管道只能支持单向通信即数据只能从一个进程流向另一个进程无法实现双向通信。存在于内存中匿名管道存在于内存中并且只能用于相关进程之间的通信。一旦相关进程终止管道也会自动被销毁。只能用于父子进程间通信匿名管道通常用于父子进程之间的通信因为它要求通信的进程具有一定的亲缘关系。通常用于shell命令间的通信在Unix/Linux系统中匿名管道经常用于将一个命令的输出传递给另一个命令作为输入。
2. 命名管道
持久性命名管道以文件的形式存在于文件系统中具有持久性即使创建管道的进程终止管道依然存在其他进程也可以访问和使用它。可用于不相关的进程通信命名管道可以用于不相关的进程之间的通信这些进程可以位于不同的终端或主机上。支持阻塞和非阻塞模式命名管道可以选择阻塞或非阻塞模式进行读写操作。适用于多种场景命名管道适用于需要不相关进程之间进行通信的各种场景例如进程之间的数据共享、控制和协调等。
温馨提示
感谢您对博主文章的关注与支持如果您喜欢这篇文章可以点赞、评论和分享给您的同学这将对我提供巨大的鼓励和支持。另外我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新不要错过任何精彩内容
再次感谢您的支持和关注。我们期待与您建立更紧密的互动共同探索Linux、C、算法和编程的奥秘。祝您生活愉快排便顺畅