当前位置: 萬仟网 > IT编程>开发>C/C++ > C扩展 从共享内存shm到memcache外部内存

C扩展 从共享内存shm到memcache外部内存

38  人参与 | 时间:2019-01-07 | 我要评论

引言 - 背景

  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. 就没有其它了.

 

正文 - 共享内存封装

  前戏做完, 是时候进入接口设计正式封装环节. 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

◎已有 0 人评论

Copyright © 2019  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com