前言
巨页的实现,涉及到两个模块:hugetlb 和 hugetlbfs。
hugetlb 相当于是 huge page 页面管理者,页面的分配及释放,都由此模块负责。
hugetlbfs 则用于向用户提供一套基于文件系统的巨页使用界面,其下层功能的实现,则依赖于 hugetlb。
目录
1、hugetlb模块
struct hstate hstates[HUGE_MAX_HSTATE];定义了一个hstate数组,每个元素是一个巨页池。不同的巨页池,其巨页尺寸是不一样的,例如2M的,4M的,1G的等等。系统中可能会有多个巨页池,每一个池的巨页尺寸都是不一样的。max_hstate标识当前有多少个hstate,即数组的前多少个元素是有效的。默认的话,hugetlb_init中会创建一个hstate,其page size是默认大小,此hstate也就成了默认的hstate。如果hugetlb.c被编译进内核,并且内核启动的时候,命令行参数中有hugepagesz=选项。那么就会调用setup_hugepagesz进行处理,setup_hugepagesz应该会在hugetlb_init之前执行。setup_hugepagesz中会调用hugetlb_add_hstate添加一个hstate,其pagesize大小为命令行参数中指定的大小。每一个hstate,在/sys/kernel/mm/hugepages下面会有一个目录与之对应。通过读写此目录下的文件,即可实现对此hstate的各种属性的查看与修改。例如,可以查看或修改此hstate的页面数量。例如,下面的命令将2M巨页池的页面数设置为N
echo N > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
如果 N 小于当前池中的页面数量,则会归还相应的页面给内核;N大于当前池的页面数量,则更新,但是需要校验结果,因为内核一次性可能没有分配那么多。如果 N 为0,就全还给内核了。但是,必须等程序释放了内存,这些内存才会真正还给内核。如果程序还没释放,则这种需要释放还没释放的页面数,可以通过如下文件得知。
/sys/kernel/mm/hugepages/hugepages-2048kB/surplus_hugepages 或者 /proc/meminfo文件的 HugePages_Surp 字段
另外,也可以通过sysctl命令,来修改巨页的配置。
copy_bootdata函数 (arch\x86\kernel\head64.c)中,会根据dpdk的设置来设置巨页相关的命令行参数。
当前系统具体是什么参数,可以在host上查看:cat /proc/cmdline
Hugepagesz 参数规定了巨页尺寸。
Hugepages 规定了页面数量。其页面在内核上电时,由hugetlb_nrpages_setup函数申请。
2、hugetlbfs模块
hugetlbfs 在加载时,会向内核注册 hugetlbfs 文件系统(该文件系统是“伪”文件系统,不占用外部存储设备,而是占用少量内存),并mount hugetlbfs文件系统到内核中,结果保存到hugetlbfs_vfsmount 中。这个 mount,没有使用什么参数,因此对应到默认的 hstate。
3、使用巨页
有两种方式,mmap方式和共享内存方式(shmget/shmat)。,无论是通过哪种方式,最终都是通过对一个hugetlbfs类型的文件做内存映射而实现的(通过file->f_op->mmap完成)。
3.1、 mmap方式
这种方式,需要先通过如下命令 mount 一个 hugetlbfs 文件系统,通过 pagesize 指定页面大小。
mount -t hugetlbfs none /wq/some_func/huge_mem_learn/file_path -o pagesize=2048K
这样的话,新挂载的文件系统,与页面大小为2048K的 hstate 相关联。接下来,在/wq/some_func/huge_mem_learn/file_path下面创建文件,然后打开文件并通过mmap进行内存映射即可,代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#define ERR_DBG_PRINT(fmt, args...) \
do \
{ \
printf("ERR_DBG:%s(%d)-%s:\n"fmt": %s\n", __FILE__,__LINE__,__FUNCTION__,##args, strerror(errno)); \
} while (0)
#define HUGE_SIZE 2048*1024 //2M
#define ALLOC_HUGE_SIZE (HUGE_SIZE*35) //申请大于当前空余页数,申请失败errno=12
/* mmap方式可以使用指定尺寸的巨页池 */
int main(int argc, char *argv[])
{
int ret = -1,fd = -1;
char *huge_mem = NULL;
ret = system("mount -t hugetlbfs none /wq/some_func/huge_mem_learn/file_path -o pagesize=2048K");
if (ret<0)
{
ERR_DBG_PRINT("mount fail");
return -1;
}
fd = open("/wq/some_func/huge_mem_learn/file_path/test", O_CREAT | O_RDWR, 0755);
if (fd<0)
{
ERR_DBG_PRINT("open fail");
return -1;
}
huge_mem = mmap(NULL, ALLOC_HUGE_SIZE, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, 0);
if (-1 == (int)(unsigned long)huge_mem)
{
ERR_DBG_PRINT("mmap fail");
return -1;
}
close(fd);
//启动hugetlbfs后,即使test文件为NULL,也不会发生Bus error
memset(huge_mem, 0, ALLOC_HUGE_SIZE);
sleep(30);
/* 下面代码一执行,内存就释放回巨页池了 */
if (unlink("/sbc/wq/some_func/huge_mem_learn/file_path/test")==-1)
ERR_DBG_PRINT("unlink fail");
/*在同路径下执行,则卸载失败,显示信息如下:
umount: /wq/some_func/huge_mem_learn/file_path: device is busy.
(In some cases useful info about processes that use
the device is found by lsof(8) or fuser(1))
*/
ret = system("umount /wq/some_func/huge_mem_learn/file_path");
if (ret < 0)
{
ERR_DBG_PRINT("umount fail");
return -1;
}
return 0;
}
3.2、共享内存方式
这种方式,不需要上面提到的 mount 及创建文件操作。直接用 shmget 和 shmat,即可使用巨页内存。虽然用户没有 mount 及创建文件,但 shmget 内部还是创建了一个文件,并且是在上面提到的 hugetlbfs_vfsmount 挂载点下面。这样的话,就与mmap方式殊途同归了。hugetlbfs_vfsmount 挂载点对应的是默认的 hstate,因此所用巨页的页面大小也是默认的。代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#define HUGE_SIZE 2048*1024 //2M
#define ALLOC_HUGE_SIZE (HUGE_SIZE*35) //申请大于当前空余页数,申请失败errno=12
#define ERR_DBG_PRINT(fmt, args...) \
do \
{ \
printf("ERR_DBG:%s(%d)-%s:\n"fmt": %s\n", __FILE__,__LINE__,__FUNCTION__,##args, strerror(errno)); \
} while (0)
/* shmget方式只能使用默认尺寸的巨页池 */
int main(int argc, char *argv[])
{
key_t our_key = -1;
int shm_id = -1;
char *huge_mem = NULL;
our_key = ftok("/wq/some_func/huge_mem_learn/src/huge_shmat_test.c", 6);
if (-1 == (int)our_key)
{
ERR_DBG_PRINT("ftok fail:");
return -1;
}
shm_id = shmget(our_key, ALLOC_HUGE_SIZE, IPC_CREAT|IPC_EXCL|SHM_HUGETLB);
if (-1 == shm_id)
{
ERR_DBG_PRINT("shmget fail:");
return -1;
}
huge_mem = shmat(shm_id, NULL, 0);
if (-1 == (int)(unsigned long)huge_mem)
{
ERR_DBG_PRINT("shmat fail:");
return -1;
}
memset(huge_mem, 0, ALLOC_HUGE_SIZE);
printf("huge alloc write ok \n");
sleep(30);
/* 下面代码一执行,内存就释放回巨页池了 */
shmdt(huge_mem);
if (shmctl(shm_id, IPC_RMID, NULL)==-1)
ERR_DBG_PRINT("shmctl fail:");
return 0;
}
来源:CSDN
作者:切斯特-威廉-尼米兹
链接:https://blog.csdn.net/wangquan1992/article/details/103963108