linux内核中的get_user和put_user

发布者:泉趣人最新更新时间:2016-03-02 来源: eefocus关键字:linux内核  get_user  put_user 手机看文章 扫描二维码
随时随地手机看文章
内核版本:2.6.14

CPU平台:arm

 

在内核空间和用户空间交换数据时,get_user和put_user是两个两用的函数。相对于copy_to_user和copy_from_user(将在另一篇博客中分析),这两个函数主要用于完成一些简单类型变量(char、int、long等)的拷贝任务,对于一些复合类型的变量,比如数据结构或者数组类型,get_user和put_user函数还是无法胜任,这两个函数内部将对指针指向的对象长度进行检查,在arm平台上只支持长度为1,2,4,8的变量。下面我具体分析,首先看get_user的定义(linux/include/asm-arm/uaccess.h):

 

[plain] view plain copy
 
 print?
  1. extern int __get_user_1(void *);  
  2. extern int __get_user_2(void *);  
  3. extern int __get_user_4(void *);  
  4. extern int __get_user_8(void *);  
  5. extern int __get_user_bad(void);  
  6.   
  7. #define __get_user_x(__r2,__p,__e,__s,__i...)               \  
  8.        __asm__ __volatile__ (                   \  
  9.         __asmeq("%0", "r0") __asmeq("%1", "r2")         \ //进行判断(#define __asmeq(x, y)  ".ifnc " x "," y " ; .err ; .endif\n\t")  
  10.         "bl __get_user_" #__s               \ //根据参数调用不同的函数,此时r0=指向用户空间的指针,r2=内核空间的变量  
  11.         : "=&r" (__e), "=r" (__r2)              \  
  12.         : "0" (__p)                     \  
  13.         : __i, "cc")  
  14.   
  15. #define get_user(x,p)                           \  
  16.     ({                              \  
  17.         const register typeof(*(p)) __user *__p asm("r0") = (p);\ //__p的数据类型和*(p)的指针数据类型是一样的,__p = p,且存放在r0寄存器中  
  18.         register typeof(*(p)) __r2 asm("r2");           \ //__r2的数据类型和*(p)的数据类型是一样的,且存放在r2寄存器中  
  19.         register int __e asm("r0");             \ //定义__e,存放在寄存器r0,作为返回值  
  20.         switch (sizeof(*(__p))) {               \ //对__p所指向的对象长度进行检查,并根据长度调用响应的函数  
  21.         case 1:                         \  
  22.             __get_user_x(__r2, __p, __e, 1, "lr");      \  
  23.                 break;                      \  
  24.         case 2:                         \  
  25.             __get_user_x(__r2, __p, __e, 2, "r3", "lr");    \  
  26.             break;                      \  
  27.         case 4:                         \  
  28.                 __get_user_x(__r2, __p, __e, 4, "lr");      \  
  29.             break;                      \  
  30.         case 8:                         \  
  31.             __get_user_x(__r2, __p, __e, 8, "lr");      \  
  32.                 break;                      \  
  33.         default: __e = __get_user_bad(); break;         \ //默认处理  
  34.         }                           \  
  35.         x = __r2;                       \  
  36.         __e;                            \  
  37.     })  

 

上面的源码涉及到gcc的内联汇编,不太了解的朋友可以参考前面的博客(http://blog.csdn.net/ce123/article/details/8209702)。继续,跟踪__get_user_1等函数的执行,它们的定义如下(linux/arch/arm/lib/getuser.S)。

 

[plain] view plain copy
 
 print?
  1. .global __get_user_1  
  2. __get_user_1:  
  3. 1:  ldrbt   r2, [r0]  
  4.     mov r0, #0  
  5.     mov pc, lr  
  6.   
  7.     .global __get_user_2  
  8. __get_user_2:  
  9. 2:  ldrbt   r2, [r0], #1  
  10. 3:  ldrbt   r3, [r0]  
  11. #ifndef __ARMEB__  
  12.     orr r2, r2, r3, lsl #8  
  13. #else  
  14.     orr r2, r3, r2, lsl #8  
  15. #endif  
  16.     mov r0, #0  
  17.     mov pc, lr  
  18.   
  19.     .global __get_user_4  
  20. __get_user_4:  
  21. 4:  ldrt    r2, [r0]  
  22.     mov r0, #0  
  23.     mov pc, lr  
  24.   
  25.     .global __get_user_8  
  26. __get_user_8:  
  27. 5:  ldrt    r2, [r0], #4  
  28. 6:  ldrt    r3, [r0]  
  29.     mov r0, #0  
  30.     mov pc, lr  
  31.   
  32. __get_user_bad_8:  
  33.     mov r3, #0  
  34. __get_user_bad:  
  35.     mov r2, #0  
  36.     mov r0, #-EFAULT  
  37.     mov pc, lr  
  38.   
  39. .section __ex_table, "a"  
  40.     .long   1b, __get_user_bad  
  41.     .long   2b, __get_user_bad  
  42.     .long   3b, __get_user_bad  
  43.     .long   4b, __get_user_bad  
  44.     .long   5b, __get_user_bad_8  
  45.     .long   6b, __get_user_bad_8  
  46. .previous  

这段代码都是单条汇编指令实现的内存操作,就不进行详细注解了。如果定义__ARMEB__宏,则是支持EABI的大端格式代码(http://blog.csdn.net/ce123/article/details/8457491),关于大端模式和小端模式的详细介绍,可以参考http://blog.csdn.net/ce123/article/details/6971544。这段代码在.section __ex_table, "a"之前都是常规的内存拷贝操纵,特殊的地方在于后面定义“__ex_table”section 。

 

标号1,2,...,6处是内存访问指令,如果mov的源地址位于一个尚未被提交物理页面的空间中,将产生缺页异常,内核会调用do_page_fault函数处理这个异常,因为异常发生在内核空间,do_page_fault将调用search_exception_tables在“ __ex_table”中查找异常指令的修复指令,在上面这段带面的最后,“__ex_table”section 中定义了如下数据:

 

[plain] view plain copy
 
 print?
  1. .section __ex_table, "a"  
  2.     .long   1b, __get_user_bad //其中1b对应标号1处的指令,__get_user_bad是1处指令的修复指令。  
  3.     .long   2b, __get_user_bad  
  4.     .long   3b, __get_user_bad  
  5.     .long   4b, __get_user_bad  
  6.     .long   5b, __get_user_bad_8  
  7.     .long   6b, __get_user_bad_8  
当标号1处发生缺页异常时,系统将调用do_page_fault提交物理页面,然后跳到__get_user_bad继续执行。get_user函数如果成果执行则返回1,否则返回-EFAULT。

 

put_user用于将内核空间的一个简单类型变量x拷贝到p所指向的用户空间。该函数可以自动判断变量的类型,如果执行成功则返回0,否则返回-EFAULT。下面给出它们的定义(linux/include/asm-arm/uaccess.h)。

 

[plain] view plain copy
 
 print?
  1. extern int __put_user_1(void *, unsigned int);  
  2. extern int __put_user_2(void *, unsigned int);  
  3. extern int __put_user_4(void *, unsigned int);  
  4. extern int __put_user_8(void *, unsigned long long);  
  5. extern int __put_user_bad(void);  
  6.   
  7. #define __put_user_x(__r2,__p,__e,__s)                  \  
  8.        __asm__ __volatile__ (                   \  
  9.         __asmeq("%0", "r0") __asmeq("%2", "r2")         \  
  10.         "bl __put_user_" #__s               \  
  11.         : "=&r" (__e)                       \  
  12.         : "0" (__p), "r" (__r2)                 \  
  13.         : "ip", "lr", "cc")  
  14.   
  15. #define put_user(x,p)                           \  
  16.     ({                              \  
  17.         const register typeof(*(p)) __r2 asm("r2") = (x);   \  
  18.         const register typeof(*(p)) __user *__p asm("r0") = (p);\  
  19.         register int __e asm("r0");             \  
  20.         switch (sizeof(*(__p))) {               \  
  21.         case 1:                         \  
  22.             __put_user_x(__r2, __p, __e, 1);        \  
  23.             break;                      \  
  24.         case 2:                         \  
  25.             __put_user_x(__r2, __p, __e, 2);        \  
  26.             break;                      \  
  27.         case 4:                         \  
  28.             __put_user_x(__r2, __p, __e, 4);        \  
  29.             break;                      \  
  30.         case 8:                         \  
  31.             __put_user_x(__r2, __p, __e, 8);        \  
  32.             break;                      \  
  33.         default: __e = __put_user_bad(); break;         \  
  34.         }                           \  
  35.         __e;                            \  
  36.     })  
__put_user_1等函数的的定义如下(linux/arch/arm/lib/putuser.S)。

 

 

[plain] view plain copy
 
 print?
  1. .global __put_user_1  
  2. __put_user_1:  
  3. 1:  strbt   r2, [r0]  
  4.     mov r0, #0  
  5.     mov pc, lr  
  6.   
  7.     .global __put_user_2  
  8. __put_user_2:  
  9.     mov ip, r2, lsr #8  
  10. #ifndef __ARMEB__  
  11. 2:  strbt   r2, [r0], #1  
  12. 3:  strbt   ip, [r0]  
  13. #else  
  14. 2:  strbt   ip, [r0], #1  
  15. 3:  strbt   r2, [r0]  
  16. #endif  
  17.     mov r0, #0  
  18.     mov pc, lr  
  19.   
  20.     .global __put_user_4  
  21. __put_user_4:  
  22. 4:  strt    r2, [r0]  
  23.     mov r0, #0  
  24.     mov pc, lr  
  25.   
  26.     .global __put_user_8  
  27. __put_user_8:  
  28. 5:  strt    r2, [r0], #4  
  29. 6:  strt    r3, [r0]  
  30.     mov r0, #0  
  31.     mov pc, lr  
  32.   
  33. __put_user_bad:  
  34.     mov r0, #-EFAULT  
  35.     mov pc, lr  
  36.   
  37. .section __ex_table, "a"  
  38.     .long   1b, __put_user_bad  
  39.     .long   2b, __put_user_bad  
  40.     .long   3b, __put_user_bad  
  41.     .long   4b, __put_user_bad  
  42.     .long   5b, __put_user_bad  
  43.     .long   6b, __put_user_bad  
  44. .previous  
put_user函数就不具体分析了。get_user和put_user仅能完成一些简单类型变量的拷贝任务,后面我们将分析copy_to_user和copy_from_user。
关键字:linux内核  get_user  put_user 引用地址:linux内核中的get_user和put_user

上一篇:linux内核中的copy_to_user和copy_from_user(一)
下一篇:glibc中的printf如何输出到串口

推荐阅读最新更新时间:2024-03-16 14:45

改善Linux内核实时性方法的研究与实现
   0 引言   由于Linux具有功能强大、源代码开放、支持多种硬件平台、模块化的设计方案以及丰富的开发工具支持等特点,在实际系统中,得到了广泛的应用。但由于其最初的设计目标为通用分时操作系统,对于实时系统而言,Linux仍然存在核心不可抢占、关中断、时钟粒度粗糙等缺陷。为了使其应用于嵌入式系统,实时控制等领域,越来越多的厂家和研究机构热衷于改善其实时性,构建基于Linux的实时操作系统。   在Linux 2.4和以前的版本,内核是不可抢占的,也就是说,如果当前任务运行在内核态,即使当前有更紧急的任务需要运行,当前任务也不能被抢占。因此那个紧急任务必须等到当前任务执行完内核态的操作返回用户态后或当前任务因需要等待某
[嵌入式]
ARMv8 Linux内核源码分析:__flush_dcache_all()
1.1 /* * __flush_dcache_all() * Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY(__flush_dcache_all) //保证之前的访存指令的顺序 dsb sy //读cache level id register mrs x0, clidr_el1 // read clidr //取bits (Level of Coherency for the cache hierarchy.) //需要遵循cache一致性的cac
[单片机]
ARMv8 <font color='red'>Linux内核</font>源码分析:__flush_dcache_all()
ARM linux内核启动时几个关键地址
1.内核启动地址 1.1.名词解释 ZTEXTADDR 解压代码运行的开始地址。没有物理地址和虚拟地址之分,因为此时MMU处于关闭状态。这个地址不一定时RAM的地址,可以是支持读写寻址的flash等存储中介。 Start address of decompressor. here's no point in talking about virtual or physical addresses here, since the MMU will be off at the time when you call the decompressor code. You normally call the kernel at
[单片机]
linux内核启动过程——基于S3C2410
本文以流行的Samsung公司的S3C2410,openmoko平台和linux-2.6.24为例,介绍如何在ZIX嵌入式开发环境下探索linux内核启动过程。 Linux内核启动一般由外部的bootloader引导,也可以在内核头部嵌入一个loader,实际的应用中这两种方式都会经常遇到。所以要了 解内核启动最开始的过程,必须对bootloader如何引导内核有所熟悉。下面我们从u-boot加载linux内核的代码开始分析(关于u-boot 自身的启动流程,请参考u-boot 启动过程 基于S3C2410)。 在u-boot的do_bootm_linux函数里,实现了处理器架构相关的linux内核加载代码,特别是tags传递。
[单片机]
<font color='red'>linux内核</font>启动过程——基于S3C2410
Linux内核可能最终逐步取消对英特尔i486 CPU的支持
Linus Torvalds支持可能从Linux内核中取消英特尔486(i486)处理器支持的想法。在十年前Linux内核放弃对i386的支持后,i486一直是Linux内核主线的最低x86处理器支持。最近一次试图取消对i486的支持的提法来自Linus Torvalds本人,他表达了可能需要x86 32位CPU支持 cmpxchg8b 的想法,这指的是Pentium CPU及以后的CPU。 也许我们应该咬紧牙关,说我们只支持带有 cmpxchg8b 的x86-32(即奔腾及更高版本)。大多数(所有)Linux发行版已经启用了X86_PAE,这使得X86_CMPXCHG64成为基本要求的一部分。我并不相信现在大多数发行版
[嵌入式]
基于S3C2440的Linux内核移植和yaffs2文件系统制作--Linux源码参数
1.3.2 修改Linux源码中参数 1、解压内核源码 mkdir /opt/studyarm cd /mnt/hgfs/share tar –jxvf linux-2.6.29.1.tar.bz2 –C /opt/studyarm 2、 进入内核目录,修改makefile,并对内核进行默认配置进行修改 193行,修改 ARCH ?=arm CROSS_COMPILE ?=arm-linux- 3、 修改平台输入时钟 找到内核源码arch/arm/mach-s3c2440/mach-smdk2440.c文件,在函数 static void __init smdk24
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved