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

玳瑁的嵌入式日记D29-0829(进程间通信)

加锁和解锁   --------》原子操作  ------》

原子操作(Atomic Operation) 是指一个 “不可分割、不可中断” 的操作 


char *strstr(const char *haystack, const char *needle);

功能说明

  • 参数

    • haystack:指向要被检索的主字符串("干草堆")
    • needle:指向要查找的子字符串("针")
  • 返回值

    • 若找到子串,返回指向主串中首次出现子串的起始位置的指针
    • 若未找到子串,返回 NULL
    • 若子串为空字符串(needle[0] == '\0'),则返回 haystack 本身
int fprintf(FILE *stream, const char *format, ...);

参数说明

  • stream:指向 FILE 类型的指针,指定输出的目标文件流(如标准输出 stdout、文件指针等)
  • format:格式化字符串,包含普通字符和格式说明符(如 %d%s 等)
  • ...:可变参数列表,对应格式说明符的具体数据

返回值

  • 成功时:返回写入的字符总数(不包括终止符 \0
  • 失败时:返回负数(通常为 -1),并设置 errno 表示错误原因
#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;
pthread_mutex_t mutex;
void do_check(char* filename, FILE* dst)
{printf("%s\n", filename);char* tmp = filename;if (strlen(tmp) < 3){return;}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;}if (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};// /home/linux    /    1sprintf(newpath, "%s/%s", path, info->d_name);if (DT_DIR == info->d_type)  // dir{// .  ../if (0 == strcmp(info->d_name, ".") || 0 == strcmp(info->d_name, "..")){continue;}if (pthread_self() == main_th){// main_thstrcpy(data.path, newpath);pthread_mutex_lock(&mutex);EnterSeqQue(sq, &data);pthread_mutex_unlock(&mutex);sem_post(&sem_task);}else{// work_th//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);SeqQue* sq = CreateSeqQue(10000);FILE* dst = fopen("log", "w");TH_ARG arg = {0};arg.dst = dst;arg.sq = sq;main_th = pthread_self();pthread_mutex_init(&mutex, NULL);pthread_t tid[3] = {0};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;
}

IPC   进程间通信  interprocess communicate

1、古老的通信方式
无名管道             有名管道                信号

2、IPC对象通信 system v    BSD     suse fedora   kernel.org
消息队列(用的相对少,这里不讨论)
共享内存
信号量集 
3、socket通信
网络通信

        
线程信号,posix  sem_init

特列:古老的通信方式中信号是唯一的异步通信
所有的通信方式中共享内存是唯一的最高效

管道                                ==》                无名管道、有名管道

    无名管道         ===》        pipe                 ==》只能给有亲缘关系进程通信
有名管道         ===》        fifo                         ==》可以给任意单机进程通信


 管道的特性:
1、管道是 半双工的工作模式
2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd  fseek ->>FILE* 
3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetc,
open,read,write,close;;

1,   读端存在,一直向管道中去写(写得快,读得慢),超过64k,写会阻塞。
2,写端是存在的,读管道(读得快,写得慢),如果管道为空的话,读会阻塞
3.  管道破裂,,读端关闭,写管道。
4.   read 0 ,写端关闭,如果管道没有内容,read 0 ;

写阻塞:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv)
{int fd[2] = {0};// create pipe + open pipe int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0){  // writeclose(fd[0]);char buf[1024] = {0};memset(buf, 'a', sizeof(buf));int i = 0;for (i = 0; i < 65; i++){write(fd[1], buf, sizeof(buf));printf("i is %d\n", i);}}else if (pid == 0){  // readclose(fd[1]);sleep(5);char buf[50];read(fd[0], buf, sizeof(buf));printf("child  buf is %s\n", buf);}else{perror("fork");return 1;}return 0;
}

读阻塞:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int	main(int argc, char **argv)
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){perror("pipe error");return -1;}pid_t pid = fork();if (pid == -1){perror("fork error");return -1;}else if (pid == 0){close(fd[1]);char buf[1024] = {0}; read(fd[0], buf, sizeof(buf));printf("child read: %s\n", buf);}else //pid>0{close(fd[0]);char buf[1024] = "hello world";sleep(3);write(fd[1], buf, strlen(buf)); }//system("pause");return 0;
}

管道破裂:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int	main(int argc, char **argv)
{int fd[2];int ret = pipe(fd);if (ret == -1){perror("pipe error");return -1;}pid_t pid = fork();if(pid > 0){sleep(1);close(fd[0]);char buf[]= "hello world";//会管道破裂,因为子进程读端关闭,写管道就会管道破裂//需要用gdb下断点,就会看到了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 error");return 1;}//system("pause");return 0;
}

read 0:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int	main(int argc, char **argv)
{int fd[2];int ret = pipe(fd);if (ret == -1){perror("pipe error");return -1;}pid_t pid = fork();if (pid == -1){perror("fork error");return -1;}else if(pid >0){close(fd[0]);char buf[] = "hello world";write(fd[1], buf, strlen(buf));}else{close(fd[1]);while(1){char buf[1024] = {0};int ret = read(fd[0], buf, sizeof(buf));if(0 == ret){printf("IPC end\n");break;}printf("read buf: %s\n", buf);}}//system("pause");return 0;
}


使用框架:
创建管道 ==》读写管道 ==》关闭管道


1. 无名管道(Anonymous Pipe)

  • 特性

    • 没有文件系统中的实体(不占磁盘空间),仅存在于内存中
    • 只能用于有亲缘关系的进程间通信(如父进程与子进程、兄弟进程)
    • 半双工通信(数据只能单向流动,通常需要创建两个管道实现双向通信)
    • 随进程生命周期存在,进程结束后自动销毁
  • 创建方式
    使用pipe()系统调用创建,返回两个文件描述符:

    • fd[0]:用于读取数据
    • fd[1]:用于写入数据

1. 父子进程是否都有 fd[0] 和 fd[1]

是的

  • 管道创建时(pipe(fd)),当前进程会获得两个文件描述符:fd[0](读端)和 fd[1](写端)。
  • 当进程调用 fork() 创建子进程时,子进程会复制父进程的所有文件描述符,因此子进程也会拥有 fd[0] 和 fd[1],且指向与父进程相同的管道。

但注意:

  • 实际使用中,通常会在父子进程中关闭不需要的端(例如父进程关闭读端 fd[0],子进程关闭写端 fd[1]),避免管道两端被同一进程持有导致异常。

2. 管道的数据存储方式

管道的数据存储在内核缓冲区中,本质是一块由内核管理的环形队列(FIFO 结构),遵循 "先进先出"(First-In-First-Out)原则。

  • 缓冲区大小是固定的(通常为 64KB,可通过 fcntl 或 sysconf(_SC_PIPE_BUF) 获取具体值)。
  • 写入数据时,内核会将数据从用户空间复制到管道缓冲区;读取数据时,再从缓冲区复制到用户空间。
  • 数据在缓冲区中是连续存储的,读取操作会按顺序消费数据(读走的数据会从缓冲区移除)。

2. 有名管道(Named Pipe/FIFO)

  • 特性

    • 在文件系统中有实体(通过mkfifo创建,可见文件名)
    • 可用于无亲缘关系的任意进程间通信
    • 半双工通信(同样需要两个 FIFO 实现双向通信)
    • 生命周期独立于进程,需手动删除unlink()
  • 创建方式

    • 命令行:mkfifo 管道名
    • 程序中:mkfifo(const char *pathname, mode_t mode)
  • 使用特点

    • 打开 FIFO 时,open()调用会阻塞,直到另一端也被打开(读写双方都准备好)
    • 读写操作与普通文件类似(read()/write()
操作阶段函数原型关键补充说明常见错误点
创建管道int mkfifo(const char *pathname, mode_t mode);1. mode 权限的实际生效:mode 是 8 进制权限(如 0664),但最终权限会受 umask(文件创建掩码) 影响,实际权限 = mode & (~umask)(默认 umask 通常为 0022,需提前用 umask(0) 清除掩码影响)。
2. 重复创建:若 pathname 对应的文件已存在(无论是否为管道),mkfifo 会失败并设置 errno = EEXIST
1. 忘记加 0 前缀(如写 664 而非 0664,导致权限错误);
2. 未检查文件是否已存在,直接创建。
打开管道int open(const char *pathname, int flags);1. 阻塞特性
- 用 O_RDONLY 打开时,会阻塞直到有进程用 O_WRONLY 打开该管道;
- 用 O_WRONLY 打开时,会阻塞直到有进程用 O_RDONLY 打开该管道;
- 若需非阻塞,可加 O_NONBLOCK 标志(如 `open ("./fifo", O_RDONLY
O_NONBLOCK)`)。
2. 禁止 O_RDWR 的原因:管道是半双工(同一时间只能单向传输),若用 O_RDWR 打开,进程可能同时读写,导致数据混乱(Linux 虽允许,但不符合管道设计逻辑,严禁使用)。
3. 禁止 O_CREAT 的原因:mkfifo 已明确创建 “管道文件”,若用 O_CREAT 打开不存在的路径,会创建普通文件(而非管道),后续读写会完全失效。
1. 未处理阻塞:单独运行读进程 / 写进程时,程序会卡住(误以为报错);
2. 误用 O_RDWR:导致数据传输异常(如写端发送的数据被自己读回)。
读写数据读:ssize_t read(int fd, void *buf, size_t count);
写:ssize_t write(int fd, const void *buf, size_t count);
1. 读操作特性
- 若管道中无数据,且写端未关闭:读进程会阻塞(除非 O_NONBLOCK);
- 若管道中无数据,且写端已关闭:read 会返回 0(类似读普通文件到末尾)。
2. 写操作特性
- 若管道已满(Linux 管道默认大小约 4KB,可通过 ulimit -p 查看),且读端未关闭:写进程会阻塞(除非 O_NONBLOCK);
- 若读端已关闭,写进程继续写:会触发 SIGPIPE 信号(默认导致进程终止),需提前处理该信号。
3. 返回值含义
- 成功:返回实际读写的字节数(可能小于 count,需循环读写);
- 失败:返回 -1,并设置 errno(如 EBADF 表示文件描述符无效)。
1. 未循环读写:当数据量超过 count 时,只传输部分数据;
2. 未处理 SIGPIPE:读端意外关闭后,写进程直接崩溃。
关闭管道int close(int fd);1. 关闭的影响:关闭读端后,写端的 write 会触发 SIGPIPE;关闭写端后,读端的 read 会返回 0。
2. 必须关闭的原因:若进程退出时未关闭 fd,系统会自动回收,但长期运行的进程(如服务)会导致 “文件描述符泄漏”,最终无法打开新文件。
遗漏 close:导致文件描述符泄漏,或写端未关闭时读端一直阻塞。
卸载管道int unlink(const char *pathname);1. 与 remove 的关系:在 Linux 中,remove() 是标准库函数,底层调用 unlink()(删除文件)和 rmdir()(删除目录);对管道文件而言,unlink() 和 remove() 功能完全一致,均可删除管道文件。
2. 删除的时机:管道文件被删除后,若已有进程正在通过 fd 读写,不影响当前传输(fd 关联的是内核中的管道对象,而非文件系统的路径);只有当所有进程关闭 fd 后,内核才会释放管道资源。
误以为 “删除管道文件会中断当前传输”:实际需先关闭所有 fd,再删除文件。

在有名管道(FIFO)中,open() 函数的阻塞行为是一个常见的特性,需要特别注意其工作机制。

当使用 open() 打开有名管道时:

  1. 默认行为

    • 如果以只读模式(O_RDONLY)打开 FIFO,open() 会阻塞,直到有另一个进程以写模式(O_WRONLY)打开同一个 FIFO
    • 如果以只写模式(O_WRONLY)打开 FIFO,open() 会阻塞,直到有另一个进程以读模式(O_RDONLY)打开同一个 FIFO
  2. 非阻塞方式
    如果希望 open() 不阻塞,可以在打开时加入 O_NONBLOCK 标志:

// 非阻塞方式打开FIFO
int fd = open("myfifo", O_RDONLY | O_NONBLOCK);

核心区别总结

特性无名管道有名管道
存在形式内存中文件系统中有实体
适用进程有亲缘关系的进程任意进程
创建方式pipe()系统调用mkfifo()或命令
生命周期随进程结束而消失需手动删除
打开方式通过文件描述符直接使用需要通过路径open()

信号通信

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>void myhandle(int num)
{pid_t pid = wait(NULL);printf("father recycle pid:%d\n",pid);
}int	main(int argc, char **argv)
{signal(SIGCHLD,myhandle);pid_t pid = fork();if(pid >0){int i = 0;while(i < 10){printf("father process pid:%d\n",getpid());i++;sleep(1);}}else if(0 == pid){int i = 0;while(i < 5){printf("child process pid:%d\n",getpid());i++;sleep(1);}}else{perror("fork error");return 1;}//system("pause");return 0;
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>void myhandle1(int signo)
{static int i = 0;printf(" signal 1 \n");i++;if(i == 3){signal(SIGUSR1,SIG_IGN);}
}
void myhandle2(int signo)
{static int i = 0;printf(" signal 2 \n");i++;if(i == 3){signal(SIGUSR1,SIG_DFL);}
}
int	main(int argc, char **argv)
{signal(SIGUSR1,myhandle1);signal(SIGUSR2,myhandle2);while(1){printf("signal test...pid:%d\n",getpid());sleep(1);}//system("pause");return 0;
}

http://www.dtcms.com/a/357039.html

相关文章:

  • ZooKeeper 安装配置
  • idea2025.2中maven编译中文乱码
  • Altium Designer 22使用笔记(10)---PCB铺铜相关操作
  • c++ const 关键字
  • 聊聊Prompt Engineering (提示词工程)
  • 【工具类】得到多个数组中的相同元素
  • 考研数据结构Part3——二叉树知识点总结
  • Vue学习Ⅳ
  • 二手车估值查询-二手车估值api接口
  • el-table实现双击编辑-el-select选择框+输入框限制非负两位小数
  • HunyuanVideo-Foley视频音效生成模型介绍与部署
  • 非标设计 机架模板 misumi 设计组合案例
  • 浏览器自动化工具怎么选?MCP 控制浏览器 vs Selenium 深度对比
  • 预测模型及超参数:3.集成学习:[1]LightGBM
  • LangChain实战(三):深入理解Model I/O - Prompts模板
  • 顶会顶刊图像分类的云服务器训练方法
  • 闭包与内存泄漏:深度解析与应对策略
  • Spring boot 启用第二数据源
  • Java全栈工程师的实战面试:从基础到微服务架构
  • 【SOD】目标检测
  • 2025.8.29机械臂实战项目
  • 基于STM32单片机的智能温室控制声光报警系统设计
  • leetcode 461 汉明距离
  • 基于MSRDCN、FEAM与AMSFM的轴承故障诊断MATLAB实现
  • 【工具】开源大屏设计器 自用整理
  • golang接口详细解释
  • websocket的应用
  • 【Spring Cloud Alibaba】前置知识
  • 微信小程序调用蓝牙打印机教程(TSPL命令)
  • Android 14 PMS源码分析