linux操作系统-命名管道共享内存
命名管道是什么
我们知道匿名管道,是用于两个进程之间有父子关系而所使用的,当两个进程没有血缘关系时,两个不相干的进程想通信,这个时候就需要命名管道了
命名管道会在指定目录下创建一个管道文件,如下图所示

这个管道文件就是用于进程之间通信的
下面我们来讲解他的原理
命名管道原理
命名管道的本质就是创建一个文件,供两个进程进行通信,那么两个进程会同时打开同一个文件两次吗?答案是不是的,进程间通信的本质就是让不同进程看到同一个资源,只需要打开一个文件即可,操作系统是不会浪费资源的

操作系统会把两个进程的struct file里面的内容给映射到同一个打开的文件当中去
命名管道会让不同的进程看到位于同一个路径下的同一个资源,文件的路径具有唯一性,文件的路径有名字,这个名字就是创建的命名管道的名字
匿名管道与命名管道的区别匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建,打开⽤openFIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的⽅式不同,⼀但这些 ⼯作完成之后,它们具有相同的语义。
命名管道的使用
我们先来shell端操作一下
这里使用mkfifo来创建命名管道

这里我们使用echo命令重定向到管道文件中去
![]()
这时候我们发现这个操作阻塞了,这里为什么阻塞,这里其实是是在阻塞等待,等待另一个进程进行读取,这个是管道的特性,只有写端打开 FIFO,没有读端,open () 就会阻塞,等待对方进程打开管道读端。我们再登录一个终端执行一下cat testfifo


这里就没有阻塞了,下面我们讲讲如何在编写代码中使用mkfifo
代码层面命名管道的使用
命名管道的打开规则
如果当前打开操作是为读⽽打开FIFO时:O_NONBLOCK disable:阻塞直到有相应进程为写⽽打开该FIFOO_NONBLOCK enable:⽴刻返回成功如果当前打开操作是为写⽽打开FIFO时:O_NONBLOCK disable:阻塞直到有相应进程为读⽽打开该FIFOO_NONBLOCK enable:⽴刻返回失败,错误码为ENXIO
实现通信样例
#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函数

总结命名管道和匿名管道是一样的只有一点不同,匿名管道只能用于有血缘关系的进程进行通信,而命名管道没有限制
共享内存是什么
#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_RND和SHM_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;
};
这里和上面的功能一样都是客户端发送服务端读取,注意这里的共享内存不像上面的管道有同步功能
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)