在学习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. deep learning3

    9.3.Restricted Boltzmann Machine (RBM)受限玻尔兹曼基 假设有一个二部图,每一层的节点之间没有链接,一层是可视层,即输入数据层(v),一层是隐藏层(h),如果假设所 ...

  2. BZOJ 1202 狡猾的商人(带权并查集)

    给出了l,r,w.我们就得知了s[r]-s[l-1]=w.也就是说,点l-1和点r的距离为w. 于是可以使用带权并查集,定义dis[i]表示点i到根节点的距离.查询和合并的时候维护一下就OK了. 如果 ...

  3. 51NOD 1149:Pi的递推式——题解

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1149 F(x) = 1 (0 <= x < 4) F(x) ...

  4. 学习web安全之--初识安全

    随笔:随着互联网行业的飞速发展,互联网行业可谓日新月异,然而在繁华的背后,大多的互联网公司对于网络安全还是处于无重视,不作为的阶段,而作为一个程序员,如果也对信息安全视而不见的话,那将是这个公司的噩梦 ...

  5. 使用rsyslog编程,产生trace信息,记录到日志中

    参考:http://blog.csdn.net/nowayings/article/details/38926501 https://www.cnblogs.com/bonelee/p/6234647 ...

  6. Linux之初试驱动20160613

    这篇文章主要介绍一下Linux内核下的驱动结构与书写,以及介绍Linux下简单使用驱动的应用程序: 首先我们直接看使用驱动的简单应用程序: #include <sys/types.h> # ...

  7. hdu 5621

    KK's Point Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total ...

  8. [codeforces/edu2]总结(F)

    链接:http://codeforces.com/contest/600 A题: 字符串处理. B题: sort+upper_bound C题: 统计一下每种字符的个数,然后贪心. (1) 如果没有奇 ...

  9. 【java】AES加密解密|及Base64的使用

    转载自:http://www.cnblogs.com/arix04/archive/2009/10/15/1511839.html AES加解密算法,使用Base64做转码以及辅助加密: packag ...

  10. maven项目执行run as/maven install时提示找不到包

    选中项目,右键 右键项目->MAVEN->Update Project,如下图 点击ok,clean相关项目,再打包.如果还是不行看一下你jdk 的版本和你编译的版本是否一致