Linux —— 进程间通信

目录

1.进程通信的目的

2.进程间通信的方法

2.1管道

2.2System V IPC

2.2POSIX IPC

3.进程间通信实例

3.1匿名管道

3.2匿名管道小项目

3.3命名管道

3.4命名管道的实例

3.5system V共享内存

3.6System V共享内存代码实例

4.System V信号量

5.操作系统如何维护IPC资源

1.进程通信的目的

        1.数据传输:一个进程需要将它的数据发送给另一个进程

        2.资源共享:多个进程之间共享同样的资源

        3.通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某件事情(例如子进程退出时要通知父进程)

        4.进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时指导它的状态改变

2.进程间通信的方法

通信的本质就是操作系统直接或间接给通信双方的进程提供"内存空间",要通信的进程必须看到一份公共的资源。这份公共资源是有操作系统的不同模块提供的(所以有多种通信的方法)。

2.1管道

管道分为匿名管道命名管道,这些管道是基于文件系统的文件。管道文件是一个内存级文件,它有属于自己的内核缓冲区,它并不需要从磁盘中读取数据

管道的特征有如下几点:

        1.管道的生命周期跟随创建此管道的进程

        2.管道可以让具有亲缘关系的进程相互通信(常用于父子通信)

        3.管道是面向字节流的

        4.半双工 -- 管道一般只用来做单向通信

        5.互斥与同步机制 -- 对共享资源进行保护的方案

2.2System V IPC

这种方案聚焦在本地通信,在现代这个万物互联的时代,这种方案是明显落后的。

        1.System V 消息队列

        2.System V 共享内存

        3.System V 信号量

2.2POSIX IPC

这种方案使得通信可以跨主机,是现在主流的通信方法。

        1.消息队列

        2.共享内存

        3.信号量

        3.互斥量

        4.条件变量

        5.读写锁

3.进程间通信实例

3.1匿名管道

管道是一个文件,它是由进程打开的,也就说明当某个进程打开管道文件时,就与管道建立了读写关系

87b4c34b43fe43bbadf3f8b6e20c51a0.png

如果在这个进程基础之上创建一个子进程,这个子进程就会拷贝父进程的东西(除了管道文件)。 

a9d5f6d618374498a1103a80a4d27ce6.png

 此时建立的管道文件是双向通信的,不符合管道通信的特点,我们手动关闭一些文件,即可得到父子进程间的单向通信。
8a5ce4cde4ce46c5944d423c1fb5db59.png

下面用一份很简单的代码来实现上述的通信过程:


#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cassert>

// 实现父进程向管道写入 "i am father"
// 子进程从管道中读取父进程发送的数据
int main()
{
    int fds[2] = {0};   // 用来保存管道读端、写端的文件描述符
    int n = pipe(fds);    // 创建管道文件的系统调用
    assert(n == 0);   // pipe返回0证明创建管道成功
    // 管道文件创建创建成功后,会将读、写端的文件描述符写入fds
    // 其中,fds[0]表写端,fds[1]表读端

    pid_t id = fork();  // 创建子进程
    assert(id >= 0);

    if(id == 0) // 子进程
    {
        close(fds[1]);  // 子进程关闭写端

        while(true)
        {
            char buffer[1024] = {0};
            int s = read(fds[0],buffer,sizeof(buffer)-1);   // 从管道读数据
            if(s > 0)
            {
                buffer[s] = 0;
                std::cout <<  buffer << std::endl;
            }
            else if(s == 0)
            {
                std::cout << "未从管道读到任何数据!" << std::endl;
            }
        }

        close(fds[0]);
        exit(0);
    }
    else  // 父进程
    {
        close(fds[0]);  // 父进程关闭读端

        while(true)
        {
            char buffer[1024] = {0};
            snprintf(buffer,sizeof(buffer),"i am father");
            write(fds[1],buffer,strlen(buffer));    //向管道写入信息
            sleep(1);   
        }
    }

    n = waitpid(id,nullptr,0);  // 阻塞等待子进程退出
    assert(n == id);

    close(fds[1]);
    return 0;
}

以上代码能够实现一个最基本的通信,但我们需要注意一些细节:

        1.程序中的pipe是一个系统调用,它接收一个指针类型的参数,其内部将会在内存开辟一个管道文件,并将读端、写端的文件描述符放入参数所指的空间。返回值为0则表示正常退出

        2.用来存放管道读写端的fds数组,fds[0]表管道的读端文件描述符,fds[1]表管道的写端文件描述符

        3.为了更加严格的使用管道做单向通信,父进程需要关闭读端、子进程需要关闭写端

        4.如果管道的写端没有被关闭,且管道内没有任何数据,则读端将会阻塞,一直等待写端向管道发送数据(即程序会停留在read,不往下执行)

        5.如果管道的写端关闭,read函数将会返回0(读到0个数据)

       6.管道是一个固定大小的文件(其容量是有限的)

        7.如果管道的读端被关闭,那么写端存在就没有任何意义。此时write便会收到来自操作系统的终止信号

        8.如果读写端都没有被关闭,写端写数据的速度较快,而读端读数据的速度较慢,就会造成管道被写满,此时就会发生写端阻塞(即程序会停留在write,不往下执行)

        9.如果读写端都没有被关闭,写端写数据较慢,而读端读数据较快,就会造成管道没有任何数据,此时就会发生读端阻塞(见4)

3.2匿名管道小项目

上面的程序只实现了数据的传送。现在我们应该实现事件通知和进程控制。我们的想法如下:

        1.父进程创建N个子进程和N个管道

        2.父进程向某个管道发送"控制命令"

        3.子进程从对应的管道读取"控制命令"

        4.子进程根据"控制命令"做出相应的动作

7cf118cd9373435eb4da213ea8da686a.png

#include <unistd.h>
#include <iostream>
#include <cassert>
#include <string>
#include <cstdio>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAKE_RAND() srand((size_t)time(nullptr) ^ 15423 ^ 12)
///子进程执行的任务
void IO_mission(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小龙向钱进

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

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

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

打赏作者

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

抵扣说明:

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

余额充值