Linux内核API find_vma

find_vma函数功能描述:find_vma( )函数根据一个属于某个进程的虚拟地址,找到其所属的进程虚拟区间,并返回相应的vma_area_struct结构体指针。

find_vma文件包含

#include<linux/mm.h>

find_vma函数定义

在内核源码中的位置:linux-3.19.3/mm/mmap.c

函数定义格式:

struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)

find_vma输入参数说明

  • mm:是进程整个用户空间的抽象,也是总的控制结构,一个进程只有一个mm_struct结构,一个进程整个用户空间通常有若干离散的虚拟区间,这些虚拟区间由vm_area_struct结构描述。
  • addr:是进程用户空间中一虚拟地址,它属于某一虚拟区间。

find_vma返回参数说明

struct vm_area_struct是对进程虚拟区间抽象的数据结构,find_vma( )函数返回一个该结构类型指针,该指针指向描述进程中虚拟地址addr所在虚拟区间的结构体。

其中,struct mm_struct和struct vm_area_struct在文件linux-3.19.3/include/linux/mm_types.h中定义,它们的具体结构如下,这里对部分字段的含义进行了说明:

struct mm_struct {
    struct vm_area_struct *  mmap;           /* 指向线性区对象的链表头 */
    struct rb_root             mm_rb;
    u32 vmacache_seqnum;                      /*每一个线程的vmacache序列号*/
    #if def CONFIG_MMU
    /* 在进程地址空间忠搜索有效线性地址区间的方法 */
    unsigned long (*get_unmapped_area) (struct file *filp, unsigned long addr,
            unsigned long len, unsigned long pgoff, unsigned long flags);
    #endif
    /* 标识第一个分配的匿名线性区或文件内存映射的线性地址 */
    unsigned long      mmap_base;
    unsigned long      mmap_legacy_base;
    unsigned long      task_size;
    unsigned long      highest_vm_end;
    /* 内核从这个地址开始搜索进程地址空间中线性地址的空闲区间 */
    pgd_t *           pgd;                   /*指向页全局目录 */
    atomic_t           mm_users;              /* 次使用计数器 */
    atomic_t           mm_count;              /* 主使用计数器*/
    atomic_long_t      nr_ptes;               /*页表所在的页*/
    int                map_count;             /* 线性区vma的个数 */
    spinlock_t         page_table_lock;       /* 线性区的自旋锁和页表的自旋锁 */
    struct rw_semaphore    mmap_sem;          /* 线性区的读/写信号量 */
    struct list_head       mmlist;            /* 指向内存描述符链表中的相邻元素*/
    unsigned long          hiwater_rss;
    unsigned long          hiwater_vm;

    /**total_vm指进程地址空间的大小(页数), locked_vm指“锁住”而不能换出的页的个数,
     **shared_vm指共享文件内存映射中的页数,exec_vm指可执行内存映射中的页数*/
    unsigned long          total_vm, locked_vm, pinned_vm, shared_vm, exec_vm;

    /*stack_vm指用户堆栈中的页数*/
    unsigned long      stack_vm, def_flags;

    /*start_code指可执行的起始地址,end_code指可执行代码的最后地址,start_data
     **指已初始化数据的起始地址,end_data指已初始化数据的最后地址*/
    unsigned long     start_code, end_code, start_data, end_data;
    /*start_ brk指堆的起始地址,brk指堆的当前最后地址,start_ stack指用户态堆栈的起始地址*/
    unsigned long          start_brk, brk, start_stack;

    /* arg_start指命令行参数起始地址,arg_end指命令行参数的最后地址,
     **env_start指环境变量的起始地址,env_end指环境变量的最后地址*/
    unsigned long     arg_start, arg_end, env_start, env_end;
    ……
    ……
};

struct vm_area_struct {
    unsigned long             vm_start;              /* 线性区的第一个线性地址 */
    unsigned long             vm_end;                /* 线性区之后的第一个线性地址 */
    struct vm_area_struct     *vm_next, *vm_prev; /* 进程链表中的下一个线性区及上一个线性区*/
    struct rb_node            vm_rb;                 /* 用于红黑树的数据*/
    unsigned long             rb_subtree_gap;
    struct mm_struct *       vm_mm;                 /*指向线性区所在的内存描述符 */
    pgprot_t                  vm_page_prot;          /* 线性区中页框的访问许可权 */
    unsigned long             vm_flags;              /* 线性区的标志*/
    ……
    ……
    struct list_head          anon_vma_node;         /* 指向匿名线性区链表的指针 */
    struct anon_vma *        anon_vma;              /* 指向anon_vma数据结构的指针 */
    const struct vm_operations_struct * vm_ops; /* 指向线性区的方法 */
    unsigned long  vm_pgoff; /*在映射文件中的偏移量,对于匿名页,它等于0或vm_start/PAGE_SIZE*/
    struct file *    vm_file;                       /* 指向映射文件的文件对象 */
    void *    vm_private_data;                      /* 指向内存区的私有数据 */
    ……
    ……
};

find_vma实例解析

编写测试文件:find_vma.c

头文件及全局变量声明如下:

#include <linux/security.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
static int __init find_vma_init(void);
static void __exit find_vma_exit(void);

模块初始化函数:

int __init find_vma_init(void)
{
    struct mm_struct *mm ;
    unsigned long addr ;
    struct vm_area_struct * vma ;

    mm = current->mm;        //mm指向当前进程
    addr = mm->mmap->vm_next->vm_start + 1;
    printk("addr = 0x%lx\n", addr);

    vma = find_vma(mm, addr);
    if(vma ! = NULL )
    {
        /*输出所查找的虚拟区间的起始地址*/
        printk("vma->vm_start = 0x%lx\n", vma->vm_start);

        /*输出所查找虚拟区间的结束地址*/
        printk("vma->vm_end = 0x%lx\n", vma->vm_end);
    }
    else
        printk("UNLUCK! You have failed! \n");

    return 0;
}

模块退出函数:

void __exit find_vma_exit(void)
{
    printk("exit! \n");
}

模块初始化及退出函数调用:

module_init(find_vma_init);
module_exit(find_vma_exit);

实例运行结果及分析:

首先编译模块,执行命令insmod find_vma.ko插入模块,然后执行命令dmesg -c,会出现如图所示的结果。

Linux内核API find_vma

结果分析:

““mm = current->mm; ”`获取当前进程用户空间。

“ addr = mm->mmap->vm_next->vm_start + 1; ”此时addr即为用户空间中的某一虚拟地址,这里为当前进程第二个虚拟区间的起始地址加1,由输出结果可知addr = 0x7fb896ce6001。然后调用find_vma( )函数查询addr所在的虚拟区间,将描述该虚拟区间的结构体指针赋值给vma,最后通过输出vma的vm_start和vm_end值验证了查找成功。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程