玳瑁的嵌入式日记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()
)
- 打开 FIFO 时,
操作阶段 | 函数原型 | 关键补充说明 | 常见错误点 | |
---|---|---|---|---|
创建管道 | 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()
打开有名管道时:
默认行为:
- 如果以只读模式(
O_RDONLY
)打开 FIFO,open()
会阻塞,直到有另一个进程以写模式(O_WRONLY
)打开同一个 FIFO - 如果以只写模式(
O_WRONLY
)打开 FIFO,open()
会阻塞,直到有另一个进程以读模式(O_RDONLY
)打开同一个 FIFO
- 如果以只读模式(
非阻塞方式:
如果希望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;
}