目前我正在实现一种mmap()的版本,其目的是在客户端机器上映射远程文件。在实现过程中,我不能使用任何内置或第三方库。话虽如此,我还是对以下两个选项中的哪一个来实现存在疑虑:
- 从客户端读取文件内容后,在客户端机器上加载文件,并使用从客户端机器获得的文件描述符来使用mmap()系统调用;或者
- 通过使用sbrk()为客户端接收到的每个文件数据块分配内存。
非常感谢您提出的任何建议!
目前我正在实现一种mmap()的版本,其目的是在客户端机器上映射远程文件。在实现过程中,我不能使用任何内置或第三方库。话虽如此,我还是对以下两个选项中的哪一个来实现存在疑虑:
非常感谢您提出的任何建议!
static void emulate(mcontext_t *const context,
void (*fetch)(void *const data,
const unsigned long addr,
size_t bytes),
void (*store)(const unsigned long addr,
const void *const data,
size_t bytes));
(void *)context->gregs[REG_IP]
,而在x86-64上则位于(void *)context->gregs[REG_RIP]
。该函数必须通过增加机器指令中字节数的数量来跳过指令,即通过增加context->gregs[REG_IP]
/context->gregs[REG_RIP]
/等。如果不这样做,程序代码将一直停留在该指令中,导致SIGSEGV
不断被触发!
该函数必须仅使用fetch
和store
回调来访问导致SEGV的内存。在您的情况下,它们将被实现为联系远程计算机的函数,要求其对指定的字节执行所需操作。
假设您已经实现了以上三个函数,其余部分就非常简单了。为简单起见,让我们假设您有:
static void *map_base;
static size_t map_size;
static void *map_ends; /* (char *)map_base + map_size */
static void sigsegv_handler(int signum, siginfo_t *info, void *context)
{
if (info->si_addr >= map_base && info->si_addr < map_ends) {
const int saved_errno = errno;
emulate(&((ucontext_t *)context)->uc_mcontext,
your_load_function, your_store_function);
errno = saved_errno;
} else {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_DFL;
act.sa_flags = 0;
if (sigaction(SIGSEGV, &act, NULL) == 0)
raise(SIGSEGV);
else
raise(SIGKILL);
}
}
static int install_sigsegv_handler(void)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_sigaction = handle_sigsegv;
act.sa_mask = SA_SIGINFO;
if (sigaction(SIGSEGV, &act, NULL) == -1)
return errno;
return 0;
}
map_size
已经从远程机器获取(并向上舍入为sysconf(_SC_PAGESIZE)
),那么您只需要执行以下操作:if (install_sigsegv_handler()) {
/* Failed; see errno. Abort. */
}
map_base = mmap(NULL, map_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0);
if ((void *)map_base != MAP_FAILED)
map_ends = (void *)(map_size + (char *)map_base);
else {
/* Failed; see errno. Abort. */
}
内存映射作用于页面大小的单元(参见sysconf(_SC_PAGESIZE)
)。除非对齐到页面边界,否则无法将特定字节或任意字节范围设置为不可访问或只读。您可以将任何页面更改为可读写、只读或不可访问(分别为PROT_READ|PROT_WRITE
、PROT_READ
和PROT_NONE
;请参见mmap()
和mprotect()
)。
所有者概念非常简单。当一台机器拥有一个页面时,它可以自由地读写该页面,否则不能。注意:如果有文件支持,原子更新映射文件内容非常困难。我真的建议采用没有后备文件的方法,或者使用基于fcntl()
的租赁或锁定以页面大小的块更新后备文件。
PROT_READ|PROT_WRITE
,而在其他所有机器上则为PROT_NONE
。SIGSEGV
处理程序。它会联系其他机器,并请求拥有该特定页面的所有权。然后拥有者收到这样的消息后,将其映射更改为PROT_NONE
,并将页面发送给新的所有者。新的所有者更新映射,将保护更改为PROT_READ|PROT_WRITE
,并从SIGSEGV
处理程序返回。mremap()
}}将旧页面这里有一个想法:
SIGSEGV
信号处理程序。mprotect(2)
将整个分配空间设置为不可访问状态(PROT_NONE
)。siginfo_t
参数的 si_addr
参数来判断分段错误是否在步骤 1 中分配的区域内。如果不是,请将分段错误传递下去,它很可能会像大多数程序一样致命。我们实现的效果类似于“页故障”,我们按需加载所需的远程文件部分。当然,如果您了解访问模式(例如,整个文件将始终按某个特定顺序被需要,或将由多个进程随时间需要),则可以做得更好,可能是更简单的事情。