一、命名管道
1.概念
匿名管道解决了具有血缘关系的进程之间的通信,如果两个进程毫不相干,如何进行通信呢?通过文件,管道文件。
对于两个不同进程,打开同一路径下的同一文件,inode和文件内核缓冲区不会加载多次,因为没有必要,操作系统不会做没有意义的事情。
那么两个进程的file*就看到了同一份资源,文件内核缓冲区 -> 通过打开同一路径下的同一个文件 -> 文件有路径,有名字且路径具有唯一性 -> 命名管道。
例如:父子进程向同一个显示器打印,向同一个键盘读取。
2.操作
指令:创建mkfifo xxx 删除unlink xxx
int mkfifo(const char *pathname, mode_t mode);
pathname:路径名,mode:权限
返回值:成功返回0,错误返回-1,错误码被设置
int unlink(const char *pathname); //删除命名管道
返回值:成功返回0,失败返回-1,错误码被设置。
特别注意:
命名管道具有同步机制,以读方式open时,会阻塞,直到对应的写端也open。
命名管道代码如下:
namedpipe.hpp
#pragma once#include <iostream> #include <string> #include <cstdio> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>#define EXIT_ERROR(m) \do \{ \perror(m); \exit(1); \} while (0)#define PATH "." #define PIPE_NAME "fifo" #define BUFF_SIZE 1024 #define MODE_DEFAULT 0666class Namedpipe { public:Namedpipe(const std::string &path, const std::string &name, int mode): _mode(mode){_pathname = path + "/" + name;// 1.创建命名管道int ret = mkfifo(_pathname.c_str(), _mode);if (ret < 0){EXIT_ERROR("mkfifo");}printf("mkfifo success!\n");}~Namedpipe(){// 删除命名管道std::cout << "~Namedpipe" << std::endl;int num = unlink(_pathname.c_str());if (num < 0){EXIT_ERROR("unlink");}printf("unlink %s success!\n", _pathname.c_str());}private:std::string _pathname;int _mode; };class FileOper { public:FileOper(const std::string &path, const std::string &name){_pathname = path + "/" + name;}~FileOper(){}void OpenForRead(){// 2.以只读方式打开文件,从文件里读,最后关闭_fd = open(_pathname.c_str(), O_RDONLY);if (_fd < 0){EXIT_ERROR("open for read");}printf("open for read success!\n");}void OpenForWrite(){_fd = open(_pathname.c_str(), O_WRONLY);if (_fd < 0){EXIT_ERROR("open for write");}printf("open for write success!\n");}void Read(){char buff[BUFF_SIZE];while (true){std::cout << "client says# ";int num = read(_fd, buff, BUFF_SIZE - 1);if (num > 0){buff[num] = 0;std::cout << buff << std::endl;}else if (num == 0){std::cout << "client exit!" << std::endl;break;}else{EXIT_ERROR("read");}}}void Write(){std::string msg;while (true){std::cout << "please Enter# ";std::getline(std::cin, msg);int ret = write(_fd, msg.c_str(), msg.size());}}void Close(){close(_fd);printf("close fd:%d\n", _fd);}private:std::string _pathname;int _fd; };
二、System V
1.共享内存的原理
进程间通信本质:让不同的进程,先看到同一份资源。
System V是一种标准 -> Linux内核支持了这种标准,专门设计了一个IPC通信模块 -> 通信的接口设计,原理,接口,相似。
共享内存的原理:先在内存上开辟一段空间,然后将该空间映射到两个进程的地址空间中,拿到起始虚拟地址,就可以利用这段内存进行通信。
1.图中所有工作,都是由OS自己完成的。OS提供系统调用,我们通过系统调用完成。
2.取消关联关系,OS释放内存。
3.可能同时存在多组进程都在使用共享内存来进行通信,即有多个共享内存。
要对共享内存进行管理,先描述在组织。
2.使用共享内存的接口
1.创建共享内存 shmget、ftok
int shmget(key_t key, size_t size, int shmflg);
size表示共享内存大小。
shmflg表示对应的操作:IPC_CREATE、IPC_EXCL。其中 IPC_EXCL 要和 IPC_CREATE 一起使用,如果shm不存在就新建,存在就出错返回,表示存在了。如果新建,要带上权限位,直接 | 或上权限即可。
核心问题:如何保证两个进程看到的是同一份内存?
key,不同的进程,shm来进行通信,标识共享内存的唯一性 -> key用来区分,不是内核直接形成的,而是在用户端,构造并传入的。
为什么要用户端约定一个数字?
因为如果不约定,而是采用使用共享内存标识符的方式,那么创建共享内存的那个进程创建完后要将这个标识符给另一个进程,这个标识符是不确定的值,那么怎么给?只能是进程间能通信,相悖。
解决:用户端约定一个确定的数字,确定的数字由用户端直接传入就行了。
对于key值的生成,尽量冲突越小越好。
key_t fok(const char *pathname, int proj_id);
参数可以随便传,但最好是传路径,因为路径是唯一的。
2.删除共享内存
进程结束,如果没有删除共享内存,共享内存资源会一直存在 -> 共享内存的资源,生命周期随内核 -> 没有显示删除,即使进程退出,IPC资源依然存在
1.ipcs/ipcrm删除
2.代码删除
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid为共享内存描述符。
cmd为操作,IPC_RMID表示删除。
buf传nullptr就行。
返回值:失败返回-1,错误码被设置。
删除,控制共享内存,在用户层,不能使用key!key只能用来区分唯一性!
需要用shmid来管理共享内存,指令本质是运行在用户空间的。
3.共享内存的 挂载 和 去关联(重点)
void *shmat(int shmid, const void *shmaddr, int shmflg); //shmat: at 表示 attach。
shmid为共享内存描述符。
shmaddr为虚拟地址,可以固定地址进行挂接,用户层数使用填nullptr就行。
shmflg为设置选项,填0用默认设置就行,
返回值:挂载后共享内存在进程地址空间中的起始虚拟地址。错误,-1返回,错误码设置。
读写共享内存时,没有用到系统调用 -> 共享区属于用户空间,可以让用户直接使用
共享内存时进程间通信中,速度最快的方式:
1.映射之后,读写,直接被对方看到!
2.不需要进行系统调用进行读写!
但是,缺点是通信双方,没有所谓的“同步机制”!对共享内存的数据,没有保护机制。
如何用现有的知识解决?利用命名管道的同步机制解决。
去关联:
int shmdt(const void *shmaddr); //dt:detach 去关联
shmaddr为关联的起始虚拟地址。
返回值:成功返回0;失败返回-1,错误码被设置。
代码如下:
shm.hpp
#pragma once#include <iostream> #include <string> #include <cstdio> #include <cstdlib> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h>#define SIZE 4096 #define MODE_DEFAULT 0666 #define EXIT_ERROR(m) \do \{ \perror(m); \exit(1); \} while (0)#define PATH_NAME "." #define PROJ_ID 1class Shm { private:void Create(const key_t key, const int shmflg){_shmid = shmget(key, SIZE, shmflg);if (_shmid < 0){EXIT_ERROR("shemget");}printf("create shm success!\n");}public:Shm(const std::string &pathname, const int projid, const std::string &identity): _identity(identity){// 1.创建共享内存key_t key = ftok(pathname.c_str(), projid);if (_identity == "user"){Create(key, IPC_CREAT);}else if (_identity == "server"){Create(key, IPC_CREAT | IPC_EXCL | MODE_DEFAULT);}else{printf("identity false!\n");exit(1);}}~Shm(){// 4.删除共享内存if (_identity == "server"){int ret = shmctl(_shmid, IPC_RMID, nullptr);if (ret < 0){EXIT_ERROR("shmctl");}printf("delete shm success!\n");}}void Attach(){// 2.挂载,将共享内存映射到进程地址空间中,拿到起始虚拟地址_addr = shmat(_shmid, nullptr, 0);if ((long long)_addr < 0){EXIT_ERROR("shmat");}printf("attach shm success!\n");}void *Virtualaddr() const { return _addr; }void Detach() const{// 3.去关联int ret = shmdt(_addr);if (ret < 0){EXIT_ERROR("shmdt");}printf("detach shm success!\n");}private:int _shmid;void *_addr;std::string _identity; };
server.cc
#include "shm.hpp" #include "namedpipe.hpp"int main() {Namedpipe npipe(PATH, PIPE_NAME, MODE_DEFAULT);FileOper fp(PATH, PIPE_NAME);fp.OpenForRead();Shm shm(PATH_NAME, PROJ_ID, "server");shm.Attach();printf("%p\n", shm.Virtualaddr());char *addr = (char *)shm.Virtualaddr();while (true){if (fp.Wait()){printf("%s\n", addr);}elsebreak;}shm.Detach();fp.Close();return 0; }
client.cc
#include "shm.hpp" #include "namedpipe.hpp"int main() {FileOper fp(PATH, PIPE_NAME);fp.OpenForWrite();sleep(3);Shm shm(PATH_NAME, PROJ_ID, "user");shm.Attach();printf("%p\n", shm.Virtualaddr());char *s = (char *)shm.Virtualaddr();int k = 0;for (char c = 'A'; c <= 'Z'; c++){sleep(1);s[k++] = c;s[k++] = c;s[k] = 0;fp.Wakeup();}shm.Detach();fp.Close();return 0; }
4.共享内存相关的结构
其中buf是输出型参数,里面的struct shmid_ds内第一个成员为 ipc_perm ,其中的第一个成员为 key,就是用户约定传入的key,标识共享内存的唯一性,用这个输出型参数还能获取共享内存相关的属性。
共享内存创建的时候,它的大小必须是4KB的整数倍,原因:内存基本是4KB。