该功能在内部究竟是如何工作的copy_from_user()
?考虑到内核确实有权访问用户内存空间这一事实,它是否使用任何缓冲区或是否完成了任何内存映射?
4 回答
的实现copy_from_user()
高度依赖于架构。
在 x86 和 x86-64 上,它只是直接从用户空间地址读取并写入内核空间地址,同时如果已配置,则暂时禁用 SMAP(监督模式访问保护)。其中棘手的部分是copy_from_user()
代码被放置在一个特殊的区域中,以便页面错误处理程序可以识别其中何时发生错误。发生的内存保护错误copy_from_user()
不会像由任何其他进程上下文代码触发的那样杀死进程,也不会像在中断上下文中发生的那样使内核恐慌——它只是在代码路径中恢复执行返回-EFAULT
给调用者。
关于“由于内核正在传递内核空间地址,用户空间进程如何访问它”
用户空间进程可以尝试访问任何地址。但是,如果地址没有映射到该进程的用户空间(即该进程的页表中),或者如果访问出现问题,例如对只读位置的写入尝试,则会产生页面错误。请注意,至少在 x86 上,每个进程都将所有内核空间映射到该进程虚拟地址空间的最低 1 GB,而 4GB 总地址空间的高 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
函数,因为他们可以为我们检查它,如果用户空间寻址的页面不是常驻的,它会直接为我们修复它。
系统调用的实现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); }