copy_{to,from}_user Vs memcpy

简介: 熟悉Linux内核的开发人员都知道,Linux下的进程地址空间分为内核空间和用户空间,对于32bit系统来说,典型的空间划分为:1G(内核空间)+3G(用户空间)

Linux地址空间


熟悉Linux内核的开发人员都知道,Linux下的进程地址空间分为内核空间和用户空间,对于32bit系统来说,典型的空间划分为:1G(内核空间)+3G(用户空间),对于这种划分来说,内核空间地址范围:0xC000 0000 ~0xFFFF FFFF,用户空间地址范围为:0x0000 0000 ~ 0xBFFF FFFF。当然,为了需要,我们可以将地址空间配置成其他方式,比如2G:2G等等。


网络异常,图片无法展示
|

# Linux虚拟地址机制


大家知道,Linux进程中使用的地址是虚拟地址,进程在操作这些地址时,MMU通过页表完成虚拟地址物理地址之间的映射,如果页表miss,就触发缺页中断,然后,内核通过缺页异常处理机制分配可用的物理页,更新关于虚拟地址和物理地址页表项,从而完成整个虚拟地址的操作过程。


网络异常,图片无法展示
|


简单明确了Linux系统关于地址空间的管理机制之后,下面,就来具体讨论一下今天的主题:在内核中,如何实现内核空间和用户空间的数据交互。


内核APIs


内核提供了用于内核空间和用户空间数据相互拷贝的API:


  • access_ok:用于检查用户空间指针的有效性;


  • get_user:用于从用户空间拷贝一个简单的变量到内核空间,比如,1、2、4、8字节的变量。


  • put_user:用于从内核空间拷贝一个简单的变量到用户空间。


  • copy_to_user:用于从内核空间拷贝一个块数据到用户空间。


  • copy_from_user:用于从用户空间拷贝一块数据到内核空间。


上面这些API一般都和具体的CPU架构相关,比如,X86架构相关的实现都在/linux/arch/x86/include/asm/uaccess.h和/linux/arch/x86/lib/usercopy_32.c、usercopy_64.c这些文件中。


get/put_user与copy_(to,from}_user的区别是所拷贝的数据类型不同,前者用于简单的数据,比如,int、long、char等,而后者用于一整块数据的拷贝。


网络异常,图片无法展示
|


网络异常,图片无法展示
|


下面详细的讲解一下上述各个API的具体作用。


access_ok


函数的原型如下:


access_ok(type, addr, size)


  • type: VERIFY_WRITE、VERIFY_READ检查一段地址区域是否可读或者可写。


  • 用于指定检查以addr为起始地址,长度为size为的用户空间是否可读或者可写。


  • 如果可正常访问,函数返回0,否则返回-EFAULT。


  • 该函数仅用于检测用户空间,不用于内核空间。


get/put_user


函数原型如下: get/_user( x, ptr ); put/_user( x, ptr );


get_user和put_user用于在内核空间和用户空间拷贝一个简单的数据,对于像结构体这样的数据需要使用copy_{to,from}_user。access_ok函数会检测ptr指针的有效性,之后,通过get_user_x或者 put_user_x完成数据拷贝。相对于copy_{to,from}_user,对于小型数据的拷贝,这两个函数的性能更好。两个函数都是执行成功之后返回0。


copy_{to, from}_user


函数的原型如下:


copy_to_user(to, from, n)
copy_from_user(to, from, n)


两个函数用于在内核空间和用户空间之间拷贝数据,这个函数都是成功时返回0,失败时返回大于0的数据,表明未能拷贝成功的字节个数。两个函数都是通过access_ok来检测用户空间的地址的有效性,之后的数据拷贝实现,与CPU的体系结构有关,比如,ARM平台下,


_copy_from_user(void *to, const void __user *from, unsigned long n)
{
  unsigned long res = n;
  might_fault();
  if (likely(access_ok(VERIFY_READ, from, n))) {
    kasan_check_write(to, n);
    res = raw_copy_from_user(to, from, n);
  }
  if (unlikely(res))
    memset(to + (n - res), 0, res);
  return res;
}


最终会调用arm平台下的raw_copy_from_user:


raw_copy_to_user(void __user *to, const void *from, unsigned long n)
{
#ifndef CONFIG_UACCESS_WITH_MEMCPY
  unsigned int __ua_flags;
  __ua_flags = uaccess_save_and_enable();
  n = arm_copy_to_user(to, from, n);
  uaccess_restore(__ua_flags);
  return n;
#else
  return arm_copy_to_user(to, from, n);
#endif
}


copy_{to,from}_user Vs memcpy


还有一个经常被误用的API,那就是,memcpy,大家可能听过,或者看过memcpy不能用于用户空间和内核空间之间的数据交互,那原因是什么呢?本节将会详细的讲解copy_{to,from}_user与memcpy的区别和联系。


之所以在进行内核空间与用户空间拷贝时,使用copy_{to,from}_user,主要有两个原因:


  • copy*函数会通过access_ok检测用户空间地址有效性。这样可以防止内核操作非法的地址,比如,用户传过来的是内核空间的地址,如果内核检测该地址,那么就会写坏该地址指向的内容,造成系统崩溃。同时,也会系统安全问题,比如,黑客会故意传入一个内核地址,并且这个地址保存着关键信息,比如用户id,内核在没有检测的情况下,会修改地址中的内容,比如,将用户id写为0,那么黑客就可以获取系统的最高权限。参考CVE-2017-5123内核漏洞


  • copy*函数可以在发生page fault时,进行自我修复。所谓的page fault时,是说用户空间的地址是一个非法的地址(非栈,非堆地址),这是内核在访问该地址时,MMU会检测到该地址的非法性,从而产生一个异常。对于该异常,内核有两种处理方式:


  • 向当前进行发送SIGSEGV信号,并抛出Oops信息,如果内核开启了/proc/sys/kernel/panic_on_oops,那么内核直接panic。
  • 处理该异常,正常返回。


  • 如果这时使用的是copy_{to,from}_user函数的话,其会捕获该异常,并正常返回到用户空间。但是如果使用的是memcpy的话,对不起,内核直接Oops或者panic。


  • 为了更加安全,硬件加入了PAN功能(Privileged Access Never),其可以限制内核访问用户空间的能力,所以,访问之前必须通过硬件指令关闭PAN,访问完之后,开启PAN,ARM V8架构增加了这项功能。只有copy_{to,from}_user函数具有打开和关闭PAN的能力,所以,对于开启了PAN功能的平台可,copy_{to,from}_user是唯一的选择。


所以,对于一个合格的内核开发人员,在涉及到用户空间和内核空间数据拷贝的场景时,杜绝使用memcpy是避免出现bugs的首要注意事项。



目录
打赏
0
0
0
0
2
分享
相关文章
什么是NUMA,我们为什么要了解NUMA
在IA多核平台上进行开发时,我们经常会提到NUMA这个词 ,那么NUMA到底指的是什么?我们怎么可以感受到它的存在?以及NUMA的存在对于我们编程会有什么影响
什么是NUMA,我们为什么要了解NUMA
【玩转AIGC系列】AIGC文本生成视频
本文介绍如何使用GPU云服务器搭建Stable Diffusion模型,并基于ModelScope框架,实现使用文本生成视频。
【玩转AIGC系列】AIGC文本生成视频
SQL 中删除超出时间限制的数据,并返回删除数据信息(Mybatis+postgresql)
前言 前一阵子,接到一个活,主要内容是这样的,数据库中存在一些过期的日志(可能是一天前的数据,或者是一个月前的数据,等等),将这些数据删除掉,并且返回这些数据的信息(就是说我得知道自己到底删了哪些数据呀?)。
2144 0
Webpack技术深度解析:模块打包与性能优化
【10月更文挑战第13天】Webpack技术深度解析:模块打包与性能优化
​Lindorm/HBase增强版技术解密|每秒7亿次请求,阿里新一代数据库如何支撑?
Lindorm,就是云操作系统飞天中面向大数据存储处理的重要组成部分。Lindorm是基于HBase研发的、面向大数据领域的分布式NoSQL数据库,集大规模、高吞吐、快速灵活、实时混合能力于一身,面向海量数据场景提供世界领先的高性能、可跨域、多一致、多模型的混合存储处理能力。目前,Lindorm已经全面服务于阿里经济体中的大数据结构化、半结构化存储场景。
4698 2
​Lindorm/HBase增强版技术解密|每秒7亿次请求,阿里新一代数据库如何支撑?
Long Story of Block - segment
## segment segment 的概念实际来自 DMA controller,DMA controller 可以实现一段内存物理地址区间与一段设备物理地址区间之间的数据拷贝,segment 就描述 DMA 数据传输过程中的一段连续的内存空间,也就是说 DMA controller 可以将内存中一个 segment 中的数据拷贝到设备,或将设备中的数据拷贝到 segment 中 s
996 1
Long Story of Block - segment
PolarDB Proxy配置与优化:提升数据库访问效率
【9月更文挑战第6天】PolarDB是阿里云推出的高性能分布式关系型数据库,PolarDB Proxy作为其关键组件,位于客户端与PolarDB集群间,负责SQL请求的解析与转发,并支持连接池管理、SQL过滤及路由规则等功能。本文详细介绍了PolarDB Proxy的配置方法,包括连接池、负载均衡和SQL过滤设置,并探讨了监控调优、缓存及网络优化策略,以帮助提升数据库访问效率。
169 1

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问