共享内存应用
正如共享内存应用程序页面中所解释的那样,SmartEDB 允许两个或多个进程共享一个公共数据库。
以下提供了在不同操作系统上创建共享内存段的一般说明。以下各节提供了 C 应用程序的进一步实现细节。
关于多进程共享内存应用程序的重要说明
在使用共享内存的多进程数据库应用程序中,首先仅需由一个进程(主进程)调用 mco_runtime_start()
。这使得 SmartEDB 运行时能够正确创建并初始化数据库注册表。主进程初始化完成后,所有其他(辅助)进程可以同时调用 mco_runtime_start()
。
主进程示例
以下代码片段演示了一个“主”进程如何确定已链接正确的共享内存库,然后初始化内存设备并以共享内存方式打开数据库:
MCO_RET open_shared_db(
const char * db_name, /* 数据库名称 */
mco_dictionary_h dict, /* 模式指针 */
mco_size_t db_sz, /* 内存部分的内存段大小 */
uint2 mem_pg_sz, /* 内存页大小 */
uint2 max_conn_no /* 最大连接数 */
)
{
mco_runtime_info_t info;
mco_db_params_t db_params;
mco_device_t dev;
/* 获取运行时信息 */
mco_get_runtime_info(&info);
if (info.mco_shm_supported)
{
/* 将设备设置为共享命名内存设备 */
dev.type = MCO_MEMORY_NAMED;
sprintf( dev.named.name, "%s-db", db_name ); /* 设置内存名称 */
dev.named.flags = 0; /* 0 标志 */
dev.named.hint = 0; /* 设置映射地址或将其置空 */
}
else
{
/* 共享内存的库文件错误 */
return MCO_E_UNSUPPORTED;
}
dev.assignment = MCO_MEMORY_ASSIGN_DATABASE; /* 主数据库内存 */
dev.size = db_sz; /* 设置设备大小 */
/* 初始化并自定义数据库参数 */
mco_db_params_init ( &db_params ); /* 用默认值初始化参数 */
db_params.mem_page_size = mem_pg_sz;
db_params.disk_page_size = 0;
db_params.db_max_connections = max_conn_no;
/* 使用给定的参数在设备上打开一个数据库 */
return mco_db_open_dev(db_name, dict, dev, 1, &db_params );
}
void StartDB()
{
MCO_RET rc;
mco_db_h db;
void* start_mem = 0;
/* 设置致命错误处理程序 */
mco_error_set_handler( &errhandler );
mco_runtime_start();
rc = open_shared_db( dbname, DemoShm1_get_dictionary(),
SEGSZ, PAGESIZE, MAX_CONNECTIONS );
if ( MCO_S_OK == rc )
{
rc = mco_db_connect( dbname, &db );
/* 正常处理在此进行。 */
...
rc = mco_db_disconnect( db );
rc = mco_db_close( dbname );
}
mco_runtime_stop();
}
在传递给 mco_db_open_dev()
的 mco_device_t 结构中,将共享内存标志和提示字段设置为零或有效值是很重要的。
二级进程示例
一旦主进程通过 open_shared_db()
函数为数据库设置好共享内存,后续进程只需按照如下所示进行连接即可:
void DbAttach()
{
MCO_RET rc;
mco_db_h db;
/* 设置致命错误处理程序 */
mco_error_set_handler( &errhandler );
mco_runtime_start();
/* 通过名称“dbname”连接到数据库 */
rc = mco_db_connect( dbname, &db );
if ( MCO_S_OK != rc )
{
printf("\n Could not attach to instance: %d\n", rc);
exit( 1 );
}
/* 正常的数据库处理在此进行。 */
...
rc = mco_db_disconnect( db );
rc = mco_db_close( dbname );
mco_runtime_stop();
}
有关共享内存应用程序的完整示例,请参阅SDK示例 samples/core/03-connect_multiprocess。
共享内存运行时选项
SmartEDB 运行时可以通过运行时选项告知共享内存配置。共享内存选项因操作系统而异。要设置运行时选项,应用程序可以在调用 mco_runtime_start()
之前调用 mco_runtime_setoption() 。例如:
mco_runtime_setoption(MCO_RT_OPTION_UNIX_SHM_MASK, 0600 );
rc = mco_runtime_start();
...
应用程序可以在运行时通过检查mco_get_runtime_info()
函数的输出来确定是否将正确的SmartEDB运行时库链接到支持共享内存。
Windows应用程序
运行时选项分为两类:
- 一类用于确定共享内存块名称的作用域(本地或全局);
- 另一类用于确定应用于共享内存块的安全级别。这些选项的组合将应用于共享(命名)内存块设备中指定的名称。
例如,以下代码片段创建了一个名为“shared-db”的共享内存块:
if (info.mco_shm_supported)
{
/* 将设备设置为共享命名内存设备 */
dev.type = MCO_MEMORY_NAMED;
sprintf( dev.named.name, "%s-db", db_name ); /* 设置内存名称 */
dev.named.flags = 0; /* 0 标志 */
dev.named.hint = 0; /* 设置映射地址或将其置空 */
}
默认的运行时选项如下:
MCO_RT_WINDOWS_SHM_PREFIX_NONE | MCO_RT_WINDOWS_SHM_SEC_DESCR_NULL
这种组合使得数据库只能从本地会话可见和访问。要使数据库可以从同一用户的所有会话访问,例如在同一用户下运行的服务和桌面程序,需要以下运行时选项:
MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL | MCO_RT_WINDOWS_SHM_SEC_DESCR_SAMEUSER
要用这些选项设置共享内存块,必须在调用mco_runtime_start()
之前进行以下调用:
mco_runtime_setoption( MCO_RT_WINDOWS_SHM_OPT,
MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL |
MCO_RT_WINDOWS_SHM_SEC_DESCR_SAMEUSER );
...
rc = mco_runtime_start();
...
请注意:
- 值 MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL 仅适用于服务;
- 桌面会话能够使用模式 MCO_RT_WINDOWS_SHM_PREFIX_LOCAL 或 MCO_RT_WINDOWS_SHM_PREFIX_NONE 创建数据库;
- 它们可以使用匹配的安全设置连接到使用 MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL 打开的数据库。
Linux应用程序
当使用 SmartEDB 库 libmcompsx 时,Linux 共享内存应用程序使用 POSIX 函数 mmap()
为当前进程提供虚拟共享内存的映射。
MCO_RT_POSIX_SHM_ANONYMOUS 启用函数
mmap()
的选项 MAP_ANONYMOUS;MCO_RT_POSIX_SHM_SHARED 指定函数
mmap()
的选项 MAP_SHARED;否则使用 MAP_PRIVATE。
默认的选项组合是:
MCO_RT_POSIX_SHM_ANONYMOUS | MCO_RT_POSIX_SHM_SHARED.
若要为共享内存块指定不同的选项,请在调用 mco_runtime_start()
之前调用 mco_runtime_setoption()
。例如,若要指定 mmap()
选项 MAP_PRIVATE:
mco_runtime_setoption(
MCO_RT_POSIX_SHM_OPT,
MCO_RT_POSIX_SHM_ANONYMOUS
);
...
rc = mco_runtime_start();
...
请注意
Linux AIO 库 mcfu98aio 并非为进程间的共享内存而设计。它可以用于多线程访问,但不能用于多个进程共享数据库。
Unix应用程序
在 Unix 系统中,共享内存的访问模式由文件系统访问权限的 Unix 特定值指定。共享内存和信号量与普通文件具有相同的权限系统,以限制不同用户和组的进程访问。
默认值为 0666 的 MCO_RT_OPTION_UNIX_SHM_MASK 表示用户、组和其他人都可以读取和写入此内存或信号量。值为 0600 表示只有用户可以访问(读取和写入)此内存或信号量。要将默认值更改为 0600,则必须在调用 mco_runtime_start()
之前执行以下调用:
mco_runtime_setoption(MCO_RT_OPTION_UNIX_SHM_MASK, 0600);
...
rc = mco_runtime_start();
...
共享内存和直接指针库
使用 SmartEDB 直接指针算术库(DPTR)时,必须在每个进程中将共享内存段映射到相同的虚拟内存地址,因为在 DPTR 实现中,SmartEDB 使用实际内存地址(即执行指针算术)来计算 SmartEDB 数据库中对象的位置。
每个运行中的基于 SmartEDB 的应用程序实例中的指针必须相同,否则指针算术将无法正常工作。将提示参数设置为零会使 SmartEDB 确定实际的共享内存段地址。但在第二个进程尝试打开共享数据库时调用此操作可能会失败。
在这种情况下,应用程序有责任提供有效的提示地址。 有几种方法可以确定运行时应将共享内存数据库映射到何处。开发人员可以使用操作系统提供的实用程序收集内存使用信息(进程内存映射)。这些实用程序通常会显示系统上每个正在运行的进程及其使用的库的代码、数据和堆栈内存使用情况。有必要检查输出并选择任何地址空间之外的一个地址。
无论如何,内存地址都是每个进程虚拟内存中的一个地址。应当选择一个距离每个将共享数据库的进程的数据、堆栈和堆段都足够远的地址。
因此,MAP_ADDRESS
的默认值为 0x20000000
。假定不太可能,但不能保证,任何单个进程的数据、堆栈和堆会延伸到 0x20000000 字节。通过使用偏移量库而非直接指针算术库,可以避免上述与 MAP_ADDRESS
相关的潜在问题。偏移量方法从内存数据库的起始地址计算偏移量来定位对象,因此它不依赖于内存数据库在所有进程中的起始地址相同且已知。
不过,直接指针算术比计算偏移量快约 5% - 15%。