【问题标题】:Page Fault in Linux KernelLinux内核中的页面错误
【发布时间】:2020-12-29 19:51:41
【问题描述】:

阅读Mel Gorman的书Understanding the Linux Virtual Memory Manager后,我有几个问题。 4.3 Process Address Space Descriptor 部分说 kernel threads never page fault or access the user space portion. The only exception is page faulting within the vmalloc space 。以下是我的问题。

  1. kenrel 线程从不缺页:这是否意味着只有用户空间代码会触发缺页?如果调用了kmalloc()vmalloc(),会不会出现页面错误?我相信内核必须将这些映射到匿名页面。当执行对这些页面的写入时,会发生页面错误。我的理解正确吗?

  2. 为什么内核线程不能访问用户空间? copy_to_user()copy_from_user() 不这样做吗?

  3. Exception is page faulting within vmalloc space:这是否意味着vmalloc() 会触发页面错误而kmalloc() 不会?为什么kmalloc() 不会出现页面错误?内核虚拟地址的物理帧不需要作为页表项保存吗?

【问题讨论】:

  • 内核代码被锁定在内存中,所以不会出错。错误发生在页面被使用时,而不是在分配时。

标签: memory-management linux-kernel kernel page-fault


【解决方案1】:
  1. 内核线程永远不会出现页面错误:所讨论的页面错误是指使虚拟页面常驻,或将其从交换中恢复。内核页面不仅在 kmalloc() 上被分页,而且在其生命周期内保持常驻。这同样不适用于用户空间页面,其中 A) 可能是惰性分配的(即仅保留为 malloc() 上的页表条目,但在 memset() 或其他取消引用之前实际上不会出错)和 B)可能被交换在内存不足的情况下退出。

  2. 为什么内核线程不能访问用户空间? copy_to_user() 或 copy_from_user() 不是这样做的吗?

这是一个很好的问题,有一个特定于硬件的答复。过去,不鼓励内核线程访问用户空间,正是因为如果访问用户空间中的未分页/分页内存,可能会发生页面错误命中(回想一下,这不会在内核空间中发生,如上所述确保)。所以 copy_to/from 将是正常的 memcpy,但包装在页面错误处理程序中。这样,任何潜在的页面错误都将被透明地处理(即内存将被分页)并且一切都会好起来的。但是在某些情况下,memcpy 进出用户内存的糟糕方法会起作用 - 更糟糕的是,它会更频繁地起作用,因为页面错误非常与 RAM 驻留和可用性有关 - 因此未处理的错误会导致随机恐慌。因此,始终使用 copy_from/to_user 的法令。

然而,最近,从安全角度来看,内核/用户内存隔离变得很重要。这是由于许多利用技术(NULL 指针取消引用是一种非常常见且功能强大的技术),其中可以在用户空间(因此很容易控制)内存中构造假内核对象(或代码),并可能导致代码执行内核。

因此,大多数体系结构都有一个页表位,它在物理上防止属于用户模式的页面被内核访问。以ARM64为例,这个特性称为PAN/PXN(Privileged Access/Execute Never)。

因此,copy_from/to 现在不仅处理页面错误,还可以在操作前禁用 PAN/PXN,并在操作后恢复。

  1. 异常是vmalloc 空间内的页面错误:vmalloc() 分配可交换的内存,而kmalloc 不分配。区别在于实现(kmalloc 使用 GFP_KERNEL)。这也意味着 kmalloc 更有可能失败(如果没有可用的 RAM),但不会出现页面错误(它会返回 NULL,这本身就是一个问题..)

【讨论】:

  • 谢谢。如果我理解正确,在调用 kmalloc 时会出现页面错误(异常处理),并且会创建一个 PTE 条目。然而,kmalloced 内存不会移动到交换区,因此不会换页。我可以这样总结吗?
  • 不完全是。这取决于标志。当您使用 GFP_ATOMIC 进行 kmalloc 时,您可以保证它不会休眠,即不会出现需要服务的页面错误。它尝试使用已经预先分配的页面。如果找不到,则返回 NULL。当您使用 GFP_KERNEL 时,它可能会休眠、释放一些 RAM 和/或回收页面。但是您确实理解正确,因为 GFP_KERNEL 内存不会被换出。一篇相当古老但很好的文章是 linuxjournal.com/article/6930 - 由 Robert Love 撰写,他还撰写了权威的“Linux 内核开发”一书
【解决方案2】:

我认为你会感到困惑,因为你对内核、进程和虚拟内存的启动没有清楚地了解。

  1. kenrel 线程从不缺页:这是因为内核空间和用户空间的页面使用不同的分配方法。对于内核空间,我们在初始化时分配页面,而对于用户空间,我们在运行进程和调用malloc()等函数时分配它们,映射后,当真正使用该虚拟内存时,我们会触发页面错误。

  2. 为什么内核线程不能访问用户空间? kenrel启动时,进程0会创建进程1和进程2。进程1用于形成用户空间进程树,进程2用于管理内核线程。而你提到的函数总是被那些用户线程用来将数据传入/传出内核以实现一些功能,如打开文件或套接字等。

  3. 异常是vmalloc空间内的页面错误:vmalloc空间不是函数vmalloc(),它是内核内存空间中的一个区域,用于一些用作异常的动态内存分配。

【讨论】:

  • 考虑我有一个内核模块/kthread,它在内核初始化之后并在稍后的某个时间点使用 kmalloc 分配内存。当写入该部分内存时,不会出现页面错误,因为该部分内存将首先被写入?页表不需要为那个框架创建一个PTE?
  • 内核启动时会在 start_kernel()->setup_arch()->init_mem_mapping()->init_memory_mapping()->kernel_physical_mapping_init() 中进行初始化。最好看源码来理解。
猜你喜欢
  • 2014-02-23
  • 2017-01-29
  • 2011-08-23
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-31
  • 1970-01-01
相关资源
最近更新 更多