作者主页: 作者主页
本篇博客专栏:Linux
创作时间 :2024年11月2日
命名管道:
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件我们可以使用mkfifo命令来创建一个管道。
然后通过echo往里面写入一段内容:
回车之后管道不会关闭,在终端2查看可以发现他的内存大小仍然是0,当我们在管道2打印出内容后,管道就自动关闭了
当我们这样执行的时候,我们就可以发现在一直不停的打印我们输入到管道的内容
下面我们在程序里面建立一个管道:
返回值为0是成功,不为0就是失败。
下面是一个运用命名管道进行通信的例子:
Pipe.hpp:(这里是一些的共享的资源,包括路径,打开管道,关闭管道)
#include <iostream>#include <string>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>using namespace std;const string gpathname = "./myfifo";const mode_t gmode = 0600;const int gdefault = -1;const int gsize = 1024;const int gForWrite = O_WRONLY;const int gForRead = O_RDONLY;int OpenPipe(int flag) { int _fd = ::open(gpathname.c_str(), flag); if (_fd == -1) { std::cerr << "open errno" << std::endl; return _fd; } return _fd; } void ClosePipeHelper(int fd) { if (fd >= 0) ::close(fd); }// class NamePipe// {// private: // public:// };// int CreateNamePipe(string pathname)// {// umask(0);// int n = mkfifo(pathname.c_str(), 0600);// if (n != 0)// {// std::cerr << "mkfifo errno" << std::endl;// return -1;// }// return n;// }// class NamePipe// {// public:// NamePipe()// {// }// ~NamePipe()// {// }// private:// const string fifo_path;// int _id;// int _fd;// };
Server.hpp及Server.cc
#include "Pipe.hpp"class Init{public: Init() { umask(0); int n = ::mkfifo(gpathname.c_str(), gmode); if (n < 0) { std::cerr << "mkfifo errno" << std::endl; return; } std::cout << "mkfifo success" << std::endl; } ~Init() { int n = ::unlink(gpathname.c_str()); if (n != 0) { std::cerr << "unlink failed" << std::endl; } std::cout << "unlink success" << std::endl; }private:};Init init;class Server{public: Server() : _fd(gdefault) { } bool OpenPipeForRead() { _fd = OpenPipe(gForRead); if (_fd < 0) { std::cerr << "Open cerrno" << std::endl; return false; } return true; } int RecvPipe(std::string *out) { char buffer[gsize]; ssize_t n = ::read(_fd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = 0; *out = buffer; } return n; } void ClosePipe() { ClosePipeHelper(_fd); } ~Server() { }private: int _fd;};
#include "Server.hpp"int main(){ Server s; // 打开管道 s.OpenPipeForRead(); string message; while (1) { if (s.RecvPipe(&message) > 0) { std::cout << "Client Say:" << message << std::endl; } else { break; } } std::cout << "Client quit,me too" << std::endl; // 关闭管道 s.ClosePipe(); return 0;}
Client.hpp及Client.cc
#include "Pipe.hpp"class Client{public: Client() : _fd(gdefault) { } bool OpenPipeForWrite() { _fd = OpenPipe(gForWrite); if (_fd < 0) return false; return true; } int SendPipe(const std::string &in) { return ::write(_fd, in.c_str(), in.size()); } void ClosePipe() { if (_fd >= 0) { ::close(_fd); } } ~Client() { }private: int _fd;};
#include "Client.hpp"int main(){ Client c; c.OpenPipeForWrite(); while (1) { std::string message; std::cout << "Client Enter:" << std::endl; std::getline(std::cin, message); c.SendPipe(message); } c.ClosePipe(); return 0;}
system V共享内存
system V IPC是一种本地通信方案。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
共享内存在系统中可以同时存在多份,供不同对进程进行通信。
共享内存不是简单的一段内存空间,它也要有描述并管理共享内存的数据结构和匹配算法。
共享内存函数
shmget 函数
该函数是系统调用,操作系统提供系统调用,让我们创建共享内存。
功能:用来创建共享内存
参数:
key:这个共享内存段名字(由用户形成)
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
常见标志位组合和使用:
IPC_CREAT 、IPC_EXCL
IPC_CREAT:如果你要创建的共享内存不存在,就创建。如果存在,就获取该共享内存并返回。
IPC_EXCL:单独使用没意义,只有和IPC_CREAT组合才有意义
IPC_CREAT | IPC_EXCL:如果你要创建的共享内存不存在,就创建。如果存在,就出错返回。(如果成功返回,意味着这shm是全新的)
key,用来标志共享内存,可以让进程a和b找到共享内存。
系统提供了随机生成key值的方法ftok
返回值:成功则返回共享内存段的标识码;失败返回-1
ftok函数:
ftok不是系统调用,通过我们提供路径和id(这两个值可以随便写)帮我们生成一个key值。我们给a、b两个进程提供同样的路径和id,调用ftok,就能形成同样的key,就能看到同一个共享内存了。
返回值:成功了就返回key值,失败就返回-1。
共享内存的释放
共享内存不随着进程的结束而自动释放,需要我们手动释放(指令或者其他系统调用),否则会一直存在,直到系统重启。
共享内存的生命周期随内核,文件的生命周期随进程。
我们可以通过指令ipcs -m 来查看共享内存
可以通过ipsrm -m shmid(自己得到)来释放共享内存
若不想通过指令释放,可以通过下面这个函数释放
shmctl 函数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值,如下图)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
传IPC_STAT可以获取共享内存的属性,传IPC_RMID可以删除共享内存。
key和shmid的比较
key:属于用户形成,内核使用的一个字段,用户不能用key来进行shm的管理。它是内核用来进行区分shm的唯一性的。
shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。
shmat 函数
功能:将共享内存段连接到进程地址空间。
如果未来不想使用该共享内存,可以用shmdt去关联
参数
shmid: 共享内存标识
shmaddr:指定共享内存想挂接到哪个地址上
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回共享内存的起始地址;失败返回-1
shmdt 函数
功能:将共享内存段与当前进程脱离
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
接口使用例子
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;}
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
共享内存虽然速度上占有一定优势,但是共享内存对内存不会提供任何的保护机制,会导致数据不一致的问题,即双方不存在谁等谁的情况,比如我想传一个hello,可能我刚写入一个h就已经被读走了,这样就会导致数据不一致的问题,我们在访问共享内存时,没有任何系统调用,所以速度是所有IPC中最快的。
system V消息队列
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值消息队列的接口的使用跟共享内存函数很像。
如果要发消息队列的数据,用:
如果要接收数据,用:
要查消息队列就用 ipcs -q ,它的指令跟共享内存就一字之差
system V信号量
信号量主要用于同步和互斥的。
System V 信号量是由内核维护的一组整数,它可以被多个进程同时访问和修改。每个信号量代表一个资源的可用数量,进程可以通过对信号量进行操作来控制对资源的访问。信号量的操作包括增加、减少和等待信号量的值达到某个特定条件等。
信号量特点:
原子性操作:信号量的操作是原子性的,这意味着多个进程同时对信号量进行操作时,系统会保证操作的完整性和一致性,不会出现部分操作完成而其他操作未完成的情况。可用于同步和互斥:信号量可以用于实现进程间的同步和互斥。例如,可以使用信号量来确保多个进程不会同时访问同一个共享资源,或者确保一个进程在某个条件满足之前等待另一个进程完成某个任务。内核维护:信号量是由内核维护的,这意味着即使进程崩溃或退出,信号量的值也不会丢失。当进程重新启动时,可以继续使用信号量来控制对共享资源的访问。进程互斥
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区多个执行流(进程),都能看到的一份资源:共享资源
被保护起来的资源:临界资源。 用互斥的方式进行保护。
互斥:任何时刻只能有一个进程在访问共享资源。
信号量的主要作用是用来保护共享资源的。经过保护,共享资源就变成临界资源。
假设上面的方格是电影院的座位。看电影时,只要有了票,位置就一定是你的,而不是谁先坐到就是谁的。 所以成功申请了信号量,即使不访问共享资源,也会留着一部分资源给你。
这里的信号量也叫多元信号量。
对共享资源的整体使用,即资源只有一个,也就是有人用了,别人就用不了了,即互斥。申请信号量时,这种信号量叫二元信号量。
信号量也是一个公共资源。
信号量本质是一个计数器,申请信号量时,计数器--,也叫P操作。释放信号量时,计数器++,也叫V操作。
信号量的操作
Linux中允许用户一次申请多个信号量,用信号量集保存,信号量集用数组来维护。
如果申请了多个信号量,上面的nsems就是申请的信号量的个数。
如果信号量不需要了,就用 semctl 。 semid就是要删除的信号量集,semnum就是要删除的信号量集的下标。
要对信号量进行PV操作,就用 semop 。
查看信号量,用 ipcs -s 。删除操作跟前面类似。
最后:
十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:
1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。
2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。
3.成年人的世界,只筛选,不教育。
4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。
5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。
最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)
愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!