Linux内核巨页代码解析和使用

混江龙づ霸主 提交于 2020-01-13 23:22:39

前言

巨页的实现,涉及到两个模块:hugetlb 和 hugetlbfs。
hugetlb 相当于是 huge page 页面管理者,页面的分配及释放,都由此模块负责。
hugetlbfs 则用于向用户提供一套基于文件系统的巨页使用界面,其下层功能的实现,则依赖于 hugetlb。

目录

1、hugetlb模块

2、hugetlbfs模块

3、使用巨页

3.1、 mmap方式

3.2、共享内存方式


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;
}


 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!