什么是管道?(详解进程间是如何通过管道通信的)

引言:

我们都知道,进程运行时是具有独立性的,要让两个进程进行通信是一件很困难的事情。因此两个进程通信的前提条件是,需要让两个进程看到同一份资源(物理内存)。

进程通信分类

管道:

  • 1.匿名管道pipe(有“亲情”关系(多用于父子进程)的进程进行通信)
  • 2.命名管道(实现不相关进程之间的通信)

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

  • pipe函数:
头文件: #include
原型:int pipe(int fd[2])
参数:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
成功,返回0。错误返回错误码
  • 工作原理
    • 父进程读写端一起打开文件(管道)
    • 用fork创建子进程(子进程拷贝父进程代码,一样由读写段打开文件)
    • 父子进程依照需求,分别关闭读端/写端。
    • 让我们看看代码是如何实现匿名管道的:
	int main()
{
    int fd[2] = {0};
    pipe(fd);

    pid_t id = fork();
    if(id == 0)//子进程写
    {
        close(fd[0]);//关闭读操作符
        char buf[] = "I'm child";
        while(1)
        {
            write(fd[1], buf, strlen(buf));
            sleep(1);
        }
    }
    else//父进程读
    {
        close(fd[1]);//关闭写操作符
        char ret[1024];
        while(1)
        {
            ssize_t s = read(fd[0], ret, sizeof(ret) - 1);
            if(s < 0)
            {
                printf("printf error");
                break;
            }
            else if(s > 0)
            {
                ret[s] = 0;
                printf("parent get child : %s\n", ret);
            }
            else
            {
                printf("parent get child : %s\n", ret);
                break;
            }
        }
    }
    return 0;
}

结果:我们可以看到父进程读到了子进程中的信息。
什么是管道?(详解进程间是如何通过管道通信的)_第1张图片
什么是管道?(详解进程间是如何通过管道通信的)_第2张图片
什么是管道?(详解进程间是如何通过管道通信的)_第3张图片
什么是管道?(详解进程间是如何通过管道通信的)_第4张图片

管道读写的规则

关于临界关系的一些概念:

  • 1.临界资源:多进程共享的内存资源(管道)
  • 2.临界区:访问临界资源的代码
  • 3.互斥:任何一个时刻只能由一个人访问临界区
  • 4.饥饿问题:一个排在R队列中的进程很久都无法获得资源
  • 5.同步:在保证临界资源安全的前提条件下(通常为互斥),让多进程访问临界资源具有一定的顺序性。功能:协同进程步调,避免饥饿问题
  • 6.原子性:对于临界资源,要么访问完毕,要么不访问。

管道读写会发生的四种情况:

  • 1.读端不读,读端关闭(写端到/0处,出异常)
  • 2.写端不写,写端关闭(写端到/0处,出异常)
  • 3.写端一直写,读端一直不读(阻塞)
  • 4.读端一直读,写端一直不写(阻塞)

让我们看看这四种情况:

  • 1.子进程一直在写,父进程不读
#include
#include
#include
#include


int main()
{
    int fd[2] = {0};
    pipe(fd);

    pid_t id = fork();
    if(id == 0)//子进程写
    {
        close(fd[0]);
        int count = 0;
        char buf[] = "I'm child";
        while(1)
        {
            write(fd[1], buf, strlen(buf));
            printf("%d\n", count += strlen(buf));
        }
    }
    else//父进程不读
    {
        close(fd[1]);
        waitpid(id, NULL, 0);

    }
    return 0;
}

结果:进程运行到一定程度,停止(被阻塞)
什么是管道?(详解进程间是如何通过管道通信的)_第5张图片
原因:这个进程被阻塞,不能被调度,不能被运行,由R状态-》非R状态,进程被挂起,将PCB挂到等待队列中。

  • 2.写端不仅不写,写端写完毕后将描述符关闭。
#include
#include
#include
#include


int main()
{
    int fd[2] = {0};
    pipe(fd);

    pid_t id = fork();
    if(id == 0)//子进程写完后关闭
    {
        close(fd[0]);
        int count = 0;
        char buf[] = "I'm child";
        while(1)
        {
            write(fd[1], buf, strlen(buf));
            printf("%d\n", count += strlen(buf));
            close(fd[1]);
            sleep(1);//子进程一秒之后关闭
            break;
        }
    }
    else//父进程读
    {
        close(fd[1]);
        char ret[1024];
        while(1)
        {
            sleep(1);
            ssize_t s = read(fd[0], ret, sizeof(ret) - 1);
            if(s < 0)
            {
                printf("printf error");
                break;
            }
            else if(s > 0)
            {
                ret[s] = 0;
                printf("parent get child : %s\n", ret);
            }
            else
            {
                printf("read file end!\n");
                break;
            }
        }
        waitpid(id, NULL, 0);
    }
    return 0;
}

结果:子进程一秒打完,父进程读取完之后,退出。
在这里插入图片描述

  • 3.子进程一直写,父进程直接关闭
#include
#include
#include
#include


int main()
{
    int fd[2] = {0};
    pipe(fd);

    pid_t id = fork();
    if(id == 0)//子进程写
    {
        close(fd[0]);
        int count = 0;
        char buf[] = "I'm child";
        while(1)
        {
            write(fd[1], buf, strlen(buf));
            printf("%d\n", count += strlen(buf));
            sleep(1);

        }
    }
    else//父进程读
    {
        close(fd[1]);
        char ret[1024];
        while(1)
        {
            sleep(1);
            ssize_t s = read(fd[0], ret, sizeof(ret) - 1);
            if(s < 0)
            {
                printf("printf error");
                break;
            }
            else if(s > 0)
            {
                ret[s] = 0;
                printf("parent get child : %s\n", ret);
            }
            else
            {
                printf("read file end!\n");
                break;
            }
            break;
        }
        close(fd[0]);
        int status = 0;
        waitpid(id, &status, 0);//获取信号
        printf("sign : %d\n", status & 0x7F);//信号再第7位
    }
    return 0;
}

结果:子进程退出,父进程读取子进程的退出码
在这里插入图片描述
原因:子进程被终止,父进程获得子进程的状态

匿名管道的特点:

  • 1.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信
  • 2.管道提供的是半双工的服务,是单向的
  • 3.管道是面向字节流服务的(连续的,无边界)
  • 4.文件生命周期随进程,进程结束了,管道也就释放了。

命名管道

如果我们想在不相关的进程之间交换数据,就需要用到FIFO文件,被称为命名管道。

如何创建匿名管道

  • 1.通过mkfifo指令
    ```c
  • 2.通过mkfifo函数实现
    函数原型: int mkfifo(const char* filename, mode_t mode)
    参数:管道命名,设置权限。
    
    • 实现:
#include
#include

int main()
{
    mkfifo("ALA", 0644);
    return 0;
}

结果:创建了一个管道文件
什么是管道?(详解进程间是如何通过管道通信的)_第6张图片

命名管道与匿名管道的区别:

  • 创造方式不同:命名管道由mkfifo函数创建,匿名管道由pipe函数创建
  • 打开方式:命名管道通过open打开,匿名管道通过read打开
  • 适用范围不同:命名管道可以实现不想管进程间的通信,匿名管道只能实现有亲缘关系进程间的关系。

命名管道打开的规则:

  • 1.如果当前操作是为读打开命名管道的:
    • O_NONBLOCK disable:阻塞直到有相应进程为写打开该命名管道(FIFO)
    • O_NONBLOCK enaable: 立即返回成功
  • 2.如果当前操作是为写打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为读打开该命名管道(FIFO)
    • O_NONBLOCK enaable: 立即返回失败,错误码为ENXIO

实现服务端和客户端的通信(单向)

server(服务器端)

#include
#include
#include
#include
#include
#include
#include
int main()
{
    umask(0);
    mkfifo("ALA", 0644);//创建命名管道
    int op = open("ALA", O_RDONLY);//打开匿名管道
    char buf[1024];
    while(1)
    {
        printf("wait ....");
        ssize_t s = read(op, buf, sizeof(buf) - 1);//从管道中读数据
        if(s > 0)
        {
            buf[s] = 0;
            printf("server read : %s", buf);
        }
        else if(s == 0)
        {
            printf("client quit!\n");
            break;
        }
        else
        {
            printf("read error!\n");
            break;
        }
    }
    close(op);
    return 0;
}

client(客户端)

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

int main()
{
    int op = open("ALA", O_WRONLY);//打开命名管道
    char buf[1024];
    while(1)
    {
        printf("Please Enter : ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf) - 1);//从标准输入(键盘中读取数据)
        if(s > 0)
        {
            buf[s] = 0;
            write(op, buf, strlen(buf));//读完后,再写入管道中
        }
        else if(s == 0)
        {
            printf("client quit!\n");
            break;
        }
        else
        {
            printf("read error!\n");
            break;
        }
    }
    close(op);
    return 0;
}

结果:客户端从标准输入(键盘中)读取数据,放入命名管道中。客户端再从命名管道中读取数据,通过Printf打印到电脑屏幕上。
在这里插入图片描述

你可能感兴趣的:(linux)