💬 hello! 各位铁子们大家好哇。 今日更新了Linux进程间通信的内容 🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
如上图,当我们在终端1创建了一个命名管道后,往里面写东西,管道不会关闭,在终端2上发现,它的内存大小还是0。当我们在终端2打印出内容后,管道就自动关闭了。如下图:
如上图,当我们在终端1上循环写到管道时,终端2可以一直打印出管道里的内容。
返回值为0是成功,不为0就是失败。
如上图,用mkfifo函数创建管道。
unlink函数可以删除指定路径下的文件。 成功就返回0。如果要关闭管道,就可以用unlink。
下面是用命名管道进行进程间通信的例子:
namedPipe.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096
class NamePiped
{
private:
bool OpenNamedPipe(int mode)
{
_fd = open(_fifo_path.c_str(), mode);
if (_fd < 0)
return false;
return true;
}
public:
NamePiped(const std::string &path, int who)
: _fifo_path(path), _id(who), _fd(DefaultFd)
{
if (_id == Creater)
{
int res = mkfifo(_fifo_path.c_str(), 0666);
if (res != 0)
{
perror("mkfifo");
}
std::cout << "creater create named pipe" << std::endl;
}
}
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
bool OpenForWrite()
{
return OpenNamedPipe(Write);
}
int ReadNamedPipe(std::string *out)
{
char buffer[BaseSize];
int n = read(_fd, buffer, sizeof(buffer));
if(n > 0)
{
buffer[n] = 0;
*out = buffer;
}
return n;
}
int WriteNamedPipe(const std::string &in)
{
return write(_fd, in.c_str(), in.size());
}
~NamePiped()
{
if (_id == Creater)
{
int res = unlink(_fifo_path.c_str());
if (res != 0)
{
perror("unlink");
}
std::cout << "creater free named pipe" << std::endl;
}
if(_fd != DefaultFd) close(_fd);
}
private:
const std::string _fifo_path;
int _id;
int _fd;
};
client.cc
#include "namedPipe.hpp"
// write
int main()
{
NamePiped fifo(comm_path, User);
if (fifo.OpenForWrite())
{
std::cout << "client open namd pipe done" << std::endl;
while (true)
{
std::cout << "Please Enter> ";
std::string message;
std::getline(std::cin, message);
fifo.WriteNamedPipe(message);
}
}
return 0;
}
server.cc
#include "namedPipe.hpp"
// server read: 管理命名管道的整个生命周期
int main()
{
NamePiped fifo(comm_path, Creater);
// 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
if (fifo.OpenForRead())
{
std::cout << "server open named pipe done" << std::endl;
sleep(3);
while (true)
{
std::string message;
int n = fifo.ReadNamedPipe(&message);
if (n > 0)
{
std::cout << "Client Say> " << message << std::endl;
}
else if(n == 0)
{
std::cout << "Client quit, Server Too!" << std::endl;
break;
}
else
{
std::cout << "fifo.ReadNamedPipe Error" << std::endl;
break;
}
}
}
return 0;
}
匿名管道与命名管道的区别:
命名管道是通过文件路径让不同进程看到同一份资源。 命名管道可以让两个毫不相干的进程进行进程间通信。
system V IPC是一种本地通信方案。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
共享内存在系统中可以同时存在多份,供不同对进程进行通信。 共享内存不是简单的一段内存空间,它也要有描述并管理共享内存的数据结构和匹配算法。
该函数是系统调用,操作系统提供系统调用,让我们创建共享内存。
功能:用来创建共享内存 参数: key:这个共享内存段名字(由用户形成) size:共享内存大小 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的 常见标志位组合和使用: IPC_CREAT 、IPC_EXCL
key,用来标志共享内存,可以让进程a和b找到共享内存。 系统提供了随机生成key值的方法ftok 返回值:成功则返回共享内存段的标识码;失败返回-1
ftok不是系统调用,通过我们提供路径和id(这两个值可以随便写)帮我们生成一个key值。我们给a、b两个进程提供同样的路径和id,调用ftok,就能形成同样的key,就能看到同一个共享内存了。 返回值:成功了就返回key值,失败就返回-1。
共享内存不随着进程的结束而自动释放,需要我们手动释放(指令或者其他系统调用),否则会一直存在,直到系统重启。 共享内存的生命周期随内核,文件的生命周期随进程。
如何释放?如下图:
通过指令 ipcs -m 可以查共享内存。
删除共享内存可通过指令 ipcrm -m 数字(id) 来删除。注意数字是shmid。
如果不想用指令释放,可以用shmctl 函数进行,如下:
功能:用于控制共享内存 参数 shmid:由shmget返回的共享内存标识码 cmd:将要采取的动作(有三个可取值,如下图)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构 返回值:成功返回0;失败返回-1 传IPC_STAT可以获取共享内存的属性,传IPC_RMID可以删除共享内存。
key:属于用户形成,内核使用的一个字段,用户不能用key来进行shm的管理。它是内核用来进行区分shm的唯一性的。
shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。
功能:将共享内存段连接到进程地址空间。 如果未来不想使用该共享内存,可以用shmdt去关联 参数 shmid: 共享内存标识 shmaddr:指定共享内存想挂接到哪个地址上 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY 返回值:成功返回共享内存的起始地址;失败返回-1
功能:将共享内存段与当前进程脱离 参数 shmaddr: 由shmat所返回的指针 返回值:成功返回0;失败返回-1 注意:将共享内存段与当前进程脱离不等于删除共享内存段
Shm.hpp
#ifndef __SHM_HPP__
#define __SHM_HPP__
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*n
class Shm
{
private:
key_t GetCommKey()
{
key_t k = ftok(_pathname.c_str(), _proj_id);
if (k < 0)
{
perror("ftok");
}
return k;
}
int GetShmHelper(key_t key, int size, int flag)
{
int shmid = shmget(key, size, flag);
if (shmid < 0)
{
perror("shmget");
}
return shmid;
}
std::string RoleToString(int who)
{
if (who == gCreater)
return "Creater";
else if (who == gUser)
return "gUser";
else
return "None";
}
void *AttachShm()
{
if (_addrshm != nullptr)
DetachShm(_addrshm);
void *shmaddr = shmat(_shmid, nullptr, 0);
if (shmaddr == nullptr)
{
perror("shmat");
}
std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
return shmaddr;
}
void DetachShm(void *shmaddr)
{
if (shmaddr == nullptr)
return;
shmdt(shmaddr);
std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
}
public:
Shm(const std::string &pathname, int proj_id, int who)
: _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
{
_key = GetCommKey();
if (_who == gCreater)
GetShmUseCreate();
else if (_who == gUser)
GetShmForUse();
_addrshm = AttachShm();
std::cout << "shmid: " << _shmid << std::endl;
std::cout << "_key: " << ToHex(_key) << std::endl;
}
~Shm()
{
if (_who == gCreater)
{
int res = shmctl(_shmid, IPC_RMID, nullptr);
}
std::cout << "shm remove done..." << std::endl;
}
std::string ToHex(key_t key)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", key);
return buffer;
}
bool GetShmUseCreate()
{
if (_who == gCreater)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
if (_shmid >= 0)
return true;
std::cout << "shm create done..." << std::endl;
}
return false;
}
bool GetShmForUse()
{
if (_who == gUser)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
if (_shmid >= 0)
return true;
std::cout << "shm get done..." << std::endl;
}
return false;
}
void Zero()
{
if(_addrshm)
{
memset(_addrshm, 0, gShmSize);
}
}
void *Addr()
{
return _addrshm;
}
void DebugShm()
{
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds);
if(n < 0) return;
std::cout << "ds.shm_perm.__key : " << ToHex(ds.shm_perm.__key) << std::endl;
std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;
}
private:
key_t _key;
int _shmid;
std::string _pathname;
int _proj_id;
int _who;
void *_addrshm;
};
#endif
client.cc
#include "Shm.hpp"
#include "namedPipe.hpp"
int main()
{
// 1. 创建共享内存
Shm shm(gpathname, gproj_id, gUser);
shm.Zero();
char *shmaddr = (char *)shm.Addr();
sleep(3);
// 2. 打开管道
NamePiped fifo(comm_path, User);
fifo.OpenForWrite();
// 当成string
char ch = 'A';
while (ch <= 'Z')
{
shmaddr[ch - 'A'] = ch;
std::string temp = "wakeup";
std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;
fifo.WriteNamedPipe(temp);
sleep(2);
ch++;
}
return 0;
}
server.cc
#include "Shm.hpp"
#include "namedPipe.hpp"
int main()
{
// 1. 创建共享内存
Shm shm(gpathname, gproj_id, gCreater);
char *shmaddr = (char*)shm.Addr();
shm.DebugShm();
// 2. 创建管道
NamePiped fifo(comm_path, Creater);
fifo.OpenForRead();
while(true)
{
std::string temp;
fifo.ReadNamedPipe(&temp);
std::cout << "shm memory content: " << shmaddr << std::endl;
}
sleep(5);
return 0;
}
共享内存有一个缺点:共享内存不提供对共享内存的任何保护机制。会导致数据不一致的问题。即双方不存在谁等谁的情况。比如我想传hello,结果写到he就被读走了,就会导致数据不一样。 我们在访问共享内存的时候,没有用任何系统调用。因此共享内存是所有进程IPC中,速度最快的。
消息队列的接口的使用跟共享内存函数很像。
如果要发消息队列的数据,用
如果要接收数据,用
要查消息队列就用 ipcs -q ,它的指令跟共享内存就一字之差
信号量主要用于同步和互斥的。
多个执行流(进程),都能看到的一份资源:共享资源 被保护起来的资源:临界资源。 用互斥的方式进行保护。 互斥:任何时刻只能有一个进程在访问共享资源。
信号量的主要作用是用来保护共享资源的。经过保护,共享资源就变成临界资源。
假设上面的方格是电影院的座位。看电影时,只要有了票,位置就一定是你的,而不是谁先坐到就是谁的。 所以成功申请了信号量,即使不访问共享资源,也会留着一部分资源给你。 这里的信号量也叫多元信号量。
对共享资源的整体使用,即资源只有一个,也就是有人用了,别人就用不了了,即互斥。申请信号量时,这种信号量叫二元信号量。 信号量也是一个公共资源。 信号量本质是一个计数器,申请信号量时,计数器--,也叫P操作。释放信号量时,计数器++,也叫V操作。
Linux中允许用户一次申请多个信号量,用信号量集保存,信号量集用数组来维护。 如果申请了多个信号量,上面的nsems就是申请的信号量的个数。
如果信号量不需要了,就用 semctl 。 semid就是要删除的信号量集,semnum就是要删除的信号量集的下标。
要对信号量进行PV操作,就用 semop 。struct sembuf结构体如下:
sem_op表示信号量的操作,如果为-1,相当于对信号量值作--。如果为1,表示作++。
查看信号量,用 ipcs -s 。删除操作跟前面类似。