Chinaunix首页 | 论坛 | 博客
  • 博客访问: 935914
  • 博文数量: 403
  • 博客积分: 27
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-25 22:20
文章分类

全部博文(403)

文章存档

2016年(3)

2015年(16)

2014年(163)

2013年(222)

分类: LINUX

2013-05-21 17:31:03

VMA现身


一个程序编译、连接后形成的地址空间就在虚拟地址空间,在内核中,用 mm_struct结构描述,为了有效的管理整个虚拟空间,又把此空间分为一个个区间叫做VMA(virtual memory Area),用vma_area_struct结构描述。VMA是虚存管理的基本单位,其组织方式有链表和红黑树。如何编写代码查看自己的进程到底有哪些 虚拟区? (以下程序均来自intel OTC的nanhai.zou)

static void mtest_dump_vma_list(void)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma;
    printk("The current process is %s\n",current->comm);
    printk("mtest_dump_vma_list\n");
    down_read(&mm->mmap_sem);
    for (vma = mm->mmap;vma; vma = vma->vm_next) {
        printk("VMA 0x%lx-0x%lx ",
            vma->vm_start, vma->vm_end);
        if (vma->vm_flags & VM_WRITE)
            printk("WRITE ");
        if (vma->vm_flags & VM_READ)
            printk("READ ");
        if (vma->vm_flags & VM_EXEC)
            printk("EXEC ");
        printk("\n");
    }
    up_read(&mm->mmap_sem);
}

此代码片段比较简单,注意为什么要加down_read()和up_read()?
   
如果知道某个虚地址,比如,0x8049000, 又如何找到这个地址所在VMA是哪个?

static void  mtest_find_vma(unsigned long addr)
{
    struct vm_area_struct *vma;
    struct mm_struct *mm = current->mm;

    printk("mtest_find_vma\n");

    down_read(&mm->mmap_sem);
    vma = find_vma(mm, addr);
    if (vma && addr >= vma->vm_start) {
        printk("found vma 0x%lx-0x%lx flag %lx for addr 0x%lx\n",
            vma->vm_start, vma->vm_end, vma->vm_flags, addr);
    } else {
        printk("no vma found for %lx\n", addr);
    }
    up_read(&mm->mmap_sem);
}

----------------------------------

虚实转换


把一个虚地址转换为物理地址本是一个复杂的过程,在此,去掉哪些枝枝叶叶,只留下树干。
   一个虚地址转换成物理地址是通过页表这个桥梁的,三级页表包括:

·PGD(Page Global Directory)
·PUD(Page Upper Directory)
·PMD(Page Middle Directory)
·PT(Page Table)
其中页表项用pte(Page Tabe Entry)表示


一个物理页在内核中用struct page来描述。给定一个虚存区VMA和一个虚地址addr,找出这个地址所在的物理页面page.

static struct page *
my_follow_page(struct vm_area_struct *vma, unsigned long addr)
{

                pud_t *pud;
                pmd_t *pmd;
                pte_t *pte;
                spinlock_t *ptl;
        struct page *page = NULL;
        struct mm_struct *mm = vma->vm_mm;
                pgd = pgd_offset(mm, addr);
                if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd))) {
                        goto out;
        }
                pud = pud_offset(pgd, addr);
                if (pud_none(*pud) || unlikely(pud_bad(*pud)))
                        goto out;
                pmd = pmd_offset(pud, addr);
                if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd))) {
                        goto out;
        }
                pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
                if (!pte)
                        goto out;
        if (!pte_present(*pte))
            goto unlock;
        page = pfn_to_page(pte_pfn(*pte));
            if (!page)
            goto unlock;
        get_page(page);
unlock:
        pte_unmap_unlock(pte, ptl);
out:
        return page;
}

以此为例,给定一个虚地址,编写程序,求出其对应的物理地址。

提示,调用page_address(page)函数,求出页在内核中的虚地址,以此可以求出其对应的物理 地址,请大家动手写出来。

------------------------------------

触及真实数据


你是否有这样的想法,给某个地址写入自己所想写的数据?

  static void
mtest_write_val(unsigned long addr, unsigned long val)
{
    struct vm_area_struct *vma;
    struct mm_struct *mm = current->mm;
    struct page *page;
    unsigned long kernel_addr;
    printk("mtest_write_val\n");
    down_read(&mm->mmap_sem);
    vma = find_vma(mm, addr);
    if (vma && addr >= vma->vm_start && (addr + sizeof(val)) < vma->vm_end) {
        if (!(vma->vm_flags & VM_WRITE)) {
            printk("vma is not writable for 0x%lx\n", addr);
            goto out;
        }
        page = my_follow_page(vma, addr);
        if (!page) {   
            printk("page not found  for 0x%lx\n", addr);
            goto out;
        }
        
        kernel_addr = (unsigned long)page_address(page);
        kernel_addr += (addr&~PAGE_MASK);
        printk("write 0x%lx to address 0x%lx\n", val, kernel_addr);
        *(unsigned long *)kernel_addr = val;
        put_page(page);
        
    } else {
        printk("no vma found for %lx\n", addr);
    }
out:
    up_read(&mm->mmap_sem);
}

在前面我们编写了以下几个函数:
mtest_dump_vma_list():打印出当前进程的各个VMA,这个功能我们简称"listvma"
mtest_find_vma(): 找出某个虚地址所在的VMA,这个功能我们简称“findvma"
my_follow_page( ):根据页表,求出某个虚地址所在的物理页面,这个功能我们简称"findpage"
mtest_write_val(), 在某个地址写上具体数据,这个功能我们简称“writeval".

但是,这些函数还停留在纸面上,如何调用他们,又如何看到具体而真实的数据,于是,我们想到了proc文件系统,其较详细内容参看
使用 /proc 文件系统来访问 Linux 内核的内容



在proc文件系统下,我们准备建立一个可读写的mtest文件,每当向这个文件中写入以下一个字符串的时候,就调用相应的函数:
"listvma"-mtest_dump_vma_list()
"findvma"-mtest_find_vma()
"findpage"-my_follow_page( )
"wirtval"-mtest_write_val()

于是,针对proc文件系统,我们写出如下函数
static ssize_t
mtest_write(struct file *file, const char __user * buffer,
                                   size_t count, loff_t * data)
{
    char buf[128];
    unsigned long val, val2;
    if (count > sizeof(buf))
        return -EINVAL;
    if (copy_from_user(buf, buffer, count))
        return -EINVAL;
    if (memcmp(buf, "listvma", 7) == 0)
        mtest_dump_vma_list();
    else if (memcmp(buf, "findvma", 7) == 0) {
        if (sscanf(buf + 7, "%lx", &val) == 1) {
            mtest_find_vma(val);
        }
    }
    else if (memcmp(buf, "findpage", 8) == 0) {
        if (sscanf(buf + 8, "%lx", &val) == 1) {
            mtest_find_page(val);
        }
    }else  if (memcmp(buf, "writeval", 8) == 0) {
        if (sscanf(buf + 8, "%lx %lx", &val, &val2) == 2) {
            mtest_write_val(val, val2);
        }
    }
    return count;
}

static struct
file_operations proc_mtest_operations = {
    .write        = mtest_write
};

static struct proc_dir_entry *mtest_proc_entry;

整个操作我们以模块的形式实现,因此,模块的初始化和退出函数如下:
static int __init
mtest_init(void)
{
    mtest_proc_entry = create_proc_entry("mtest", 0777, NULL);
    if (mtest_proc_entry == NULL) {
        printk("Error creating proc entry\n");
        return -1;
    }
    mtest_proc_entry->proc_fops = &proc_mtest_operations;
    return 0;
}

static void
__exit mtest_exit(void)
{
    remove_proc_entry("mtest", NULL);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("mtest");
MODULE_AUTHOR("Zou Nan hai");

module_init(mtest_init);
module_exit(mtest_exit);


至此,不要以为万事大吉了,写出一个浮出水面的用户程序才真正的达及目标。
#include
#include

int main()
{
    void volatile *p1, *p2;
    FILE *file;
    file = fopen("/proc/mtest", "w");
    if (file == NULL) {
        fprintf(stderr, "can not open /proc/mtest\n");
        return 1;
    }   
    p1 = mmap(0, 10*4096, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
    if (p1 == MAP_FAILED)
        perror("mmap");
   
    printf("mmap readonly area %p, length %lx\n", p1, (long unsigned int)10*4096);
    fprintf(file, "listvma");
    fflush(file);

    p2 = mmap(0, 10*4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
    if (p2 == MAP_FAILED)
        perror("mmap");
    printf("mmap read/write %p, length %lx\n", p2, (long unsigned int)10*4096);

    fprintf(file, "findvma %p", p2);
    fflush(file);

    fprintf(file, "findpage %p", p2);
    fflush(file);
   
    *(long *)p2 = 0x100;
   
    fprintf(file, "findpage %p", p2);
   
    fflush(file);

    fprintf(file, "findpage %p", p2);
    fflush(file);
   
    fprintf(file, "writeval %p %lx", p2, (long unsigned int)0x1234);
    fflush(file);

    fprintf(file, "writeval %p %lx", p2, (long unsigned int)0x4321);
    fflush(file);
    system("dmesg -c");

    printf("value of address %p changed to %lx\n", p2, *(unsigned long *)p2);
    munmap((void *)p1, 10*4096);
    munmap((void *)p2, 10*4096);
    fclose(file);
    return 0;
}

仔细分析其中的一些语句,比如去掉fflush()是否可以。更重要的是,在用户程序执行的过程中,是怎样调用了内核函数(实在想不出的时候,可以问鼎STRACE),分析执行过程,才能真正触及虚存管理的本质

阅读(503) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~