20242817李臻《Linux⾼级编程实践》第6周
一、AI对学习内容的总结
Linux进程间通信(IPC)
1. 进程间通信基本概念
- 作用:
- 数据传输:进程间传递数据(字节到兆字节级别)。
- 共享数据:多个进程操作同一数据,修改后其他进程立即可见。
- 通知事件:通知其他进程事件发生(如进程终止通知父进程)。
- 资源共享:通过锁和同步机制共享资源。
- 进程控制:控制其他进程的执行(如GDB调试)。
- 分类:
- 低级通信:传递控制信息(同步、互斥等),如信号量。
- 高级通信:传递大批数据,如管道、共享内存、消息队列。
- 实现方式:
- 继承自UNIX的System V IPC和BSD套接字机制。
- Linux支持的IPC方式:管道、信号量、共享内存、消息队列、套接字。
2. 管道通信
2.1 无名管道(匿名管道)
- 特点:
- 单向通信,数据按FIFO顺序传输。
- 仅限父子进程或兄弟进程间通信。
- 基于内存,无持久性(进程退出后消失)。
- 系统调用:
int pipe(int pipefd[2]); // pipefd[0]为读端,pipefd[1]为写端
- 示例:
- 父子进程通过管道传递字符串并倒序返回,需注意并发控制(如使用
sleep
等待数据读取)。
- 父子进程通过管道传递字符串并倒序返回,需注意并发控制(如使用
2.2 命名管道(FIFO)
- 特点:
- 通过文件系统路径名标识,允许无关进程通信。
- 需显式创建(
mkfifo
命令或mkfifo()
系统调用)。 - 支持阻塞和非阻塞模式(通过
O_NONBLOCK
标志)。
- 系统调用:
int mkfifo(const char *pathname, mode_t mode);
- 示例:
- 两个进程通过命名管道读写数据,写入端和读取端需协调打开顺序以避免阻塞。
2.3 管道与重定向
- 实现原理:
- 使用
dup2()
重定向标准输入/输出到管道。 - 示例:
ps -aux | grep httpd
通过管道将ps
的输出重定向到grep
的输入。
- 使用
2.4 popen
函数
- 功能:
- 创建管道并执行Shell命令,返回文件指针用于读写。
- 需配合
pclose()
关闭管道,避免僵尸进程。
- 示例:
FILE *fp = popen("ps aux", "r"); // 读取命令输出
3. 信号量
3.1 System V信号量
- 核心操作:
- P操作(申请资源):信号量减1,若为负则阻塞。
- V操作(释放资源):信号量加1,唤醒等待进程。
- 系统调用:
semget()
:创建/获取信号量。semctl()
:初始化或删除信号量。semop()
:执行P/V操作。
- 示例:
- 父子进程通过信号量控制执行顺序,父进程先阻塞,子进程执行后唤醒父进程。
3.2 POSIX有名信号量
- 特点:
- 通过文件名标识,可用于无关进程。
- 支持线程和进程间同步。
- 系统调用:
sem_open()
:创建/打开信号量。sem_wait()
/sem_post()
:P/V操作。sem_unlink()
:删除信号量。
- 示例:
- 两个独立进程交替写入文件,通过信号量实现同步。
4. 共享内存
4.1 System V共享内存
- 步骤:
shmget()
:创建共享内存段。shmat()
:映射到进程地址空间。shmdt()
:解除映射。shmctl()
:删除共享内存。
- 特点:
- 直接访问内存,无需数据复制,速度最快。
- 需配合信号量实现互斥访问。
- 示例:
- 父子进程通过共享内存传递字符串,父进程写入后子进程读取。
4.2 mmap
共享内存
- 特点:
- 将文件映射到内存,支持无关进程通信。
- 匿名映射(
MAP_ANON
)可用于父子进程。
- 系统调用:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int mummap(void *addr, size_t length);
- 示例:
- 两个进程通过映射同一文件实现学生信息共享。
5. 消息队列
- 特点:
- 消息按类型分类,支持优先级。
- 独立于进程存在(内核持久化)。
- 系统调用:
msgget()
:创建/获取消息队列。msgsnd()
:发送消息。msgrcv()
:接收指定类型消息。msgctl()
:控制消息队列。
- 示例:
- 一个进程发送随机类型消息,多个进程分别接收指定类型消息。
6. 不同IPC方式对比
方式 | 适用场景 | 特点 | 缺点 |
---|---|---|---|
无名管道 | 父子进程简单通信 | 简单、高效 | 单向、仅限亲缘进程 |
命名管道 | 无关进程通信 | 支持任意进程、文件系统可见 | 需协调打开顺序 |
信号量 | 同步/互斥控制 | 灵活控制执行顺序 | 不传递数据 |
共享内存 | 高速数据共享 | 零复制、性能最优 | 需额外同步机制 |
消息队列 | 松散耦合的进程通信 | 支持消息类型、异步通信 | 内核开销较大 |
套接字 | 跨网络或本地进程通信 | 通用性强、支持复杂协议 | 配置复杂、性能较低 |
7. 关键总结
- 管道适合简单数据流,无名管道限于亲缘进程,命名管道扩展至无关进程。
- 信号量用于同步控制,System V用于进程间,POSIX支持更广泛场景。
- 共享内存效率最高,但需结合信号量避免竞态条件。
- 消息队列提供结构化通信,支持异步和优先级,适合复杂交互。
- 实际应用中常组合多种IPC方式(如共享内存+信号量)。
二、 对AI总结的反思与补充
反思
以下是对上述关于Linux进程间通信(IPC)总结的反思:
1. 内容的全面性和准确性
- 优点:
- 总结内容涵盖了Linux进程间通信的主要方式,包括管道(无名管道和命名管道)、信号量(System V和POSIX)、共享内存(System V和
mmap
)、消息队列以及它们的对比分析,内容较为全面。 - 对每种IPC方式的特点、系统调用、示例场景等进行了详细说明,有助于理解每种方式的适用场景和优缺点。
- 总结内容涵盖了Linux进程间通信的主要方式,包括管道(无名管道和命名管道)、信号量(System V和POSIX)、共享内存(System V和
- 不足:
- 虽然提到了套接字,但对套接字的介绍不够详细,仅在对比表中简单提及,未深入展开其在进程间通信中的具体应用和实现方式。
- 对于一些高级IPC机制(如共享内存结合信号量或消息队列的复杂场景)的示例不够丰富,缺乏实际代码实现的深度分析。
2. 内容的逻辑性和连贯性
- 优点:
- 文章按照从简单到复杂的顺序介绍了不同IPC方式,逻辑清晰,便于读者逐步理解。
- 在对比表中对各种IPC方式的特点进行了总结,有助于读者快速把握它们的差异。
- 不足:
- 在介绍每种IPC方式时,部分地方的过渡不够自然。例如,在介绍无名管道和命名管道时,未明确指出它们之间的区别和联系,容易让读者混淆。
- 对于一些IPC方式的系统调用,仅列出了函数原型,未对参数和返回值进行详细解释,可能导致读者理解困难。
3. 内容的实用性
- 优点:
- 通过示例展示了每种IPC方式的基本用法,有助于读者在实际开发中快速上手。
- 对不同IPC方式的适用场景进行了分析,为读者在选择IPC方式时提供了参考。
- 不足:
- 示例过于简单,缺乏对实际复杂场景的分析。例如,在介绍共享内存时,未涉及如何处理多个进程同时访问共享内存时的同步问题。
- 缺乏对性能优化和错误处理的讨论。在实际应用中,IPC的性能和稳定性至关重要,但总结中未提及如何优化IPC性能以及如何处理IPC过程中可能出现的错误。
4. 改进建议
- 补充内容:
- 增加对套接字的详细介绍,包括套接字的类型(如TCP、UDP)、创建和使用方法,以及套接字在跨主机通信中的应用。
- 补充更多实际代码示例,展示如何在复杂场景下使用IPC机制,例如多个进程并发访问共享内存时的同步控制。
- 优化逻辑:
- 在介绍每种IPC方式时,明确指出其与其他IPC方式的区别和联系,增强内容的连贯性。
- 对系统调用的参数和返回值进行详细解释,帮助读者更好地理解其使用方法。
- 增加深度:
- 对每种IPC方式的底层实现原理进行深入探讨,例如共享内存的内存管理机制、消息队列的存储结构等。
- 介绍一些新兴的IPC技术,如基于内存映射文件的通信、多线程环境下的IPC优化等,使内容更具前瞻性和实用性。
- 注重实践:
- 增加对性能优化和错误处理的讨论,例如如何通过调整IPC参数来提高通信效率,以及如何处理IPC过程中可能出现的错误。
- 提供一些实际项目中的IPC案例分析,帮助读者更好地理解IPC在实际应用中的使用场景和解决方案。
补充
1. 进程间通信(IPC)补充
- IPC的重要性:在多进程系统中,进程间通信是实现任务协作、资源共享和系统功能的基础。例如,Web服务器可能需要通过IPC将用户请求分发给多个工作进程处理,或者多个进程需要协作完成一个复杂的计算任务。
- 安全性问题:在设计IPC时,需要考虑数据的安全性。例如,防止未授权进程访问或篡改通信数据,尤其是在网络环境下,需要对数据进行加密和身份验证。
2. 管道通信补充
- 无名管道的限制:无名管道的大小通常受到系统限制(如管道缓冲区大小)。如果写入数据超过缓冲区大小,写入进程可能会阻塞,直到读取进程读取数据。
- 命名管道的性能优化:在高并发场景下,命名管道可能会因为频繁的阻塞和唤醒操作而影响性能。可以通过设置非阻塞模式(
O_NONBLOCK
)来优化性能,但这需要在应用程序中处理可能的错误和重试逻辑。 - 管道的可靠性:管道通信是可靠的,数据不会丢失,但需要注意的是,如果读取进程崩溃或未正确关闭管道,可能会导致写入进程阻塞。
3. 信号量补充
- 信号量的性能开销:信号量操作(如
sem_wait
和sem_post
)可能会引入一定的性能开销,尤其是在高并发场景下。这是因为信号量操作需要进行系统调用,并且可能涉及到进程的阻塞和唤醒。 - 信号量的死锁问题:在使用信号量时,如果不正确地管理信号量的使用顺序,可能会导致死锁。例如,两个进程分别持有对方需要的信号量,而彼此都在等待对方释放信号量。
- 信号量的调试:信号量的调试相对复杂,因为它们的执行顺序和状态依赖于多个进程的行为。可以使用日志记录或调试工具(如
strace
)来跟踪信号量的操作。
4. 共享内存补充
- 共享内存的同步机制:虽然共享内存提供了高效的内存访问,但需要额外的同步机制(如信号量或互斥锁)来避免竞态条件。例如,在多线程环境中,多个线程可能同时访问共享内存,需要使用互斥锁来确保数据的一致性。
- 共享内存的内存管理:共享内存段的大小通常在创建时固定,如果需要动态调整大小,需要重新创建共享内存段。此外,共享内存的内存管理需要谨慎,避免内存泄漏。
- 共享内存的跨平台问题:不同操作系统对共享内存的支持可能有所不同。例如,Windows和Linux在共享内存的实现和API上存在差异,需要在跨平台开发时进行适配。
5. 消息队列补充
- 消息队列的优先级机制:消息队列支持消息的优先级,高优先级的消息可以优先被接收。这使得消息队列适合用于任务调度和优先级管理。例如,在一个任务队列中,紧急任务可以被赋予更高的优先级,从而更快地被处理。
- 消息队列的可靠性:消息队列通常具有较高的可靠性,因为消息会存储在内核空间,即使发送进程崩溃,消息也不会丢失。然而,如果消息队列被删除或系统崩溃,消息可能会丢失。
- 消息队列的性能优化:在高并发场景下,消息队列的性能可能会受到限制。可以通过优化消息队列的大小、消息的大小和发送/接收频率来提高性能。
6. 套接字补充
- 套接字的类型:除了用于网络通信的套接字外,还可以使用Unix域套接字(
AF_UNIX
)进行本地进程间通信。Unix域套接字比网络套接字更高效,因为它们不需要经过网络协议栈。 - 套接字的可靠性:套接字通信可以是可靠的(如TCP)或不可靠的(如UDP)。TCP套接字提供了可靠的数据传输,但可能会引入额外的延迟;UDP套接字则提供了更高的性能,但可能会丢失数据。
- 套接字的安全性:在网络环境下,套接字通信需要考虑安全性。可以使用SSL/TLS协议对数据进行加密,以防止数据被窃取或篡改。
三、学习思维导图
第七章
mindmap
root((进程间通信))
基本概念
进程通信的作用
数据传输::icon(fa fa-exchange-alt)
共享数据::icon(fa fa-database)
通知事件::icon(fa fa-bell)
资源共享::icon(fa fa-share-alt)
进程控制::icon(fa fa-cogs)
通信的实现和方法
继承自UNIX::icon(fa fa-linux)
Linux支持多种通信方式::icon(fa fa-check-circle)
通信的分类
低级通信::icon(fa fa-arrow-down)
高级通信::icon(fa fa-arrow-up)
通信方式
管道
无名管道::icon(fa fa-link)
命名管道::icon(fa fa-link)
管道通信模型::icon(fa fa-stream)
管道与重定向::icon(fa fa-random)
popen的介绍::icon(fa fa-terminal)
信号量
SystemV信号量::icon(fa fa-stopwatch)
POSIX有名信号量::icon(fa fa-stopwatch)
共享内存
SystemV共享内存::icon(fa fa-memory)
mmap共享内存机制::icon(fa fa-memory)
消息队列
消息队列的实现原理::icon(fa fa-list-alt)
消息队列系统调用::icon(fa fa-code)
消息队列实例::icon(fa fa-check-square)
套接字
基于socket IPC::icon(fa fa-network-wired)
应用场景
数据传输::icon(fa fa-exchange-alt)
进程同步::icon(fa fa-sync-alt)
进程互斥::icon(fa fa-lock)
资源共享::icon(fa fa-share-alt)
进程控制::icon(fa fa-cogs)
优缺点
共享内存
速度最快::icon(fa fa-tachometer-alt)
耦合性强::icon(fa fa-link)
信号量
适合控制信号传递::icon(fa fa-check-circle)
不能传输数据::icon(fa fa-times-circle)
消息队列
无进程关系要求::icon(fa fa-check-circle)
可传输大量数据::icon(fa fa-database)
管道
无名管道::icon(fa fa-link)
命名管道::icon(fa fa-link)
结合使用
共享内存与信号量::icon(fa fa-link)
管道与信号量::icon(fa fa-link)
消息队列与信号量::icon(fa fa-link)
四、基于AI的学习
1、关于进程间通信
-
我:
我在学习Linux进程间通信知识点,请你以苏格拉底的方式对我进行提问,一次一个问题 -
AI:
很好,让我们从基础开始。你知道在Linux中,进程间通信(IPC)的主要方式有哪些吗? -
我:
在 Linux 中,进程间通信(IPC)的主要方式包括管道(分为匿名管道和命名管道),匿名管道用于具有亲缘关系的进程间通信,命名管道可以通过文件系统实现不相关进程的通信;消息队列,允许一个或多个进程向队列中写入消息,其他进程读取;信号,用于进程间的通知和控制,可以传递简单的信息;共享内存,允许多个进程共享同一块内存区域,实现快速数据共享;信号量,用于进程间同步,控制对共享资源的访问;以及套接字,不仅可以用于网络通信,也可以用于同一台机器上进程间的通信。 -
AI:
很好!你对Linux进程间通信的方式有了全面的了解。接下来,让我们深入一点。你提到“共享内存”是一种快速的通信方式,那么你能解释一下共享内存是如何在进程间实现数据共享的吗?它是如何确保数据的一致性和同步的? -
我:
共享内存是一种高效的进程间通信方式,它允许多个进程通过访问同一块内存区域来共享数据。操作系统分配一块共享内存区域,多个进程可以将其映射到自己的地址空间中,从而直接读写这块内存,实现数据的快速共享。
然而,共享内存本身并不提供数据一致性和同步机制。由于多个进程可能同时访问共享内存,这就需要额外的同步手段来确保数据的一致性和完整性。通常会结合使用信号量或其他同步机制来控制对共享内存的访问。例如,通过信号量实现互斥锁,确保同一时间只有一个进程可以写入共享内存,或者通过信号量实现读写锁,允许多个进程同时读取但写入时互斥。这样可以避免数据竞争和冲突,保证数据的一致性。 -
AI:
非常好!你已经清楚地解释了共享内存的基本概念以及如何通过同步机制来确保数据一致性。接下来,让我们进一步探讨信号量的使用。你提到信号量可以用于同步,那么你能具体描述一下信号量是如何工作的吗?它如何在共享内存中实现进程间的互斥访问? -
我:
信号量是一种同步机制,通过P操作(减1)和V操作(加1)来控制对共享资源的访问。在共享内存中,信号量用于互斥访问:进程访问共享内存前执行P操作,若信号量大于0则减1并访问,否则阻塞;访问完成后执行V操作,增加信号量并唤醒等待的进程。 -
AI:
很好!你已经清楚地描述了信号量的基本操作及其在共享内存中的作用。接下来,让我们进一步探讨信号量的实现细节。你知道在Linux中,信号量是如何具体实现的吗?有哪些系统调用可以用来创建和管理信号量? -
我:
在 Linux 中,信号量可以通过系统 V 的 IPC 机制或 POSIX 信号量来实现。系统 V 信号量使用semget
创建信号量集,semop
进行 P 和 V 操作,semctl
管理信号量;POSIX 信号量则通过sem_init
初始化信号量,sem_wait
实现 P 操作,sem_post
实现 V 操作,sem_destroy
销毁信号量。 -
AI:
非常好!你已经清楚地了解了Linux中信号量的两种实现方式及其相关系统调用。接下来,让我们思考一个更深入的问题:在实际应用中,选择System V信号量和POSIX信号量的依据是什么?它们各自有哪些优缺点? -
我:
你的理解是什么?
- AI:
我的理解是,除了PID,操作系统还会为每个进程维护以下关键信息:
- PPID(父进程ID):标识创建当前进程的父进程,帮助操作系统管理进程的层次结构。
- 进程状态:如运行、就绪、阻塞等,帮助操作系统调度进程。
- CPU寄存器状态:保存进程的上下文,以便在进程切换时恢复执行。
- 内存分配信息:记录进程使用的内存区域,确保内存的合理分配和回收。
- 文件描述符表:跟踪进程打开的文件和I/O资源。
这些信息帮助操作系统有效地管理、调度和监控进程,确保系统的稳定性和效率。现在,你的理解是什么呢既然你让我表达看法,那我就尝试总结一下:
System V 信号量和 POSIX 信号量在实际应用中的选择依据主要基于以下几点:
System V 信号量的优点:
- 历史悠久,兼容性好,适合需要在不同 UNIX 系统间移植的代码。
- 支持信号量集,可以同时操作多个信号量,适合复杂的同步场景。
System V 信号量的缺点:
- 接口较为复杂,使用
semget
、semop
、semctl
等系统调用,学习成本较高。 - 需要显式管理 IPC 资源(如
ipcrm
命令),容易出现资源泄漏。
POSIX 信号量的优点:
- 接口简洁,易于使用,符合现代编程习惯。
- 支持线程间和进程间同步,灵活性更高。
- 不需要显式管理 IPC 资源,减少了资源泄漏的风险。
POSIX 信号量的缺点:
- 在某些老旧的 UNIX 系统上可能不支持。
- 功能相对单一,不直接支持信号量集。
因此,选择依据通常是:
- 如果需要跨平台兼容性或复杂同步场景,优先考虑 System V 信号量。
- 如果追求简洁性和现代性,或者仅需基本的同步功能,优先选择 POSIX 信号量。
你觉得这样总结是否合理?或者你有其他补充吗??
- 我:
针对我该知识点,我理解了吗?我的回答结束了,请对我的回答进行评价总结。
- AI:
你的回答表明你对Linux进程间通信(IPC)的知识点有了较为全面的理解。以下是对你回答的总结和评价:
1. 知识广度
- 你能够清晰地列举出Linux中主要的进程间通信方式,包括管道、消息队列、信号、共享内存、信号量和套接字。这表明你对IPC的多种机制有较为全面的了解。
2. 深度理解
- 你对共享内存的工作原理及其同步机制(如信号量)有深入的理解,能够解释如何通过信号量实现进程间的互斥访问,确保数据一致性。
- 你能够区分System V信号量和POSIX信号量,并详细说明它们的使用场景、优缺点以及相关的系统调用。这表明你对信号量的实现细节有较好的掌握。
3. 逻辑清晰
- 你的回答逻辑清晰,能够从基础概念逐步深入到具体实现细节,说明你能够系统地组织和表达知识。
4. 改进建议
- 虽然你已经掌握了大部分关键知识点,但在某些细节上可以进一步深化。例如,可以结合具体的代码示例或实际应用场景来进一步说明信号量的使用。
- 你还可以进一步探讨其他同步机制(如互斥锁、条件变量)在进程间通信中的应用,以及它们与信号量的区别和联系。
总结
你对Linux进程间通信的知识点已经有了扎实的理解,尤其是在共享内存和信号量方面表现突出。继续保持这种深入学习的习惯,结合实际代码和应用场景,你的理解会更加深刻和全面。如果你有时间,可以尝试编写一些简单的IPC程序,通过实践来巩固和扩展你的知识。
2、关于共享内存
- 我:
我在学习Linux共享内存知识点,请你以苏格拉底的方式对我进行提问,一次一个问题 - AI:
好的,让我们开始。首先,你能告诉我什么是Linux中的共享内存吗? - 我:
在Linux系统中,共享内存是一种高效的进程间通信方式,它允许多个进程共享同一块内存区域,这些进程可以对这块内存进行读写操作,从而实现数据的快速交换和共享。共享内存通常通过系统调用创建和管理,比如使用shmget
创建共享内存段,shmat
将共享内存段映射到进程地址空间等,这种方式减少了数据拷贝的开销,提高了程序运行效率,常用于需要频繁交换大量数据的场景。 - AI:
很好,你的理解很到位。接下来,让我们深入一点。既然你提到共享内存允许多个进程共享同一块内存区域,那么你能解释一下,当多个进程同时访问共享内存时,如何确保数据的一致性和避免竞争条件呢? - 我:
在Linux中,为了避免多个进程同时访问共享内存导致的竞争条件和数据不一致问题,通常会使用同步机制,比如信号量、互斥锁或读写锁,来控制进程对共享内存的访问顺序,确保同一时刻只有一个进程可以写入,或者多个进程可以安全地读取。 - AI:
很好,你已经提到了同步机制。那么,假设我们选择使用信号量作为同步机制,你能详细描述一下信号量是如何在共享内存中工作的吗?具体来说,信号量的初始值应该如何设置,以及它如何控制进程的访问? - 我:
信号量是一种计数器,用于控制对共享资源的访问。在共享内存中使用信号量时,信号量的初始值通常设置为1(表示共享内存可用),当进程访问共享内存时,信号量值减1;当进程释放共享内存时,信号量值加1。信号量通过P操作(等待)和V操作(释放)来控制进程对共享内存的访问。 - AI:
很好,你已经清楚地描述了信号量的基本操作。现在让我们进一步探讨一下,假设一个进程在执行P操作时发现信号量的值为0,会发生什么?这个进程会被如何处理?
- 我:
如果一个进程在执行P操作时发现信号量的值为0,这意味着共享内存当前正在被其他进程使用,无法立即访问。此时,该进程会被阻塞,进入等待状态,直到其他进程释放共享内存,信号量的值变为大于0时,它才会被唤醒并继续执行。
针对我该知识点,我理解了吗?我的回答结束了,请对我的回答进行评价总结。
- AI:
你的回答非常准确!你清楚地理解了信号量在共享内存中的作用,以及它在进程同步中的行为。你不仅掌握了信号量的基本操作(P操作和V操作),还理解了进程在无法访问共享内存时的状态变化(阻塞和唤醒)。这表明你对Linux共享内存的同步机制有了扎实的理解。
总结一下,你的回答表明你已经掌握了以下关键点:
- 信号量的初始值设置及其含义。
- P操作和V操作的作用。
- 进程在信号量值为0时的处理方式。
如果你有其他问题或想进一步探讨,随时可以继续提问!
五、基于AI的代码实践
使用Gitee进行代码托管
仓库链接:
例1
pipedemo.c
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
char sendbuf[]="I am Linux";
char recbuf[20];
char parrecbuf[20];
void reverse(char *str1) //字符串倒序函数
{
if (str1==NULL) return;
char *p = str1;
char *q = str1;
while (*q) ++q;
q--;
while(q > p)
{
char t = *p;
*p++ = *q;
*q-- = t;
}
}
int main(){
int mypipe[2],fd;
int status; // 用于存储子进程的退出状态
if ( pipe(mypipe)<0 ) { perror("pipe failed"); exit(0); }
if ( (fd=fork())<0 ) { perror("fork failed"); exit(0); }
if ( fd==0 ){
read(mypipe[0],recbuf,strlen(sendbuf));
printf("The child process get %s\n",recbuf);
reverse(recbuf); //倒序字符串
write(mypipe[1],recbuf,strlen(recbuf)); //向管道写入倒序后的字符串
exit(0); // 子进程结束
}
if (fd>0){
write(mypipe[1],sendbuf,strlen(sendbuf));
sleep(10); //等待子进程从管道将数据取走
read(mypipe[0],parrecbuf,strlen(sendbuf));
printf("The parent process get %s\n",parrecbuf);
wait(&status); //防止僵尸进程,并获取子进程的退出状态
printf("Child process exited with status %d\n", status);
}
}
实践截图:
例2
duptest1.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
int main() {
int newfd, oldfd1, oldfd2, newfd2, nchar;
char buf[30];
// 打开文件
oldfd1 = open("a.txt", O_RDWR);
if (oldfd1 == -1) {
perror("Failed to open a.txt");
return 1;
}
oldfd2 = open("b.txt", O_RDWR);
if (oldfd2 == -1) {
perror("Failed to open b.txt");
close(oldfd1); // 关闭之前打开的文件描述符
return 1;
}
// 复制文件描述符
newfd = dup(oldfd1);
if (newfd == -1) {
perror("dup failed");
close(oldfd1);
close(oldfd2);
return 1;
}
printf("The newfd file descriptor =%d\n", newfd);
// 使用dup2复制文件描述符到文件描述符3
newfd2 = dup2(oldfd1, 3);
if (newfd2 == -1) {
perror("dup2 failed");
close(oldfd1);
close(oldfd2);
close(newfd);
return 1;
}
printf("The newfd2 file descriptor =%d\n", newfd2);
// 从文件描述符3读取数据
nchar = read(3, buf, 28);
if (nchar == -1) {
perror("read failed");
close(oldfd1);
close(oldfd2);
close(newfd);
close(3);
return 1;
}
buf[nchar] = '\0';
printf("I have read from a.txt:%s\n", buf);
// 关闭所有文件描述符
close(oldfd1);
close(oldfd2);
close(newfd);
close(3);
return 0;
}
实践截图:
例3
ex.c
#include <stdio.h> // 包含 printf 函数的声明
#include <fcntl.h> // 包含 open 和 dup 函数的声明
#include <unistd.h> // 包含 close 函数的声明
int main() {
int fd1, fd2, fd3;
fd1 = open("test.txt", O_RDWR | O_TRUNC);
if (fd1 == -1) {
perror("Failed to open file");
return 1;
}
fd2 = dup(fd1);
if (fd2 == -1) {
perror("Failed to duplicate file descriptor");
close(fd1);
return 1;
}
printf("fd2=%d\n", fd2);
close(0); // 关闭标准输入
fd3 = dup(fd1);
if (fd3 == -1) {
perror("Failed to duplicate file descriptor");
close(fd1);
close(fd2);
return 1;
}
printf("fd3=%d\n", fd3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
实践截图:
例4
mypipe.c
/* mypipe.c */
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
void main() {
int pid, mypipe[2];
pipe(mypipe);
pid=fork();
if (pid<0) {
perror("create process failed\n");
exit(0);
}
if (pid==0) { //子进程
close(mypipe[1]);
dup2(mypipe[0],0);
close(mypipe[0]);
sleep(1);
execlp("grep","grep","init",NULL);
}
else { //父进程
close(mypipe[0]);
dup2(mypipe[1],1);
close(mypipe[1]);
execlp("ps","ps","aux",NULL);
}
}
实践截图:
例5
popen.c
/* read/write from popen */
/* popenexam.c */
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
void main() {
FILE *readfp, *writefp;
char buf[100];
readfp=popen("ps aux","r"); // r 表示读取管道命令执行结果
writefp=popen("grep init","w"); // w 表示向进程写入数据
while (fgets(buf,99,readfp)!=NULL) {
fputs(buf,writefp);
}
// 从 popen 读取数据并写入另外一个进程中
pclose(readfp);
pclose(writefp);
}
实践截图:
例6
namedpipe1.c
/* namedpipe2.c */
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h> // 包含 write 函数的声明
#include<error.h>
#define N 20
int main() {
char buf[N];
int fd = open("/home/mypipe", O_WRONLY);
if(fd != -1) {
printf("FIFO file is opened\n");
}
printf("please input string\n");
scanf("%s",buf); getchar(); // 注意这里使用 getchar() 来消耗 scanf 后的换行符
if ( write(fd,buf,sizeof(buf)) != -1 )
printf("write successful\n");
else perror("write failed:");
close(fd); // 关闭文件描述符
exit(EXIT_SUCCESS);
}
实践截图:
例7
pipelib.h
/*pipelib.h*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#define SERVER_FIFO_NAME "/tmp/server_fifo"
#define CLIENT_FIFO_NAME_FORMAT "/tmp/client_fifo%d" // 修改宏定义以包含格式说明符
#define BUFFER_SIZE PIPE_BUF
#define MESSAGE_SIZE 20
#define NAME_SIZE 256
typedef struct message {
pid_t client_pid;
char data[MESSAGE_SIZE + 1];
} message;
client.c
#include "pipelib.h"
int main() {
int server_fifo_fd;
int client_fifo_fd;
int res;
char client_fifo_name[NAME_SIZE];
message msg;
msg.client_pid = getpid();
sprintf(client_fifo_name, CLIENT_FIFO_NAME_FORMAT, msg.client_pid); // 使用修正后的宏定义
if (mkfifo(client_fifo_name, 0777) == -1) { // 创建向服务器发送数据的管道文件
fprintf(stderr, "Sorry, create client fifo failure!\n");
return -1;
}
server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY); // 以写的方式打开该管道文件
if (server_fifo_fd == -1) {
fprintf(stderr, "Sorry, open server fifo failure!\n");
return -1;
}
sprintf(msg.data, "Hello from %d", msg.client_pid); // 向服务器进程发送 Hello from PID 信息
printf("%d sent %s ", msg.client_pid, msg.data);
write(server_fifo_fd, &msg, sizeof(msg));
client_fifo_fd = open(client_fifo_name, O_RDONLY);
if (client_fifo_fd == -1) {
fprintf(stderr, "Sorry, client fifo open failure!\n");
return -1;
}
res = read(client_fifo_fd, &msg, sizeof(msg)); // 从服务器接收数据
if (res > 0)
printf("received:%s\n", msg.data);
close(client_fifo_fd);
close(server_fifo_fd);
unlink(client_fifo_name);
return 0;
}
server.c
#include "pipelib.h"
int main() {
int server_fifo_fd;
int client_fifo_fd;
int res;
char client_fifo_name[NAME_SIZE];
message msg;
char *p;
if (mkfifo(SERVER_FIFO_NAME, 0777) == -1) { // 创建服务器端接收消息的管道文件
fprintf(stderr, "Sorry, create server fifo failure!\n");
return -1;
}
server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY); // 打开管道
if (server_fifo_fd == -1) {
fprintf(stderr, "Sorry, server fifo open failure!\n");
return -1;
}
sleep(5);
while ((res = read(server_fifo_fd, &msg, sizeof(msg))) > 0) {
// 从服务器端读取客户端发送过来的数据并在循环体中处理
p = msg.data;
while (*p) {
*p = toupper(*p); // 转换为大写字母
p++;
}
sprintf(client_fifo_name, "/tmp/client_fifo%d", msg.client_pid); // 修正宏定义的使用
client_fifo_fd = open(client_fifo_name, O_WRONLY); // 打开客户端的管道
if (client_fifo_fd == -1) {
fprintf(stderr, "Sorry, client fifo open failure!\n");
return -1;
}
write(client_fifo_fd, &msg, sizeof(msg)); // 将消息通过 client 的管道发送给客户端
close(client_fifo_fd);
}
close(server_fifo_fd);
unlink(SERVER_FIFO_NAME); // 删除管道文件
exit(EXIT_SUCCESS);
}
实践截图:
例8
semlib.h
#ifndef SEMLIB_H
#define SEMLIB_H
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define DELAY_TIME 2
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);
#endif /* SEMLIB_H */
semlib.c
#include "semlib.h"
int init_sem(int sem_id, int init_value) {
union semun sem_union;
sem_union.val = init_value;
if (semctl(sem_id, 0, SETVAL, sem_union) == -1) {
perror("initializing semaphore");
return -1;
}
return 0;
}
int del_sem(int sem_id) {
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) {
perror("delete semaphore failed");
return -1;
}
return 0;
}
int sem_p(int sem_id) {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
perror("P operation failed");
return -1;
}
return 0;
}
int sem_v(int sem_id) {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
perror("V operation failed");
return -1;
}
return 0;
}
semexample.c
#include "semlib.h"
int main(void) {
pid_t result;
int sem_id;
sem_id = semget(ftok("./a", 'a'), 1, 0666 | IPC_CREAT);
init_sem(sem_id, 0);
result = fork();
if (result == -1) {
perror("Fork failed\n");
} else if (result == 0) { /* 子进程 */
printf("Child progress is waiting for parent process\n");
sleep(DELAY_TIME);
printf("The child progress output\n");
printf("BBBBBBBBBBBB\n");
sem_v(sem_id); // V 操作
} else { /* 父进程 */
sem_p(sem_id); // P 操作
printf("The father process output \n");
printf("AAAAAAAAAAAA\n");
del_sem(sem_id);
}
exit(0);
}
实践截图:
例9
semlib.h
#ifndef SEMLIB_H
#define SEMLIB_H
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define SHMSZ 27
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);
#endif /* SEMLIB_H */
semlib.c
#include "semlib.h"
int init_sem(int sem_id, int init_value) {
union semun sem_union;
sem_union.val = init_value;
if (semctl(sem_id, 0, SETVAL, sem_union) == -1) {
perror("initializing semaphore");
return -1;
}
return 0;
}
int del_sem(int sem_id) {
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) {
perror("delete semaphore failed");
return -1;
}
return 0;
}
int sem_p(int sem_id) {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
perror("P operation failed");
return -1;
}
return 0;
}
int sem_v(int sem_id) {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
perror("V operation failed");
return -1;
}
return 0;
}
outab.c
#include "semlib.h"
char SEM_NAME[] = "process1";
int main() {
int fd;
sem_t *mutex;
mutex = sem_open(SEM_NAME, O_CREAT, 0777, 1);
if (mutex == SEM_FAILED) {
perror("unable to execute semaphore");
exit(-1);
}
fd = open("a.txt", O_CREAT | O_WRONLY | O_TRUNC | O_APPEND);
if (fd == -1) {
perror("open failed");
sem_close(mutex);
exit(-1);
}
while (1) {
sem_wait(mutex); // P 操作
write(fd, "ABC\n", sizeof("ABC\n") - 1);
close(fd);
fd = open("a.txt", O_WRONLY | O_APPEND);
sleep(1);
sem_post(mutex); // V 操作
}
sem_close(mutex);
sem_unlink(SEM_NAME);
close(fd);
exit(0);
}
outdef.c
#include "semlib.h"
char SEM_NAME[] = "process1";
int main() {
int fd;
sem_t *mutex;
mutex = sem_open(SEM_NAME, O_CREAT, 0777, 1);
if (mutex == SEM_FAILED) {
perror("unable to execute semaphore");
exit(-1);
}
fd = open("a.txt", O_CREAT | O_WRONLY | O_TRUNC | O_APPEND);
if (fd == -1) {
perror("open failed");
sem_close(mutex);
exit(-1);
}
while (1) {
sem_wait(mutex); // P 操作
write(fd, "DEF\n", sizeof("DEF\n") - 1);
close(fd);
fd = open("a.txt", O_WRONLY | O_APPEND);
sleep(1);
sem_post(mutex); // V 操作
}
sem_close(mutex);
sem_unlink(SEM_NAME);
close(fd);
exit(0);
}
实践截图:
例10
share.c
#include <sys/shm.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <stdint.h> // 添加此头文件以使用 intptr_t
#define SIZE 1024
int main() {
int shmid; // 共享内存段标识符
char *shmaddr;
char buff[30];
int shmstatus; // 获取共享内存属性信息
int pid;
shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT | 0600);
if (shmid < 0) {
perror("get shm ipc_id error");
return -1;
}
pid = fork(); // 子进程向共享内存中写入数据
if (pid == 0) { // 子进程向共享内存中写入数据
shmaddr = (char *)shmat(shmid, NULL, 0); // 映射共享内存,可读写
if ((intptr_t)shmaddr == -1) { // 修正此处的类型转换
perror("shmat addr error");
return -1;
}
strcpy(shmaddr, "Hello World!\n");
shmdt(shmaddr);
return 0;
} else if (pid > 0) {
sleep(10);
shmaddr = (char *)shmat(shmid, NULL, 0);
if ((intptr_t)shmaddr == -1) { // 修正此处的类型转换
perror("shmat addr error");
return -1;
}
strcpy(buff, shmaddr);
printf("I have got from shared memory :%s\n", buff);
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
} else {
perror("fork error");
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}
实践截图:
例11
memshare.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include "semilib.h" //前面章节的信号量操作库函数
#include <string.h>
#include <sys/wait.h> // 添加此头文件以解决 waitpid 的警告
#define BUFFER_SIZE 2048
int main()
{
pid_t pid;
int sem_id; //信号量ID
int shmid; //共享内存段ID
char *shm_addr = NULL; //共享内存首地址
char buff[BUFFER_SIZE]; //字符串
int i = 0;
sem_id = semget(ftok(".", 'a'), 1, 0666 | IPC_CREAT); //信号量
init_sem(sem_id, 0); //信号量初始化
if ((shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666)) < 0)
{
perror("shmget");
return -1;
}
pid = fork();
if (pid == -1)
{
perror("fork");
return -1;
}
else if (pid == 0)
{
if ((shm_addr = shmat(shmid, 0, 0)) == (char *)-1)
{
perror("shmat");
return -1;
}
while (i < 3)
{
printf("Child process is waiting for data:\n");
sem_p(sem_id); //P操作
strcpy(buff, shm_addr); //读取数据
printf("Child get data from shared-memory:%s\n", buff);
sem_v(sem_id); //V操作
i++;
}
del_sem(sem_id); //删除信号量
if (shmdt(shm_addr) < 0)
{
perror("shmdt");
return -1;
}
if (shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("child process delete shared memory ");
return -1;
}
}
else
{
if ((shm_addr = shmat(shmid, 0, 0)) == (char *)-1)
{
perror("Parent shmat failed");
return -1;
}
while (i < 3)
{
printf("Please input some string:\n");
fgets(buff, BUFFER_SIZE, stdin);
strncpy(shm_addr, buff, strlen(buff));
sem_v(sem_id); //V操作
sem_p(sem_id); //P操作
i++;
}
if (shmdt(shm_addr) < 0)
{
perror("Parent:shmdt");
exit(1);
}
waitpid(pid, NULL, 0);
}
return 0;
}
实践截图:
例12
first.c
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h> // 添加此头文件以声明 perror 函数
#include <stdlib.h> // 添加此头文件以声明 exit 函数
typedef struct {
char name[4];
int age;
} student;
int main(int argc, char** argv) {
int fd, i;
student *p_map;
char temp;
fd = open("stu.txt", O_CREAT | O_RDWR | O_TRUNC, 00777);
lseek(fd, sizeof(student) * 5 - 1, SEEK_SET);
write(fd, "", 1);
p_map = (student *)mmap(NULL, sizeof(student) * 10, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (p_map == (void *)-1) {
perror("mmap failed\n");
exit(0);
}
close(fd);
temp = 'a';
for (i = 0; i < 10; i++) {
temp += 1; // 学生姓名
memcpy((*(p_map + i)).name, &temp, 2);
(*(p_map + i)).name[1] = '\0';
(*(p_map + i)).age = 20 + i; // 学生年龄
}
sleep(10);
munmap(p_map, sizeof(student) * 10);
return 0;
}
second.c
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
typedef struct {
char name[4];
int age;
} student;
int main(int argc, char** argv) {
int fd, i;
student *p_map;
fd = open("stu.txt", O_CREAT | O_RDWR, 00777);
p_map = (student *)mmap(NULL, sizeof(student) * 10, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
for (i = 0; i < 10; i++) {
printf("name: %s age: %d\n", (*(p_map + i)).name, (*(p_map + i)).age);
}
munmap(p_map, sizeof(student) * 10);
return 0;
}
实践截图:
例13
msgexamp.h
#ifndef MSGQUE_EXAMP
#define MSGQUE_EXAMP
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define MAX_TEXT 512
#define MSG_KEY 335
struct my_msg_st {
long my_msg_type; // 消息类型,其值分别为 1,2,3,代表不同交易消息
char text[MAX_TEXT]; // 存放具体消息内容
};
#endif
msgsnd.c
#include "msgexamp.h"
#include <stdlib.h>
#include <time.h>
int main() {
int index = 1;
struct my_msg_st some_data;
int msgid;
char buffer[BUFSIZ];
msgid = msgget((key_t)MSG_KEY, IPC_CREAT | S_IRUSR | S_IWUSR);
if (msgid == -1) {
perror("create message queue failed");
return -1;
}
srand((int)time(0));
while (index < 5) {
printf("%d Enter some text: less than %d\n", msgid, MAX_TEXT);
fgets(buffer, BUFSIZ, stdin);
some_data.my_msg_type = rand() % 3 + 1; // 随机产生消息类型 1~3 值
printf("my_msg_type=%ld\n", some_data.my_msg_type);
strcpy(some_data.text, buffer);
if (msgsnd(msgid, (void *)&some_data, sizeof(some_data), 0) == -1) {
fprintf(stderr, "msgsnd failed\n");
exit(-1);
}
index++;
}
exit(0);
}
msgrcv.c
#include "msgexamp.h"
int main(int argc, char **argv) {
int msgid;
int type;
struct my_msg_st *my_data = (struct my_msg_st *)malloc(sizeof(struct my_msg_st));
if (argc < 2) {
printf("USAGE: msgexample msgtype\n");
return -1;
}
type = atoi(argv[1]);
if (type < 0 || type > 3) {
printf("msgtype should be one of 1,2,3\n");
return -1;
}
msgid = msgget((key_t)MSG_KEY, IPC_CREAT | S_IRUSR | S_IWUSR);
if (msgid == -1) {
perror("get message queue failed");
return -1;
}
while (1) {
if (msgrcv(msgid, (void *)my_data, sizeof(struct my_msg_st), (long)type, IPC_NOWAIT) != -1) {
printf("The message type is:%ld\n", my_data->my_msg_type);
printf("The message content is:%s\n", my_data->text);
} else if (ENOMSG == errno) {
printf("there is no any message which is matched to message type %d\n", type);
break;
}
}
return 0;
}
实践截图:
习题1
进程间通信(IPC)的几种主要方式包括管道(Pipe)、消息队列(Message Queue)、共享内存(Shared Memory)、信号量(Semaphore)和套接字(Socket)。它们的特点和不同点如下:
-
管道(Pipe)
- 特点:单向通信,数据以字节流形式传输。
- 不同点:适用于有亲缘关系的进程间通信,不适合大量数据传输。
-
消息队列(Message Queue)
- 特点:允许消息以队列形式存储和传输,支持异步通信。
- 不同点:可以跨网络通信,适合大量数据传输。
-
共享内存(Shared Memory)
- 特点:进程间共享一块内存区域,通信速度快。
- 不同点:需要同步机制,适合大量数据传输。
-
信号量(Semaphore)
- 特点:用于进程间同步,控制对共享资源的访问。
- 不同点:主要用于同步,不用于数据传输。
-
套接字(Socket)
- 特点:支持不同主机间的进程通信,灵活。
- 不同点:可以跨网络通信,适合远程通信。
习题2
System V和POSIX是两种不同的进程间通信(IPC)标准,它们之间的差异性主要包括:
-
标准来源:
- System V:源自UNIX System V操作系统。
- POSIX:由IEEE制定的可移植操作系统接口标准。
-
兼容性:
- System V:特定于UNIX系统,兼容性较差。
- POSIX:跨平台,兼容性好,支持多种操作系统。
-
功能实现:
- System V:功能实现较为简单,直接。
- POSIX:功能实现更为复杂,但更标准化。
-
API设计:
- System V:API设计较为简单,使用方便。
- POSIX:API设计更复杂,但更统一和规范。
-
应用范围:
- System V:主要应用于UNIX和类UNIX系统。
- POSIX:广泛应用于各种操作系统,包括Linux、Windows等。
-
发展和维护:
- System V:发展和维护较为封闭。
- POSIX:由IEEE维护,更新和改进更为开放和活跃。
总的来说,POSIX标准在兼容性、标准化和跨平台方面具有优势,而System V则在某些特定环境中可能更为简单直接。
习题3
pipe.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1024
void child_process(int read_fd) {
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
// 打开接收文件
int file_fd = open("received.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (file_fd < 0) {
perror("open received file");
exit(1);
}
// 从管道读取数据并写入接收文件
while ((bytes_read = read(read_fd, buffer, sizeof(buffer))) > 0) {
write(file_fd, buffer, bytes_read);
}
close(read_fd);
close(file_fd);
}
void parent_process(const char* filename, int write_fd) {
// 打开发送文件
int file_fd = open(filename, O_RDONLY);
if (file_fd < 0) {
perror("open file");
exit(1);
}
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
// 从发送文件读取数据并写入管道
while ((bytes_read = read(file_fd, buffer, sizeof(buffer))) > 0) {
write(write_fd, buffer, bytes_read);
}
close(file_fd);
close(write_fd);
}
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
// 创建管道
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
// 创建子进程
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
child_process(pipefd[0]);
exit(0);
} else { // 父进程
close(pipefd[0]); // 关闭读端
parent_process(argv[1], pipefd[1]);
wait(NULL); // 等待子进程结束
}
return 0;
}
实践截图:
运行完成后生成了received.txt且内容与test.tst中的一致。
习题4
fd1
打开文件 “a.txt” 用于读取,其值是文件 “a.txt” 的文件描述符。fd2
打开文件 “b.txt” 用于写入,其值是文件 “b.txt” 的文件描述符。fd3
是fd1
的副本,因此fd3
和fd1
都指向文件 “a.txt”。fd4
通过dup2
被设置为与fd2
相同,所以fd4
也指向文件 “b.txt”。
fd1
和 fd3
指向同一个文件 “a.txt”,fd2
和 fd4
指向同一个文件 “b.txt”。
习题5
六、学习实践过程遇到的问题与解决方式
1. fork()函数的使用与理解
2. 僵尸进程的产生与处理
七、参考资料
- AI工具(你使用的AI工具及其链接)
- 问小白
- Kimi
- 图书
- 《Linux编程基础.李养群》电子版
- 网站
- CSDN
“20242817李臻 原创作品转载请注明出处 《Linux高级编程实践》”**