当前位置: 首页 > news >正文

Day31 进程间通信(IPC)多线程目录扫描

day31 进程间通信(IPC)多线程目录扫描

扫描目录的序列队列实现

序列队列头文件 (seqque.h)

定义了循环队列结构体及操作函数,用于目录扫描任务中的路径存储。

#ifndef __SEQQUE__H__
#define __SEQQUE__H__// 定义队列中存储的数据类型(文件路径)
typedef struct 
{char path[1024];  // 存储文件路径的字符数组
} DATATYPE;// 定义循环队列结构体
typedef struct 
{DATATYPE* array;  // 指向数据数组的指针int head;         // 队头指针int tail;         // 队尾指针int tlen;         // 队列总长度
} SeqQue;// 队列操作函数声明
SeqQue* CreateSeqQue(int len);          // 创建指定长度的队列
int EnterSeqQue(SeqQue* sq, DATATYPE* data);  // 元素入队
int QuitSeqQue(SeqQue* sq);             // 元素出队
DATATYPE* GetHeadSeqQue(SeqQue* sq);    // 获取队头元素
int IsEmptySeqQue(SeqQue* sq);          // 检查队列是否为空
int IsFullSeqQue(SeqQue* sq);           // 检查队列是否为满
int DestroySeqQue(SeqQue* sq);          // 销毁队列#endif  //!__SEQQUE__H__

序列队列实现 (seqque.c)

实现循环队列的核心操作逻辑,采用模运算处理循环特性。

#include "seqque.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 创建指定长度的循环队列
SeqQue* CreateSeqQue(int len)
{SeqQue* sq = malloc(sizeof(SeqQue));  // 分配队列结构体内存if(NULL == sq){perror("CreateSeqQue malloc");  // 内存分配失败处理return NULL;}sq->array = malloc(sizeof(DATATYPE)*len);  // 分配数据存储数组if(NULL == sq->array){perror("CreateSeqQue malloc2");  // 数组分配失败处理return NULL;}sq->head = 0;     // 初始化队头指针sq->tail = 0;     // 初始化队尾指针sq->tlen = len;   // 设置队列总长度return sq;  // 返回创建的队列指针
}// 元素入队操作
int EnterSeqQue(SeqQue* sq, DATATYPE* data)
{if(IsFullSeqQue(sq))  // 检查队列是否已满{printf("queue is full\n");return 1;}memcpy(&sq->array[sq->tail], data, sizeof(DATATYPE));  // 复制数据到队尾sq->tail = (sq->tail + 1) % sq->tlen;  // 移动队尾指针(循环处理)return 0;
}// 元素出队操作
int QuitSeqQue(SeqQue* sq)
{if(IsEmptySeqQue(sq))  // 检查队列是否为空{printf("queue is empty\n");return 1;}sq->head = (sq->head + 1) % sq->tlen;  // 移动队头指针(循环处理)return 0;
}// 获取队头元素
DATATYPE* GetHeadSeqQue(SeqQue* sq)
{return &sq->array[sq->head];  // 返回队头元素指针
}// 检查队列是否为空
int IsEmptySeqQue(SeqQue* sq)
{return sq->head == sq->tail;  // 头尾指针重合表示空队列
}// 检查队列是否为满
int IsFullSeqQue(SeqQue* sq)
{// 尾指针+1模长度等于头指针表示队列满(预留一个空位)return (sq->tail + 1) % sq->tlen == sq->head;
}// 销毁队列
int DestroySeqQue(SeqQue* sq)
{free(sq->array);  // 释放数据数组free(sq);         // 释放队列结构体return 0;
}

目录扫描主程序

多线程扫描目录查找包含#define的头文件,使用信号量和互斥锁实现线程同步。

#include <dirent.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "seqque.h"sem_t sem_task;           // 任务信号量
pthread_t main_th;        // 主线程ID
pthread_mutex_t mutex;    // 互斥锁// 检查文件中是否包含#define并记录结果
void do_check(char* filename, FILE* dst)
{printf("%s\n", filename);  // 打印当前处理文件名char* tmp = filename;if (strlen(tmp) < 3)  // 跳过短文件名{return;}// 检查是否为.h头文件if (0 != strcmp(".h", &tmp[strlen(tmp) - 2])){return;}FILE* fp = fopen(filename, "r");  // 打开文件if (NULL == fp){perror("fopen error");return;}int num = 1;  // 行号计数器while (1){char buf[1024] = {0};// 读取一行内容if (NULL == fgets(buf, sizeof(buf), fp)){return;}// 检查是否包含#defineif (strstr(buf, "#define")){buf[strlen(buf) - 1] = '\0';  // 移除换行符// 写入结果到日志文件(格式:内容 行号 文件名)fprintf(dst, "%s %d %s\n", buf, num, filename);fflush(dst);  // 立即刷新缓冲区}num++;}fclose(fp);  // 关闭文件
}// 递归扫描目录
void do_ls(char* path, SeqQue* sq, FILE* fp)
{DIR* dir = opendir(path);  // 打开目录if (NULL == dir){perror("opendir");return;}DATATYPE data;while (1){bzero(&data, sizeof(data));  // 清空数据结构struct dirent* info = readdir(dir);  // 读取目录项if (NULL == info){break;  // 目录遍历结束}char newpath[1024] = {0};// 构建完整路径sprintf(newpath, "%s/%s", path, info->d_name);if (DT_DIR == info->d_type)  // 处理子目录{// 跳过当前目录和父目录if (0 == strcmp(info->d_name, ".") || 0 == strcmp(info->d_name, "..")){continue;}if (pthread_self() == main_th)  // 主线程处理{strcpy(data.path, newpath);  // 复制路径pthread_mutex_lock(&mutex);  // 加锁EnterSeqQue(sq, &data);      // 入队pthread_mutex_unlock(&mutex);  // 解锁sem_post(&sem_task);  // 增加信号量}else  // 工作线程处理{do_ls(newpath, sq, fp);  // 递归扫描}}else if (DT_REG == info->d_type)  // 处理普通文件{do_check(newpath, fp);  // 检查文件内容}}closedir(dir);  // 关闭目录
}// 线程参数结构体
typedef struct
{SeqQue* sq;   // 队列指针FILE* dst;    // 输出文件指针
} TH_ARG;// 工作线程函数
void* th(void* arg)
{TH_ARG* tmp = (TH_ARG*)arg;DATATYPE back_data;while (1){bzero(&back_data, sizeof(back_data));sem_wait(&sem_task);  // 等待任务信号量pthread_mutex_lock(&mutex);  // 加锁DATATYPE* dat = GetHeadSeqQue(tmp->sq);  // 获取队头memcpy(back_data.path, dat->path, sizeof(DATATYPE));  // 复制路径QuitSeqQue(tmp->sq);  // 出队pthread_mutex_unlock(&mutex);  // 解锁// 检测结束标志if (0 == strcmp(back_data.path, "over")){break;}do_ls(back_data.path, tmp->sq, tmp->dst);  // 扫描目录}return NULL;
}// 主函数
int main(int argc, char** argv)
{sem_init(&sem_task, 0, 0);  // 初始化信号量(值为0)SeqQue* sq = CreateSeqQue(10000);  // 创建大容量队列FILE* dst = fopen("log", "w");  // 打开日志文件TH_ARG arg = {0};arg.dst = dst;  // 设置输出文件arg.sq = sq;    // 设置队列main_th = pthread_self();  // 记录主线程IDpthread_mutex_init(&mutex, NULL);  // 初始化互斥锁pthread_t tid[3] = {0};  // 工作线程ID数组// 创建3个工作线程for (int i = 0; i < 3; i++){pthread_create(&tid[i], NULL, th, &arg);}// 主线程开始扫描根目录do_ls("/home/linux", sq, dst);// 发送结束信号给所有工作线程for (int i = 0; i < 3; i++){DATATYPE data = {0};strcpy(data.path, "over");  // 设置结束标志pthread_mutex_lock(&mutex);EnterSeqQue(sq, &data);  // 入队结束标志pthread_mutex_unlock(&mutex);sem_post(&sem_task);  // 唤醒工作线程}// 等待所有工作线程结束for (int i = 0; i < 3; i++){pthread_join(tid[i], NULL);}// 清理资源pthread_mutex_destroy(&mutex);sem_destroy(&sem_task);DestroySeqQue(sq);fclose(dst);return 0;
}

理想运行结果
程序将扫描 /home/linux 目录及其子目录,查找所有 .h 头文件中包含 #define 的行。结果会写入 log 文件,每行格式为:
#define语句 行号 文件路径
例如:
#define MAX_SIZE 100 15 /home/linux/include/config.h
最终生成完整的头文件宏定义日志。


IPC 进程间通信

进程间通信(Inter-Process Communication)是操作系统提供的重要机制,主要分为三大类:

通信方式分类

  1. 古老通信方式

    • 无名管道、有名管道、信号
    • 特点:信号是唯一的异步通信机制
  2. IPC对象通信(System V)

    • 消息队列(较少使用)
    • 共享内存(最高效的通信方式)
    • 信号量集
  3. Socket通信

    • 用于网络通信

管道通信

管道是半双工通信机制,所有管道都是特殊文件,不支持定位操作(如 lseek),读写使用标准文件I/O操作。

核心特性

  1. 半双工模式:数据单向流动
  2. 特殊文件:读写使用 read/write,不支持文件定位
  3. 容量限制:Linux系统中管道容量为64KB(16页,每页4KB)
  4. 阻塞规则
    • 读端存在时,写满64KB会阻塞
    • 写端存在时,空管道读操作会阻塞
    • 读端关闭时写操作触发 SIGPIPE 信号
    • 写端关闭时读操作返回0(表示结束)

无名管道

特性
  • 仅限亲缘关系进程使用(父子/兄弟进程)
  • 有固定读写端:fd[0] 为读端,fd[1] 为写端
  • 必须在 fork() 前创建
使用框架
  1. 创建管道:pipe()
  2. 读写管道:read()/write()
  3. 关闭管道:close()
创建函数
#include <unistd.h>
int pipe(int pipefd[2]);
  • 功能:创建并打开无名管道
  • 参数
    • pipefd[0]:管道读端
    • pipefd[1]:管道写端
  • 返回值:成功返回0,失败返回-1
代码示例
管道读阻塞示例 (01pipe_read_block.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};  // 管道文件描述符数组int ret = pipe(fd);  // 创建无名管道if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();  // 创建子进程if (pid > 0)  // 父进程{close(fd[0]);  // 关闭读端char buf[] = "hello,child";  // 待写入数据sleep(3);  // 延迟3秒write(fd[1], buf, strlen(buf) + 1);  // 写入管道}else if (0 == pid)  // 子进程{close(fd[1]);  // 关闭写端char buf[50] = {0};  // 读缓冲区read(fd[0], buf, sizeof(buf));  // 从管道读取(会阻塞等待)printf("child, buf:%s\n", buf);  // 打印读取内容}else {perror("fork");return 1;}return 0;
}

理想运行结果
子进程阻塞3秒后,打印:
child, buf:hello,child


管道写阻塞示例 (02pipe_write_block.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{close(fd[0]);char buf[1024] = {0};memset(buf, 'a', sizeof(buf));  // 填充'a'int i = 0;for (i = 0; i < 65; i++)  // 超过64KB容量{write(fd[1], buf, sizeof(buf));printf("i is %d\n", i);  // 观察写阻塞点}}else if (0 == pid)  // 子进程{close(fd[1]);sleep(5);  // 延迟读取char buf[50] = {0};read(fd[0], buf, sizeof(buf));printf("child, buf:%s\n", buf);}else {perror("fork");return 1;}return 0;
}

理想运行结果
父进程打印 i is 0i is 63 后阻塞(64次×1024=64KB),第64次写入后继续打印 i is 64。子进程5秒后读取并打印64个 'a'

在这里插入图片描述


管道破裂示例 (03pipe_broken.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{sleep(1);  // 确保子进程先关闭close(fd[0]);  // 关闭读端char buf[] = "hello,child";write(fd[1], buf, strlen(buf) + 1);  // 触发管道破裂printf("write end\n");  // 不会执行(已崩溃)}else if (0 == pid)  // 子进程{close(fd[1]);  // 关闭写端close(fd[0]);  // 关闭读端sleep(3);      // 等待父进程写操作printf("child, pid:%d\n", getpid());}else {perror("fork");return 1;}return 0;
}

理想运行结果
子进程先关闭所有管道端,父进程写操作触发 SIGPIPE 信号,进程异常终止。终端显示:
Broken pipe


读端关闭示例 (04pipe_read_zero.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{close(fd[0]);char buf[] = "hello,child";write(fd[1], buf, strlen(buf) + 1);}else if (0 == pid)  // 子进程{close(fd[1]);while (1){char buf[50] = {0};int ret = read(fd[0], buf, sizeof(buf));if (0 == ret)  // 写端关闭且无数据{printf("IPC end\n");break;}printf("child, buf:%s\n", buf);}}else {perror("fork");return 1;}return 0;
}

理想运行结果
子进程先读取到 hello,child,父进程退出后写端关闭,子进程下一次 read() 返回0并打印:
IPC end


有名管道 (FIFO)

特性
  • 文件系统可见(有路径名)
  • 适用于任意进程通信
  • 打开时需指定读/写模式
  • 无亲缘关系限制
使用框架
  1. 创建管道:mkfifo()
  2. 打开管道:open()(必须指定 O_RDONLY/O_WRONLY
  3. 读写管道:read()/write()
  4. 关闭管道:close()
  5. 删除管道:remove()
核心函数
int mkfifo(const char *pathname, mode_t mode);
  • 功能:创建有名管道文件
  • 参数
    • pathname:管道路径
    • mode:8进制权限(如 0666
  • 返回值:成功返回0,失败返回-1
代码示例
写端程序 (01fifo_w.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);  // 创建管道if (-1 == ret && EEXIST != errno)  // 忽略已存在错误{perror("mkfifo error");return 1;}int fd = open("myfifo", O_WRONLY);  // 以写方式打开if (-1 == fd){perror("open error");return 1;}char buf[] = "hello, this is fifo tested...\n";sleep(3);  // 等待读端准备write(fd, buf, strlen(buf) + 1);  // 写入数据close(fd);return 0;
}

理想运行结果
等待3秒后写入数据到管道(无直接输出,需配合读端程序)。


读端程序 (02fifo_r.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret && EEXIST != errno){perror("mkfifo error");return 1;}int fd = open("myfifo", O_RDONLY);  // 以读方式打开if (-1 == fd){perror("open error");return 1;}char buf[256] = {0};read(fd, buf, sizeof(buf));  // 从管道读取printf("fifo: %s\n", buf);   // 打印内容close(fd);return 0;
}

理想运行结果
当写端程序运行后,读端打印:
fifo: hello, this is fifo tested...


文件传输示例(写端)
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>int main()
{mkfifo("myfifo", 0666);  // 创建管道int fifo_w = open("myfifo", O_WRONLY);  // 写端int src = open("/home/linux/1.png", O_RDONLY);  // 源文件char buf[4096];while (1){int ret = read(src, buf, sizeof(buf));  // 读取图片if (ret <= 0) break;write(fifo_w, buf, ret);  // 写入管道}close(src);close(fifo_w);return 0;
}
文件传输示例(读端)
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>int main()
{mkfifo("myfifo", 0666);  // 创建管道int fifo_r = open("myfifo", O_RDONLY);  // 读端int dst = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);  // 目标文件char buf[4096];while (1){int ret = read(fifo_r, buf, sizeof(buf));  // 从管道读取if (ret <= 0) break;write(dst, buf, ret);  // 写入目标文件}close(fifo_r);close(dst);return 0;
}

理想运行结果
1.png 被完整传输到 2.png,两个文件内容完全一致。


信号通信

核心特性
  • 唯一的异步通信机制
  • 信号处理方式:
    • SIG_DFL:默认处理(终止、忽略等)
    • SIG_IGN:忽略信号
    • 自定义处理函数
  • 不可捕获信号:SIGKILL(9)SIGSTOP(19)
核心函数
int kill(pid_t pid, int sig);
  • 功能:向指定进程发送信号
  • 参数
    • pid:目标进程ID
    • sig:信号编号(kill -l 查看)
  • 返回值:成功返回0,失败返回-1
sighandler_t signal(int signum, sighandler_t handler);
  • 功能:注册信号处理函数
  • 参数
    • signum:信号编号
    • handler:处理方式(SIG_DFL/SIG_IGN/函数指针)
  • 返回值:旧处理函数指针
代码示例
信号发送程序 (01kill.c)
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>int main(int argc, char *argv[])
{if (argc < 3){printf("usage: ./a.out pid sig_num\n");return 1;}int pid = atoi(argv[1]);int sig_num = atoi(argv[2]);int ret = kill(pid, sig_num);  // 发送信号if (-1 == ret){perror("kill error");return 1;}return 0;
}

理想运行结果
成功向指定进程发送信号(无输出),目标进程按信号类型执行相应操作。


信号处理示例 (02signal_2.c)
#include <signal.h>
#include <unistd.h>
#include <stdio.h>int flag = 0;void myhandle(int num)  // 信号处理函数
{flag = 1;  // 修改全局标志
}int main()
{signal(SIGINT, myhandle);  // 注册Ctrl+C处理while (1){if (0 == flag)printf("i'm processing..., pid:%d\n", getpid());elseprintf("i'm resting..., pid:%d\n", getpid());sleep(1);}return 0;
}

理想运行结果

  • 正常运行时每秒打印处理中信息
  • Ctrl+C 后,后续打印变为休息状态

自定义信号处理 (03signal_usr1.c)
#include <signal.h>
#include <unistd.h>
#include <stdio.h>void myhandle1(int num)
{static int i = 0;printf("老爸叫你....\n");if (++i == 3) signal(SIGUSR1, SIG_IGN);  // 第3次后忽略信号
}void myhandle2(int num)
{static int i = 0;printf("老妈叫你....\n");if (++i == 5) signal(SIGUSR2, SIG_DFL);  // 第5次后恢复默认
}int main()
{signal(SIGUSR1, myhandle1);  // 注册USR1处理signal(SIGUSR2, myhandle2);  // 注册USR2处理while (1){printf("i'm playing..., pid:%d\n", getpid());sleep(1);}return 0;
}

理想运行结果

  • 收到 SIGUSR1 信号时打印"老爸叫你",第3次后不再响应
  • 收到 SIGUSR2 信号时打印"老妈叫你",第5次后恢复默认处理(终止进程)

子进程回收 (04signal_child.c)
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>void myhandle(int num)  // SIGCHLD处理函数
{pid_t pid = wait(NULL);  // 非阻塞回收printf("father recycle pid: %d\n", pid);
}int main()
{signal(SIGCHLD, myhandle);  // 注册子进程结束信号pid_t pid = fork();if (pid > 0)  // 父进程{for (int i = 10; i > 0; i--){printf("father.... pid:%d\n", getpid());sleep(1);}}else if (0 == pid)  // 子进程{for (int i = 5; i > 0; i--){printf("child... pid:%d\n", getpid());sleep(1);}}return 0;
}

理想运行结果
子进程结束后,父进程自动回收:

father.... pid:1234
...
child... pid:5678
father recycle pid:5678
http://www.dtcms.com/a/358705.html

相关文章:

  • mysql(自写)
  • 谈物质的运动与运动的物质
  • 基于立创・庐山派 K230CanMV 开发板的视觉引导舵机追踪系统技术分析
  • kafka(自写)
  • 【MYSQL】GET_LOCK使用方法简单解析
  • C++_静态多态、运行多态和模板多态
  • 下一代 AI 交互革命:自然语言对话之外,“意念控制” 离商用还有多远?
  • AWD相关知识
  • Scikit-learn Python机器学习 - 数据集介绍
  • 矿物类型分类实战:从数据预处理到多模型对比
  • 幻觉与模仿:深入剖析当前大语言模型为何未能跨越“理解”与“推理”的鸿沟
  • TuringComplete游戏攻略(2.1算数运算)
  • 基础的汇编指令
  • 如何快速了解项目管理基础
  • 【机器学习入门】4.1 聚类简介——从“物以类聚”看懂无监督分组的核心逻辑
  • destoon8.0使用post插入keyword热搜到表
  • 深入理解shared_ptr与循环引用问题
  • 超越传统SEO:用生成引擎优化(GEO)驱动下一轮增长
  • 【蓝桥杯 2024 省 Python B】缴纳过路费
  • Markdown 文件编辑基础教程
  • 基于YOLO8的垃圾识别检测系统(数据集+源码+文章)
  • 【开题答辩全过程】以 线上游戏商城为例,包含答辩的问题和答案
  • Java学习day_14之API(正则表达式)
  • 【LeetCode】大厂面试算法真题回忆(121) —— 经典屏保
  • 嵌入式Linux驱动开发:蜂鸣器驱动
  • 图解LLM(AI大模型)的工作原理
  • SRE命令行兵器谱之二:lsof - 解密“端口被占用”与“文件句柄泄漏”的终极侦探
  • 吴恩达机器学习作业九:kmeans聚类
  • php电子签名
  • 2025年09月计算机二级MySQL选择题每日一练——第十二期