在学习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. httpservlet在创建实例对象时候默认调用有参数的init方法 destroy()方法 service方法, 父类的init方法给子类实例一个config对象

  2. bzoj1968 约数研究

    题意 令\(f(i)\)表示\(i\)的约数个数,求\(\Sigma^n_{i=1}f(i)\). 做法 我们直接算每个数的贡献,问题可以转化成每个数在\(1\)到\(n\)中有多少个倍数,累加答案. ...

  3. 【bzoj4444】[Scoi2015]国旗计划 倍增

    题目描述 给出一个圈和若干段,问:对于所有的 $i$ ,选择第 $i$ 段的情况下,最少需要选择多少段(包括第 $i$ 段)能够覆盖整个圈? 输入 第1行,包含2个正整数N,M,分别表示边防战士数量和 ...

  4. 前端基础:JavaScript介绍

    JavaScript介绍 一.JavaScript简介 1.在1995年时,由Netscape公司的Brendan Eich,在网景导航者浏览器上首次设计实现二层,因为Netscape与Sun合作,N ...

  5. 封装一个jquery库

    现在Javascript库海量,流行的也多,比如jQuery,YUI等,虽然功能强大,但也是不万能的,功能不可能涉及方方面面,自己写一个的JS库是对这些的补充,很多也比较实用,把应用到项目中中去也比较 ...

  6. BZOJ4735 你的生命已如风中残烛(组合数学)

    将每个位置上的数都-1,则显然相当于前缀和始终非负. 然后就是完全想不到的了.考虑往里面加一张-1的牌.假设在一个合法排列的最后添上一个-1,那么在该排列的所有循环同构排列中,满足前m个前缀和都非负的 ...

  7. HDU5696:区间的价值——题解

    http://acm.hdu.edu.cn/showproblem.php?pid=5696 题面是中文的我就不粘贴过来了…… ———————————————————————— 这题垃圾题!!神tm卡 ...

  8. linux 文件检索操作

    linux命令太多了,作为一个后端开发人员,常用的也就这几个 uname -a 查看版本 tail tail -f /data/wealth-consignment-service/logs/stat ...

  9. Ark组件[转]

    Ark组件简介 Ark组件是基于.NET 4.0框架开发的基础组件,封装了一些常用的功能方法,并提供了若干程序开发的基础框架. HttpSession简介 HttpSession是Ark组件中负责HT ...

  10. 多线程中Local Store Slot(本地存储槽)[转]

    1. 使用ThreadStatic特性 ThreadStatic特性是最简单的TLS使用,且只支持静态字段,只需要在字段上标记这个特性就可以了: [ThreadStatic]   static str ...