【Linux操作系统】进程间通信(1)

一、认识进程间通信

进程间不能直接传递数据,因为进程具有独立性,直接传递会破坏进程的独立性。

进程间通信是什么?

一个进程把自己的数据交给另一个进程。

为什么要有进程间通信?

需要多个进程协同,共同完成一些事(数据共享、资源共享、通知事件、进程控制)

怎么做?

先让不同的进程看到同一份资源,这个同一份资源由操作系统提供。交换数据的内存,不能由通信双方的任何一个提供。

具体:因为操作系统提供空间有不同的方式,所以有不同的通信方式——管道、共享内存、消息队列、信号量

二、匿名管道

一个进程的PCB对象里面有指向files_struct类型结构体的指针,该结构体里面有文件描述符表,0-2分别指向的是系统默认打开的标准输入、标准输出和标准错误。当往一个文件(磁盘中的log.txt)写入数据,然后读取数据,共两次操作,struct file会有两个,一个是写的,另一个是读的(不同指针指向同一个文件)。w操作调用系统接口,将写入的数据给缓冲区,然后再刷新到磁盘的文件中;r操作,磁盘的文件的数据先到缓冲区,然后对应的系统接口再把数据读取出来。总之,写入和读取都是在缓冲区进行的,因为缓冲区也是内存,调用系统接口,就是运行某个程序,程序和磁盘中文件要加载到内存。如果让当前进程fork后创建一个子进程,那么子进程的文件描述符表里面的下标3和4也指向父进程指向的struct file,files_struct是进程的属性之一,所以创建子进程时子进程的PCB对象也有files_struct,而struct file是文件系统的,就一份。因此,此时的父子进程如图指向struct file的w文件和r文件,w和r指向同一个内存缓冲区,然后再到磁盘中的一个文件log.txt。
在这里插入图片描述
既然有创建子进程,就是要子进程办事的,如果让子进程w,父进程r,那么,子进程写的通过缓冲区刷新到磁盘文件中(写的时候,文件要先被加载到内存,即缓冲区,),然后,父进程通过缓冲区读取文件的信息(也是在缓冲区,即内存中。不管读写,都要先将文件加载到内存执行)。这样,子进程写的,父进程就能看到,所以,在这里父子进程通过缓冲区,即一块内存,就能够看到同一份资源的,这种方式或者说是通信方式就叫管道。

管道只能是单向通信。父进程最开始时的权限是rw,因为这样给子进程也是rw,然后关闭权限(父进程关闭w,子进程关闭r,注意,父进程刚开始不能只有一个r或w,因为不能新打开权限),一个读,一个写。

为了实现管道通信,OS提供了系统调用pipe() 。pipefd数组得到两个fd,分别是读r和写w(输出型参数)
在这里插入图片描述
特点:不需要向磁盘刷新内容,它不是一个文件,磁盘中也不存在这个文件。也就是说它会有一块内存,但是与磁盘无关,它是一个内存级文件,没有名字,是匿名的,一般就叫它——管道。

匿名管道如何让不同的进程看到同一份资源?创建子进程,子进程继承父进程相关的属性信息。只能具有“血缘”关系的进程可以进程间通信,常用于父子进程。

代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>

void reader(int rfd)
{
    char buffer[1024];
    while(1)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer));
        printf("message: %s\n", buffer);
        sleep(1);
    }
}
void writer(int wfd)
{
    char* str = "I am father";
    char buf[1024];
    int cnt = 1;
    pid_t pid = getpid();
    while(1)
    {
        snprintf(buf, sizeof(buf), "message: %s, pid: %d, cnt: %d", str, pid, cnt);
        write(wfd, buf, sizeof(buf));
        cnt++;
        sleep(1);
    }
}
int main()
{
    //1、创建管道
    int pipefd[2];
    int n = pipe(pipefd);
    if(n < 0) return 1;

    //2、创建子进程
    pid_t id = fork();
    if(id == 0)
    {
        //子进程读
        close(pipefd[1]);//关闭写
        reader(pipefd[0]);//读
        exit(0);
    }
    //父进程写
    close(pipefd[0]);//关闭读
    writer(pipefd[1]);//写
    wait(NULL);//防止僵尸
    return 0;
}

在这里插入图片描述
4种情况:一、管道内部没有数据,并且写端没有关闭,那么读端就要阻塞等待,直到pipe有数据;二、管道内部被写满,并且读端不关闭,那么写端写满之后,就要阻塞等待;三、对于写端,不写了,并且管道关闭,那么读端会把管道内的所有数据读完,最后读到返回值为0,表示读结束;四、读端不读并且关闭,写端在写,那么操作系统就会直接终止写入的进程

5种特性:一、自带同步机制(管道内部有数据,读端就读,没有就等待;管道没有写满,写端就写,满了就等待);二、血缘关系进程间通信,常见于父子进程;三、pipe是面向字节流的(写可能是一个一个的写,但是读可以一次性全部读出来);四、父子进程退出,管道自动释放,文件的生命周期是随进程的;五、管道只能单向通信。

命令行管道| 本质是匿名管道

三、命名管道

两个特点:1、让不同进程看到同一份资源;2、让没有亲缘关系的进程可以进行通信
在这里插入图片描述

怎么保证两个不同的进程打开的是同一个文件?找到文件:文件名+文件路径

系统调用接口:mkfifo、unlink
在这里插入图片描述
在这里插入图片描述

验证代码:
两个不相干的进程,一个发送消息,另一个接收

Comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <unistd.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <cstdlib>
#include <fcntl.h>

using namespace std;
#define Mode 0666
#define Path "fifo"

class Fifo
{
public:
    Fifo(const string &path) : _path(path)
    {
        umask(0);
        //创建管道
        int n = mkfifo(_path.c_str(), Mode);
        if(n == 0)
        {
            cout << "fifo create success" <<endl;
        }
        else 
        {
            cout << "fifo fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
        }
    }
    ~Fifo()
    {
        //删除管道,管道也是文件
        int n = unlink(_path.c_str());
        if(n == 0)
        {
            cout << "fifo remove success" <<endl;
        }
        else 
        {
            cout << "fifo remove, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
        }
    }

private:
    string _path;//文件名+文件路径
};

#endif

pipeServer.cc

#include "Comm.hpp"

int main()
{
    Fifo fifo(Path);//实例化
    //打开文件
    int rfd = open(Path, O_RDONLY);
    if(rfd < 0)
    {
        cout << "open fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
        return 1;
    }
    char buffer[1024];
    while(true)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        if(n > 0)//读取成功
        {
            buffer[n] = 0;
            cout << "message: " <<buffer<<endl;
        }
        else if(n == 0)//读停止 
        {
            cout << "read quit.. " <<endl;
            break;
        }
        else //读失败
        {
            cout << "read fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
            break;
        }
    }
    close(rfd);//关闭文件
    return 0;
}

pipeClient.cc

#include "Comm.hpp"

int main()
{
    //打开文件
    int wfd = open(Path, O_WRONLY);
    if(wfd < 0)
    {
        cout << "open fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
        return 1;
    }
    string buffer;
    while(true)
    {
        cout << "Please message# ";
        getline(cin, buffer);
        ssize_t n = write(wfd, buffer.c_str(), buffer.size());
        if(n < 0)
        {
            cout << "write fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
            break;
        }
    }

    close(wfd);//关闭文件
    return 0;
}

在这里插入图片描述

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值