【发布时间】:2012-01-06 03:07:39
【问题描述】:
copy_from_user() 函数在内部是如何工作的?考虑到内核确实有权访问用户内存空间这一事实,它是否使用任何缓冲区或是否完成了任何内存映射?
【问题讨论】:
标签: linux linux-kernel memory-mapping
copy_from_user() 函数在内部是如何工作的?考虑到内核确实有权访问用户内存空间这一事实,它是否使用任何缓冲区或是否完成了任何内存映射?
【问题讨论】:
标签: linux linux-kernel memory-mapping
copy_from_user() 的实现高度依赖于架构。
在 x86 和 x86-64 上,它只是直接从用户空间地址读取并写入内核空间地址,同时在配置时暂时禁用 SMAP(监督模式访问保护)。其中棘手的部分是copy_from_user() 代码被放置在一个特殊区域中,以便页面错误处理程序可以识别其中何时发生错误。 copy_from_user() 中发生的内存保护错误不会像由任何其他进程上下文代码触发那样杀死进程,或者像在中断上下文中发生那样使内核恐慌 - 它只是在将-EFAULT 返回给调用者的代码路径。
【讨论】:
copy_to_user() 的工作方式完全相同,只是源地址和目标地址交换了——它从内核地址读取并写入用户空间地址。在两者中进行的唯一检查是用户空间地址确实位于内核/用户拆分之下;不需要检查属于其他用户进程的地址,因为copy_*_user() 仅在进程上下文中被调用,这意味着所有地址要么是内核地址,要么属于当前进程。跨度>
copy_from_user() 系统调用的实现是使用来自不同地址空间的两个缓冲区完成的:
当copy_from_user()系统调用被调用时,数据从用户缓冲区复制到内核缓冲区。
下面给出了使用copy_from_user()的字符设备驱动程序代码的一部分(写操作):
ssize_t cdev_fops_write(struct file *flip, const char __user *ubuf, size_t count, loff_t *f_pos) { unsigned int *kbuf; copy_from_user(kbuf, ubuf, count); printk(KERN_INFO "Data: %d",*kbuf); }
【讨论】:
关于“copy_to_user 既然内核在传递内核空间地址,用户空间进程如何访问它”
用户空间进程可以尝试访问任何地址。但是,如果该地址未映射到该进程用户空间(即该进程的页表中),或者如果访问出现问题(例如对只读位置的写入尝试),则会生成页面错误。请注意,至少在 x86 上,每个进程都将所有内核空间映射到该进程虚拟地址空间的最低 1 GB,而 4 GB 总地址空间的高 3 GB(我在这里使用的是 32 位经典case) 用于过程文本(即代码)和数据。 到用户空间或从用户空间复制是由代表进程执行的内核代码执行的,实际上它是在复制期间正在使用的该进程的内存映射(即页表)。这发生在内核模式下执行时 - 即 x86 语言中的特权/主管模式。 假设用户空间代码已通过合法目标位置(即在该进程地址空间中正确映射的地址)将数据复制到 copy_to_user,从内核上下文运行将能够正常写入该地址/区域而无需问题和控制权返回给用户后,用户空间也可以从这个位置读取进程本身设置的开始。 更多有趣的细节可以在 Daniel P. Bovet 和 Marco Cesati 所著的《Understanding the Linux Kernel, 3rd Edition》的第 9 章和第 10 章中找到。特别是,access_ok() 是必要但不充分的有效性检查。用户仍然可以传递不属于进程地址空间的地址。在这种情况下,内核代码在执行复制时会发生页面错误异常。最有趣的部分是内核页面错误处理程序如何确定这种情况下的页面错误不是由于内核代码中的错误而是来自用户的错误地址(特别是如果有问题的内核代码来自内核模块已加载)。
【讨论】:
最佳答案有问题,copy_(from|to)_user不能在中断上下文中使用,他们可能会休眠,copy_(from|to)_user函数只能在进程上下文中使用,
进程的页表包含了内核需要访问它的所有信息,因此如果我们可以确保所寻址的页面在内存中,内核可以直接访问用户空间地址,使用copy_(from|to)_user函数,因为他们可以为我们检查它并且如果用户空间寻址的页面不是常驻的,它会直接为我们修复它。
【讨论】: