C扩展 从共享内存shm到memcache外部内存
引言 - 背景
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 3 /* 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 = 0; for (unsigned c, a = 63689; (c = *s); ++s) { hash = hash * a + c; a *= 378551; } 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 < 0) { perror("shm_init "str_shm); return exit_failure; } if (ret == 0) { // shm 刚创建 return exit_success; } // ret == 1 shm 已经存在我们开始获取共享内存 int shmid = shmget(key, 0, 0); if (shmid < 0) { perror("shmget"); return exit_failure; } printf("key = %d -> shmid = %d\n", key, shmid); // 开始附加共享内存段 void * shmaddr = shmat(shmid, null, 0); if ((intptr_t)shmaddr < 0) { 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, 1); 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|0666); if (shmid < 0) { if (errno == eexist) { // 当前的共享内存已经存在 return 1; } return shmid; } // create shm and shm at void * shmaddr = shmat(shmid, null, 0); if ((intptr_t)shmaddr < 0) { return -1; } // memset zero memset(shmaddr, 0, 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 753664 wang 600 524288 2 目标 0x00000000 622593 wang 600 524288 2 目标 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 = 1294752001 wang@zhi:~/code/shm$ ./a.out name = shm_1_to_2 -> key = 1294752001 key = 1294752001 -> shmid = 242122773 shmid = 242122773, shmaddr = 0x7f3ec7fde000 ptr = 0x7f3ec7fde000, *ptr = 0 second run now old = 0, *ptr = 1 shmid = 242122773, shmdt after > ipcs -m -i 242122773 共享内存段 shmid=242122773 uid=1000 gid=1000 cuid=1000 cgid=1000 模式=0666 访问权限=0666 bytes=4096 lpid=7282 cpid=7280 nattch=1 附加时间=wed dec 26 21:06:33 2018 脱离时间=wed dec 26 21:06:33 2018 更改时间=wed dec 26 21:06:32 2018 shmid = 242122773, shmdt after > ipcs -m -i 242122773 ipcs: id 242122773 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. 就没有其它了.
正文 - 共享内存封装
前戏做完, 是时候进入接口设计正式封装环节.
上一篇: 初探Android
下一篇: 清除cookies的批处理(bat)