
用户态与内核态数据交互的理解我们知道在类似Linux这样严格划分用户态和内核态的系统上用户程序和内核交互数据需要使用内核提供的专用接口copy_from_user/copy_to_user而不能使用通用的memcpy接口这里我们来分析一下这种情况以加深对这种机制的理解。我们以下面的交互场景为例用户程序通过某个驱动程序的read/write接口与驱动程序交互数据当处于用户态的应用程序调用系统调用Read/Write来读写外设数据时内核态中驱动注册的读写函数会被调用此时驱动程序中就需要使用copy_from_user/copy_to_user来进行用户态和内核态的数据交互我们以copy_from_user为例unsigned long copy_from_user(void *to, const void *from, unsigned long n);我们重点关注函数参数to和fromto目标地址内核空间from: 源地址用户空间这里有一个问题在linux这种多进程系统上to和from都是虚拟地址要访问这两个虚拟地址对应的物理地址就需要知道他们的映射表from是属于用户进程的地址空间的它的映射表由用户进程维护而to是属于内核的地址空间它的映射表由内核维护copy_from_user如何同时访问这两个映射表从而获取到相应的物理地址呢在64位Linux系统中用户态和内核态的虚拟地址空间这样划分的用户态地址空间User0x0000_0000_0000_0000 ~ 0x0000_FFFF_FFFF_FFFF内核态地址空间Kernel0xFFFF_0000_0000_0000 ~ 0xFFFF_FFFF_FFFF_FFFF每个用户进程有独立的用户态虚拟地址空间每个用户进程维护自己的内存映射表将自己的虚拟地址映射到唯一的物理地址上而内核态虚拟地址空间是所有进程共享的其内存映射页表是唯一且全局的用户地址空间和内核地址空间没有重叠的部分两者完全隔离。另一个需要注意的点是驱动程序中的函数被调用时其实是处在用户进程上下文的只是陷入到内核态拥有访问内核资源的权限而已所以当驱动中的copy_from_user被调用时其可以获取当前进程即用户进程的内存映射表从而可以访问到用户内存我们以下图为例进程A和B触发copy_from_user调用时分别使用各自的内存映射表去获取处于用户地址空间的源地址对应的物理地址但是使用公有的内核的内存映射表去获取处于内核地址空间的目的地址对应的物理地址通过这种方式来完成用户态和内核态的同时寻址及后续的访存操作。现在我们思考另一个问题copy_from_user被调用时是处于内核态也即是特权模式这种模式下进程是拥有所有内存的访问权限的那为什么还要使用copy_from_user而不能直接使用memcpy呢这是因为copy_from_user 不是为了 “权限”而是为了安全、正确、稳定地完成跨地址空间拷贝在内核态和用户态的内存访问处理存在一些差异所以在内核态进行内存拷贝时需要一些特殊的处理比如内核态不能处理用户态的缺页异常如果用户态传入的地址未被映射需要依靠缺页异常来分配直接使用memcpy会导致内核崩溃这种情况必须要使用copy_from_user来处理内存页的分配总结来说内核的copy_from_user提供了内核态下相比memcpy更安全更稳定的内存拷贝方法。