IPC的概念
即进程间的通信
常用方式:
1,管道通信:有名管道,无名管道
2,信号- 系统开销小
3,消息队列-内核的链表
4,信号量-计数器
5,共享内存
6,内存映射
7,套接字
无名管道
管道的概念
本质:
内核缓冲区
伪文件--不占用磁盘空间
特点:
分为两部分:
读端,写端,对应两个文件描述符
数据写端流入,读端流出
操作管理的进程被销毁之后,管道自动被释放
适用于有血缘关系的进程(父子进程)
管道默认是阻塞的:
阻塞模式确保了在数据写入管道之前,读取端不会继续执行,从而避免了读取到不完整或错误的数据。这种同步机制保证了数据的完整性和准确性。
阻塞模式下的代码通常更加直观和易于理解,因为它遵循了“等待-处理”的简单模式。
在阻塞模式,如果管道中没有数据可读,进程会挂起,等待数据到来,从而节省CPU资源。
阻塞模式可以防止在数据未完全写入管道之前就被读取,从而避免了潜在的数据泄露风险。
管道的管理
内部实现方式:队列(先进先出),而且是环形队列,确保数据的顺序性和高效性。
缓冲区大小:默认4k,大小会根据实际情况做出适当调整
管道局限性:队列使数据只能读取一次,不能重复读取,而且管道使半双工工作模式,只能进行单项通信。
匿名管道的创建
函数原型
#include <unistd.h> //包含的头文件
int pipe(int pipefd[2]);
参数:
创造管道的函数:pipe()
pipefd()是一个整型数组,用来存放管道的两个文件描述符,pipefd(0)是读端,pipefd(1)是写端。
返回值:如果创建成功,pipe()返回0;如果失败,返回-1,并用exit退出。
这里打印的文件描述符是3和4,是因为在通常的UNIX和类UNIX系统(如Linux)中,标准文件描述符(file descriptors)0、1、2 分别被预留给标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)。当你调用 pipe()
函数时,它会在系统中分配两个新的文件描述符用于管道的读端和写端,这两个是连续的,当前可用的最小文件描述符。如果系统中已经有其他文件描述符被打开(例如,通过打开文件、套接字等),那么 pipe()
返回的文件描述符可能会更高(那就可能文件描述不是3和4了)。
父子进程间使用管道通信
这里我们通过父子进程管道通信实现ps aux | grep "bash"这个功能
在父进程中,我们需要先关闭读端,在子进程关闭写端,这样就实现了数据在父进程到子进程的方向上流动。
在进程中,我们调用了dup2函数:
起到数据重定向功能通过将特定文件描述符复制到标准输入、输出或错误输出的文件描述符,可以实现I/O重定向。例如,将一个文件描述符复制到标准输出,可以使程序的输出结果保存到文件中而非显示在屏幕上
函数原型为:int dup2(int oldfd, int newfd)。
这里,oldfd表示要被复制的文件描述符,而newfd则表示目标文件描述符的新编号。
当dup2成功执行时,它会返回新的文件描述符编号,即newfd的值;如果出错,则返回-1。
值得注意的是,在调用dup2之前,若newfd已经打开,系统会先关闭newfd指向的文件,以确保不会出现悬挂指针的情况。
使用execlp让父子进程执行两个程序
成功实现功能,对于两个grep bash进程的编号不同,是因为在我们使用终端执行命令时所产生的进程,所以会略有不同(进程编号一般比较接近)
管道的读写功能
1. 写入管道
- 写入函数:
ssize_t write(int fd, const void *buf, size_t count);
fd
:管道写端的文件描述符(pipefd[1]
)。buf
:指向要写入数据的缓冲区的指针。count
:要写入的数据的字节数。
- 写入行为:
- 如果管道未满,
write
函数将数据写入管道,并返回实际写入的字节数。 - 如果管道已满且写端未被关闭,
write
函数将阻塞,直到有空间可用或管道被关闭。 - 如果管道读端全部被关闭,且写端尝试写入数据,将导致进程异常终止(除非捕获SIGPIPE信号)。
- 如果管道未满,
2. 读取管道
- 读取函数:
ssize_t read(int fd, void *buf, size_t count);
fd
:管道读端的文件描述符(pipefd[0]
)。buf
:指向用于存储读取数据的缓冲区的指针。count
:请求读取的字节数。
- 读取行为:
- 如果管道中有数据,
read
函数从管道中读取指定数量的数据(或管道中剩余的所有数据,如果剩余数据少于请求的数量),并返回实际读取的字节数。 - 如果管道中没有数据且写端未被关闭,
read
函数将阻塞,直到有数据可读或管道被关闭。 - 如果管道写端全部被关闭,且读端尝试读取数据,
read
函数将返回0,表示文件结束(EOF)。
- 如果管道中有数据,
学到消息队列时候会详细讲
这边简单的举个例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipefd[2]; // 定义一个整型数组来存储管道的两个文件描述符
pid_t pid; // 定义一个变量来存储子进程的PID
char buf[100]; // 定义一个缓冲区来存储从管道中读取的数据
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
// 子进程代码
if (pid == 0) {
close(pipefd[1]); // 关闭子进程中的写端,因为我们只从管道读取数据
// 从管道中读取数据
ssize_t bytes_read = read(pipefd[0], buf, sizeof(buf) - 1);
if (bytes_read > 0) {
buf[bytes_read] = '\0'; // 确保字符串以null结尾
printf("Received from parent: %s\n", buf);
}
close(pipefd[0]); // 关闭读端
exit(EXIT_SUCCESS); // 子进程结束
}
// 父进程代码
close(pipefd[0]); // 关闭父进程中的读端,因为我们只向管道写入数据
// 向管道写入数据
const char *msg = "Hello from parent";
write(pipefd[1], msg, strlen(msg) + 1); // 包括字符串的null终止符
close(pipefd[1]); // 关闭写端
// 等待子进程结束
wait(NULL);
return 0;
}
管道缓冲区大小
查看缓冲区大小命令:ulimit -a
可以看出pipe size(管道缓冲区大小时512bytes*8=4096bytes=4kb)
通过函数fpathcon也可查看,fpathcon是一个用于查询与文件描述符相关联的运行时配置信息的函数
函数原型:
#include <unistd.h>
long fpathconf(int fd, int name);
返回值时长整型,正确返回时,返回查询到的配置值,查询失败,返回-1,并设置error指示错误原因
有名管道
函数原型
头文件:#include<sys/types.h> #include <sys/stat.h>
int mkfifo(const char \*filename,mode_t mode);
功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。
返回值:创建成功返回0,创建失败返回-1。
1.特点
有名管道 在磁盘上有这样一个文件 ls -l ->p
也是一个伪文件,在磁盘大小永久为0
数据存在内核中有一个对应的缓冲区
半双工通信方式
2.使用场景
没有血缘关系的进程间通信
3.创建方式
命令:mkfifo 管道名
函数:mkfifo
4.fifo文件可以使用io函数进程操作
open/close
read/write
不能执行lseek操作
创建有名管道,即创建了一个节点,让这个节点指向我们的内核缓冲区
创建成功
我们可以在设置的路径中找到所创建的管道文件,并且文件权限第一个为p,表示这个文件是一个管道文件
有名管道的进程间通信
使用open函数读取管道文件中的数据
使用write写入数据到管道文件中
当我们分两个终端执行的时候,我们先执行read命令,但是管道文件里面没有,会等待管道里面写入东西,我们在另外一个终端执行write命令时,会把数据写入到管道文件中,同时read命令也会读取到这个数据并显示