命名管道是什么

我们知道匿名管道,是用于两个进程之间有父子关系而所使用的,当两个进程没有血缘关系时,两个不相干的进程想通信,这个时候就需要命名管道了

命名管道会在指定目录下创建一个管道文件,如下图所示

这个管道文件就是用于进程之间通信的

下面我们来讲解他的原理

命名管道原理

命名管道的本质就是创建一个文件,供两个进程进行通信,那么两个进程会同时打开同一个文件两次吗?答案是不是的,进程间通信的本质就是让不同进程看到同一个资源,只需要打开一个文件即可,操作系统是不会浪费资源的

操作系统会把两个进程的struct file里面的内容给映射到同一个打开的文件当中去

命名管道会让不同的进程看到位于同一个路径下的同一个资源,文件的路径具有唯一性,文件的路径有名字,这个名字就是创建的命名管道的名字

匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开⽤open
FIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的⽅式不同,⼀但这些 ⼯作完成之后,它们具有相同的语义。
我们要知道命名管道它只是一个文件系统入口
我们来写入一长串的字符串看看
再去看看他的大小
看到的大小还是0
数据不存磁盘,全部放在内核内存缓冲区

命名管道的使用

我们先来shell端操作一下

这里使用mkfifo来创建命名管道

这里我们使用echo命令重定向到管道文件中去

这时候我们发现这个操作阻塞了,这里为什么阻塞,这里其实是是在阻塞等待,等待另一个进程进行读取,这个是管道的特性,只有写端打开 FIFO,没有读端,open () 就会阻塞,等待对方进程打开管道读端。我们再登录一个终端执行一下cat testfifo

这里就没有阻塞了,下面我们讲讲如何在编写代码中使用mkfifo


代码层面命名管道的使用

命名管道的打开规则

如果当前打开操作是为读⽽打开FIFO时:
O_NONBLOCK disable:阻塞直到有相应进程为写⽽打开该FIFO
O_NONBLOCK enable:⽴刻返回成功
如果当前打开操作是为写⽽打开FIFO时:
O_NONBLOCK disable:阻塞直到有相应进程为读⽽打开该FIFO
O_NONBLOCK enable:⽴刻返回失败,错误码为ENXIO
下面我们看看接口
我们来依次说明一下参数的意义,第一个就是路径,第二个就是权限,我们创建的管道,本质也是文件,文件就要有权限,不给权限就要乱码

实现通信样例

下面这个例子就是创建一个命名管道,让两个进程之间进行通信,a进程是客户端,b进程是服务端,a进程写,b进程读取
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

#define MODE "server"
#define UMASK 0666
#define ERROR(m)            \
    do                      \
    {                       \
        perror(m);          \
        exit(EXIT_FAILURE); \
    } while (0)

class create_myfifo
{
public:
    create_myfifo(const std::string &path, const std::string &name, const std::string &mode)
        : _path(path), _name(name), _fd(-1),_mode(mode)
    {
        if (_mode == "server")
        {
            _fifoname = path + "/" + name;
            int ret = mkfifo(_fifoname.c_str(), 0666);
            if (ret < 0)
            {
                ERROR("mkfifo");
            }
            else
            {
                std::cout << "create fifo succsee !!" << std::endl;
            }
        }
        else 
        {
            _fifoname = path + "/" + name;
        }
    }
    ~create_myfifo()
    {
        if (_mode == "server")
        {
            int ret = unlink(_fifoname.c_str());
            if (ret < 0)
            {

                ERROR("unlink");
            }
            else
            {
                std::cout << "unlink_success !!" << std::endl;
            }
        }
    }

    bool open_for_read()
    {
        _fd = open(_fifoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            ERROR("open");
        }
        else
        {
            std::cout << "open read success " << std::endl;
            return true;
        }
    }

    bool open_for_write()
    {
        _fd = open(_fifoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            ERROR("open");
        }
        else
        {
            std::cout << "open write success " << std::endl;
            return true;
        }
    }

    void to_write()
    {
        std::string buffer;
        while (1)
        {
            getline(std::cin,buffer);
            ssize_t ret = write(_fd, buffer.c_str(), strlen(buffer.c_str()));
            if (ret > 0)
            {
                std::cout << "write success " << std::endl;
            }
            else if (ret == 0)
            {
                std::cout << "write end " << std::endl;
                return;
            }
            else
            {
                ERROR("write");
            }
        }
    }

    void to_read()
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        while (1)
        {
            ssize_t ret = read(_fd, buffer, sizeof(buffer));
            if (ret > 0)
            {
                std::cout << "clinet say" << buffer << std::endl;
            }
            else if (ret == 0)
            {
                std::cout << "read end " << std::endl;
                return;
            }
            else
            {
                ERROR("read");
            }
        }
    }

    void to_close()
    {
        if (_fd > 0)
        {
            close(_fd);
        }
    }

private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
    std::string _mode;
    int _fd;
};

客户端

# include"comm.hpp"

void func()
{
    std::string path = "/home/thr/code/fifo";
    std::string name = "1234";
    create_myfifo f1(path,name,"user");
    f1.open_for_write();
    f1.to_write();
    f1.to_close();
}
int main()
{
    func();
    return 0;
}

服务端

# include"comm.hpp"

void func()
{
    std::string path = "/home/thr/code/fifo";
    std::string name = "1234";
    create_myfifo f1(path,name,MODE);
  f1.open_for_read();
  f1.to_read();
  f1.to_close();
}

int main()
{
    func();
    return 0;
}

管道用完一定要销毁资源调用unlink函数

总结命名管道和匿名管道是一样的只有一点不同,匿名管道只能用于有血缘关系的进程进行通信,而命名管道没有限制

共享内存是什么

共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递 不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递彼此的数据
他的本质是就是在物理内存中创建一段内存空间,然后映射到对于进程各自的程序地址空间中,如下图所示
上面的操作都是由系统调用完成的,在操作系统内,可能会存在多个共享内存,操作系统会把他们描述组织管理起来,共享内存一定要有对于的struct内核结构体对象+物理内存
我们可以用代码来查看
#include <stdio.h>
#include <sys/ipc.h>
# include<stdlib.h>
#include <sys/shm.h>
# include<string>
int main(int argc,char** argv)
{
    std::string pathname = "/home/thr/code/fifo";
    key_t key = ftok(pathname.c_str(),0x66);
    int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL | 0666); 
    char* ret = (char*)shmat(shmid,nullptr,0);
    struct shmid_ds ds;

    // 读取内核共享内存描述结构体
    int ret1 = shmctl(shmid, IPC_STAT, &ds);
    if(ret1 == -1){
        perror("shmctl IPC_STAT");
        return 1;
    }

    // 打印各个字段
    printf("key: 0x%lx\n", ds.shm_perm.__key);
    printf("shm_segsz(段大小): %zu bytes\n", ds.shm_segsz);
    printf("shm_nattch(当前挂载进程数): %ld\n", ds.shm_nattch);
    printf("shm_cpid(创建进程pid): %d\n", ds.shm_cpid);
    printf("shm_lpid(最后操作pid): %d\n", ds.shm_lpid);
    printf("mode权限:0%o\n", ds.shm_perm.mode);

    return 0;
}

共享内存的使用

首先我们需要了解4个函数

函数使用
shmget函数
功能:⽤来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存⼤⼩
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,
出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
shmat
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RNDSHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
注意
shmaddr为NULL,核⼼⾃动选择⼀个地址
shmaddr不为NULL且shmflg⽆SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会⾃动向下调整为SHMLBA的整数
倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表⽰连接操作⽤来只读共享内存
shmdt
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);参数
shmaddr: shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl
功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

代码样例

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string>
#include<cstdio>
#include<unistd.h>
# include<cstring>

#define SIZE 4096
#define USER "server"
#define UMASK 0666

#define ERROR(m)  \
do{             \
    perror(m);\
    exit(EXIT_FAILURE);\
}while(0)

int projid = 0x66;
class shm
{
public:
    shm(size_t size,const std::string& user,const std::string pathname)
        : _size(size)
        ,_user(user)
        ,_pathname(pathname)
        ,_shm_addr(nullptr)
    {
        if (_user == "server")
        {
            _key = ftok(_pathname.c_str(),projid);
            if(_key < 0)
            {
                ERROR("ftok");
            }
            else
            {
                std::cout<<"server get key success "<<std::endl;
                _shmid = shmget(_key,_size,IPC_CREAT | IPC_EXCL | UMASK);
                if(_shmid < 0)
                {
                    ERROR("shmget");
                }
                else
                {
                    std::cout<<"server get shm succes _shmid = "<<_shmid<<std::endl;
                }
            }
        }
        else
        {
            std::cout<<"i am client just get shmid"<<std::endl;
             _key = ftok(_pathname.c_str(),projid);
            if(_key < 0)
            {
                ERROR("ftok");
            }
            else
            {
                std::cout<<"clinet get key success "<<std::endl;
                _shmid = shmget(_key,_size,IPC_CREAT);
                 if(_shmid < 0)
                {
                    ERROR("shmget");
                }
                else
                {
                    std::cout<<"clinet get shm succes "<<std::endl;
                }

            }
        }
    }
    ~shm()
    {
        if(_user == "server")
        {   
            disconnet();
            std::cout<<"server already delete shm_addr"<<std::endl;
            int ret = shmctl(_shmid,IPC_RMID,nullptr);
            if(ret <0)
            {
                ERROR("shmctl");
            }
            else 
            {
                std::cout<<"delete shm success"<<std::endl;
            }
        }
        else
        {
            disconnet();
        }
    }
    void* ret_addr()
    {
        return _shm_addr;
    }

    void attach()
    {
        void* vir = shmat(_shmid,nullptr,0);
        if((long long)vir < 0)
        {
            ERROR("shmat");
        }
        else 
        {
            std::cout<<"attach vir success"<<std::endl;
            _shm_addr = vir;
            printf("attach addr:  %p\n",_shm_addr);
        }
    }

    void disconnet()
    {
        if(_shm_addr != nullptr)
        {
              int ret  = shmdt(_shm_addr);
        if(ret < 0)
        {
            ERROR("shmdt");
        }
        else
        {
            std::cout<<"disconnet successs"<<std::endl;
        }
        }
      
    }


private:
   
    key_t _key;
    size_t _size;
    std::string _user;
    std::string _pathname;
    void* _shm_addr;
    int _shmid;
};

这里和上面的功能一样都是客户端发送服务端读取,注意这里的共享内存不像上面的管道有同步功能

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐