linux进程间通信

进程间通信介绍

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

进程间通信发展

管道
System V进程间通信
POSIX进程间通信

在这里插入图片描述
我们想象一个场景。一个警察卧底在毒窝里面,他想要跟外面的警察取得联系该怎么做啊。他们可以先约定一个地方(这里就是操作系统会提供一个地方),卧底把传达信息放在该地方的一个砖头下(往该地方写信息),警察队从这个地方读信息(读信息)。这样是不是就可以通信了。
我们知道创建子进程访问文件如上图所示。但是进程之间具有独立性,不可能将这个区域放到进程里面,那么放在哪里呢?显而易见 , 在struct_file位置。
上图显示了一个流程,假设我父进程把东西写进磁盘,子进程再把磁盘的数据写到显示屏上,但是我们通信的目标就只是为了让父子进程取得通信,如果非要加上IO操作的话就会很慢。碰巧,操作系统有能力。在定义struct——file结构体的时候,里面会有一个union(联合体)。这个联合体区分了这个结构体是文件还是管道(管道就是在缓冲区的数据不会往磁盘写,但是文件结构体会往磁盘写数据)。这个时候这个管道没有名字就叫做匿名管道。这里同样也明确了,那个公共位置就是其实就是文件缓冲区。
在这里插入图片描述

操作原理

在这里插入图片描述

具体的流程分为3个步骤
1 父进程创建一个能读能写的进程
2 父进程创建子进程
3 父进程和子进程各关闭读写进程中的一个

操作代码如图所示:
pipe这个函数的意义就是一个输入参数,就是将这个参数作为输入参数传进去,然后再函数内部的时候会给这个数组的赋值,就是赋值两个文件操作符。

 int fds[2];
 int n = pipe(fds);
 assert(n==0);

 pid_t id = fork();
 assert(id>=0);

一个实现的代码

#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string>
#include<cstring>
#include <stdio.h>
// 更好的写法
#include<cassert>
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n==0);

    // 谁是读 谁是写
    // 0 是 read
    // 1 是 write
    // std::cout<<"fds[0]:"<<fds[0]<<std::endl;
    // std::cout<<"fds[1]:"<<fds[1]<<std::endl;
    pid_t id = fork();
    assert(id>=0);
    const char* s = "我是子进程,我在给你发消息";
    if(id==0)
    {
        // 子进程负责写入
        // 子进程通信代码
        // cout<<111<<endl;
        close(fds[0]);
        // string msg ="hello vscode";
        // std::string msg = "hello world";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到
            snprintf(buffer,sizeof buffer,"child -> parent[%d] say: %s[%d][pid=%d]\n",getppid(),s,cnt,getpid());
            cout<<buffer<<endl;
            write(fds[1],buffer,strlen(buffer));
            cout<<"count:"<<cnt<<endl;
            // sleep(1); // 每隔一秒写一次
            break;
        }
        close(fds[1]);
        cout<<"子进程正在关闭写入"<<endl;
        exit(0);
    }
    // 父进程通信代码
    // 父进程表示读取

    close(fds[1]);
        
    while(true)
    {
        char buffer[1024];
        ssize_t n = read(fds[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<"父进程的pid# "<<getpid()<<buffer<<endl;
        }else if (n==0)
        {
            // 读到文件结尾
            cout<<"read:"<<n<<endl;
            break;
        
        }
        
        
    }
    n =  waitpid(id,nullptr,0);
    assert(n==id);
    close(fds[0]);
    return 0;
}

一种一种的分析 ,写慢 ,读快 。

代码

#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string>
#include<cstring>
#include <stdio.h>
// 更好的写法
#include<cassert>
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n==0);

    // 谁是读 谁是写
    // 0 是 read
    // 1 是 write
    // std::cout<<"fds[0]:"<<fds[0]<<std::endl;
    // std::cout<<"fds[1]:"<<fds[1]<<std::endl;
    pid_t id = fork();
    assert(id>=0);
    const char* s = "我是子进程,我在给你发消息";
    if(id==0)
    {
        // 子进程负责写入
        // 子进程通信代码
        // cout<<111<<endl;
        close(fds[0]);
        // string msg ="hello vscode";
        // std::string msg = "hello world";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到
            snprintf(buffer,sizeof buffer,"child -> parent[%d] say: %s[%d][pid=%d]\n",getppid(),s,cnt,getpid());
            cout<<buffer<<endl;
            write(fds[1],buffer,strlen(buffer));
            cout<<"count:"<<cnt<<endl;
            sleep(10); // 每隔一秒写一次
            // break;
        }
        close(fds[1]);
        cout<<"子进程正在关闭写入"<<endl;
        exit(0);
    }
    // 父进程通信代码
    // 父进程表示读取

    close(fds[1]);
        
    while(true)
    {
        char buffer[1024];
        cout<<1111111111111111<<endl;
        ssize_t n = read(fds[0],buffer,sizeof(buffer)-1);
        cout<<2222222222222222<<endl;
        if(n>0)
        {
            buffer[n]=0;
            cout<<"父进程的pid# "<<getpid()<<buffer<<endl;
        }else if (n==0)
        {
            // 读到文件结尾
            cout<<"read:"<<n<<endl;
            break;
        
        }
        
        
    }
    n =  waitpid(id,nullptr,0);
    assert(n==id);
    close(fds[0]);
    return 0;
}

结果 : 发现read函数有阻塞功能,会等到缓冲区刷新之后,才会有数据

1111111111111111
child -> parent[9719] say: 我是子进程,我在给你发消息[1][pid=9720]

count:1
2222222222222222
父进程的pid# 9719child -> parent[9719] say: 我是子进程,我在给你发消息[1][pid=9720]

1111111111111111

第二种 写快 读慢

代码

#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string>
#include<cstring>
#include <stdio.h>
// 更好的写法
#include<cassert>
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n==0);

    // 谁是读 谁是写
    // 0 是 read
    // 1 是 write
    // std::cout<<"fds[0]:"<<fds[0]<<std::endl;
    // std::cout<<"fds[1]:"<<fds[1]<<std::endl;
    pid_t id = fork();
    assert(id>=0);
    const char* s = "我是子进程,我在给你发消息";
    if(id==0)
    {
        // 子进程负责写入
        // 子进程通信代码
        // cout<<111<<endl;
        close(fds[0]);
        // string msg ="hello vscode";
        // std::string msg = "hello world";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到
            snprintf(buffer,sizeof buffer,"child -> parent[%d] say: %s[%d][pid=%d]\n",getppid(),s,cnt,getpid());
            // cout<<buffer<<endl;
            write(fds[1],buffer,strlen(buffer));
            // cout<<"count:"<<cnt<<endl;
            // sleep(10); // 每隔一秒写一次
            // break;
        }
        close(fds[1]);
        cout<<"子进程正在关闭写入"<<endl;
        exit(0);
    }
    // 父进程通信代码
    // 父进程表示读取

    close(fds[1]);
        
    while(true)
    {
        sleep(1);
        char buffer[1024];
        // cout<<1111111111111111<<endl;
        ssize_t n = read(fds[0],buffer,sizeof(buffer)-1);
        // cout<<2222222222222222<<endl;
        if(n>0)
        {
            buffer[n]=0;
            cout<<"父进程的pid# "<<getpid()<<buffer<<endl;
        }else if (n==0)
        {
            // 读到文件结尾
            cout<<"read:"<<n<<endl;
            break;
        
        }
        
        
    }
    n =  waitpid(id,nullptr,0);
    assert(n==id);
    close(fds[0]);
    return 0;
}

结果,发现他是一直写,然后一次读完

父进程的pid# 11840child -> parent[11840] say: 我是子进程,我在给你发消息[1][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[2][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[3][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[4][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[5][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[6][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[7][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[8][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[9][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[10][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[11][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[12][pid=11841]
child -> parent[11840] say: 我是�
父进程的pid# 11840�进程,我在给你发消息[13][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[14][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[15][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[16][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[17][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[18][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[19][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[20][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[21][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[22][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[23][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[24][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发�
父进程的pid# 11840�息[25][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[26][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[27][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[28][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[29][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[30][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[31][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[32][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[33][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[34][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[35][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[36][pid=11841]
child -> parent[11840] say: 我是子进程,我在给你发消息[37][pid=11841]
child -
^C
[zk@VM-24-17-centos pipe]$ make clean
rm -rf mypipe
[zk@VM-24-17-centos pipe]$ make
g++ -o mypipe mypipe.cc -std=c++11
[zk@VM-24-17-centos pipe]$ ./mypipe 
父进程的pid# 12206child -> parent[12206] say: 我是子进程,我在给你发消息[1][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[2][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[3][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[4][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[5][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[6][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[7][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[8][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[9][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[10][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[11][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[12][pid=12207]
child -> parent[12206] say: 我是�
父进程的pid# 12206�进程,我在给你发消息[13][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[14][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[15][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[16][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[17][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[18][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[19][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[20][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[21][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[22][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[23][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[24][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发�
父进程的pid# 12206�息[25][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[26][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[27][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[28][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[29][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[30][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[31][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[32][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[33][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[34][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[35][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[36][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[37][pid=12207]
child -
父进程的pid# 12206> parent[12206] say: 我是子进程,我在给你发消息[38][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[39][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[40][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[41][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[42][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[43][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[44][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[45][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[46][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[47][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[48][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[49][pid=12207]
child -> parent[12206] say: 我是
父进程的pid# 12206子进程,我在给你发消息[50][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[51][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[52][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[53][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[54][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[55][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[56][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[57][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[58][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[59][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[60][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[61][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发
父进程的pid# 12206消息[62][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[63][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[64][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[65][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[66][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[67][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[68][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[69][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[70][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[71][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[72][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[73][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[74][pid=12207]
child
父进程的pid# 12206 -> parent[12206] say: 我是子进程,我在给你发消息[75][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[76][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[77][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[78][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[79][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[80][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[81][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[82][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[83][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[84][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[85][pid=12207]
child -> parent[12206] say: 我是子进程,我在给你发消息[86][pid=12207]
child -> parent[12206] say: 我�

第三种 正常读 ,但是写关闭了

这里需要解释
如果管道中有数据:read 函数读取所请求的字节数(或管道中可用的字节数,取较小者),并返回读取的字节数。
如果管道中无数据且管道写端仍打开:read 函数会阻塞,直到有数据可读或所有写端关闭。
如果管道中无数据且所有管道的写端已经关闭:read 函数返回0,这表示已到达文件(管道)结束(EOF)。这通常被解释为“读到文件结尾”

代码

#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string>
#include<cstring>
#include <stdio.h>
// 更好的写法
#include<cassert>
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n==0);

    // 谁是读 谁是写
    // 0 是 read
    // 1 是 write
    // std::cout<<"fds[0]:"<<fds[0]<<std::endl;
    // std::cout<<"fds[1]:"<<fds[1]<<std::endl;
    pid_t id = fork();
    assert(id>=0);
    const char* s = "我是子进程,我在给你发消息";
    if(id==0)
    {
        // 子进程负责写入
        // 子进程通信代码
        // cout<<111<<endl;
        close(fds[0]);
        // string msg ="hello vscode";
        // std::string msg = "hello world";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到
            snprintf(buffer,sizeof buffer,"child -> parent[%d] say: %s[%d][pid=%d]\n",getppid(),s,cnt,getpid());
            // cout<<buffer<<endl;
            write(fds[1],buffer,strlen(buffer));
            // cout<<"count:"<<cnt<<endl;
            // sleep(10); // 每隔一秒写一次
            break;
        }
        close(fds[1]);
        cout<<"子进程正在关闭写入"<<endl;
        exit(0);
    }
    // 父进程通信代码
    // 父进程表示读取

    close(fds[1]);
        
    while(true)
    {
        sleep(1);
        char buffer[1024];
        // cout<<1111111111111111<<endl;
        ssize_t n = read(fds[0],buffer,sizeof(buffer)-1);
        // cout<<2222222222222222<<endl;
        if(n>0)
        {
            buffer[n]=0;
            cout<<"父进程的pid# "<<getpid()<<buffer<<endl;
        }else if (n==0)
        {
            // 读到文件结尾
            cout<<"read:"<<n<<endl;
            break;
        
        }
        
        
    }
    n =  waitpid(id,nullptr,0);
    assert(n==id);
    close(fds[0]);
    return 0;
}

结果

子进程正在关闭写入
父进程的pid# 15983child -> parent[15983] say: 我是子进程,我在给你发消息[1][pid=15984]

read:0

第4种 正常写,但是读端关闭了

写入一个没有读者的管道:如果你试图向一个读端已被关闭的管道写数据,操作系统将向该进程发送 SIGPIPE 信号。如果该信号没有被处理(即没有被捕获或忽略),那么默认动作是终止调用的进程。
处理 SIGPIPE 信号:如果你的程序需要处理这种情况,而不是简单地让进程退出,你可以捕获 SIGPIPE 信号,并在信号处理函数中进行适当处理。你也可以选择忽略这个信号,这样写操作将不会导致程序终止,但 write 函数将返回 -1 并设置 errno 为 EPIPE,表示管道破裂。
代码

#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string>
#include<cstring>
#include <stdio.h>
// 更好的写法
#include<cassert>
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n==0);

    // 谁是读 谁是写
    // 0 是 read
    // 1 是 write
    // std::cout<<"fds[0]:"<<fds[0]<<std::endl;
    // std::cout<<"fds[1]:"<<fds[1]<<std::endl;
    pid_t id = fork();
    assert(id>=0);
    const char* s = "我是子进程,我在给你发消息";
    if(id==0)
    {
        // 子进程负责写入
        // 子进程通信代码
        // cout<<111<<endl;
        close(fds[0]);
        // string msg ="hello vscode";
        // std::string msg = "hello world";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到
            snprintf(buffer,sizeof buffer,"child -> parent[%d] say: %s[%d][pid=%d]\n",getppid(),s,cnt,getpid());
            // cout<<buffer<<endl;
            write(fds[1],buffer,strlen(buffer));
            // cout<<"count:"<<cnt<<endl;
            // sleep(10); // 每隔一秒写一次
            break;
        }
        close(fds[1]);
        cout<<"子进程正在关闭写入"<<endl;
        exit(0);
    }
    // 父进程通信代码
    // 父进程表示读取

    close(fds[1]);
    close(fds[0]);
        
    // while(true)
    // {
        
    //     sleep(1);
    //     char buffer[1024];
    //     // cout<<1111111111111111<<endl;
    //     ssize_t n = read(fds[0],buffer,sizeof(buffer)-1);
    //     // cout<<2222222222222222<<endl;
    //     if(n>0)
    //     {
    //         buffer[n]=0;
    //         cout<<"父进程的pid# "<<getpid()<<buffer<<endl;
    //     }else if (n==0)
    //     {
    //         // 读到文件结尾
    //         cout<<"read:"<<n<<endl;
    //         break;
        
    //     }
        
        
    // }
    int status;
    n =  waitpid(id,&status,0);
    int mand = (status&0x7f);
    cout<<mand<<endl;
    assert(n==id);
    close(fds[0]);
    return 0;
}

结果,

[zk@VM-24-17-centos pipe]$ ./mypipe 
13
[zk@VM-24-17-centos pipe]$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

13号信号是杀死管道

system V (共享内存 ,消息队列 ,信号量)

共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

我们知道进程间通信的话需要让进程能看到同一份资源 ,前面已经有两种方式了,第一种是匿名管道,第二种是命名管道。这里有第三种方式,共享内存。共享内存的原理图如下图所示:

在这里插入图片描述

这里提出2个问题?

1 这里的写入不会发生写时拷贝嘛?不对触发时拷贝的原理是什么?

2 为什么是最快的之一?

问题一 这里的写入不会发生写时拷贝嘛?不对触发时拷贝的原理是什么?:

在实现共享内存时,避免写时拷贝(Copy-On-Write, COW)主要依赖于操作系统的内存管理策略和进程映射共享内存段时的特定设置。这涉及到以下几个关键技术和步骤:

1. 共享内存的创建和配置
共享内存段创建:一般使用系统调用(如Linux中的shmget)创建一个共享内存段。这个调用请求操作系统分配一块可以被多个进程共享的物理内存。
属性设置:在创建共享内存时,可以设置属性以指示这块内存是为了共享而不是私有复制。这个设置确保操作系统在处理内存页时不会应用COW策略。
2. 映射共享内存到进程空间
内存映射:进程通过调用如shmat(在UNIX-like系统中)将共享内存段映射到其地址空间。这一步是显式的操作,告诉操作系统这块内存是被多个进程共享的。
映射标志:在映射时,可以指定标志来控制访问权限和行为。这些标志中不包括启用COW的选项,从而保证映射的内存区域是直接共享的,不会在写入时进行拷贝
3. 操作系统的页表管理
**页表配置:**操作系统的内存管理单元(MMU)使用页表来管理虚拟地址到物理地址的映射。对于共享内存,系统会设置页表项,使得来自不同进程的多个虚拟地址指向同一个物理地址。
避免COW机制:由于共享内存区的页表项被配置为直接映射到物理内存,而非标记为COW,因此在这些页上的写入操作直接修改物理内存,不会触发页面复制
后续的同步和互斥是有关信号量的,我们先不讲

重点: 这个避免写时拷贝的属性是保存在页表里面 ,既然不会发生写时拷贝了,那我们对于数据的修改就是直接在物理地址上写入。

问题二:为什么是最快的之一?

首先我们看看管道操作需要发生多少次写时拷贝 ? (从显示屏上输入到显示屏输出)
在这里插入图片描述

(个人学习用)
操作系统先把数据和代码加载进内存 ,并且生成PCB块。(这里需要注意的是,一共有两个页表。 一个是系统页表,所有进程看到的都是同一份,这里有write函数的接口啊等等。)
1 程序执行 scanf:当你的程序执行到 scanf 函数的时候,它会暂停其他操作,并等待用户的输入。
等待输入:在这个时刻,程序并不会自动从显示器上获得数据。相反,它在等待用户通过键盘输入数据。此时,你会看到你的终端或命令行界面似乎“冻结”了,实际上它是在等待输入。
**2 用户输入:**用户开始在键盘上输入数据。这些输入数据随着你的键盘敲击被发送到计算机。
输入硬件(键盘):
当你在键盘上输入数据时,键盘硬件将按键操作转换为电子信号。
中断和硬件驱动:
键盘生成的信号通常会触发一个硬件中断。Linux 内核通过键盘设备驱动程序响应这个中断。
设备驱动程序读取按键信息,并将其转换为对应的字符或命令。
内核输入子系统:
转换后的输入数据由内核的输入子系统处理,然后放入一个称为键盘缓冲区的地方。
**3 数据到达 scanf:**操作系统将收到的数据通过终端驱动传递到应用程序的标准输入流(stdin)。只有当你按下 Enter 键时,输入的数据才会完全被发送到 scanf。
**scanf 处理输入:**scanf 函数从标准输入流中读取输入的数据,根据你在函数中指定的格式,解析这些数据并将解析后的值存储到提供的变量地址中。
这里的sacnf函数就是read函数的封装。
后面还有一些信息在信号量部分讲解

这里看,驱动到缓冲区一次 , 缓冲区到buffer一次 。 buffer到缓冲区再一次 。 缓冲区再到显示屏。 一共4次。

共享内存方式

共享内存允许多个进程直接访问同一块内存区域。这意味着一旦数据被写入共享内存,其他进程就可以立即看到并访问这些数据,无需任何中介或额外的数据拷贝。
使用共享内存时,进程不需要执行系统调用来发送或接收数据,这减少了操作系统层面的上下文切换。
着里就buffer可以减少2次拷贝

代码实现
comm.hpp

#pragma once
#include <stdio.h>
#include<iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
#define PATHNAME "."
#define PROJ_ID 0x6666

int shmHelper(int size, int flags)
{
    key_t _key = ftok(PATHNAME, PROJ_ID);
    std::cout<<_key<<std::endl;
    if (_key < 0)
    {
        perror("ftok");
        return -1;
    }
    int shmid = 0;
    if ((shmid = shmget(_key, size, flags)) < 0)
    {
        perror("shmget");
        return -2;
    }
    return shmid;
}

int shmCreate(int size)
{
    return shmHelper(size, IPC_CREAT | IPC_EXCL|0666);
}

int shmGet(int size)
{
    return shmHelper(size, IPC_CREAT);
}

int destroyShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) < 0)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}


server.cc

#include "comm.hpp"

int main()
{
    int shmid = shmCreate(4096);
    // 要是4kb的整数倍
    if (shmid < 0) {
        perror("shmCreate failed");
        return 1;
    }

    char* addr = (char*)shmat(shmid,nullptr,0);
    sleep(2);

    int i = 0;
    while(i++<26)
    {
        printf("client %s\n",addr);
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    destroyShm(shmid);
    return 0;
}

client.cc

#include"comm.hpp"

int main()
{
    int shmid = shmGet(4096);
    sleep(1);
    char* addr = (char*)shmat(shmid,NULL,0);
    sleep(2);
    int i = 0;
    while(i<26){
        addr[i] = 'A'+i;
        i++;
        addr[i] = 0;
        sleep(1);
    }

    shmdt(addr);
    sleep(2);
    return 0;
}

system V消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

资源的创建与持久性:
System V IPC 资源在创建时,会被注册到内核中,这使得它们可以被系统中任何具有适当权限的进程访问。
这些资源与创建它们的进程的生命周期是独立的。即使创建它们的进程已经终止,这些资源仍然存在,除非它们被显式删除。
显式删除的必要性:
在许多操作系统资源管理中,资源如临时文件或内存在进程结束时会自动清理。然而,System V IPC 资源不遵循这样的自动清理机制。
必须使用相应的系统调用(如 semctl 用于信号量、msgctl 用于消息队列、shmctl 用于共享内存)显式地删除这些资源。
生命周期随内核:
System V IPC 资源的生命周期是与内核的运行周期相绑定的。这意味着,这些资源会持续存在直到它们被显式删除或者内核被重新启动(如系统重启)。
如果不进行显式删除,这些资源可能导致系统资源泄漏,因为它们会持续占用内核内存和其他系统资源。
重启的影响:
系统重启时,所有内核管理的资源,包括 System V IPC 资源,都会被清理和重置。这是因为重启过程中内核本身会被重新初始化。
这里的意思是,我们不手动的释放这个共享内存的话,进程结束不会释放这个共享内存的资源。

 shmdt(addr);

system V信号量

信号量是什么呢?本质就是一个计数器,对资源的管理的物品.
我们知道进程之间通信需要访问公共资源,但是访问公共资源又分为几种。对于没有保护的公共资源,我们在对其进行访问的时候,会存在数据不一致的问题(就是我可能还在写,你就已经读了)。
我们把保护起来的公共资源叫坐临界资源。
进程之中有相应的代码来访问临界资源 ,叫做临界区,没有访问的那部分代码叫做非临界区。

如何保护呢? 同斥和同步?
怎么理解这两个概念呢?
开学了,老师在上课,这时候老师叫张山去帮全班的同学拿课本,由于开学的时候,同学都是没有书的,所以老师和同学们在等张三同学把书搬回来。等张三回来之后,老师将课本发了就上课。这就是互斥的概念。这个时候呢?张三出去搬书了,但是老师在给同学们讲班规。这就是同步的概念。其中理解到的就是原子性。要么不做,要么就做好。

为什么要有信号量?
这种时候,我们要举一个例子。
我们去电影院看票是你坐在电影院一个位置上就代表了你有这个座位了吗?我想答案肯定是 否!为什么呢?这里需要根据你的电影票上的座位来确定啊。这个时候,就算你不来看这场电影,那么这个位置还是属于你的。
那么这里这个电影票就是信号量。
在这里插入图片描述
下面这是多态的原理。暂不讲解。
在这里插入图片描述

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值