管道和有命管道

管道的概念

  管道是一种最基本的IPC机制,作用于有血缘关系的进程之间(父子进程,爷孙进程,兄弟进程),完成数据传输。调用pipe系统函数即可创建一个管道。管道有如下特质

1.其本质是一个伪文件(实为内核缓冲区)
2.有两个文件描述符引用,一个代表读端,一个代表写写端
3.规定数据从管道的写端流入,从读端流出

管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:

1.数据一旦被读走,就不再管道中,不可反复读取
2.由于管道采用半双工通信方式。因此,数据只能在一个方向上流动
3.只能在有共有祖先的进程进程间使用

pipe函数

  创建管道

    函数原型:
         int pipe(int pipefd[2]); 
    参数:
        一个大小为2的int数组,如果管道创建成功,下标为0的文件描述符表示写端,下标为1的文件描述符为读端
    返回值:
       成功:0;失败:-1,设置errno

管道创建成功之后如何通信:

  1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道
  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

管道示例代码

#include 
#include 
#include 
#include 

int main()
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();

    if(pid == 0){
        //son 
        sleep(3);
        close(fd[0]);//关闭读端
        printf("child write\n");
        write(fd[1],"hello\n",6);
        
        while(1){
            sleep(1);
        }
        close(fd[1]); 
    }else if(pid > 0){
        //parent 
        close(fd[1]);//关闭写端
        char buf[12]={0};
        while(1){
            printf("parent read\n");
            int ret = read(fd[0],buf,sizeof(buf));
            if(ret == 0){
                printf("read over!\n");
                break;
            }
            if(ret > 0){
                
                write(STDOUT_FILENO,buf,ret);
            }
        }
        int status;
        wait(&status);
        if(WIFSIGNALED(status)){
            printf("killed by %d\n",WTERMSIG(status));
        }
        //父进程只是关闭读写两端,但是不退出
        while(1){
            sleep(1);
        }
        close(fd[0]);
    }
    return 0;
}


管道的读写行为

   使用管道需要注意一下4中特殊情况(假设都是阻塞I/O)

1.如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0,close一次引用计数-1),而仍然有从管道读端写入的数据,那么管道中生于的数据都被读取之后,再次read返回0,就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没有关闭,而持有管道的写端的进程也没有向管道中写数据,这是有进程冲管道中读数据,那么管道中生于的数据被读取后,再次read会阻塞,知道管道中有数据可读了。
3.如果所有指向读端的文件描述符都关闭了,这是有进程向管道的写端write,那么进程会收到一个SIGPIPE信号,通常会导致进程异常终止。当然也可以对SIGPIPE信号进行捕捉,不终止进程。
4.如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

管道缓冲区大小

可以通过 ulimit -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。

[root@VM_0_11_centos linux]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3893
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 100001
pipe size (512 bytes, -p) 8         //这里 512个字节
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3893
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:

  1. 只能单向通信,双向通信需建立两个管道。
  2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。

FIFO

   FIFO通常被乘坐有名管道,以区分管道(PIPE)。管道只能用于有血缘关系的进程间通信。但通过FIFO,不想管的进程也能进行数据交换
   FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标志一条内核通道。各个进程可以打开这个文件进行read/write,实际上是写在内核通道上。

创建方式
1.通过命令 mkfifo 来创建管道

mkfifo mkfifo(第二个参数为管道名)
[root@VM_0_11_centos linux]# mkfifo mkinfo
[root@VM_0_11_centos linux]# ll
total 28
-rwxr-xr-x 1 root root 8955 May 17 15:34 a.out
prw-r--r-- 1 root root 0 May 17 15:51 mkinfo

2.mkfifo文件也可以通过库函数来创建

 函数原型:
        int mkfifo(const char *pathname,  mode_t mode);
参数:
        pathname, 文件路径
        mode 文件权限
返回值:
        成功:0; 失败:-1

常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

3.读写示例代码:
fifo_r.c

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc,char *argv[])
{
    if(argc != 2){
        printf("./a.out fifoname\n");
        return -1;
    }
    printf("begin oepn read...\n");
    int fd = open(argv[1],O_RDONLY);
    printf("end oepn read...\n");
    
    char buf[256];
    int ret;
    while(1){
        //循环读
        memset(buf,0x00,sizeof(buf));
        ret = read(fd,buf,sizeof(buf));
        if(ret > 0){
            printf("read:%s\n",buf);
        }
    }

    close(fd);
    return 0;
}

fifo_w.c

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc,char * argv[])
{
    if(argc != 2){
        printf("./a.out fifoname\n");
        return -1;
    }
    
    // 当前目录有一个 myfifo 文件
    //打开fifo文件
    printf("begin open ....\n");
    int fd = open(argv[1],O_WRONLY);
    printf("end open ....\n");
    //写
    char buf[256];
    int num = 1;
    while(1){
        memset(buf,0x00,sizeof(buf));
        sprintf(buf,"xiaoming%04d",num++);
        write(fd,buf,strlen(buf));
        sleep(1);
        //循环写
    }
    //关闭描述符
    close(fd);
    return 0;
}



你可能感兴趣的:(管道和有命管道)