大学毕业后从事unix上的银行综合业务系统开发工作已有一年半的时间,向众多前辈高手学习了很多经验和技巧,自己也创新了些好的开发技术,特写出来与奋斗在一线的unix程序员们共享。本人大学时专注于windows平台应用开发,工作后才转入unix平台,故沿袭了不少windows编码风格。
--------------------------------------------------------------------------------
正文:
在一个带有数据库的unix系统中进行E-SQL嵌入式开发,必然用到很多混合式编程方式。当系统对表的SELECT操作频繁时,会使数据库效率大幅下降。于是我们很当然的这样设计:当应用开始运行时把数据库中需要频繁查询的表装载入共享内存,通过编写一批共享内存查询函数实现对表数据的快速查询、定位。这里借用windows的一些名词把这一技术命名为“内存映射表”技术。
内存映射表的格式设计有很多方式,下面介绍一下我设计的一种格式,该格式已经应用于某省级银行信用卡全省大前置系统,取得非常好的效果。
| | | | |
| 内存映射表记录条数 | 第一条记录结构单元 | 第二条记录结构单元 | ... |
| 10个字节 | 记录结构的大小 | 记录结构的大小 | |
| | | | |
共享内存数据存放格式如上图所示。开头的10个字节存放内存映射表的记录条数数值,由于标准c的有符号长整数类型最大值约为21亿,所以预留10个字节存放ASCII编码的记录条数数值已绰绰有余且取得最大限度值了。第11个字节开始存放数据库表第一条记录对应的c语言结构体,称为一个结构单元。后面依次存放所有数据库表记录形成结构体数组。
一张数据库表装载入一块共享内存,可以通过表名给共享内存的ipckey取名。比如“公共系统参数表”对应的内存映射表的ipckey在头文件里这样添加"#define SHMY_KEY_GGXTCS 0x00001138 /* 4408 */",以便于在程序里引用。
内存映射表共占用共享内存大小为该表记录对应的数据结构体大小乘以记录条数加上10个字节。比如“公共系统参数表”记录条数为10条,表定义如下。那么总占用共享内存大小=(20+30+40)*10+10=910个字节。
字段名 字段属性 长度 空值标志 备注 包括中文注释和取值范围
csxh char 20 N.N 参数序号
csz char 30 N.N 参数值
cssm char 40 参数说明
索引1 unique csxh
内存映射表的操作大致有装载和查询两种操作,其它还可以有简单的更新操作。考虑到每个内存映射表的操作大致一样以及以某个关键字段查询、更新操作的相似性,再以“公共系统参数表”我这样设计内存映射表的操作函数原形:
int LoadMapGGXTCS();
int FetchMapGGXTCS ( void *pvCondValue ,
struct REPLACE_STRUCT_TYPE *pREPLACE_STRUCT_ARG ,
int (* REPLACE_FUNCNAME_COMPARE_PROC)
( void *pvCondValue ,
struct REPLACE_STRUCT_TYPE *pREPLACE_STRUCT_ARG
)
);
int UpdateMapGGXTCS ( void *pvCondValue ,
void *pvUpdateValue ,
int (* REPLACE_FUNCNAME_UPDATE_PROC)
( void *pvCondValue ,
void *pvUpdateValue ,
struct REPLACE_STRUCT_TYPE *pREPLACE_STRUCT_ARG
)
);
两个函数内所有涉及到具体表名、结构体名、回调函数名我都已宏的方式替换掉,这样做的好处是可以形成代码模板,如果以后要添加一张表的映射只要复制代码模板到实现文件的最后面,把代码模板最前面的宏定义成具体的值。代码模板最后面把所有用过的宏都反定义掉,不妨碍后面的程序使用。
装载表函数我不用多说了,即把表数据装载入共享内存,不需要参数。
查询函数第一个参数为关键字段值,与REPLACE_FUNCNAME_COMPARE_PROC回调函数配合使用。参数类型为void*类型,这样就可以兼容所有类型的数据甚至是结构体、共用体,额外麻烦的只是把变量传入前强制传换成void*,在回调函数里再转换回具体的变量类型。第二个参数是结构体宏,用于函数成功返回时把符合要求的记录结构体返回。第三个参数是指向回调函数的指针,其作用是针对某一关键字段,分别取出共享内存里的每条记录进行比较,当条件符合时,回调函数返回0,否则返回1,这样可以不改变外层遍历函数的条件下,使用不同判断方式、不同的判断值对内存映射表中所有记录进行遍历。
本文最后附件中附有装载、查询和更新三个内存映射表的代码模板,由于完全采用参数化宏替换方式设计,甚至可以不加修改的立即应用到您的系统中去。
下面跳跃的介绍一下查询函数极其回调函数的操作原理。更新函数原理与之相似,结构稍稍复杂一些,重点是回调函数。
查询函数
{
...
遍历内存映射表中所有结构记录
{
调用遍历函数最后一个参数即回调函数指针(条件值,当前结构记录);
如果回调函数返回0,即条件符合且更新成功
{
复制内存映射表中当前结构记录内容到pREPLACE_STRUCT_ARG指针空间里,输出给用户
跳出遍历;
}
偏移到内存映射表中下一条结构记录;
}
...
返回回调函数的返回值
}
查询回调函数(条件值,内存映射表中当前结构记录)
{
把便利函数传入的条件值强制转换成char*类型;
与内存映射表中当前结构记录的csxh进行比较,如果相等即找到
返回0;
否则
返回1;
}
查询函数的使用示例
...
/* 获取 对帐场次 */
iRt = FetchMapGGXTCS( (void *)"dzcc" , &stGgxtcs , &CompareKeyFromGGXTCSWhereCSXHProc );
if( iRt != 0 )
printf( "获取对帐场次 失败" );
else
printf( "当前对帐场次为[%s]" , stGgxtcs.csz );
...
使用该设计通过创建与数据库表映射的共享内存数据提高对数据库静态表(运行时数据内容不改变或者只做少量更新的表)的查询访问速度,而不需要额外占用数据库宝贵的效率,尤其在一个对数据库操作频繁的系统中能很大程度的提高整个系统的运行效率。本设计也有不足之处,主要是只能代替数据库简单的SELECT操作和UPDATE操作,不支持INSERT、DELETE操作,不过对于一些静态表的查询使用已经足够了,不是吗 ^___^
--------------------------------------------------------------------------------
附件一、内存映射表 代码模块(以“公共系统参数表”为例)
/********************************************
** 获取 公共系统参数表 内存映射表 相应记录 **
********************************************/
#define REPLACE_STRUCT_TYPE ggxtcs
#define REPLACE_STRUCT_ARG stGgxtcs
#define REPLACE_SHEKEY SHMY_KEY_GGXTCS
#define REPLACE_TABLENAME ggxtcs
#define REPLACE_TABLEDESC "公共系统参数表"
#define REPLACE_CURSORNAME curGGXTCS
#define REPLACE_DBVAR R_GGXTCS
#define REPLACE_DBVARFUNC pubVtoSGgxtcs
#define REPLACE_FUNCNAME_FETCH "FetchMapGGXTCS"
#define REPLACE_FUNCNAME_LOAD "LoadMapGGXTCS"
#define REPLACE_FUNCNAME_COMPARE_PROC CompareKeyFromGGXTCSProc
#define REPLACE_FUNCNAME_UPDATE_PROC UpdateValueFromGGXTCSProc
int FetchMapGGXTCS ( void *pvCondValue ,
struct REPLACE_STRUCT_TYPE *pREPLACE_STRUCT_ARG ,
int (* REPLACE_FUNCNAME_COMPARE_PROC)
( void *pvCondValue ,
struct REPLACE_STRUCT_TYPE *pREPLACE_STRUCT_ARG
)
);
{
int iReturnValue;
struct REPLACE_STRUCT_TYPE *pmpREPLACE_STRUCT_ARG;
char *pmp;
char acRecordAmount[11];
long lRecordAmount;
long l;
_IPC_ID_T ipcid;
/* 判断 内存映射表 是否存在 */
iReturnValue = IPCIsShareMemoryExist( REPLACE_SHEKEY );
if( iReturnValue == IPC_SHAREMEMORY_RETURN_ISNT_EXIST )
{
/* 若不存在 则创建之 */
iReturnValue = LoadMapGGXTCS();
if( iReturnValue != 0 )
{
WriteLog( gacLogFilename,
"%s | "REPLACE_FUNCNAME_FETCH" | "LOG_LINELEN" | 创建 内存映射表 "REPLACE_TABLEDESC" 失败 错误码[%d] errno[%d],请重启应用\n",
GetLocalTimeString( gacTimeStringBuffer , 256 , "%Y-%m-%d %H:%M:%S" ),
__LINE__,
iReturnValue,
errno );
return -1;
}
}
/* 打开 内存映射表 */
ipcid = IPCOpenShareMemory( REPLACE_SHEKEY );
if( ipcid < 0 )
{
/* 打开失败,写出错日志,函数返回 */
WriteLog( gacLogFilename,
"%s | "REPLACE_FUNCNAME_FETCH" | "LOG_LINELEN" | 打开 内存映射表 "REPLACE_TABLEDESC" 失败 errno[%d],请重启应用\n",
GetLocalTimeString( gacTimeStringBuffer , 256 , "%Y-%m-%d %H:%M:%S" ),
__LINE__,
errno );
return -2;
}
/* 连接 内存映射表 地址 */
pmp = IPCAttachShareMemory( ipcid );
if( pmp == NULL )
{
/* 连接失败,写出错日志,函数返回 */
WriteLog( gacLogFilename,