红联Linux门户
Linux帮助

linux使用copy_from_user而不是memcpy拷贝用户空间数据原因

发布时间:2017-03-28 11:34:35来源:linux网站作者:jerry_ms
我们平时在内核中访问用户进程的地址的时候一般会用到copy_from_user,而不是用memcpy直接拷贝。
为什么有这样的要求?
另外在走读代码的时候发现有同事直接用了memcpy将用户空间数据直接拷贝到内核空间,但是并没有发现导致死机,这是什么原因呢?
接下来我们从代码实现中找到问题答案。
 
//这个函数首先检查用户空间地址范围是否满足要求,接下来再去调用__copy_from_user的实现
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else /* security hole - plug it */
memset(to, 0, n);
return n;
}
#define access_ok(type,addr,size) (__range_ok(addr,size) == 0)
#define __range_ok(addr,size) ({ \
unsigned long flag, roksum; \
__chk_user_ptr(addr);
\
__asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
: "=&r" (flag), "=&r" (roksum) \
: "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
: "cc"); \
flag; })
 
这个地方使用了GCC内联汇编,语法大概如下:
asm(    
代码列表    
: 输出运算符列表    
: 输入运算符列表    
: 被更改资源列表    
);
上面汇编含义:
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值
如果(addr + size) < (current_thread_info()->addr_limit),返回零  
addr_limit是限制了用户程序的起始地址范围。
通过这个实现检查用户传来的地址范围是否合法。
 
接下来查看__copy_from_user的实现,这个函数使用汇编编写,在这个文件中arch/arm/lib/uaccess.S
主要实现过程就是数据拷贝,关键是要注意里面的这段代码:
.pushsection .fixup,"ax"
.align  0
9001:           ldmfd   sp!, {r0, r4 - r7, pc}
.popsection
 
用户进程传来的地址是虚拟地址,这段虚拟地址可能还未真正分配对应的物理地址。
对于用户进程访问虚拟地址,如果还未分配物理地址,就会触发内核缺页异常,接着内核会负责分配物理地址,并修改映射页表。这个过程对于用户进程是完全透明的。
但是在内核空间发生缺页时,必须显式处理,否则会导致内核oops。
 
上面这段代码是汇编器as中提供的.section伪操作,用来定义一个fixup 段,指令格式如下.section NAME[, "FLAGS"]。
这段代码目的是发生缺页异常后,可以从异常处理表中找到下一条可以继续处理的指令运行。
 
这里提到的异常表包含一些地址对,地址对中的前一个地址表示出现异常的指令的地址,后一个表示当前一个指令出现错误时,程序可以继续得以执行的修复地址。
如果这个查找操作成功的话,缺页异常处理程序将堆栈中的返回地址(regs->eip)修改成修复地址并返回,随后,发生异常的进程将按照fixup中安排好的指令继续执行下去。当然,如果无法找到与之匹配的修复地址,系统只有打印出出错信息并停止运作。
而这个异常地址对就是fixup中实现的,9001标号定义了异常发生后的修复指令地址。
 
从上面的代码分析我们知道之所以用copy_from_user主要是这个函数提供了两个功能:
1. 对用户进程传过来的地址范围进行合法性检查;
2.当用户传来的地址没有分配物理地址时,定义了缺页处理后的异常发生地址,保证程序顺利执行;
 
另外一个问题,直接使用memcpy时为什么没有出现异常?
在上面的分析过程中我们知道,只有用户传来的地址空间没有分配对应的物理地址时才会进行修复,如果用户进程之前已经使用过这段空间,代表已经分配了物理地址,自然不会发生缺页异常。
 
本文永久更新地址:http://www.linuxdiyf.com/linux/29544.html