Linux 内核中的页面错误

Page Fault in Linux Kernel

读完 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 线程从不出现页面错误:这是否意味着只有用户 space 代码会触发页面错误?如果调用 kmalloc()vmalloc() ,它不会发生页面错误吗?我相信内核必须将这些映射到匿名页面。执行对此页面的写入时,会发生页面错误。我的理解正确吗?

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

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

我觉得你是因为对kernel、process、virtual memeory的启动没有搞清楚而搞糊涂了

  1. kenrel threads never page fault: 这是因为内核space和用户space的页面使用不同的分配方法。对于内核 space,我们在初始化时分配页面,但是对于用户 space,我们在 运行 处理和调用 malloc() 等函数时分配它们,并在映射之后,真正使用时分配它们虚拟内存,我们触发页面错误。

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

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

  1. 内核线程从不出现页面错误:所讨论的页面错误是在使虚拟页面驻留或将其从交换中取回时发生的。内核页面不仅在 kmalloc() 上被分页,而且在其生命周期内保持驻留。这同样不适用于用户 space 页面,其中 A) 可能是延迟分配的(即只是保留为 malloc() 上的页面 table 条目,但实际上直到 memset() 或其他取消引用) 和 B) 可能会在内存不足的情况下被换出。

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

这个问题很好,有 hardware-specific 回复。过去,内核线程不鼓励访问用户 space,正是因为如果访问 unpaged/paged 用户 space 中的内存不足,可能会发生页面错误命中(回想一下,这不会发生在内核 space 中,正如上面所确保的那样)。所以 copy_to/from 将是正常的 memcpy,但包含在页面错误处理程序中。这样,任何潜在的页面错误都将被透明地处理(即内存将被调入)并且一切都会很好。但是在某些情况下,memcpy to/from 用户内存的糟糕方法会起作用——更糟糕的是,它会更频繁地起作用,因为页面错误与 RAM 驻留和可用性密切相关——因此未处理的错误会导致随机恐慌.因此,始终使用 copy_from/to_user.

的法令

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

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

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

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