【操作系统】进程间通信

目录

一、基本概念

1.进程间通信的目的

2.进程间通信的分类

二、管道通信

1.匿名管道

(1)匿名管道示意图

(2)匿名管道原理

(3)管道读写特征

(4)管道特征

2.命名管道

(1)命名管道示意图

(2)命名管道的实现

三、共享内存

1.共享内存概念

2.共享内存原理

3.共享内存理解

4.共享内存相关接口

(1)ftok

(2)shmget

(3)shmctl

(4)shmat

(5)shmdt

5.共享内存特点

四、消息队列

1.消息队列原理

2.消息队列相关接口

五、信号量

1.信号量概念

(1)相关概念

(2)信号量

2.信号量相关接口


一、基本概念

进程具有独立性,所以进程间通信成本很高:让不同的进程看到同一份资源;进行通信

1.进程间通信的目的

数据传输、资源共享、通知事件、进程控制

2.进程间通信的分类

(1)POSIX -> 可以跨主机通信

(2)System V -> 聚焦本地通信 

(3)管道 -> 基于文件系统的通信方式;分为匿名管道和命名管道两种

二、管道通信

1.匿名管道

(1)匿名管道示意图

(2)匿名管道原理

父子进程要分别以读和写的方式打开同一个文件!要创建管道文件,要使用pipe()函数!

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

//父进程进行读取,子进程进行写入
int main()
{
    //第一步:创建管道文件,打开读写端
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);

    //第二步:fork()
    pid_t id = fork();
    assert(id >= 0);

    if(id == 0)
    {
        //子进程的通讯代码
        //子进程进行写入 -> 关闭读
        close(fds[0]);
        const char* msg = "i am child";
        int count = 0;
        while(true)
        {
            count++;
            char buffer[1024];//只有子进程能看到
            snprintf(buffer, sizeof(buffer), "child say: %s", msg);
            //写端写满的时候,在写就会阻塞,等待对方读取
            write(fds[1], buffer, strlen(buffer));
            sleep(1);//每隔一秒写一次
        }
        exit(0);
    }

    //父进程进行读取 -> 关闭写
    close(fds[1]);
    //父进程的通讯代码
    while(true)
    {
        char buffer[1024];
        //如果管道里没有数据了,读端在读,默认会直接阻塞当前正在读取的进程
        ssize_t s = read(fds[0], buffer, sizeof(buffer)-1);
        if(s > 0) buffer[s] = '\0';
        std::cout << "Get Massage# " << buffer << std::endl;

        //父进程没有进行sleep
    }
    
    //等待子进程
    n = waitpid(id, nullptr, 0);
    assert(n == id);

    return 0;
}

(3)管道读写特征

①读慢 写快:write调用阻塞,一直等到有读进程来读取数据

②读快 写慢:read调用阻塞,一直等到有写进程来写入数据

③写关闭:读到结束

④读关闭:操作系统会给写进程发送信号,终止写端

(4)管道特征

①进程退出,管道就被释放了,所以管道的生命周期随进程

②管道可以用来进行具有血缘关系的进程之间的通信,常用于父子通信

③管道是面向字节流的

④管道是半双工的,数据只能向一个方向流动

⑤内核会对管道操作进行同步和互斥

2.命名管道

匿名管道只能用于具有血缘关系的进程;那不相关的进程之间的通信怎么办?命名管道!

命名管道是如何做到让不同的进程,看到了同一份资源?

让不同的进程打开同一个文件(路径 + 文件名)。因为:路径 + 文件名 = 唯一性

(1)命名管道示意图

(2)命名管道的实现

①$ mkfifo filename  // 创建命名管道的Linux指令

②int mkfifo(const char *pathname, mode_t mode);  // 创建命名管道的函数

a.pathname:命名管道的路径

b.mode:命名管道的起始权限

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define NAMED_PIPE "/tmp/mypipe"

// 创建命名管道
bool createFIfo(const std::string &path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0600);
    if(n == 0) 
    {
        return true;
    }
    else
    {
        std::cout << errno << strerror(errno) << std::endl;
        return false;
    }
}

// 删除命名管道
void removeFilo(const std::string &path)
{
    int n = unlink(path.c_str());
    assert(n == 0);
}

三、共享内存

共享内存、消息队列、信号量 都是 system V 进程间通信

1.共享内存概念

让不同的进程看到同一个内存块的方式 -> 就是共享内存

2.共享内存原理

(1)首先要在内存中申请一块内存(共享内存)

(2)将创建好的内存映射到进程地址空间 -> 进程和共享内存挂接

(3)未来不在通信时:取消进程和内存的映射关系(去关联);释放内存(释放共享内存)

3.共享内存理解

(1)共享内存是为了进程间通信专门设计的

(2)共享内存是一种通信方式,所有想通信的进程都可以用共享内存通信

(3)因为会有很多进程在通信,所以操作系统中一定会同时存在很多共享内存

4.共享内存相关接口

(1)ftok

key_t ftok(const char *pathname, int proj_id); // 生成一个独一无二的key

①pathname:路径,必须是真实存在且可以访问的路径

②proj_id:int类型数字,且必须传入非零值

ftok()函数通过 pathname 和 proj_id 来生成一个独一无二的key

(2)shmget

int shmget(key_t key, size_t size, int shmflg); // 创建共享内存

①key:共享内存唯一的标识(利用ftok()函数创建)

②size:共享内存的大小(一般建议是4KB的整数倍)

③shmfig:IPC_CREAT -> 不存在创建;存在则获取

                    IPC_EXCL | IPC_CREAT ->不存在创建;存在就出错返回

④返回值:返回一个有效的共享内存标识符shmid

(3)shmctl

int shmctl(int shmid, int cmd, struct shmid_ds *buf); // 控制共享内存(通常用来删除)

①shmid    ②cmd:删除 -> IPC_RMID    ③buf设置为nullptr即可

(4)shmat

void *shmat(int shmid, const void *shmaddr, int shmflg); // 进程和共享内存挂接

①shmid    ②shmaddr设置为nullptr即可    ③shmflg设置为0即可

返回值:返回共享内存的起始地址

(5)shmdt

int shmdt(const void *shmaddr); // 断开进程和共享内存挂接

shmaddr:要去关联的共享内存的首地址

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAME "."  // 上级目录
#define PROJ_ID 0x66  // 自定义
#define MAX_SIZE 4096 // 共享内存大小

key_t getKey()
{
    key_t key = ftok(PATHNAME, PROJ_ID);
    if (key < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return key;
}

int getShmHelper(key_t key, int flags)
{
    int shmid = shmget(key, MAX_SIZE, flags);
    if (shmid < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(2);
    }
    return shmid;
}

// 获取到共享内存
int getShm(key_t key)
{
    return getShmHelper(key, IPC_CREAT);
}

// 创建共享内存
int createShm(key_t key)
{
    // 0600:把共享内存的权限设置为0600,这样拥有者就可以读写了(和文件权限一样)
    // 不加权限无法完成 进程和共享内存挂接
    return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0600);
}

// 删除共享内存
void delShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
    }
}

// 完成进程和共享内存挂接
void *shmAttach(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if ((long long)mem == -1L)
    {
        std::cerr << " shmat: " << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

// 去关联
void shmDetach(void *start)
{
    if (shmdt(start) == -1)
    {
        std::cerr << "shmdt: " << strerror(errno) << std::endl;
    }
}

5.共享内存特点

(1)共享内存的生命周期是随OS的,不是随进程的(system V 通信的生命周期都是随进程的)

(2)共享内存在所有的进程间通信中,速度是最快的

(3)没有同步和互斥的操作,没有对数据做任何保护

四、消息队列

1.消息队列原理

2.消息队列相关接口

(1)int msgget(key_t key, int msgflg); // 创建消息队列

(2)int msgctl(int msqid, int cmd, struct msqid_ds *buf); // 控制消息队列

(3)int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); // 放数据

(4)ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//读

五、信号量

信号量主要是用于同步和互斥的

1.信号量概念

(1)相关概念

①公共资源:可以被多个进程"同时"访问的资源(访问没有保护的公共资源会出问题)

②临界资源:我们将被保护起来的公共资源 称为 临界资源

③临界区:访问临界资源的代码

④非临界区:访问非临界资源的代码

⑤原子操作:要么不做,要做就做完,只有两态

(2)信号量

本质上是一个计数器,通常用来表示公共资源中 资源数量的多少

为什么要有信号量?类比看电影买票:对影院的座位预订->信号量就相当于是一场电影的总票数

同理:当我们想要某种资源的时候,我们可以进行预定(参照下图流程)

所有的进程在访问公共资源之前,都必须先申请sem信号量 -> 申请sem信号量的前提,是所有

进程必须先得看到同一个信号量 -> 信号量本身就是公共资源 -> 信号量也要保证自己的安全

-> 所以信号量的 ++ / -- 操作是原子操作!

2.信号量相关接口

(1)int semget(key_t key, int nsems, int semflg); // 创建信号量

(2)int semctl(int semid, int semnum, int cmd, ...); // 控制信号量(可以用来删除信号量)

(3)int semop(int semid, struct sembuf *sops, unsigned nsops); // 实现PV操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值