虚拟内存管理

更新时间:
2024-04-02
下载文档

虚拟内存管理

SylixOS 作为一个多进程操作系统,像其他多进程操作系统一样,有内核空间和用户空间之分。内核线程、驱动程序和内核模块均存在于内核空间,应用程序(即进程)和动态链接库均存在于用户空间。

内存划分

下图描述了 SylixOS 在物理内存上的布局以及与虚拟内存的关系。

通用内存区就是操作系统本身使用的内存空间,即内核空间,主要包括操作系统镜像、系统使用的内存堆和栈空间,它们的物理地址和虚拟地址是完全相同的,因此可以看到,它们没有对应的虚拟页面。

VMM(Virtual Memory Management),即虚拟内存管理单元,以页面的方式管理除通用内存区外的所有物理内存,VMM 还负责以页面的方式管理一片虚拟内存空间,并在需要的时候,将虚拟内存页面映射到物理内存页面。虚拟页面和物理页面的大小是相同的值为 PAGESIZE(通常为 4KB)。上图系统内存划分中,有一个特别的 DMA 页面区,专门用于 DMA 数据传输(因为 DMA 硬件只能访问物理地址)。SylixOS 专门提供了分配 DMA 内存的 API,仅供内核模块和驱动程序使用,应用程序不应该使用这些 API,因此这里不作介绍。

剩下的就是供应用程序和动态链接库使用的物理页面,它们均有对应的虚拟页面。通常我们所说的虚拟内存就是这一片地址连续的虚拟页面空间。操作系统会保证虚拟页面地址不会与通用内存和 DMA 内存地址有任何重叠。设想一下,若有任何重叠,则进程本身的数据(全局变量、栈空间、代码等)都可能会映射到系统内存或 DMA 内存,这将造成不可预知的错误。我们把这片不能重叠的空间通常叫做操作系统保留空间。

上图中的虚拟页面与物理页面的映射关系仅仅表示两者之间有页面的对应关系(后面将会讲到应用程序使用特殊的方法在虚拟空间直接访问 DMA 内存),但 DMA Page 物理地址空间不能与虚拟地址空间重合,因此图中将 DMA Page 与 Physical Page 作了明显的区分。

进程页面管理

前面提到,一个进程访问的都是虚拟地址,这包括两方面:其一是创建进程时,装载器会为进程自身分配虚拟页面,包括进程的数据段、代码段、堆内存等;其二是进程运行时,访问栈内存或使用前面所讲的内存分配函数分配的内存。SylixOS 当前为每个新创建的进程预分配 32MB 地址连续的虚拟内存页面,并会为进程自身的某些必要数据(如代码段、数据段等)分配物理内存,除此之外,只有进程在运行时,根据内存访问的需要才分配物理内存。

虚拟内存空间可以大于物理内存空间的范围,系统可同时支持的进程数不仅受限于物理内存的大小,同时也受限于虚拟内存空间的大小。前面介绍过,由于保留空间的原因,虚拟空间总是小于硬件所能访问的最大空间(如在 32 位 CPU 中,虚拟空间小于 4GB),这是所有多进程操作系统的共同特点。

VMM 可保证每次分配的虚拟页面是地址连续的,但对应的物理页面地址不一定连续。当进程释放内存时,仅仅释放对应的物理页面内存,虚拟页面不会被回收。当进程退出时,虚拟页面和物理页面均全部被回收。

虚拟内存映射

应用程序可以使用 mmap 函数将一个设备文件与应用程序虚拟空间建立映射关系,这使对文件的 I/O 访问转变为内存访问。mmap 函数原型声明位于 <sys/mman.h>,该文件包含一组应用程序显式处理虚拟内存映射相关操作的函数。

#include <sys/mman.h>
void *mmap(void  *pvAddr, size_t  stLen, int  iProt, 
int  iFlag, int  iFd, off_t  off);
void *mmap64(void  *pvAddr, size_t  stLen, int  iProt, 
int  iFlag, int  iFd, off64_t  off);

以上函数原型分析:

  • 此函数成功时返回分配的虚拟内存地址,失败时返回 MAP_FAILED 并设置错误号。
  • 参数 pvAddr 表示需要映射的进程虚拟地址,如果不为 NULL,则返回值与其相同,此时 pvAddr 必须是页面对齐的地址。大多数情况下,我们应该使用 NULL,这表示让系统自动分配新的虚拟内存。
  • 参数 stLen 指定了需要映射对应文件部分内容的大小,以字节为单位。由于 VMM 以页为单位管理内存,当 stLen 不是页面大小的整数倍时,实际分配的虚拟页面字节数将大于 stLen
  • 参数 iProt 为映射内存保护选项,其值可以为以下选项之一或它们的组合(多个选项以“或”的形式组成)。
保护选项名称解释
PROT_READ内存页可以进行读访问
PROT_WRITE内存页可以进行写访问
PROT_EXEC内存页可以执行代码
PROT_NONE内存页不能进行任何访问

注意
当使用 PROT_NONE 选项时,任何访问(读、写、执行)映射内存的操作将导致一个页面无效的内存访问错误。另外,保护选项的设置不能超越文件本身的打开权限,也就是说不能以 PROT_WRITE 方式与一个以只读方式打开的文件建立映射。

  • 参数 iFlag 为内存映射标识,其值可以为以下选项之一或它们的组合(多个选项以“或”的形式组成)。
标识名称解释
MAP_SHARED共享映射
MAP_PRIVATE私有映射
MAP_FIXED固定虚拟地址映射
MAP_ANONYMOUS匿名映射

注意
使用 MAP_SHARED 时,映射的文件被多个进程共享,这意味着,一个进程对其映射空间的修改对其他所有共享映射的进程是可见的。如果多个进程使用 MAP_PRIVATE 映射同一个文件,则每个进程对其映射空间的修改操作对其他进程不可见。一旦有进程对映射区域执行写入操作,系统会为该进程复制一份私有的映射空间,这就和我们熟知的写时拷贝技术一样,SylixOS 同样支持该技术。同时,写入的数据不会被同步到文件本身。使用 MAP_FIXED 时,将使用用户传入的 pvAddr 参数作为虚拟地址进行映射,系统假设该地址是一个有效的虚拟地址,不会作任何安全性检测以及对应的处理。因此这是一种比较危险的方式,POSIX 也不鼓励程序使用此选项。在 SylixOS 中,使用 MAP_FIXED 选项将总是导致映射失败。使用 MAP_ANONYMOUS 时,将忽略文件描述符 iFd 和文件偏移值 off ,但是为了程序的可移植性,在使用此选项时, iFd 应该设置为-1。

  • 参数 iFd 为需要映射的文件描述符。
  • 参数 off 指定从文件的某一个起始位置进行映射。该参数必须为页面大小的整数倍。

参数 stLenoff 确定了映射文件的范围,当该范围超过文件本身的大小时,仍然能够成功创建映射,不过超出范围的数据将不会被同步到文件中,即所写的内容不会超过文件的大小。如果文件本身的大小为 0,则会导致映射失败。

需要注意的是,并不是所有的设备文件都能使用 mmap 函数映射内存,通常需要设备驱动程序的支持。在 SylixOS 中,磁盘文件、FrameBuffer 设备文件支持 mmap 函数操作,而像串口设备文件等不能使用 mmap 函数。mmap 函数还会将映射的文件引用计数加一,这意味着,即使调用 close 函数关闭该文件,其文件描述符依然有效,因为该文件实际上并未被真正关闭,因为 close 函数只是减少了一次文件描述符引用计数。因此在调用 mmap 后,何时关闭映射的文件并没有严格的要求。

mmap64 可支持 64 位文件偏移量,实际上在 SylixOS 中,数据类型 off_t 和 off64_t 均定义为 64 位有符号类型,因此这两个函数的功能是完全相同的。SylixOS 提供 mmap64 函数是为了提高对程序的兼容性。

某些时候,应用程序可能需要扩大或缩小当前的虚拟内存映射,可调用 mremap 函数进行处理。

#include <sys/mman.h>
void *mremap(void *pvAddr, size_t stOldSize, size_t stNewSize, int iFlag, ...);

函数 mremap 原型分析:

  • 此函数成功时返回新虚拟内存的首地址,失败时返回 MAP_FAILED 并设置错误号。
  • 参数 pvAddr 为当前内存映射的虚拟地址,必须是页面对齐的地址。
  • 参数 stOldSize 为当前内存映射的大小,以字节为单位。
  • 参数 stNewSize 为新的映射的大小,以字节为单位。
  • 参数 iFlag 为重映射选项,如下表所示。
选项名称解释
MREAP_MAYMOVE允许移动映射的虚拟空间
MREAP_FIXED使用指定的新的虚拟地址映射

mremap 函数的行为与前面所讲的 Lw_Region_Realloc 函数和 realloc 函数非常相似,当 stNewSize 大于 stOldSize 时,如果存在足够的与原有虚拟地址连续的页面,则直接扩展原有虚拟内存,返回原映射页面地址。如果没有满足该条件的虚拟页面,同时 iFlag 设置了 MREMAP_MAYMOVE 标志,则会分配新的虚拟页面,同时回收原有的虚拟页面,返回新的页面地址,这从结果来看,相当于在扩展虚拟内存的同时,将虚拟内存移动了。当 stNewSize 小于 stOldSize 时,会将多余的虚拟页面以及对应的物理页面回收。

如果使用了 MREMAP_FIXED 选项,函数将接受第 5 个可变参数,其参数为 void pvNewAddr ,该参数由用户指定需要重新映射的虚拟地址,而不是内部自动分配,这意味着将解除 pvNewAddr 之前使用 mmap 函数进行的映射。使用该选项存在很大风险,且不具有可移植性(不同系统对此选项的支持不相同),因此不建议在程序中使用。在 SylixOS 中使用该选项将直接返回错误。

mremap 函数虽然不是 POSIX 标准规定的函数,但从 Linux 2.3.1 开始便已经支持,为了有更好的兼容性,SylixOS 仍然提供该函数。

#include <sys/mman.h>
int munmap(void  *pvAddr, size_t  stLen);

函数 munmap 原型分析:

  • 该函数成功返回 0,失败返回错误码。
  • 参数 pvAddr 为使用 mmap 映射的虚拟地址。
  • 参数 stLen 为需要解除映射的内存大小,以字节为单位,内部会以页的整数倍处理。

munmap 函数执行与 mmap 函数相反的工作:将文件引用计数减一(如果该文件的引用计数为 0,则关闭文件),解除虚拟空间映射,回收虚拟页面和对应的物理页面(如果存在)等。注意,munmap 不能保证对映射空间修改的数据能够同步到文件本身。因此,为确保修改的数据能够完全回写到文件,应该手动调用 msync 函数。

#include <sys/mman.h>
int msync(void  *pvAddr, size_t  stLen, int  iFlag);

函数 msync 原型分析:

  • 参数 pvAddr 为映射的虚拟空间地址。
  • 参数 stLen 为需要同步的数据大小,以字节为单位。
  • 参数 iFlag 为同步选项,如下表所示。
选项名称解释
MS_ASYNC以异步方式回写文件
MS_SYNC以同步方式回写文件
MS_INVALIDATE无效映射的虚拟空间

使用异步方式时,调用 msync 函数将立即返回,内核在适当的时候异步地将数据回写到文件。使用同步方式时,将等待数据回写完成才返回。参数 MS_INVALIDATE 将虚拟内存无效,随后对该虚拟内存的读取操作,将会从文件读取数据。实际使用时,MS_INVALIDATE 可与 MS_ASYNC 或 MS_SYNC 组合使用,但 MS_ASYNC 与 MS_SYNC 不能同时使用。

使用虚拟内存映射,主要有以下用途:

  • 以内存方式代替 I/O 方式访问设备文件,提高效率。
  • 多个进程的虚拟空间映射到同一个设备文件,实现内存共享。
  • 使用匿名映射,相当于分配虚拟内存,或用于父子进程间通信。

接下来,将从实际应用出发,通过几个例子说明 mmap 系列函数的使用方法。

以内存方式访问 I/O 设备

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#define FILE_NAME          "mmap.dat"
#define FILE_SIZE          32
#define DATA_OFF           10
char data_buff[] = "<mmap data>";
int main(int argc, char *argv[])
{
    char   *file_buff;
    char    tmp_buff[FILE_SIZE];
    int     fd;
    int     ret;
    ret = access(FILE_NAME, F_OK);
    if (ret < 0) {
        char    tmp = 'X';
        int     i;
        fd = open(FILE_NAME, O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
        if (fd < 0) {
            fprintf(stderr, "create file failed.\n");
            return  (-1);
        }
        for (i = 0; i < FILE_SIZE; i++) {
             write(fd, &tmp, 1);
        }
        close(fd);
    }
    fd = open(FILE_NAME, O_RDWR);
    if (fd < 0) {
        fprintf(stderr, "open file failed.\n");
        return  (-1);
    }
    file_buff = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (file_buff == MAP_FAILED) {
        fprintf(stderr, "mmap failed.\n");
        return  (-1);
    }
    /*
     * 使用内存访问写入数据
     */
    memcpy(file_buff + DATA_OFF, data_buff, sizeof(data_buff) - 1);
    msync(file_buff, FILE_SIZE, MS_SYNC);
    munmap(file_buff, FILE_SIZE);
    /*
     * 使用I/O函数读取数据
     */
    ret = read(fd, tmp_buff, FILE_SIZE);
    if (ret <= 0) {
        fprintf(stderr, "read file data failed.\n");
    } else {
        tmp_buff[ret] = '\0';
        fprintf(stdout, "read file date: %s\n ", tmp_buff);
    }
    close(fd);
    return  (0);
}

程序运行的结果如下:

# ./MMAP_Access_Device
read file date: XXXXXXXXXX<mmap data>XXXXXXXXXXXXX

上面的程序中,首先检查需要映射的磁盘文件是否存在,如果不存在,则创建新的文件。前面说到,如果一个文件的数据长度为 0,则不能使用 mmap 建立映射,因此将该文件写入长度为 32 字节的数据。为了直观对比,数据为可显示的字母“X”。因为我们是以读写方式调用 mmap 映射该文件的,因此是以读写方式打开该文件。为了让数据能够回写到文件本身,我们必须使用 MAP_SHARED 映射选项。程序分别以内存写入和 I/O 函数读取的方式操作文件数据,从结果来看,正如我们期待的那样,操作映射的内存相当于操作文件本身,使用内存方式能更灵活地访问文件的某一部分,而无需使用 lseek 这样的文件定位函数。

有时候,应用程序需要直接访问设备文件自身的内存数据。最常见的就是帧缓冲设备(FrameBuffer),该类设备本身有一块供显示控制器通过 DMA 总线访问的物理内存,即通常所说的显存,它来自于下图所示的 DMA Page 区域。假如使用 I/O 函数操作显存,则势必存在用户缓冲区与显存之间的数据拷贝工作,极大地影响图形界面的刷新响应速度。使用 mmap 函数能够将显存直接映射到用户空间,从而让应用程序直接操作显存本身。要使用一个帧缓冲设备,通常包括以下过程:打开设备(其设备名称通常为“/dev/fb0”、“/dev/fb1”等)、获得显存相关信息(如显存大小、像素的颜色编码等)、调用 mmap 映射虚拟空间、读写映射的虚拟空间从而操作显存。可见使用 mmap 操作显存的过程与上面操作一般文件的过程相同。

使用 mmap 实现内存共享

相比消息队列、管道等进程间通信方式,内存共享在某些场合有更高的通信效率。虽然使用任何设备文件都能达到共享内存的目的,但 POSIX 定义操作系统中应该存在专门的设备,配合 mmap 函数簇实现共享内存。该设备与一般磁盘设备的外在表现没有任何差异(都可以执行创建/删除文件等操作),但是它不存在实际的物理存储介质,可以认为它是一个虚拟的设备,因此对该设备上的文件调用 msync 没有任何意义。其相关 API 函数如下:

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);

函数 shm_open 与一般的 open 功能是一样的,只是它在共享内存设备上打开或创建一个文件。其原型分析如下:

  • 此函数成功返回文件描述符,失败返回-1 并设置错误号。
  • 参数 name 为用于内存共享映射的文件名称。
  • 参数 oflag 为操作标识,如 O_CREAT、O_RDWR 等。
  • 参数 mode 为创建文件的模式。

函数 shm_unlink 用于删除内存设备上的一个文件,原型分析如下:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 name 为需要删除的文件名称。

注意,不存在与 shm_open 对应的 shm_close 函数,因为共享内存设备也是一个标准 I/O 设备,因此使用 close 函数即可关闭对应的文件。在不同的系统上,其共享内存设备可能有不同实现,有不同的名称,shm_open 隐藏了这种差异,以达到可移植性的目的。

下面通过两个进程模拟客户端与服务端进行一次登录会话的过程,展示 mmap 实现内存共享的方法。

程序清单 共享内存示例服务端程序(Server_Shared_Memory)

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <string.h>
#include <stdio.h>
#define MAX_MSG_SIZE       32
#define REQ_SIG_NAME       "req_signal"
#define ACK_SIG_NAME       "ack_signal"
#define SHM_FILE_NAME      "msg_buffer"
int main(int argc, char *argv[])
{
    char   *msg_buff;
    sem_t  *req_signal;
    sem_t  *ack_signal;
    int         shm_fd;
    req_signal = sem_open(REQ_SIG_NAME, O_CREAT, 0666, 0);
    if (!req_signal) {
        fprintf(stderr, "create request signal failed.\n");
        return  (-1);
    }
    ack_signal = sem_open(ACK_SIG_NAME, O_CREAT, 0666, 0);
    if (!ack_signal) {
        fprintf(stderr, "create acknowledeg signal failed.\n");
        return  (-1);
    }
    shm_fd = shm_open(SHM_FILE_NAME, O_CREAT | O_RDWR, 0666);
    if (shm_fd < 0) {
        fprintf(stderr, "open file failed.\n");
        return  (-1);
    }
    ftruncate(shm_fd, MAX_MSG_SIZE);
msg_buff = mmap(NULL, MAX_MSG_SIZE, PROT_READ | 
PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (msg_buff == MAP_FAILED) {
        fprintf(stderr, "mmap failed.\n");
        return  (-1);
    }
    sem_wait(req_signal);
    fprintf(stdout, "get new request message: %s.\n", msg_buff);
    strcpy(msg_buff, "welcome");
    sem_post(ack_signal);
    munmap(msg_buff, MAX_MSG_SIZE);
    close(shm_fd);
    sem_close(req_signal);
    sem_close(ack_signal);
    shm_unlink(SHM_FILE_NAME);
    sem_unlink(REQ_SIG_NAME);
    sem_unlink(ACK_SIG_NAME);
    return  (0);
}

如程序清单 共享内存示例服务端程序(Server_Shared_Memory)所示,服务端程序等待来自客户端的请求,当接收到请求后,返回给客户端相关的应答信息。这里我们用映射的共享内存用于双方通信,因此这里把它称作消息缓冲区。同时,为了同步访问消息缓冲区,创建了两个 POSIX 命名信号量,分别用于请求消息和应答消息的通知事件。从上面还可以看出,所有的资源均由服务端负责创建和销毁,这也是应用程序中最常见的处理方式。需要注意的是,在创建文件后,我们使用 ftruncate 函数调整了文件大小。如前面所讲的一样,如果文件长度为 0,使用 mmap 函数将会失败。

在之前的例子中,我们通过调用标准 I/O 函数 write 的方式改变了磁盘文件的大小,但是对于共享内存设备上的文件,不能保证在所有的系统中都能够使用 write 函数(SylixOS 中使用 write 函数操作内存设备上的文件将直接返回错误)。为了更好的可移植性,应用程序应该使用 ftruncate 函数调整文件大小。

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <string.h>
#include <stdio.h>
#define MAX_MSG_SIZE       32
#define REQ_SIG_NAME       "req_signal"
#define ACK_SIG_NAME       "ack_signal"
#define SHM_FILE_NAME      "msg_buffer"
int main(int argc, char *argv[])
{
    char       *msg_buff;
    sem_t      *req_signal;
    sem_t      *ack_signal;
    int             shm_fd;
    req_signal = sem_open(REQ_SIG_NAME, 0, 0666, 0);
    if (!req_signal) {
        fprintf(stderr, "open request signal failed.\n");
        return  (-1);
    }
    ack_signal = sem_open(ACK_SIG_NAME, 0, 0666, 0);
    if (!ack_signal) {
        fprintf(stderr, "open acknowledeg signal failed.\n");
        return  (-1);
    }
    shm_fd = shm_open(SHM_FILE_NAME, O_RDWR, 0666);
    if (shm_fd < 0) {
        fprintf(stderr, "open shm file failed.\n");
        return  (-1);
    }
msg_buff = mmap(NULL, MAX_MSG_SIZE, PROT_READ | 
PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (msg_buff == MAP_FAILED) {
        fprintf(stderr, "mmap failed.\n");
        return  (-1);
    }
    strcpy(msg_buff, "request login");
    sem_post(req_signal);
    sem_wait(ack_signal);
    fprintf(stdout, "get acknowledge message: %s.\n", msg_buff);
    munmap(msg_buff, MAX_MSG_SIZE);
    close(shm_fd);
    sem_close(req_signal);
    sem_close(ack_signal);
    return  (0);
}

客户端程序相对简单,它假设相关的资源已经存在,因此只需要打开即可,并且在程序退出前仅仅关闭相关资源。客户端中映射的 msg_buff 与服务端的 msg_buff 指针实际上都指向了同一块物理内存(这里指的是逻辑上的,物理上可能是几块地址不连续的物理内存),因此,这里发送消息的过程只需要改写消息缓冲的数据,再发送同步信号即可。如果使用消息队列,势必存在数据拷贝的过程,如果消息数据量很大,将消耗很大的内存,大量的数据拷贝也会极大地降低运行效率。

最后,我们观察一下运行结果。首先使用背景执行的方式运行服务端程序,此时不会有任何输出。随后运行客户端程序,此时终端输出如下信息,可见服务端和客户端通过共享内存正确实现了信息交互。

# ./Server_Shared_Memory
get new request msg: request login.
# ./Client_Shared_Memory
get acknowledge: welcome.

匿名映射

匿名映射即映射的虚拟内存没有任何与之关联的设备文件,在支持 fork 系统调用的系统中(如 Unix、Linux 等),由于子进程可继承父进程的虚拟内存映射,因此也可实现两者之间的内存共享。当前 SylixOS 虽然也有父子进程的概念,但不支持 fork 系统调用,其父子进程仅仅是逻辑上的联系,因此不能用此实现父子进程间内存共享。

匿名映射的另一个用途是为应用程序分配虚拟内存,功能与 malloc 相似,但两者之间存在一定差异,如下图所示:

应用程序使用 malloc 函数分配的内存来自于进程自身的内存堆,该内存堆由创建进程时操作系统从虚拟内存页面中分配而来。使用 mmap 函数分配的内存则直接来自于虚拟内存页面区。此外,malloc 函数是以字节方式分配内存,mmap 函数则使用的是页面方式分配内存,通常情况下,后者有更高的内存分配效率。匿名映射的使用方法,如下所示:

mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 
-10);

此时,我们传入的文件描述符参数为-1,并且增加了 MAP_ANONYMOUS 选项,除此之外与通常的用法没有任何不同。函数成功则返回映射的虚拟空间,该虚拟空间一开始并没有与之对应的物理内存,只有在实际访问时,操作系统在缺页中断中才为其分配合适的物理内存页面。

虚拟内存的其他操作

给内存上锁

#include <sys/mman.h>
int mlock(const void  *pvAddr, size_t  stLen);
int munlock(const void  *pvAddr, size_t  stLen);
int mlockall(int  iFlag);
int munlockall(void);

当物理内存大小不能满足当前应用程序的需要时,某些操作系统允许将一些应用程序所拥有的空闲(这里的空闲指的是相对长的时间内未被使用,不同的页面交换算法,对此时间的定义和处理也不尽相同)的物理内存页面释放出来,供其他程序使用,释放的数据被交换到磁盘空间。函数 mlock 用于锁定用户空间的某一段虚拟内存,避免其对应的物理内存被操作系统交换到磁盘空间。munlock 用于解除内存锁定。mlockall 则将进程所有的虚拟空间锁定。

页面交换虽然有它固有的优点,但同时也给应用带来了不确定性,降低磁盘的使用效率和使用寿命,这在嵌入式系统中通常是不可接受的。因此,大多数嵌入式系统都未实现页面交换算法。SylixOS 也一样,应用程序使用上面的函数将不会有任何意义。

设置内存的保护属性

#include <sys/mman.h>
int mprotect(void  *pvAddr, size_t  stLen, int  iProt); 

函数 mprotect 原型分析:

  • 该函数成功返回 0,失败返回错误码。
  • 参数 pvAddr 为虚拟页面地址。
  • 参数 stLen 为虚拟页面的大小,以字节为单位。
  • 参数 iProt 即页面保护选项,参照下表。

该函数允许应用程序指定需要访问内存的属性。mprotect 可实现某些特殊功能,应用程序通常不需要此操作。

保护选项名称解释
PROT_READ内存页可以进行读访问
PROT_WRITE内存页可以进行写访问
PROT_EXEC内存页可以执行代码
PROT_NONE内存页不能进行任何访问

内存建议

内存建议指的是应用程序告知操作系统,自己将以某种特定模式使用指定范围的一段内存空间,建议操作系统根据这些信息,优化与此内存相关的资源管理,提高系统性能。

#include <sys/mman.h>
int posix_madvise(void *addr, size_t len, int advice);

函数 posix_madvise 原型分析:

  • 该函数成功返回 0,失败返回错误码。
  • 参数 addr 为虚拟内存地址。
  • 参数 len 为内存的大小,以字节为单位。
  • 参数 advice 为内存建议选项,如下表所示。
选项名称解释
POSIX_MADV_NORMAL没有任何建议,操作系统以默认方式管理内存相关资源
POSIX_MADV_RANDOM应用程序将对指定内存进行随机访问
POSIX_MADV_SEQUENTIAL
应用程序将对指定内存进行从低地址到高地址的顺序访问
POSIX_MADV_WILLNEED应用程序在不久的将来会访问指定的内存
POSIX_MADV_DONTNEED应用程序随后将不再访问指定的内存

从上面的选项可知,它们与内存管理算法有很大联系,比如页面回收,页面交换等。但使用该函数需要应用程序明确内存的使用情况,才能达到预期的目的。当前,在 SylixOS 使用该函数将直接返回 0。

文档内容是否对您有所帮助?
有帮助
没帮助