在学习Linux内核驱动的时候,一开始就会碰到copy_from_user和copy_to_user这两个常用的函数。这两个函数在内核使用的非常频繁,负责将数据从用户空间拷贝到内核空间以及将数据从内核空间拷贝到用户空间。在4年半前初学Linux内核驱动程序的时候,我只是知道这个怎么用,并没有很深入的分析这两个函数。这次研究内核模块挂载的时候,又碰到了它们。决定还是认真跟踪一下函数。
 
首先这两个函数的原型在arch/arm/include/asm/uaccess.h文件中:

  1. static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
  2. {
  3. if (access_ok(VERIFY_READ, from, n))
  4. n = __copy_from_user(to, from, n);
  5. else /* security hole - plug it */
  6. memset(to, 0, n);
  7. return n;
  8. }
  9. static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
  10. {
  11. if (access_ok(VERIFY_WRITE, to, n))
  12. n = __copy_to_user(to, from, n);
  13. return n;
  14. }
这两个函数从结构上来分析,其实都可以分为两个部分:
1、首先检查用户空间的地址指针是否有效(难点)
2、调用__copy_from_user和__copy_to_user函数
 
在这个分析中,我们先易后难。首先看看具体数据拷贝功能的__copy_from_user和__copy_to_user函数
 
对于ARM构架,没有单独实现这两个函数,所以他们的代码位于include/asm-generic/uaccess.h

  1. /*
  2. * 带有MMU的构架应该覆盖这两个函数
  3. */
  4. #ifndef __copy_from_user
  5. static inline __must_check long __copy_from_user(void *to,
  6. const void __user * from, unsigned long n)
  7. {
  8. if (__builtin_constant_p(n)) {
  9. switch(n) {
  10. case 1:
  11. *(u8 *)to = *(u8 __force *)from;
  12. return 0;
  13. case 2:
  14. *(u16 *)to = *(u16 __force *)from;
  15. return 0;
  16. case 4:
  17. *(u32 *)to = *(u32 __force *)from;
  18. return 0;
  19. #ifdef CONFIG_64BIT
  20. case 8:
  21. *(u64 *)to = *(u64 __force *)from;
  22. return 0;
  23. #endif
  24. default:
  25. break;
  26. }
  27. }
  28. memcpy(to, (const void __force *)from, n);
  29. return 0;
  30. }
  31. #endif
  32. #ifndef __copy_to_user
  33. static inline __must_check long __copy_to_user(void __user *to,
  34. const void *from, unsigned long n)
  35. {
  36. if (__builtin_constant_p(n)) {
  37. switch(n) {
  38. case 1:
  39. *(u8 __force *)to = *(u8 *)from;
  40. return 0;
  41. case 2:
  42. *(u16 __force *)to = *(u16 *)from;
  43. return 0;
  44. case 4:
  45. *(u32 __force *)to = *(u32 *)from;
  46. return 0;
  47. #ifdef CONFIG_64BIT
  48. case 8:
  49. *(u64 __force *)to = *(u64 *)from;
  50. return 0;
  51. #endif
  52. default:
  53. break;
  54. }
  55. }
  56. memcpy((void __force *)to, from, n);
  57. return 0;
  58. }
  59. #endif

点击(此处)折叠或打开

  1. GCC的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数值是常数,函数返回 1,否则返回 0。
    从这两个函数中可以看出其实结构是一样的,首先看看n是不是常数,如果是并为1、2、4、8(64bit)则直接就用一个赋值语句拷贝数据。如果不是常数或n过大,则使用memcpy函数。而这个memcpy函数位于lib/string.c:

  1. #ifndef __HAVE_ARCH_MEMCPY
  2. /**
  3. * memcpy - Copy one area of memory to another
  4. * @dest: Where to copy to
  5. * @src: Where to copy from
  6. * @count: The size of the area.
  7. *
  8. * You should not use this function to access IO space, use memcpy_toio()
  9. * or memcpy_fromio() instead.
  10. */
  11. void *memcpy(void *dest, const void *src, size_t count)
  12. {
  13. char *tmp = dest;
  14. const char *s = src;
  15. while (count--)
  16. *tmp++ = *s++;
  17. return dest;
  18. }
  19. EXPORT_SYMBOL(memcpy);
  20. #endif
这个函数其实就是一个简单的利用循环来数据拷贝,非常简单。
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
好了如何拷贝数据我们已经了解了,现在我们来看看前面的用户空间指针检测函数access_ok,这其实是一个宏定义,位于arch/arm/include/asm/uaccess.h文件中:

  1. /* We use 33-bit arithmetic here... */
  2. #define __range_ok(addr,size) ({ \
  3. unsigned long flag, roksum; \
  4. __chk_user_ptr(addr); \
  5. __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
  6. : "=&r" (flag), "=&r" (roksum) \
  7. : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
  8. : "cc"); \
  9. flag; })
  10. ......
  11. #define access_ok(type,addr,size) (__range_ok(addr,size) == 0)
  12. ......

这个就比较麻烦了,涉及到了C语言中内联汇编,如果还不熟悉的朋友可以看看《ARM GCC 内嵌汇编手册》,我也不是很熟。

现在我们来仔细分析__range_ok这个宏:

(1)unsigned long flag, roksum;\\定义两个变量

  1. flag:保存结果的变量:非零代表地址无效,零代表地址可以访问。初始存放非零值(current_thread_info()->addr_limit),也就是当前进程的地址上限值。
  2. roksum:保存要访问的地址范围末端,用于和当前进程地址空间限制数据做比较

(2)__chk_user_ptr(addr);\\定义是一个空函数

但是这个函数涉及到__CHECKER__宏的判断,__CHECKER__宏在通过Sparse(Semantic Parser for C)工具对内核代码进行检查时会定义的。在使用make C=1或C=2时便会调用该工具,这个工具可以检查在代码中声明了sparse所能检查到的相关属性的内核函数和变量。

如果定义了__CHECKER__,在网上的资料中这样解释的:__chk_user_ptr和__chk_io_ptr在这里只声明函数,没有函数体,目的就是在编译过程中Sparse能够捕捉到编译错误,检查参数的类型。

如果没有定义__CHECKER__,这就是一个空函数。

(3)接下来的汇编,我适当地翻译如下:

adds %1, %2, %3

roksum = addr + size 这个操作影响状态位(目的是影响是进位标志C)

以下的两个指令都带有条件CC,也就是当C=0的时候才执行。

如果上面的加法指令进位了(C=1),则以下的指令都不执行,flag就为初始值current_thread_info()->addr_limit(非零值),并返回。

如果没有进位(C=0),就执行下面的指令

    sbcccs %1, %1, %0 

roksum = roksum - flag,也就是(addr + size)- (current_thread_info()->addr_limit),操作影响符号位。

如果(addr + size)>=(current_thread_info()->addr_limit),则C=1

如果(addr + size)<(current_thread_info()->addr_limit),则C=0

 
当C=0的时候执行以下指令,否则跳过(flag非零)。
    movcc %0, #0
    flag = 0,给flag赋值0

(4)flag; 

返回flag值

综上所诉:__range_ok宏其实等价于:

如果(addr + size)>=(current_thread_info()->addr_limit),返回非零值

如果(addr + size)<(current_thread_info()->addr_limit),返回零

而access_ok就是检验将要操作的用户空间的地址范围是否在当前进程的用户地址空间限制中。这个宏的功能很简单,完全可以用C实现,不是必须使用汇编。个人理解:由于这两个函数使用频繁,就使用汇编来实现部分功能来增加效率。

 

    从这里再次可以认识到,copy_from_user与copy_to_user的使用是结合进程上下文的,因为他们要访问“user”的内存空间,这个“user”必须是某个特定的进程。通过上面的源码就知道,其中使用了current_thread_info()来检查空间是否可以访问。如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用。如果在中断上下文中使用了,那代码就很可能操作了根本不相关的进程地址空间。

    其次由于操作的页面可能被换出,这两个函数可能会休眠,所以同样不可在中断上下文中使用。

基于ARM 构架(带MMU)的copy_from_user与copy_to_user详细分析的更多相关文章

  1. 在Azure New Portal上创建基于ARM的带SLB的VM

    目前Azure的New Portal在国内已经上线了.本文将介绍最常见的一种场景:通过Azure的New Portal创建带有Server Load Balance的多台虚拟机. 1 创建Resour ...

  2. 基于ARM处理器的反汇编器软件简单设计及实现

    写在前面 2012年写的毕业设计,仅供参考 反汇编的目的 缺乏某些必要的说明资料的情况下, 想获得某些软件系统的源代码.设计思想及理念, 以便复制, 改造.移植和发展: 从源码上对软件的可靠性和安全性 ...

  3. Linux 内核高-低端内存设置代码跟踪(ARM构架)

    对于ARM中内核如何在启动的时候设置高低端内存的分界线(也是逻辑地址与虚拟地址分界线(虚拟地址)减去那个固定的偏移),这里我稍微引导下(内核分析使用Linux-3.0): 首先定位设置内核虚拟地址起始 ...

  4. 课程设计小组报告——基于ARM实验箱的捕鱼游戏的设计与实现

    课程设计小组报告--基于ARM实验箱的捕鱼游戏的设计与实现 一.任务简介 1.1 任务内容 捕鱼游戏这个项目是一个娱乐性的游戏开发,该游戏可以给人们带来娱乐的同时还可以给人感官上的享受,所以很受人们的 ...

  5. 基于ARM的SoC设计入门[转]

    原文:基于ARM的SoC设计入门 我们跳过所有对ARM介绍性的描述,直接进入工程师们最关心的问题.要设计一个基于ARM的SoC,我们首先要了解一个基于ARM的SoC的结构.图1是一个典型的SoC的结构 ...

  6. 基于jquery左侧带选项卡切换的焦点图

    今天给大家分享一款基于jquery左侧带选项卡切换的焦点图.这款焦点图左侧有短标题,单击切换并显示长标题.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div class ...

  7. 基于ARM的车牌识别技术研究与实现

    在云盘里包含了我本科毕业设计的全部资料和代码.主要涉及下面摘要中的几个部分.虽然系统无法实用,但是适合机器视觉和嵌入式方向的入门.希望能对有志从事相关方向的朋友有所帮助.本人现在在深圳从事机器视觉算法 ...

  8. 课程设计个人报告——基于ARM实验箱的捕鱼游戏的设计与实现

    课程设计个人报告--基于ARM实验箱的捕鱼游戏的设计与实现 一.个人贡献 参与课设题目讨论及部分过程 资料收集 负责代码调试 捕鱼游戏相应功能的实现 实验环境 Eclipse软件开发环境: ARM实验 ...

  9. 浅析基于ARM的Linux下的系统调用的实现

    在Linux下系统调用是用软中断实现的,下面以一个简单的open例子简要分析一下应用层的open是如何调用到内核中的sys_open的. t8.c 1: #include <stdio.h> ...

随机推荐

  1. 第45天:2017webstrom下载破解汉化

    1.webstrom 11.0.3下载地址1:http://pan.baidu.com/s/1kVQjcwf 密码:uggr 下载地址2:http://pan.baidu.com/s/1kVQjcwf ...

  2. 【数据库】Sql Server备份还原脚本

    USE master RESTORE DATABASE 新建的没有任何数据的数据库名 FROM DISK = 'e:\数据库备份文件.bak' WITH MOVE '原来的逻辑名称' TO 'e:\新 ...

  3. 【Python】Python之文件操作

    1. file=open('xxx.txt', encoding='utf-8'),open()函数是Python内置的用于对文件的读写操作,返回的是文件的流对象(而不是文件本身,所以使用的方法都是流 ...

  4. Access Denied for user root @localhost 解决方案

    问题描述: C:\Users\bo.wang> mysql -u root -p Enter password: ERROR 1045 (28000): Access denied for us ...

  5. FLT_MIN,FLT_MAX,FLT_EPSILON

    FLT_MIN,FLT_MAX,FLT_EPSILON  * min positive value */最小的正值#define FLT_MIN 1.175494351e-38F /* max val ...

  6. CentOS scp远程拷贝

    scp(secure copy)是一个基于 SSH 协议在网络之间进行安全传输的命令, 其格式为“scp [参数] 本地文件 远程帐户@远程 IP 地址:远程目录”. 1.主要参数 -v 显示详细的连 ...

  7. CentOS ACL

    ACL:访问控制列表(Access Control List). 一般来说权限是针对某一类用户设置的.例如:一个文件只有拥有者.组.其他用户三种设置方式,如果希望对某个指定的用户进行单独的权限控制,就 ...

  8. bzoj 3275: Number (最小割)

    题目的意思是要选一些数,但是这些数如果满足两个条件的话就不能一起被选. type arr=record toward,next,cap:longint; end; const maxn=; maxm= ...

  9. ZOJ3874 Permutation Graph 【分治NTT】

    题目链接 ZOJ3874 题意简述: 在一个序列中,两点间如果有边,当且仅当两点为逆序对 给定一个序列的联通情况,求方案数对\(786433\)取模 题解 自己弄了一个晚上终于弄出来了 首先\(yy\ ...

  10. apply的理解和数组降维

    func.apply(thisObj, [argArray] ); apply方法用来改变函数执行时的this指向,后面的参数是一个类数组对象,可以是数组,arguments,甚至一个有length属 ...