目录
[1.1例题:实现父子进程 ls | wc -l](#1.1例题:实现父子进程 ls | wc -l)
[1.2兄弟进程 ls | wc -l](#1.2兄弟进程 ls | wc -l)
[4.命名管道 fifo](#4.命名管道 fifo)
1.pipe管道读写行为
- 读管道:
-
管道有数据, read返回实际读到的字节数。
-
管道无数据,1)无写端,read 返回 0 (类似读到文件末尾)
2)有写端,阻塞等待。
- 写管道:
-
无读端,异常终止。 ( SIGPIPE 信号)
-
有读端,1)管道已满,阻塞等待。
2)管道未满,返回实际写出的字节数。
1.1例题:实现父子进程 ls | wc -l
父进程执行 ls
命令并将其输出写入管道,子进程执行 wc -l
并从管道读取输入
cpp
int main(int argc, char *argv[])
{
pid_t pid;
int fd[2];
// 先创建pipe
pipe(fd);
pid = fork(); // ls | wc -l
if (pid == 0) { // 子进程 实现 wc -l
close(fd[1]); // 子进程读管道,关闭写端.
dup2(fd[0], STDIN_FILENO); // 让 wc 从管道的读端,读数据.
execlp("wc", "wc", "-l", NULL);
} else if (pid > 0) {
close(fd[0]); // 父进程写管道,关闭读端.
dup2(fd[1], STDOUT_FILENO);// 将 写出到 屏幕的ls 结果,写入到 管道写端.
execlp("ls", "ls", NULL);
}
return 0;
}
1.2兄弟进程 ls | wc -l
cpp
int main(int argc, char *argv[])
{
int fd[2], i = 0;
pid_t pid;
pipe(fd);
for (i = 0; i < 2; i++)
if ((pid = fork()) == 0) {
break;
}
if (i == 0) { // 兄 ls
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", NULL);
} else if (i == 1) { // 弟 wc -l
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc","-l",NULL);
} else { // 父
close(fd[0]);// 父进程不参与管道使用,应该关闭写端、读端。保证管道内部数据单向流动。
close(fd[1]);
for (i = 0; i<2; i++)
wait(NULL);
}
return 0;
}
2.管道缓存区
2.1命令查询
![](https://i-blog.csdnimg.cn/direct/c8a301d1fa204bc597ad48b685580865.png)
2.2函数查询
long fpathconf(int fd, int name);
参1 :传 fd[0]/fd[1] 都可以!
参2 :传 _PC_PIPE_BUF 宏
3.pipe管道的优劣
-
优点:简单。比信号、套接字、mmap 简单很多!
-
缺点:
-
只能单向通信,实现双向通信,需要两个管道。
-
只能应用于父子、兄弟...(有公共祖先)进程间。无血缘关系进程间,后来用 fifo 替代。
4.命名管道 fifo
-
命令创建:mkfifo 管道名
-
函数创建:
//可以用于无血缘关系进程间通信
int mkfifo(const char *pathname, mode_t mode);
------ 演示代码:fifo_w.c fifo_r.c
-
管道中的数据,一次性读取,读走没。
-
读端:以 O_RDONLY 打开 fifo 管道。
-
写端:以 O_WRONLY/O_RDWR 打开同一个 fifo 管道。
5.mmap
5.1文件进程间通信
- 有血缘关系、无血缘关系的进程,都可以使用同一个文件来实现进间通信。
5.2建立、释放映射区
- mmap 借助文件映射,创建共享内存映射区
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
addr:指定映射区的首地址。通常传NULL,表示让系统自动分配
length:共享内存映射区的大小。(<=文件的实际大小。)
prot:共享内存映射区的读写属性。PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags:标注共享内存映射区的共享属性:
MAP_SHARED: 对共享内存所做的修改,会反应到物理磁盘文件上。 IPC专用!
MAP_PRIVATE:对共享内存所做的修改,不会反应到物理磁盘文件上。
fd: 用来创建共享内存映射区的那个文件的 文件描述符。
offset:默认0,表示映射文件全部!偏移位置。必须是4k整数倍。
返回值:
成功:映射区的首地址。
失败:MAP_FAILED (void *(-1)), errno
- munmap 释放共享内存映射
int munmap(void *addr, size_t length);
参1:mmap() 函数的返回值。
参2:共享内存映射区大小
返回值:
成功:0
失败:-1, errno
mmap使用的注意事项:
-
用于创建映射区的文件的大小,必须是非0。映射区的大小 <= 文件大小。
-
创建映射区,需要read权限。指定访问权限为 MAP_SHARED, mmap需要读写权限。 应该 <= 文件打开权限。只写不行!
-
文件描述符fd, 在mmap创建映射区完成,可以立即关闭!后续访问文件使用 内存地址。
-
offset 必须是 4096 的整数倍。(MMU映射的最小单位 4k)
-
映射区访问的权限设为 MAP_PRIVATE, 对内存做的所有修改,都只在内存有效,不反应的磁盘上。不能应用于 IPC
mmap函数保险调用方式:
-
fd = open("文件名",O_RDWR);
-
mmap(NULL, 实际有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
mmap特性
-
fifo、mmap 都可以应用于非血缘关系进程间通信。区别:
-
mmap:数据可以重复读写。
-
fifo:数据只能一次性读写。
-
直接操作内存,执行速度快!
5.3匿名映射
- 只能应用于,有血缘关系的进程间通信
p = (int *)mmap(NULL, 400, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
-
MAP_ANONYMOUS 、MAP_ANON 只在 Linux 系统中有效。
-
/dev/null : 设备文件。黑洞文件。特性:无限向该文件写数据。写入没!
-
/dev/zero: 设备文件。特性:无限从该文件读取数据。读多少,有多少。读到的都是"\0"
-
类unix系统中没有 MAP_ANONYMOUS 、MAP_ANON 选项,可以借助 /dev/zero 实现。
cpp
fd = open("/dev/zero", O_RDWR);
p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
6.进程间通信
6.1父子进间通信
-
父进程 先创建 映射区。 open(O_RDWR); mmap(MAP_SHARED);
-
fork() 创建子进程。
-
一个进程使用 映射区读,另一个进程写。
6.2无血缘关系进程间通信
-
两个进程,打开同一个文件,创建映射区。
-
指定 flags 为 MAP_SHARED。
-
一个进程使用 映射区读,另一个进程写。