进程间通信【Linux】

进程间通信

进程间通信,IPC(Interprocess communication),进程间通信就是在两个或者多个进程实现数据层面的交互

进程间通信的本质就是,让不同的进程看到同一份资源
这份资源由操作系统中的不同模块提供,这份资源

在文件操作上,无论是读一个文件,还是写一个文件,都必须把数据先加载到内存中

匿名管道的原理

匿名管道用于进程间通信,且仅限于本地父子进程之间的通信

简易原理
在这里插入图片描述
匿名管道的原理:

新建的文件能被父进程访问 ,也能被子进程访问
当fork创建子进程时,在上图中父进程和子进程指向同一个文件时,引用计数为2

父进程调用pipe函数创建管道
在这里插入图片描述
父进程创建子进程
在这里插入图片描述
父进程关闭写端,子进程关闭读端
在这里插入图片描述

只想让父子进行单向通信

在这里插入图片描述

让子进程进行写入,父进程进行读取
在这里插入图片描述

如果两个进程不是父子进程 , 两个进程之间没有任何关系 ,不能使用上图中的原理进行通信,使用上图的原理进行进程通信,一般这两个进程是父子关系,
如果父进程创建多个子进程 ,多个子进程之间是可以用上图原理进行通信,(兄弟关系)
如果父进程创建子进程,子进程再创建子进程,父进程可以与子进程创建的子进程用上图原理进行通信(爷孙关系)

使用管道通信,进程之间需要有血缘关系,常用于父子
上图中的打开的文件是没有路径,没有文件inode ,没有文件名字,父子进程看到同一份资源,是通过继承父进程的方式得到的

#include<iostream>
#include<unistd.h>
#include<string> 
#include <sys/types.h>
#include <sys/wait.h>
#include<string.h>
#define N 2 
#define NUM 1024 
using namespace std ;


void Writer(int wfd)
{
 char buffer[NUM];
 string s = "hello, I am child";
    pid_t self = getpid();
  int number = 0;
  while(number<5)
  {
     
       //字符串清空, 只是为了提醒阅读代码的人,我把这个数组当做字符串了
    buffer[0]=0;
  snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number++);
    write(wfd ,buffer, strlen(buffer)) ; //本质是将用户缓冲区写入到操作系统内核的文件缓冲区
//   number++;
//   cout << number << endl;
    sleep(1);
  }

}
void Reader(int rfd)
{
 char buffer[NUM]; 

 while(true)
 {
    //字符串清空, 只是为了提醒阅读代码的人,我把这个数组当做字符串了
    buffer[0]=0;
    ssize_t n = read(rfd ,buffer , sizeof(buffer)); //操作系统的缓冲区拷贝到用户缓冲区
    // cout<<"Reader:n"<<n<<endl;
    if(n>0)
    {
        buffer[n] = 0; // 0 == '\0'
            cout << "father get a message[" << getpid() << "]# " << buffer << endl;
    }
    else if (n==0)
    {
            printf("father read file done!\n");
        break;
    }
    else // n<0
    {
       break;
    }
 }
}
int main()
{
    int pipefd[N] ={0};
    int n = pipe(pipefd);
    if( n < 0)
    {
        return 1 ;
    }
    //cout<<" pipefd[0]:" <<pipefd[0] << ",pipefd[1]:"<<pipefd[1] <<endl;

    pid_t  id = fork();
    //子进程 ,关闭读端,写文件
     if(id==0)
     {
    close(pipefd[0]);
    //IPC
        Writer(pipefd[1]);
         //最后关闭子进程的写端
         close(pipefd[1]);
        exit(0);
     }
    //父进程
    
    //父进程关闭写端,读文件
    close(pipefd[1]);
   
   Reader(pipefd[0]);
   //进程等待
  pid_t  rid=    waitpid(id ,nullptr,0);
 if( rid < 0)
 {
    return 3 ;
 }
 //最后关闭父进程的读端
 close(pipefd[0]);
    sleep(5);
    return 0 ;
}

pipe函数

int pipe(int pipefd[2]);

pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符

pipefd[0] :管道读端的文件描述符
pipefd[1] :管道写端的文件描述符

返回值:pipe函数调用成功时返回0,调用失败时返回-1

管道

管道就是文件
管道的特征:
1、具有血缘关系的进程进行进程间通信
2、管道只能单向通信
3、父子进程是会进程协同的,同步与互斥的―–保护管道文件的数据安全
5、管道是面向字节流的
6、管道是基于文件的,而文件的生命周期是随进程的,进程退出后,管道的声明周期也就结束了,管道文件被系统释放

管道是有固定大小的
不同的内核里,大小可能有差别

管道的4中情况:

1、读写端正常,管道如果为空,读端就要阻塞
2、读写端正常,管道如果被写满,写端就要阻塞
ulimit

[cxq@iZ7xviiy0goapxtblgih6oZ pipe]$ 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) 14500
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
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) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

查看操作系统众多资源的限制,文件打开数量、进程数量、内存限制等。

3、读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞

#include<iostream>
#include<unistd.h>
#include<string> 
#include <sys/types.h>
#include <sys/wait.h>
#include<string.h>
#define N 2 
#define NUM 1024 
using namespace std ;

void Writer(int wfd)
{
    string s = "hello, I am child";
    pid_t self = getpid();
    int number = 0;

    char buffer[NUM];
    while (true)
    {
        sleep(1);

        // 构建发送字符串
        //buffer[0] = 0; // 字符串清空, 只是为了提醒阅读代码的人,我把这个数组当做字符串了
        //snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number++);
        // cout << buffer << endl;
        // 发送/写入给父进程, system call
        //write(wfd, buffer, strlen(buffer)); // strlen(buffer) + 1???
        char c = 'c';
        write(wfd, &c, 1); // strlen(buffer) + 1???
        number++;
        cout << number << endl;

        if(number >= 5) break;
    }
}


void Reader(int rfd)
{
 char buffer[NUM]; 

 while(true)
 {
    sleep(1);
    //字符串清空, 只是为了提醒阅读代码的人,我把这个数组当做字符串了
    buffer[0]=0;
    ssize_t n = read(rfd ,buffer , sizeof(buffer)); //操作系统的缓冲区拷贝到用户缓冲区
    // cout<<"Reader:n"<<n<<endl;
    if(n>0)
    {
        buffer[n] = 0; // 0 == '\0'
            cout << "father get a message[" << getpid() << "]# " << buffer << endl;
    }
    cout << "n: " << n << endl;
 }
}
int main()
{
    int pipefd[N] ={0};
    int n = pipe(pipefd);
    if( n < 0)
    {
        return 1 ;
    }
    //cout<<" pipefd[0]:" <<pipefd[0] << ",pipefd[1]:"<<pipefd[1] <<endl;

    pid_t  id = fork();
    //子进程 ,关闭读端,写文件
     if(id==0)
     {
    close(pipefd[0]);
    //IPC
        Writer(pipefd[1]);
         //最后关闭子进程的写端
         close(pipefd[1]);
        exit(0);
     }
    //父进程
    
    //父进程关闭写端,读文件
    close(pipefd[1]);
   
   Reader(pipefd[0]);
   //进程等待
  pid_t  rid=    waitpid(id ,nullptr,0);
 if( rid < 0)
 {
    return 3 ;
 }
 //最后关闭父进程的读端
 close(pipefd[0]);
    sleep(5);
    return 0 ;
}

在这里插入图片描述

4、**写端是正常写入,读端关闭了。操作系统就要杀掉正在写入的进程
**

#include<iostream>
#include<unistd.h>
#include<string> 
#include <sys/types.h>
#include <sys/wait.h>
#include<string.h>
#define N 2 
#define NUM 1024 
using namespace std ;
void Writer(int wfd)
{
    string s = "hello, I am child";
    pid_t self = getpid();
    int number = 0;

    char buffer[NUM];
    while (true)
    {
        sleep(1);

        // 构建发送字符串
        buffer[0] = 0; // 字符串清空, 只是为了提醒阅读代码的人,我把这个数组当做字符串了
        snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number++);
  
       // 发送/写入给父进程, system call
        write(wfd, buffer, strlen(buffer)); // strlen(buffer) + 1???
    
    }
}

void Reader(int rfd)
{
    char buffer[NUM];
    int cnt = 0;
    while(true)
    {
        buffer[0] = 0; 
        // system call
        ssize_t n = read(rfd, buffer, sizeof(buffer)); //sizeof != strlen
        if(n > 0)
        {
            buffer[n] = 0; // 0 == '\0'
            cout << "father get a message[" << getpid() << "]# " << buffer << endl;
        }
        else if(n == 0) 
        {
            printf("father read file done!\n");
            break;
        }
        else break;

        cnt++;
        if(cnt>5) break;
        // cout << "n: " << n << endl;
    }
}
int main()
{
    int pipefd[N] ={0};
    int n = pipe(pipefd);
    if( n < 0)
    {
        return 1 ;
    }
    //cout<<" pipefd[0]:" <<pipefd[0] << ",pipefd[1]:"<<pipefd[1] <<endl;

    pid_t  id = fork();
    //子进程 ,关闭读端,写文件
     if(id==0)
     {
    close(pipefd[0]);
    //IPC
        Writer(pipefd[1]);
         //最后关闭子进程的写端
         close(pipefd[1]);
        exit(0);
     }
    //父进程
    



   // father
    close(pipefd[1]);

    // 父进程读端关闭 
    Reader(pipefd[0]); // 读取5s
    close(pipefd[0]);
    cout << "father close read fd: " << pipefd[0] << endl;
    sleep(5); //为了观察僵尸
  int status = 0;
    pid_t rid = waitpid(id, &status, 0);    
    if(rid < 0) return 3;

    cout << "wait child success: " << rid << " exit code: " << (WIFEXITED(status)) << " exit signal: " << (status & 0x7F) << endl;

    sleep(5);

    cout << "father quit" << endl;

    return 0 ;
}

在这里插入图片描述

进程池

在这里插入图片描述
ProcessPool.cc

 #include"Task.hpp"
#include<string>
#include<vector>
 #include <unistd.h>
#include<cstdlib>
#include<cassert>

const int processnum = 10;  //10个进程
std::vector<task_t>  tasks ; //任务

//管道
class channel
{
  public:
    channel( int cmdcnt , pid_t slaverid  ,   const std::string  processname)
    :_cmdcnt(cmdcnt)
    ,_slaverid(slaverid)
    ,_processname(processname)
    {}

public:
  int _cmdfd;               // 发送任务的文件描述符
    pid_t _slaverid;          // 子进程的PID
    std::string _processname; // 子进程的名字 -- 方便我们打印日志
    int _cmdcnt;
};

//进程 

void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}


//子进程读任务
void slaver()
{

} 


void Debug(const std::vector<channel> & channels)
{
    // test
    for(const auto &c :channels)
    {
        std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;
    }
}

void InitProcessPool(std::vector<channel> * channels )
{
     std::vector<int> oldfds;
  // 确保每一个子进程都只有一个写端
  for( int i =0 ; i<processnum ;i++)
  {
    //创建管道
      int pipefd[2] ={0};
      int n = pipe(pipefd);
    assert(!n);
   (void)n;

       pid_t id = fork(); 
       //父子进程通信,父进程写,子进程读
       if(id ==0 )
       {
        //child
   std::cout << "child: " << getpid() << " close history fd: ";
   //确保每一个子进程都只有一个写端
  for(auto fd : oldfds)
   {
                std::cout << fd << " ";
                close(fd);
   }
        std::cout << "\n";

     //子进程关闭写端
     close(pipefd[1]);
    //重定向 ,0指向pipefd[0],指向读端
   dup2(pipefd[0], 0);
  

     slaver(); //执行子进程相关代码
      exit(0);
std::cout << "process : " << getpid() <<  std::endl;
       }
       //father 
      
     //父进程关闭读端
      close ( pipefd[0]);

      std::string name ="process-" + std::to_string(i) ; 
     // 添加channel字段了
      
     channels->push_back(channel(pipefd[1]  , id,name) ) ; 
     oldfds.push_back(pipefd[1]);
     // sleep(1000);

  }
  

}

 int main()
 {
  LoadTask(&tasks);
  std::vector<channel>  channels;
  InitProcessPool(&channels);
 Debug(channels);

    return 0 ;
 }

关于代码中dup2的理解

在这里插入图片描述

代码一:

void  QuitProcess(std::vector<channel>  &channels)
{
  for( const auto & c : channels)
  {

    std::cout<<"c._cmdfd" <<c._cmdfd<<std::endl;
    close(c._cmdfd) ;
    waitpid(c._slaverid ,nullptr, 0);
  }
}

如果按照代码一的写法,QuitProcess有一个bug
在这里插入图片描述
没有将管道的写端全部关完,还有其他的子进程指向管道的写端,子进程在向管道的读端读取数据时, 子进程是阻塞的 ,子进程在阻塞时还没有退出 ,waitpid时,自然就不能回收子进程

解决方案:倒着回收子进程 ,让最后一个子进程释放 ,最后一个子进程指向上一个管道的写端也会被释放

命名管道

两个毫不相关的进程 进程之间可以使用命名管道通信

如果两个不同的进程,打开同一个文件的时候,在内核中,操作系统会打开几个文件?

操作系统只会打开一个文件

原理:

在这里插入图片描述

[cxq@iZ7xviiy0goapxtblgih6oZ lesson28]$ mkfifo myfifo
[cxq@iZ7xviiy0goapxtblgih6oZ lesson28]$ ll
total 0
prw-rw-r-- 1 cxq cxq 0 Jun 17 22:47 myfifo

管道文件是内存级文件 ,不需要刷新到磁盘

操作系统是如何知道两个毫不相关的进程 打开的是同一个文件?

同一路径下的文件名具有唯一性
操作系统通过 ,路径+文件名来让这两个毫不相关的进程看到同一份资源,从而实现两个毫不相关的进程之间的通信

代码(使用命名管道进行进程间通信)
name_pipe

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鄃鳕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值