Linux中进程的遍历页表

Linux中进程的遍历页表,linux,linux-kernel,kernel,Linux,Linux Kernel,Kernel,我试图在linux中导航进程的页面表。在内核模块中,我实现了以下功能: static struct page *walk_page_table(unsigned long addr) { pgd_t *pgd; pte_t *ptep, pte; pud_t *pud; pmd_t *pmd; struct page *page = NULL; struct mm_struct *mm = current->mm; pgd = p

我试图在linux中导航进程的页面表。在内核模块中,我实现了以下功能:

static struct page *walk_page_table(unsigned long addr)
{
    pgd_t *pgd;
    pte_t *ptep, pte;
    pud_t *pud;
    pmd_t *pmd;

    struct page *page = NULL;
    struct mm_struct *mm = current->mm;

    pgd = pgd_offset(mm, addr);
    if (pgd_none(*pgd) || pgd_bad(*pgd))
        goto out;
    printk(KERN_NOTICE "Valid pgd");

    pud = pud_offset(pgd, addr);
    if (pud_none(*pud) || pud_bad(*pud))
        goto out;
    printk(KERN_NOTICE "Valid pud");

    pmd = pmd_offset(pud, addr);
    if (pmd_none(*pmd) || pmd_bad(*pmd))
        goto out;
    printk(KERN_NOTICE "Valid pmd");

    ptep = pte_offset_map(pmd, addr);
    if (!ptep)
        goto out;
    pte = *ptep;

    page = pte_page(pte);
    if (page)
        printk(KERN_INFO "page frame struct is @ %p", page);

 out:
    return page;
}
此函数从
ioctl
调用,
addr
是进程地址空间中的虚拟地址:

static int my_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long addr)
{
   struct page *page = walk_page_table(addr);
   ...
   return 0;
}
奇怪的是,在用户空间进程中调用
ioctl
,这会出错……但我查找页表条目的方式似乎是正确的,因为使用
dmesg
我获得了每个
ioctl
调用的示例:

[ 1721.437104] Valid pgd
[ 1721.437108] Valid pud
[ 1721.437108] Valid pmd
[ 1721.437110] page frame struct is @ c17d9b80
那么为什么这个过程不能正确地完成“ioctl”调用呢?也许我必须在浏览页面表之前锁定一些东西

我正在使用内核2.6.35-22和三层页面表

谢谢大家!

查看
/proc//smap
文件系统,您可以看到用户空间内存:

cat smaps 
bfa60000-bfa81000 rw-p 00000000 00:00 0          [stack]
Size:                136 kB
Rss:                  44 kB
它的打印方式是通过
fs/proc/task\u mmu.c
(来自内核源代码):

你的函数有点像walk_page_range()。查看walk_page_range()可以看到,smaps_walk结构在行走时不应更改:

http://lxr.linux.no/linux+v3.0.4/mm/pagewalk.c#L153

For eg:

                }
 201                if (walk->pgd_entry)
 202                        err = walk->pgd_entry(pgd, addr, next, walk);
 203                if (!err &&
 204                    (walk->pud_entry || walk->pmd_entry || walk->pte_entry
如果要更改内存内容,则上述所有检查可能会不一致

所有这些都意味着在浏览页面表时必须锁定mmap_sem:

   if (!down_read_trylock(&mm->mmap_sem)) {
            /*
             * Activate page so shrink_inactive_list is unlikely to unmap
             * its ptes while lock is dropped, so swapoff can make progress.
             */
            activate_page(page);
            unlock_page(page);
            down_read(&mm->mmap_sem);
            lock_page(page);
    }
然后解锁:

up_read(&mm->mmap_sem);
当然,当您在内核模块内发出pagetable的printk()时,内核模块正在insmod进程的进程上下文中运行(只需打印“comm”,您可以看到“insmod”),这意味着mmap_sem是锁定的,也意味着进程没有运行,因此在进程完成之前没有控制台输出(所有printk()输出仅进入内存)

听起来合乎逻辑

pte_unmap(ptep); 
在标签取出之前丢失。请尝试以以下方式更改代码:

    ...
    page = pte_page(pte);
    if (page)
        printk(KERN_INFO "page frame struct is @ %p", page);

    pte_unmap(ptep); 

out:

是否有可能ioctl syscall成功返回,并且代码在此之后出现分段故障?否,因为ioctl syscall是
main
return
之前的最后一条指令。如果我注释
ioctl
,进程不会分段故障。为什么要隐藏使用
结构页地址的部分确定你的segfaults不是从这里来的吗?你试过在qemu上调试这个吗?在调用
walk-page\u table
之后,如果
page
NULL
,我只会执行
printk
。我也试着只保留对
walk\u-page\u table
的调用,但这个过程仍然存在segfaults。也许是的,发现错误的最快方法问题是调试。谢谢你,昆汀。用调试编译代码,并在转储期间强制堆栈跟踪,这样你就完全知道发生了什么。或者使用kgdb。另外,你确定你没有使用最近内核的新的unlocked_ioctl功能吗?谢谢你,彼得,我在第一次指令之前试着保持
mmap_sem
但是当我调用
walk\u page\u table
时,我不在进程
insmod
的上下文中:我在
my\u ioctl
内部调用它,所以我在进程调用
ioctl
系统调用的上下文中。这会有区别吗?是的,这会产生dif引用。因为不同的进程具有不同的每个进程页表-如果你遍历非内核部分。但是所有进程在涉及内核地址时都共享相同的页表。谢谢。我确信内核编译时没有CONFIG\u HIGHPTE,而是将define设置为
pte offset\u map
做了
kmap
。谢谢!我不断收到类似“返回抢占不平衡”的消息。最后跟踪抢占计数()增量到pte_偏移量_映射()!添加pte_取消映射使其减小,一切正常。
    ...
    page = pte_page(pte);
    if (page)
        printk(KERN_INFO "page frame struct is @ %p", page);

    pte_unmap(ptep); 

out: