进程间通信——管道

进程间通信目的
  1. 数据传输: 一个进程需要把它的数据发送给另一个进程
  2. 资源共享:多个进程间共享同样的资源
  3. 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程具有独立性,让其通信时具有难度的,因此,想让两个进程进行通信的前提条件就是,需要让不同的进程看到同一份资源,这个资源通常指的是内存。

进程间通信的分类
  • 管道

    • 匿名管道pipe
    • 命名管道
  • System V IPC

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

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

管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。管道只能单向通信。
它是一种半双工机制,只能一端来读,一端来写,遵循先进先出的原理,并且数据只能被读取一次,当此段数据被读取后,马上会从数据中消失。(写入的数据每次都添加到管道缓冲区的末尾,读数据的时候都是从缓冲区的头部读出数据的。)

匿名管道

匿名管道只能用于有亲缘关系的进程,例如父子进程
匿名管道只能用在同一台计算机中,它只能是单向的。

#include<unistd.h>
功能:创建一个无名管道

int pipe(int fd[2]);

参数:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端

 返回值:
    成功返回0,失败返回错误代码

在这里插入图片描述
实例:键盘读取数据,写入管道,读取管道,写到屏幕

在这里插入图片描述

站在文件描述符角度理解管道

在这里插入图片描述

  1. 父进程调用pipe()开辟管道,得到两个文件描述符,指向管道的两端,
  2. 父进程调用fork()创建子进程,子进程继承和父进程相同的数据结构,也得到两个文件描述符,指向该管道的两端;
  3. 父进程关闭管道读端,子进程关闭管道写端,父进程向管道里写数据,子进程从管道里读数据,数据从写段流入读端,这样就实现了父子进程间的通信
#include<stdio.h>
#include<unistd.h>
#include<string.h>
 
int main()
{
    int fd[2]={0};
    pipe(fd);//创建管道
 
    pid_t id=fork();//创建子进程
 
    if(id== 0)
    {
        close(fd[1]);//子进程关闭写文件描述符,让子进程读
        char buf[1024];
        while(1)
        { 
            ssize_t s=read(fd[0],buf,sizeof(buf)-1);
            if(s>0)
            {
                 buf[s]=0;
                 printf("I am child,I got parent's message: '%s'\n",buf);
            }
        }
    }
    else
    {
        close(fd[0]);//父进程关闭读文件描述符,让父进程写
        char msg[]="hello world!";
        while(1)
        {
            write(fd[1],msg,strlen(msg));
            sleep(1);
        }
    }
    return 0;
}
站在内核的角度理解管道

在这里插入图片描述
描述文件是一个面向对象的过程,c语言用结构体来描述对象,但结构体中不能放调用方法,但是可以存放函数指针,进程的file结构体中存放的就是一个个函数指针,指向各种文件的操作方法。
操作系统通过管理pcb来管理进程,而每一个进程都有一个struct_File结构体,其中含有一个指针数组,用来存放要打开的文件,这个指针数组其中一个元素又指向file结构体,file结构体中的f_inode指向文件的inode,f_op指向操作文件的方法,所以我们看待管道,就如同看待文件一样,它们的使用是类似的,印证了“Linux中一切皆文件的思想”。

管道的读写规则

当没有数据可读时
O_NONBLOCK disable(非阻塞模式禁止):read调用阻塞,即读进程暂停执行,一直等到有效数据来到为止。
O_NONBLOCK enable(非阻塞模式启动):read调用返回-1,error值为EAGAIN。
当管道满时
O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。
O_NONBLOCK enable:write调用返回-1,error值为EAGAIN。
管道写端对应的文件描述符被关闭,read返回0;
管道读端对应的文件描述符被关闭,write会产生信号SIGPIPE,进而导致write退出
当写入的数据量不大于PIPE_BUF,也就是管道的上限,linux将保证管道的原子性,相反,要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性管道的上限一般为4k或8k

原子性:通常对临界资源的访问只有两种状态,要么访问,要么不访问
临界资源:多进程共享的内存资源
临界区:访问临界资源的代码
互斥:任何时刻只允许一方进行对临界资源的访问
同步:在保证临界资源安全的条件下,(通常是互斥),让多进程访问临界资源具有一定顺序性,称为同步(同步目标:协同程序步调,避免解问题)

管道读写常遇见的四种情况

1. 父进程一直写,子进程一直等待就是不读,这样会导致管道被写满,此时write调用阻塞,直到有数据被读走

2. 父进程向管道里写入一条数据直接退出了,子进程读数据,读完此条数据后 read返回0

3. 父进程一直写,子进程几秒之后退出,管道读端对应的文件描述符关闭,write函数会产生SIGPIPE信号,导致write退出

4. 父进程不写数据,也不关闭文件描述符,子进程一直读,此时子进程调用read阻塞,程序会被卡住,即读进程暂停执行,一直等到有效数据来为止。

管道的特点

 - 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信。通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间可应用该管道。

 - 管道提供流式服务(即单向通信)。

 - 一般而言,进程退出,进程退出,管道释放,所以管道的生命周期随进程。(文件的生命周期也随进程)

 - 一般内核会对管道操作进行同步与互斥。

 - 管道是半双工的,数据只能向一个方向流动,需要双方通信时建立两个管道。


在这里插入图片描述

命名管道

管道应用的一个限制就是只能在具有公共祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这件事情,它经常被称为命名管道。命名管道是一种特殊类型的文件。

创建命名管道

  1. 从命令行创建
mkfifo filename
  1. 命名管道也可以从程序里创建,相关函数是:
int mkfifo(const char *filename,mode_t mode);

命名管道和匿名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于他们创建与打开的方式不同,但是一旦这些工作完成之后,它们具有相同的语义。

命名管道的打开规则

  1. 如果当前打开操作是为了读而打开FIFO时:
    O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    O_NONBLOCK enable:立刻返回成功。

  2. 如果当前打开操作是为写而打开FIFO时:
    O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO。
    O_NONBLOCK enable:立刻返回失败,错误码为ENXIO。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MFC是微软基于C++的一个应用程序框架,它可以帮助开发人员快速创建Windows应用程序。进程间通信(IPC)是不同进程之间进行数据交换和通信的一种方法。管道是一种常见的IPC机制,它允许两个进程之间进行双向通信。 在MFC中,使用管道进行进程间通信可以分为两个步骤:创建管道和使用管道进行通信。 首先,需要创建一个管道。可以使用CreatePipe函数来创建匿名管道,它接受两个参数,第一个参数是用于接收管道句柄的指针,第二个参数是用于发送管道句柄的指针。成功创建管道后,你将获得两个句柄,一个用于读取数据,一个用于写入数据。 然后,可以使用ReadFile和WriteFile函数来读取和写入管道中的数据。这些函数可以传入一个管道句柄,一个缓冲区来存储数据以及数据的长度。通过这些函数,可以在两个进程之间传递数据。 如果需要实现双向通信,可以在每个进程中使用一个管道来进行读取和写入操作。这样,两个进程就可以通过各自的管道进行双向通信了。例如,进程A使用管道A向进程B发送数据,进程B使用管道B向进程A发送数据。 总结起来,MFC可以使用管道进行进程间通信,通过创建管道和使用ReadFile和WriteFile函数来实现数据的读取和写入。如果需要双向通信,则可以在两个进程中分别创建管道来进行双向数据传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值