# 实验一 进程与线程——Linux 进程与线程通信 ## 一、实验内容 深刻理解线程和进程的概念,掌握线程与进程在组成成分上的差别,以及与其相适应的通信方式和应用目标。 以 Linux 系统进程和线程机制为背景,掌握 fork() 和 clone() 系统调用的形式和功能,以及与其相适应的高级通信方式: - 由 fork() 派生的子进程之间通过 pipe 通信 - 由 clone() 创建的线程之间通过共享内存通信(需要考虑互斥问题) 以生产者-消费者问题为例,实现创建 4 个进程或线程,其中包括两个生产者和两个消费者,生产者和消费者之间能够传递数据。 ## 二、实验设计 ### 2.1 基于 fork() 的进程版本 **函数设计:** - `main()` - 主函数,创建管道和4个子进程 - `producer(int id)` - 生产者函数,向管道写入数据 - `consumer(int id)` - 消费者函数,从管道读取数据 **调用关系:** ``` main() ├── pipe() 创建管道 ├── fork() → producer(1) ├── fork() → producer(2) ├── fork() → consumer(1) ├── fork() → consumer(2) └── wait() 等待所有子进程结束 ``` **通信机制:** - 使用 pipe() 创建匿名管道 - 生产者关闭管道读端,写入 w_buf 内容 - 消费者关闭管道写端,读取 r_buf 内容 ### 2.2 基于 clone() 的线程版本 **函数设计:** - `main()` - 主函数,初始化互斥锁和信号量,创建4个线程 - `producer(void *args)` - 生产者线程函数 - `consumer(void *args)` - 消费者线程函数 **调用关系:** ``` main() ├── pthread_mutex_init() 初始化互斥锁 ├── sem_init() 初始化信号量 ├── clone(producer) 创建生产者线程1 ├── clone(consumer) 创建消费者线程1 ├── clone(producer) 创建生产者线程2 ├── clone(consumer) 创建消费者线程2 └── exit() 退出 ``` **通信机制:** - 使用共享内存 buffer[8][4] 存放数据 - 使用 pthread_mutex_t mutex 进行互斥保护 - 使用 sem_t product/warehouse 进行同步 ## 三、编码实现 ### 3.1 fork() 版本修改要点 1. **头文件**:需要 `sys/types.h`、`sys/file.h`、`unistd.h` 2. **全局变量**: - `pipe_fd[2]` - 管道文件描述符 - `r_buf[4]`、`w_buf[4]` - 读写缓冲区 3. **生产者实现**: - 关闭管道读端 `close(pipe_fd[0])` - 循环写入数据 `write(pipe_fd[1], w_buf, 4)` - 每3秒生产一个数据 4. **消费者实现**: - 关闭管道写端 `close(pipe_fd[1])` - 循环读取数据 `read(pipe_fd[0], r_buf, 4)` - 读到0字节表示管道关闭,退出循环 ### 3.2 clone() 版本修改要点 1. **头文件**:需要 `sched.h`、`pthread.h`、`semaphore.h` 2. **共享资源初始化**: ```cpp pthread_mutex_init(&mutex, NULL); sem_init(&product, 0, 0); sem_init(&warehouse, 0, 8); ``` 3. **clone() 标志位**: ```cpp clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES; ``` 表示共享地址空间、信号处理、文件系统信息和文件描述符 4. **生产者实现**: - P操作申请仓库空位 `sem_wait(&warehouse)` - 加锁写入共享缓冲区 `pthread_mutex_lock(&mutex)` - V操作通知产品 `sem_post(&product)` 5. **消费者实现**: - P操作等待产品 `sem_wait(&product)` - 加锁读取共享缓冲区 `pthread_mutex_lock(&mutex)` - V操作释放仓库空位 `sem_post(&warehouse)` ### 3.3 编译命令 ```bash # fork 版本 g++ exp01_source.cpp -o exp01_fork # clone 版本 g++ exp01_clone.cpp -lpthread -o exp01_clone ``` ## 四、实验结果 ### 4.0 测试命令 ```bash # 编译 fork 版本 g++ exp01_source.cpp -o exp01_fork # 编译 clone 版本 g++ exp01_clone.cpp -lpthread -o exp01_clone # 运行 fork 版本(等待约30秒后自动结束) ./exp01_fork # 运行 clone 版本(等待约20秒后自动结束) ./exp01_clone ``` ### 4.1 fork() 版本运行结果 ``` pipe is created successfully! producer 1 is running! producer 2 is running! consumer 2 is running! consumer 1 is running! consumer 1 get aaa, while the w_buf is ccc consumer 2 get bbb, while the w_buf is ddd consumer 1 get aaa, while the w_buf is ccc consumer 2 get bbb, while the w_buf is ddd ... producer 1 is over! producer 2 is over! consumer 1 is over! consumer 2 is over! ``` **结果说明:** - 4个进程并发运行,进程间独立地址空间 - 通过管道传递数据 "aaa" 和 "bbb" - 消费者获取的数据与生产者写入的一致 - 每个进程独立运行,无数据竞争 ### 4.2 clone() 版本运行结果 ``` producer0 produce aaa in 0 producer1 produce bbb in 1 consumer0 get aaa in 1 consumer1 get bbb in 0 producer0 produce aaa in 2 producer1 produce bbb in 3 consumer0 get aaa in 3 consumer1 get bbb in 2 ... producer0 is over! producer1 is over! consumer0 is over! consumer1 is over! ``` **结果说明:** - 4个线程共享地址空间,共享 buffer[8][4] - 互斥锁保护共享缓冲区的读写操作 - 信号量实现生产者和消费者的同步 - 线程间数据一致,无竞争条件 ## 五、实验结果思考与体会 ### 5.1 fork() 与 clone() 的区别 | 特性 | fork() | clone() | |------|--------|---------| | 地址空间 | 独立 | 可共享 | | 创建开销 | 较大(复制整个进程) | 较小(可共享资源) | | 通信方式 | 管道/消息队列等 | 共享内存(天然支持) | | 同步需求 | 需要额外同步机制 | 可直接同步 | ### 5.2 思考问题解答 **问题1:用 shm/msg 实现生产者-消费者** 可以使用共享内存(shmget/shmat)替代 clone() 的共享内存机制,用消息队列(msgget/msgsnd)替代管道实现生产者和消费者通信。 **问题2:对比 pipe、clone、shm、msg 优缺点** - **pipe**:适用于有亲缘关系的进程,编程简单,但数据传输单向 - **clone()**:创建灵活,可共享多种资源,但编程复杂 - **shm**:读写效率高,适合大数据共享,但需要同步 - **msg**:支持消息类型过滤,但大数据传输需要拆分 ### 5.3 实验体会 通过本实验,深入理解了进程与线程的本质区别: - 进程拥有独立的地址空间,适合需要隔离的任务 -线程共享地址空间,适合需要频繁通信和数据共享的任务 生产者-消费者问题是经典的同步问题,通过本实验掌握了: 1. 进程/线程的创建方法 2. 进程间通信(IPC)机制 3. 线程间同步机制(互斥锁、信号量) 4. 死锁和竞争条件的避免方法