目录
ipc管道
无名管道pipe
头文件和函数原型
pipe.c文件
Makefile文件
执行过程
有名管道fifo
函数原型
fifo.c文件
Makefile文件
执行过程
FIFO的应用场景:Linux的日志系统
ipc信号,一个信号从进程中产生,发送给另一个进程,传递的是信号值,没有具体的数据传递,因此可使用管道。
管道:当数据从一个进程流向另一个进程时之间的连接,通常是把一个进程的输出通过管道连接到另一个进程的输入。Shell命令中用“|”表示。
管道本质上是一个可读写的文件,借助VFS(虚拟文件系统)给应用程序提供操作接口。
管道分类:有名管道、无名管道。
例如ps -aux | grep root,|其实就是管道,将ps命令输出的数据通过管道流向grep。其实是打开了两个进程,ps命令本应是在终端显示输出,但是它通过管道将输出的信息作为grep命令的输入信息,然后通过搜索后将合适的信息显示出来。
shell操作中的“|”就是常见的无名管道。
无名管道的特点是只能在父子进程中使用,父进程在产生子进程前必须打开一个管道文件,然后fork产生子进程,这样子进程通过拷贝父进程的进程地址空间获得同一个管道文件描述符,以达到使用同一个管道通信的目的。此时除了父子进程外,无人知道这个管道文件描述符,所以通过这个管道的信息无法传递给其他进程。这保证了传输数据的安全性,当然也降低了管道的通用性,于是系统还提供有名管道。
拥有以下特征:
没有名字的文件(仅支持在父进程创建然后将管道文件描述符继承给子进程),因此不能使用open函数打开,但可以使用close函数关闭。
半双工通信机制。
有两个文件描述符,一个只能用来读,一个只能用来写(对写操作不做任何保护)。
只能用于具有血缘关系的进程间通信,通常用于父子进程间通信。
管道是基于字节流来通信的。
依赖于文件系统,它的生命周期随进程的结束而结束。
写入操作不具有原子性,因此只能用于一对一的简单通信情形。
管道为特殊文件,可使用read、write函数进行读写,但并不属于其他任何文件系统,并且只存在于内核的内存空间中,因此不能使用lseek()来定位。
#include
int pipe(int pipefd[2]);
/*
pipefd数组用于返回两个引用管道末端的文件描述符。
pipefd[0]指管道的读取端,pipefd[1]指管道的写入端
返回值:
0:创建成功
-1:创建失败,并且设置errno
*/
向管道的写入端写入数据将会由内核缓冲,即写入内存中,直到从管道的读取端读取数据为止,而且数据遵循先进先出原则。
无名管道创建成功后,创建该无名管道的进程(父进程)同时掌握着管道的读取端和写入端,但是想要父子进程间有数据交互,则需要以下操作:
父进程调用pipe()函数创建无名管道,得到两个文件描述符pipefd[0]和pipefd[1],分别指向管道的读取端和写入端。
父进程调用fork()函数启动一个子进程,子进程继承父进程的两个文件描述符pipefd[0]和pipefd[1],分别指向管道的读取端和写入端。
由于无名管道是利用环形队列实现的,数据将从写入端流入管道,从读取端流出管道,达到进程间通信的目的。但是这个无名管道此时有两个读取端和两个写入端。
想要从父进程将数据传递给子进程,则父进程需要关闭读取端,子进程关闭写入端。
想要从子进程将数据传递给父进程,则父进程需要关闭写入端,子进程关闭读取端。
当不需要管道时,就在进程中将未关闭的一端关机即可。
#include
#include
#include
#include
#include
#define MAX_DATA_LEN 256
int main(void)
{
pid_t pid;
int pipe_fd[2];
char buf[MAX_DATA_LEN];
const char data[] = "Pipe Test Program";
int real_read, real_write;
memset((void*)buf, 0, sizeof(buf));
if(pipe(pipe_fd) < 0){
printf("pipe create error!\n");
exit(1);
}
if((pid = fork()) == 0){
// 子进程
close(pipe_fd[1]);
sleep(3);
if((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0){
printf("read size:%d\n", real_read);
printf("read data:%s\n", buf);
}
close(pipe_fd[0]);
exit(0);
}else if(pid > 0){
// 父进程
close(pipe_fd[0]);
sleep(1);
if((real_write = write(pipe_fd[1], data, strlen(data))) != -1){
printf("write size:%d\n", real_write);
printf("write data:%s\n", data);
}
close(pipe_fd[1]);
waitpid(pid, NULL, 0);
exit(0);
}
}
照旧
有名管道可以在多个无关的进程中交换数据。有名管道不同于无名管道在于它提供了一个路径名与之关联,以一个文件形式存在于文件系统中,因此可使用open、read、write、close等函数。
虽然有名管道文件存储在文件系统中,但数据却是存在于内存中。
拥有以下特征:
有名字的文件,存储在普通文件系统中。
任何具有相关权限的进程都可以使用open函数来获取有名管道的文件描述符。
可使用read、write进行文件读写。
不能使用lseek()函数来定位,因为数据存储在内存中。
写入操作具有原子性,支持多个用户同时写入数据而不会互相干扰。
遵循先进先出原则,最先被写入FIFO的数据会最先被读处理。
int mkfifo(const char *pathname, mode_t mode);
// mkfifo()会根据参数pathname建立特殊的FIFO文件,而参数mode为该文件的模式与权限
mkfifo()创建的FIFO文件,其他进程都可以进行读写操作,可以使用如open、read、write、close等。
mode模式及权限参数说明:
O_RDONLY:读管道。
O_WRONLY:写管道。
O_RDWR:读写管道。
O_NONBLOCK:非阻塞。
O_CREAT:如果该文件不存在,就创建一个新文件,并用第三个参数为其设置权限。
O_EXCL:如果使用O_CREAT时文件存在,返回错误信息。这一参数可测试文件是否存在。
函数返回值说明:
0:成功
EACCESS:参数filename所指定的目录路径无可执行的权限。
EEXIST:参数filename所指定的文件已存在。
ENAMETOOLONG:参数filename所指定的路径名称太长。
ENOENT:参数filename所指定的目录路径不存在。
ENOSPC:文件系统的剩余空间不足。
ENOTDIR:参数filename所指定的目录路径存在但非真正的目录。
EROFS:参数filename所指定的文件存在于只读文件系统中。
使用FIFO的过程中,当一个进程对管道进行读操作时:
若该管道是阻塞类型,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。
若该管道是非阻塞类型,则不论FIFO内是否有数据,读进程都会立即执行读操作。即如果FIFO内没有数据,读函数将立即返回0。
使用FIFO的过程中,当一个进程对管道进行写操作时:
若该管道是阻塞类型,则写操作将一直阻塞到数据可以被写入。
若该管道是非阻塞类型,且不能写入全部数据,则写操作进行部分写入或调用失败。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MYFIFO "myfifo" /* 有名管道文件名 */
#define MAX_BUFFER_SIZE PIPE_BUF /* 4096定义在limits.h文件中 */
void fifo_read(void)
{
char buff[MAX_BUFFER_SIZE];
int fd;
int nread;
printf("*********************** read fifo *************************\n");
if(access(MYFIFO, F_OK) == -1){
if((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST)){
printf("Cannot create fifo file!\n");
exit(1);
}
}
fd = open(MYFIFO, O_RDONLY);
if(fd == -1){
printf("Open fifo file error\n");
exit(1);
}
memset(buff, 0, sizeof(buff));
if((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0){
printf("Read:%s\n", buff);
}
printf("*********************** close fifo *************************\n");
close(fd);
exit(0);
}
void fifo_write(void)
{
char buff[] = "this is a fifo test demo";
int fd;
int nwrite;
sleep(2);
fd = open(MYFIFO, O_WRONLY | O_CREAT, 0644);
if(fd == -1){
printf("Open fifo file error\n");
exit(1);
}
printf("Write:%s\n", buff);
nwrite = write(fd, buff, MAX_BUFFER_SIZE);
// 等待子进程退出
if(wait(NULL)){
close(fd);
exit(0);
}
}
int main(void)
{
pid_t pid;
if((pid = fork()) == 0){
// 子进程
fifo_read();
}else if(pid > 0){
// 父进程
fifo_write();
}else{
printf("fork error!\n");
}
exit(0);
}
照旧
虽使用了父子进程间通信,但即使是没有血缘关系的进程也是一样的操作。
在一个以O_WRONLY(阻塞方式)打开的FIFO中,如果写入的数据长度小于等待PIPE_BUF,则写入全部字节或一个字节也不写入。如果所有的写请求都是发往一个阻塞的FIFO的,并且每个写请求的数据长度小于等于PIPE_BUF字节,字体就可以确保数据绝不会交错在一起。因此支持多用户写入而不会互相干扰。