20250829的学习笔记
一、管道
1、对比总结表
特性 | 无名管道 (匿名管道) | 有名管道 (FIFO) |
---|---|---|
标识方式 | 无文件名,通过文件描述符 | 有文件名,存在于文件系统中 |
进程关系 | 只能用于有亲缘关系的进程 | 可用于任意进程间通信 |
创建方式 | pipe() 系统调用 | mkfifo() 系统调用 |
持久性 | 随进程结束而销毁 | 持久存在于文件系统中 |
访问方式 | 通过文件描述符 | 通过文件名打开 |
通信方向 | 半双工(单向) | 半双工(单向) |
2、无名管道 (Anonymous Pipe)
(1)特点
①匿名存在:没有文件名,只在内存中存在。
②亲缘要求:只能用于父子进程或兄弟进程间通信。
③临时性:管道随进程的结束而自动销毁。
(2)创建和使用
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int pipefd[2]; // pipefd[0]用于读,pipefd[1]用于写
pid_t pid;
char buf[256];
// 1. 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 2. 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程 - 读取数据
close(pipefd[1]); // 关闭写端
int n = read(pipefd[0], buf, sizeof(buf));
printf("Child received: %.*s\n", n, buf);
close(pipefd[0]);
exit(EXIT_SUCCESS);
} else {
// 父进程 - 写入数据
close(pipefd[0]); // 关闭读端
const char *msg = "Hello from parent!";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
wait(NULL); // 等待子进程结束
}
return 0;
}
(3) 执行流程
1. pipe(pipefd) 创建管道,返回两个文件描述符。
2. fork() 创建子进程,子进程继承父进程的文件描述符。
3. 父子进程分别关闭不需要的管道。
4. 父进程写入,子进程读取。
3、有名管道 (Named Pipe / FIFO)
(1)特点
①有名称:在文件系统中有一个路径名
②无亲缘要求:任何进程都可以通过文件名访问
③持久性:管道文件一直存在直到被显式删除
(2)创建和使用
创建FIFO:
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// 方法1:使用mkfifo函数
if (mkfifo("/tmp/myfifo", 0666) == -1) {
perror("mkfifo");
}
// 方法2:使用mknod函数
if (mknod("/tmp/myfifo", S_IFIFO | 0666, 0) == -1) {
perror("mknod");
}
写入进程 (writer.c):
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
int fd;
char *fifo = "/tmp/myfifo";
// 创建FIFO
mkfifo(fifo, 0666);
// 打开FIFO进行写入
fd = open(fifo, O_WRONLY);
// 写入数据
write(fd, "Hello FIFO!", 12);
close(fd);
return 0;
}
```
读取进程 (reader.c):
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
int fd;
char buf[256];
char *fifo = "/tmp/myfifo";
// 打开FIFO进行读取
fd = open(fifo, O_RDONLY);
// 读取数据
int n = read(fd, buf, sizeof(buf));
printf("Received: %.*s\n", n, buf);
close(fd);
// 可选:删除FIFO文件
unlink(fifo);
return 0;
}
4、阻塞行为
① 默认情况下,打开FIFO会阻塞
int fd = open(fifo, O_RDONLY); // 阻塞直到有写入者
int fd = open(fifo, O_WRONLY); // 阻塞直到有读取者
②非阻塞模式
int fd = open(fifo, O_RDONLY | O_NONBLOCK);
int fd = open(fifo, O_WRONLY | O_NONBLOCK);
5、双向通信
虽然单个管道是半双工的,但可以创建两个管道实现全双工通信:
①创建两个管道
int pipe1[2], pipe2[2];
pipe(pipe1); // 父进程写 → 子进程读
pipe(pipe2); // 子进程写 → 父进程读
②或者使用两个FIFO
mkfifo("/tmp/fifo1", 0666); // 进程A → 进程B
mkfifo("/tmp/fifo2", 0666); // 进程B → 进程A
6、 选择建议
(1)使用无名管道
- 通信进程有亲缘关系(父子、兄弟进程)
- 需要简单的进程间数据传递
- 不需要持久化的临时通信
(2)使用有名管道
- 无关进程间需要通信
- 需要持久的通信通道
- 进程可能在不同时间启动
二、信号
2)SIGINT:Ctrl+C 退出信号。
3)SIGQUIT:Ctrl+\ 退出信号。
9)SIGKILL:强制关闭(权限比较高)。
10、12):预留给用户的信号。
11)SIGSEGV:访问到非法内存。
14)SIGALRM:定时器,用于周期性动作。
17)SIGCHLD:子进程消亡,操作系统发现进程变僵尸进程,就会给父进程发SIGCHLD信号通知子进程消亡(用于父进程回收子进程)!
19)SIGSTOP:强制暂停。
1、发送端
int kill(pid_t pid, int sig);
功能:
通过该函数可以给pid进程发送信号为sig的系统信号。
参数:
@pid 要接收信号的进程pid
@sig 当前程序要发送的信号编号《===kill -l
返回值:
成功 0
失败 1
2、信号
kill -l可查看信号(前32个有具体含义)
3、接收端
每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、默认处理。
2、忽略处理 9,19。
3、自定义处理 9,19捕获 (SIGKILL和SIGSTOP不能捕获)。