在学习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. bzoj1069-最大土地面积

    题目 给定\(n\ (n\le 2000)\)个坐标,求四个坐标使得围起来的四边形面积最大. 分析 最暴力的想法是枚举四个点,然而肯定超时.接着不知道怎么想到中途相遇,然而一点关系都没有.这里用到了一 ...

  2. 【bzoj1707】[Usaco2007 Nov]tanning分配防晒霜 贪心+Treap

    题目描述 奶牛们计划着去海滩上享受日光浴.为了避免皮肤被阳光灼伤,所有C(1 <= C <= 2500)头奶牛必须在出门之前在身上抹防晒霜.第i头奶牛适合的最小和最 大的SPF值分别为mi ...

  3. tcp协议的六个标识位

    6个标识位: URG 紧急指针,告诉接收TCP模块紧要指针域指着紧要数据. ACK 置1时表示确认号(为合法,为0的时候表示数据段不包含确认信息,确认号被忽略. PSH 置1时请求的数据段在接收方得到 ...

  4. 【Codeforces Round #405 ( Div 2)】题解

    Bear and Big Brother 签到题,直接模拟就可以了. Bear and Friendship Condition 满足只能是每个朋友圈中每个人和其他人都是朋友,这样的边数的确定的. 然 ...

  5. BZOJ2434:[NOI2011]阿狸的打字机——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=2434 https://www.luogu.org/problemnew/show/P2414 打字 ...

  6. Unresolved import *** (models) error on Eclipse

    Eclipse version: Oxygen.2 Release (4.7.2) Python version: 3.6 问题:系统提示:from django.db import models 语 ...

  7. 寻找最大连续子序列/Find the max contiguous subsequence

    寻找最大连续子序列 给定一个实数序列X1,X2,...Xn(不需要是正数),寻找一个(连续的)子序列Xi,Xi+1,...Xj,使得其数值之和在所有的连续子序列数值之和中为最大. 一般称这个子序列为最 ...

  8. CSS图片宽度设置百分比 , 高度同宽度相同

    在图片长宽不相等的情况下,想将长宽设置为相等并且自适应屏幕,可以通过 js 的方式进行设置并通过监听 resize 来实时更新,但是这种方式很麻烦. 这里通过 css 来达到我们想要的效果: < ...

  9. JavaScript之typedof,instanceof,Array.isArray()

    typedof value检测对象类型. value instanceof Array检测a对象是否由b对象类型 Array.isArray(value)在两个框架中判断是否为数组类型.

  10. 2015/8/30 Python基础(4):序列操作符

    序列是指成员有序排列,可以通过下标偏移量访问的类型.Python序列包括:字符串.列表和元组.序列的每个元素可以指定一个偏移量得到,多个元素是通过切片操作得到的.下标偏移量从0开始计数到总数-1结束. ...