C 共享内存封装
引言 - 背景
2016 年写过一篇关于 linux 共享内存 shm api 扫盲文. C扩展 从共享内存shm到memcache外部内存
比较简单. 没有深入分析(能力有限, 也深入分析不了). 3年(2019)过去了. 本质而言共享内存这种编程活化石般
双刃剑, 像 "redis" 这种分布式内存数据库完全可以替代它做想做的业务(硬件过剩). 这里为什么继续鞭尸呢?
想要为这种最快的数据交互 IPC 方式, 做个多平台移植实战代码. 更加详细底层原理和面试问题, 请自行搜索.
好的疑惑和思索可以分享个博主, 共同提升.
前言 - shm api 介绍
先带大家熟悉 linux 和 winds 共享内存 shm 相关的 api 介绍. 编程很简单, 无外乎学习和实践. 当然不写
代码, 狂看原理的, 当我没说. 毕竟身居高位, 指点江山, 都曾经有过梦想, 也流过无数智慧 ~
1. linux shm 相关 api 熟悉
linux shm 有两套 api 这里还是只说 System V 标准的 4 个系统 api
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> /*
* System V 风格的 IPC 函数实现共享内存接口
*/ //
// shmctl - 共享内存控制操作
// __shmid : 共享内存标识符, shmget () 返回返回值
// __cmd : IPC_STAT -> 得到共享内存的状态, 把共享内存的 shmid_ds 结构复制到 buf 中
// : IPC_SET -> 改变共享内存的状态, 把 buf 复制到共享内存的 shmid_ds 结构内
// : IPC_RMID -> 删除这片共享内存
// __buf : 共享内存管理结构体
// return : 0 正确, 出错 -1, 错误原因查 errno 表
// : EINVAL -> 参数 size 小于 SHMMIN 或大于 SHMMAX
// : EEXIST -> 预建立 key 所致的共享内存, 但已经存在
// : EIDRM -> 参数 key 所致的共享内存已经删除
// : ENOSPC -> 超过了系统允许建立的共享内存的最大值 SHMALL
// : ENOENT -> key 所指的共享内存不存在, 参数 shmflg 也未设 IPC_CREAT 位
// : EACCES -> 没有权限
// : ENOMEM -> 核心内存不足
//
//
extern int shmctl (int __shmid, int __cmd, struct shmid_ds *__buf) __THROW; //
// shmget - 得到共享内存段
// __key : 新建或者获取已经存在的共享内存的 key
// __size : 创建共享内存的大小, 获取设置默认 0
// __shmflg : 标志集合
// : IPC_CREAT -> 若不存在则创建, 需要在 shmflg 中 '|权限信息' |0664 若存在则打开
// : IPC_EXCL -> 与 IPC_CREAT 搭配使用, 若存在则创建失败 errno == EEXIST
// : 0 -> 获取已经存在的共享内存
// return : __shmid, 出错 -1
//
extern int shmget (key_t __key, size_t __size, int __shmflg) __THROW; //
// shmat - 附加共享内存段
// __shmid : 共享内存标识符, shmget () 返回返回值
// __shmaddr: NULL 表示由系统选择
// : 非 NULL 且 shmflg 是 SHM_RND, 会按照页对齐的原则从 shmaddr 开始
// 找最近的地址开始分配, 否则 shmaddr 指定的地址必须是页对齐的
// __shmflg : 0 -> 默认操作标志
// : SHM_RDONLY -> 表示挂接到该共享内存的进程必须有读权限
// : SHM_REMAP -> 表示如果要映射的共享内存已经有现存的内存, 那么就将旧的替换
// return : 成功返回映射内存的地址, 失败返回 (void *)-1 设 errno
//
extern void *shmat (int __shmid, const void *__shmaddr, int __shmflg) __THROW; //
// shmdt - 分离共享内存段
// __shmaddr: 共享内存标识符, shmat() 返回值
// return : 成功返回 0, 失败返回 -1 设 errno
//
extern int shmdt (const void *__shmaddr) __THROW;
对于 shm_ctl 用到的相关结构和宏截取部分如下
/* Mode bits for `msgget', `semget', and `shmget'. */
#define IPC_CREAT 01000 /* Create key if key does not exist. */
#define IPC_EXCL 02000 /* Fail if key exists. */
#define IPC_NOWAIT 04000 /* Return error on wait. */ /* Control commands for `msgctl', `semctl', and `shmctl'. */
#define IPC_RMID 0 /* Remove identifier. */
#define IPC_SET 1 /* Set `ipc_perm' options. */
#define IPC_STAT 2 /* Get `ipc_perm' options. */
#ifdef __USE_GNU
# define IPC_INFO /* See ipcs. */
#endif /* Special key values. */
#define IPC_PRIVATE ((__key_t) 0) /* Private key. */ /* Data structure used to pass permission information to IPC operations. */
struct ipc_perm
{
__key_t __key; /* Key. */
__uid_t uid; /* Owner's user ID. */
__gid_t gid; /* Owner's group ID. */
__uid_t cuid; /* Creator's user ID. */
__gid_t cgid; /* Creator's group ID. */
unsigned short int mode; /* Read/write permission. */
unsigned short int __pad1;
unsigned short int __seq; /* Sequence number. */
unsigned short int __pad2;
__syscall_ulong_t __glibc_reserved1;
__syscall_ulong_t __glibc_reserved2;
}; /* Permission flag for shmget. */
#define SHM_R 0400 /* or S_IRUGO from <linux/stat.h> */
#define SHM_W 0200 /* or S_IWUGO from <linux/stat.h> */ /* Flags for `shmat'. */
#define SHM_RDONLY 010000 /* attach read-only else read-write */
#define SHM_RND 020000 /* round attach address to SHMLBA */
#define SHM_REMAP 040000 /* take-over region on attach */
#define SHM_EXEC 0100000 /* execution access */ /* Commands for `shmctl'. */
#define SHM_LOCK 11 /* lock segment (root only) */
#define SHM_UNLOCK 12 /* unlock segment (root only) */ __BEGIN_DECLS /* Segment low boundary address multiple. */
#define SHMLBA (__getpagesize ())
extern int __getpagesize (void) __THROW __attribute__ ((__const__)); /* Type to count number of attaches. */
typedef __syscall_ulong_t shmatt_t; /* Data structure describing a shared memory segment. */
struct shmid_ds
{
struct ipc_perm shm_perm; /* operation permission struct */
size_t shm_segsz; /* size of segment in bytes */
__time_t shm_atime; /* time of last shmat() */
#ifndef __x86_64__
unsigned long int __glibc_reserved1;
#endif
__time_t shm_dtime; /* time of last shmdt() */
#ifndef __x86_64__
unsigned long int __glibc_reserved2;
#endif
__time_t shm_ctime; /* time of last change by shmctl() */
#ifndef __x86_64__
unsigned long int __glibc_reserved3;
#endif
__pid_t shm_cpid; /* pid of creator */
__pid_t shm_lpid; /* pid of last shmop */
shmatt_t shm_nattch; /* number of current attaches */
__syscall_ulong_t __glibc_reserved4;
__syscall_ulong_t __glibc_reserved5;
};
(满屏的恶意, 直接从系统 include 拔下来, 木有强迫症) 基于上面大概. 写个 demo 带大家熟悉其用法
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h> #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h> // str_hash_rs - Robert Sedgwicks Hash Function
unsigned str_hash_rs(const char * s) {
register unsigned hash = ;
for (unsigned c, a = ; (c = *s); ++s) {
hash = hash * a + c;
a *= ;
}
return hash & 0x7FFFFFFF;
} #define STR_SHM "shm_1_to_2"
#define INT_SHM 4096 // shm_init - // shm_init - init 操作, -1 error, 0 init create, 1 exists
int shm_init(key_t key, size_t size); //
// 文件 : shm.c
// 目标 :
// 1' 熟悉 Linux System V shm 机制
// 2' 第一次创建共享内存, 写入数据
// 3' 第二次读取共享内存, 随后销毁
//
int main(int argc, char * argv[]) {
char cmd[BUFSIZ];
key_t key = (key_t)str_hash_rs(STR_SHM);
printf("name = %s -> key = %d\n", STR_SHM, key); // shm init
int ret = shm_init(key, INT_SHM);
if (ret < ) {
perror("shm_init "STR_SHM);
return EXIT_FAILURE;
} if (ret == ) {
// shm 刚创建
return EXIT_SUCCESS;
} // ret == 1 shm 已经存在我们开始获取共享内存
int shmid = shmget(key, , );
if (shmid < ) {
perror("shmget");
return EXIT_FAILURE;
}
printf("key = %d -> shmid = %d\n", key, shmid); // 开始附加共享内存段
void * shmaddr = shmat(shmid, NULL, );
if ((intptr_t)shmaddr < ) {
perror("shmat shmid "STR_SHM);
return EXIT_FAILURE;
}
printf("shmid = %d, shmaddr = %p\n", shmid, shmaddr); snprintf(cmd, sizeof cmd, "ipcs -m -i %d", shmid); // 开始操作内存
long * ptr = shmaddr;
printf("ptr = %p, *ptr = %ld\n", ptr, *ptr); long old = __sync_lock_test_and_set(ptr, );
if (old) {
// 共享内存分离
printf("__sync_lock_test_and_set old = %ld, *ptr = %ld\n", old, *ptr);
return shmdt(shmaddr);
} printf("Second run now old = %ld, *ptr = %ld\n", old, *ptr); __sync_lock_release(ptr); printf("shmid = %d, shmdt after\n> %s\n", shmid, cmd);
system(cmd); // 内存分离
shmdt(shmaddr); // 删除共享内存
shmctl(shmid, IPC_RMID, NULL); // 最后测试共享内存状态
printf("shmid = %d, shmdt after\n> %s\n", shmid, cmd);
system(cmd); return EXIT_SUCCESS;
} // shm_init - init 操作, -1 error, 0 init create, 1 exists
int
shm_init(key_t key, size_t size) {
// 通过 key 创建共享内存 or 打开共享内存
int shmid = shmget(key, size, IPC_CREAT|IPC_EXCL|);
if (shmid < ) {
if (errno == EEXIST) {
// 当前的共享内存已经存在
return ;
}
return shmid;
} // create shm and shm at
void * shmaddr = shmat(shmid, NULL, );
if ((intptr_t)shmaddr < ) {
return -;
} // memset zero
memset(shmaddr, , size); // shm dt
return shmdt(shmaddr);
}
代码极其适合感受和练习. 写完后相应的 api 操作就熟悉了. 练习代码的设计思路是, 跑第一次初始化共享内存.
跑第二次输出共享内存中数据. 附带说一点 linux 可以通过下面命令查看和删除共享内存
ipcs -m
ipcrm -m [shmid]
ipcs -m | awk -F' ' 'NR!=1{print $2}'
详细展示是这样的
wang@zhi:~$ ipcs -m ------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 wang 目标
0x00000000 wang 目标 ipcrm -m [shmid]
顺带看 demo.c 编译完执行后输出结果
wang@zhi:~/code/shm$ gcc -g -Wall demo.c
wang@zhi:~/code/shm$ ./a.out
name = shm_1_to_2 -> key =
wang@zhi:~/code/shm$ ./a.out
name = shm_1_to_2 -> key =
key = -> shmid =
shmid = , shmaddr = 0x7f3ec7fde000
ptr = 0x7f3ec7fde000, *ptr =
Second run now old = , *ptr =
shmid = , shmdt after
> ipcs -m -i 共享内存段 shmid=
uid= gid= cuid= cgid=
模式= 访问权限=
bytes= lpid= cpid= nattch=
附加时间=Wed Dec ::
脱离时间=Wed Dec ::
更改时间=Wed Dec :: shmid = , shmdt after
> ipcs -m -i
ipcs: id not found
2. winds shm 相关 api 熟悉
跨平台是个悲伤的话题. 这只是小人物单纯的站队.
//
// http://www.office-cn.net/t/api/createfile.htm
// https://docs.microsoft.com/zh-cn/windows/desktop/api/fileapi/nf-fileapi-createfilea
// CreateFile - 创建或打开一个对象的句柄
// lpFileName : 创建或打开的对象的名字
// dwDesiredAccess : 指明对象的控制模式. 一个应用程序可以包含读控制, 写控制, 读/写控制, 设备查询控制
// dwShareMode : 指定对象的共享模式
// lpSecurityAttributes : 一个指向 SECURITY_ATTRIBUTES 结构对象的指针, 决定返回的句柄是否被子进程所继承
// dwCreationDisposition : 指明当打开的对象存在或不存在的时候各需怎样处理
// dwFlagsAndAttributes : 指定文件属性和标志
// hTemplateFile : 把具有 GENERIC_READ 权限的句柄指定为一个模板文件
// return : 对象句柄
//
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
); //
// http://www.office-cn.net/t/api/deletefile.htm
// https://docs.microsoft.com/zh-cn/windows/desktop/api/fileapi/nf-fileapi-deletefilea
// DeleteFileA - 删除指定文件
// lpFileName : 欲删除文件的名字
// return : 非零表示成功,零表示失败
//
BOOL DeleteFile(
LPCSTR lpFileName
); //
// http://www.office-cn.net/t/api/createfilemapping.htm
// https://docs.microsoft.com/zh-cn/windows/desktop/api/winbase/nf-winbase-createfilemappinga
// CreateFileMapping - 创建一个新的文件映射对象
// hFile : 指定欲在其中创建映射的一个文件句柄. INVALID_HANDLE_VALUE 表示在内存中创建一个文件映射
// lpFileMappingAttributes : 指定一个安全对象, 在创建文件映射时使用. 如果为 NULL 表示使用默认安全对象
// flProtect : 指定文件映射对象的页面保护
// dwMaximumSizeHigh : 文件映射的最大长度高32位
// dwMaximumSizeLow : 文件映射的最大长度低32位
// lpName : 指定文件映射对象的名字. 如存在这个名字的一个映射, 函数就会打开它, NULL 表示没有名字
// return : 新建文件映射对象的句柄, NULL 意味着出错
//
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCSTR lpName
); //
// http://www.office-cn.net/t/api/closehandle.htm
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
// CloseHandle - 关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等
// hObject : 欲关闭的一个对象的句柄
// return : 非零表示成功,零表示失败
//
BOOL WINAPI CloseHandle(
_In_ HANDLE hObject
); //
// http://www.office-cn.net/t/api/index.html?mapviewoffile.htm
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366761(v=vs.85).aspx
// MapViewOfFile - 将一个文件映射对象映射到当前应用程序的地址空间
// hFileMappingObject : 文件映射对象的句柄
// dwDesiredAccess : 映射对象的文件数据的访问方式
// dwFileOffsetHigh : 表示文件映射起始偏移的高32位.
// dwFileOffsetLow : 表示文件映射起始偏移的低32位
// dwNumberOfBytesToMap : 指定映射文件的字节数
// return : 文件映射在内存中的起始地址, NULL 表示出错
//
LPVOID WINAPI MapViewOfFile(
_In_ HANDLE hFileMappingObject,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwFileOffsetHigh,
_In_ DWORD dwFileOffsetLow,
_In_ SIZE_T dwNumberOfBytesToMap
); //
// http://www.office-cn.net/t/api/unmapviewoffile.htm
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366882(v=vs.85).aspx
// UnmapViewOfFile - 在当前应用程序的内存地址空间解除对一个文件映射对象的映射
// lpBaseAddress : 指定要解除映射的一个文件映射的基准地址, 这个地址是早先用 MapViewOfFile 函数获得的
// return : 非零表示成功,零表示失败
//
BOOL WINAPI UnmapViewOfFile(
_In_ LPCVOID lpBaseAddress
);
阅读完我整理注释和URL. 就没有其它了.
正文 - 共享内存封装
前戏做完, 是时候进入接口设计正式封装环节.
C 共享内存封装的更多相关文章
- c#读写共享内存操作函数封装
原文 c#读写共享内存操作函数封装 c#共享内存操作相对c++共享内存操作来说原理是一样,但是c#会显得有点复杂. 现把昨天封装的读写共享内存封装的函数记录下来,一方面希望给需要这块的有点帮助,另一方 ...
- 多进程共享内存的MemoryStream
文章转载于http://www.raysoftware.cn/?p=506 具体用处呢,有很多,比如多进程浏览器共享Cookie啦,多个进程传送点数据啦. 共享内存封装. 封装成了MemoryStre ...
- C++ 共享内存 函数封装
#pragma once #include <string> #include <wtypes.h> #include <map> using namespace ...
- linux第11天 共享内存和信号量
今天主要学习了共享内存和信号量 在此之前,有个管道问题 ls | grep a 整句话的意思是将ls输出到管道的写端,而流通到另一端的读端,grep a则是从管道的读端读取相关数据,再做筛选 共享内存 ...
- vector存入共享内存(了解)
昨天在上篇blog里描写了如何把STL容器放到共享内存里去,不过由于好久不写blog,发觉词汇组织能力差了很多,不少想写的东西写的很零散,今天刚好翻看自己的书签,看到一篇挺老的文章,不过从共享内存到S ...
- php中对共享内存,消息队列的操作
http://www.cnblogs.com/fengwei/archive/2012/09/12/2682646.html php作为脚本程序,通常生命周期都很短,如在web应用中,一次请求就是ph ...
- Android 匿名共享内存Java接口分析
在Android 匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体 ...
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6939890 在Android系统中,针对移动设 ...
- 进程间通信之信号量、消息队列、共享内存(system v的shm和mmap)+信号signal
进程间通信方式有:System v unix提供3种进程间通信IPC:信号量.消息队列.共享内存.此外,传统方法:信号.管道.socket套接字. [注意上述6种方式只能用户层进程间通信.内核内部有类 ...
随机推荐
- 带你从零学ReactNative开发跨平台App开发(三)
ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...
- SQL Server的优点与缺点
一般来说索引会加快查询速度,但会影响插入,修改,删除的数据,且占用物理空间;所以我们应该合理的创建索引,而且应该先创建聚合索引,再创建非聚合索引.要在经常进行查询的列上创建索引,而且如果表列较少的话要 ...
- nodejs+redis使用
安装 linux安装及配置之前写过了http://www.cnblogs.com/zycbloger/p/6226682.html windows安装 下载地址:https://github.com/ ...
- ssh终端常用快捷键
ssh终端常用快捷键 快捷键 描述 Ctrl+a 光标移动到行首 Ctrl+e 光标移动到行尾 Ctrl+c 终止当前程序 Ctrl+d 删除光标前的字符,或者推出当前中断 Ctrl+l 清屏 Ctr ...
- ps命令之排序
Linux中ps命令会自动选择一列进行排序,但有时这不是我们想要的. 方法一: ps+sort sort 选项与参数: -f :忽略大小写的差异,例如 A 与 a 视为编码相同:-b :忽略最前面 ...
- asp.net MVC4 框架揭秘 读书笔记系列3
IIS/ASP.net管道 本节全部用图形表示便于理解和记忆 1.3.1 IIS5.x与asp.net 1.3.2 IIS 6.0与asp.net 1.3.3 IIS7.0与asp.net 基于IIS ...
- 排序算法(2) 堆排序 C++实现
堆 1 数组对象 2 可以视为一棵完全二叉树 3 一个堆可以被看作一棵二叉树和一个数组,如下图所示: 4 下标计算(通常使用内联函数或者宏来定义下标操作): 已知某个结点的下标为i 其父节点下标:i/ ...
- playfair
又是一道实验吧的题,哈哈,我比较弱. 因为题目写了play我首先想到的是playfair,好,下面先看下百科 好了,已知了密钥: 所以有: s n f m th b g o ui c j p vy d ...
- Basestation函数解析(二)
---恢复内容开始--- 这部分从Basestation的RecvDataThread开始,流程为 RecvDataThread->RecvData->Decoder->PostDa ...
- php 上传大文件主要涉及配置upload_max_filesize和post_max_size两个选项。
今天在做上传的时候出现一个非常怪的问题,有时候表单提交可以获取到值,有时候就获取不到了,连普通的字段都获取不到了,苦思冥想还没解决,群里人问我upload_max_filesize的值改了吗,我说改了 ...