linux内核中的get_user和put_user
在
内核空间和用户空间交换数据时,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):
extern int __get_user_1(void *);
extern int __get_user_2(void *);
extern int __get_user_4(void *);
extern int __get_user_8(void *);
extern int __get_user_bad(void);
#define __get_user_x(__r2,__p,__e,__s,__i...) \
__asm__ __volatile__ ( \
__asmeq("%0", "r0") __asmeq("%1", "r2") \ //
进行判断(#define __asmeq(x, y) ".ifnc " x "," y " ; .err ; .endif\n\t")
"bl __get_user_" #__s \ //根据参数调用不同的函数,此时r0=指向用户空间的指针,r2=内核空间的变量
: "=&r" (__e), "=r" (__r2) \
: "0" (__p) \
: __i, "cc")
#define get_user(x,p) \
({ \
const register typeof(*(p)) __user *__p asm("r0") = (p);\ //__p的数据类型和*(p)的指针数据类型是一样的,__p = p,且存放在r0寄存器中
register typeof(*(p)) __r2 asm("r2"); \ //__r2的数据类型和*(p)的数据类型是一样的,且存放在r2寄存器中
register int __e asm("r0"); \ //定义__e,存放在寄存器r0,作为返回值
switch (sizeof(*(__p))) { \ //对__p所指向的对象长度进行检查,并根据长度调用响应的函数
case 1: \
__get_user_x(__r2, __p, __e, 1, "lr"); \
break; \
case 2: \
__get_user_x(__r2, __p, __e, 2, "r3", "lr"); \
break; \
case 4: \
__get_user_x(__r2, __p, __e, 4, "lr"); \
break; \
case 8: \
__get_user_x(__r2, __p, __e, 8, "lr"); \
break; \
default: __e = __get_user_bad(); break; \ //默认处理
} \
x = __r2; \
__e; \
})
上
面的源码涉及到gcc的内联汇编,不太了解的朋友可以参考前面的博客(http://blog.csdn.net/ce123/article
/details/8209702)。继续,跟踪__get_user_1等函数的执行,它们的定义如下(linux/arch/arm/lib
/getuser.S)。
.global __get_user_1
__get_user_1:
1: ldrbt r2, [r0]
mov r0, #0
mov pc, lr
.global __get_user_2
__get_user_2:
2: ldrbt r2, [r0], #1
3: ldrbt r3, [r0]
#ifndef __ARMEB__
orr r2, r2, r3, lsl #8
#else
orr r2, r3, r2, lsl #8
#endif
mov r0, #0
mov pc, lr
.global __get_user_4
__get_user_4:
4: ldrt r2, [r0]
mov r0, #0
mov pc, lr
.global __get_user_8
__get_user_8:
5: ldrt r2, [r0], #4
6: ldrt r3, [r0]
mov r0, #0
mov pc, lr
__get_user_bad_8:
mov r3, #0
__get_user_bad:
mov r2, #0
mov r0, #-EFAULT
mov pc, lr
.section __ex_table, "a"
.long 1b, __get_user_bad
.long 2b, __get_user_bad
.long 3b, __get_user_bad
.long 4b, __get_user_bad
.long 5b, __get_user_bad_8
.long 6b, __get_user_bad_8
.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 中定义了如下数据:
.section __ex_table, "a"
.long 1b, __get_user_bad //其中1b对应标号1处的指令,__get_user_bad是1处指令的修复指令。
.long 2b, __get_user_bad
.long 3b, __get_user_bad
.long 4b, __get_user_bad
.long 5b, __get_user_bad_8
.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)。
extern int __put_user_1(void *, unsigned int);
extern int __put_user_2(void *, unsigned int);
extern int __put_user_4(void *, unsigned int);
extern int __put_user_8(void *, unsigned long long);
extern int __put_user_bad(void);
#define __put_user_x(__r2,__p,__e,__s) \
__asm__ __volatile__ ( \
__asmeq("%0", "r0") __asmeq("%2", "r2") \
"bl __put_user_" #__s \
: "=&r" (__e) \
: "0" (__p), "r" (__r2) \
: "ip", "lr", "cc")
#define put_user(x,p) \
({ \
const register typeof(*(p)) __r2 asm("r2") = (x); \
const register typeof(*(p)) __user *__p asm("r0") = (p);\
register int __e asm("r0"); \
switch (sizeof(*(__p))) { \
case 1: \
__put_user_x(__r2, __p, __e, 1); \
break; \
case 2: \
__put_user_x(__r2, __p, __e, 2); \
break; \
case 4: \
__put_user_x(__r2, __p, __e, 4); \
break; \
case 8: \
__put_user_x(__r2, __p, __e, 8); \
break; \
default: __e = __put_user_bad(); break; \
} \
__e; \
})
__put_user_1等函数的的定义如下(linux/arch/arm/lib/putuser.S)。
.global __put_user_1
__put_user_1:
1: strbt r2, [r0]
mov r0, #0
mov pc, lr
.global __put_user_2
__put_user_2:
mov ip, r2, lsr #8
#ifndef __ARMEB__
2: strbt r2, [r0], #1
3: strbt ip, [r0]
#else
2: strbt ip, [r0], #1
3: strbt r2, [r0]
#endif
mov r0, #0
mov pc, lr
.global __put_user_4
__put_user_4:
4: strt r2, [r0]
mov r0, #0
mov pc, lr
.global __put_user_8
__put_user_8:
5: strt r2, [r0], #4
6: strt r3, [r0]
mov r0, #0
mov pc, lr
__put_user_bad:
mov r0, #-EFAULT
mov pc, lr
.section __ex_table, "a"
.long 1b, __put_user_bad
.long 2b, __put_user_bad
.long 3b, __put_user_bad
.long 4b, __put_user_bad
.long 5b, __put_user_bad
.long 6b, __put_user_bad
.previous
put_user函数就不具体分析了。get_user和put_user仅能完成一些简单类型变量的拷贝任务,后面我们将分析copy_to_user和copy_from_user。
(笔记)Linux内核中内存相关的操作函数
linux内核中内存相关的操作函数 1.kmalloc()/kfree() static __always_inline void *kmalloc(size_t size, gfp_t flags) ...
Linux 内核中的 Device Mapper 机制
本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...
向linux内核中添加外部中断驱动模块
本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...
Linux内核中双向链表的经典实现
概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...
Linux内核中的fastcall和asmlinkage宏
代码中看见:#define _fastcall 所以了解下fastcall -------------------------------------------------------------- ...
Linux内核中的GPIO系统之(3):pin controller driver代码分析
一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...
(十)Linux内核中的常用宏container_of
Container_of在Linux内核中是一个常用的宏,用于从包含在某个结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址. Containe ...
Apparmor——Linux内核中的强制访问控制系统
AppArmor 因为最近在研究OJ(oline judge)后台的安全模块的实现,所以一直在研究Linux下沙箱的东西,同时发现了Apparmor可以提供访问控制. AppArmor(Appli ...
KSM剖析——Linux 内核中的内存去耦合
简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging) 允许这个系统管理程序通 ...
随机推荐
TYVJ P1080 N皇后
描述 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 列号 1 2 3 4 5 6 -- ...
Merge into 使用
在进行SQL语句编写时,我们经常会遇到这样的问题:当存在记录时,就更新(Update),不存在数据时,就插入(Insert),oracle为我们提供了一种解决方法——Merge into ,具体语法如 ...
【转】Android AlertDialog 点击对话框外部区域不关闭的设置
原文网址:http://blog.sina.com.cn/s/blog_8f1c79dd0101a63u.html 在Android开发中,常常需要调用对话框,但会遇到这样一种情况,在显示对话框的时候 ...
minidump详细介绍
Effective minidump 简介 在过去几年里,崩溃转储(crash dump)成为了调试工作的一个重要部分.如果软件在客户现场或者测试实验室发生故障,最有价值的解决方式是能够创建一个故障瞬 ...
基于监听的事件处理——Activity本身作为事件监听器
这种形式使用Activity本身作为监听器类,可以直接在Activity类中定义事件处理方法,这种形式非常简洁.但这种做法有两个缺点: 这种形式可能造成程序结构混乱,Activity的主要职责应该是完 ...
PHP(Math的调用)
Mysql主从复制_模式之日志点复制
MySQL数据复制的原理 MySQL复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新.删除等等).因此,要进行复制,必须在主服务器上启用二进制日志. 每个从服务器从主服务器接收主服务器已经记 ...
飞思卡尔IMX6处理器的GPIO配置方式
在linux或android系统中,假如我们要配置飞思卡尔IMX6处理器的GPIO管脚,比如是GPIO_19这个管脚,那么要像这样: [cpp] view plaincopy #define MX6 ...
docker-mysql-cron-backup不能执行任务
https://github.com/shiningrise/docker-mysql-cron-backup CRON_TIME=“0 18 * * * ?” 改为 CRON_TIME=0 18 * ...
.net的架构模式
一:ADO.NET实现三层架构 不用三层的普通的查询写法: string sql = string.Format("select * from Studnet where StuName l ...