copy_from_user 详解.docx_第1页
copy_from_user 详解.docx_第2页
copy_from_user 详解.docx_第3页
copy_from_user 详解.docx_第4页
copy_from_user 详解.docx_第5页
已阅读5页,还剩2页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

copy_from_user 详解 copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.这么简单的一个函数却含盖了许多关于内核方面的知识,比如内核关于异常出错的处理.从用户空间拷贝数据到内核中时必须很小心,假如用户空间的数据地址是个非法的地址,或是超出用户空间的范围,或是那些地址还没有被映射到,都可能对内核产生很大的影响,如oops,或被造成系统安全的影响.所以copy_from_user函数的功能就不只是从用户空间拷贝数据那样简单了,他还要做一些指针检查连同处理这些问题的方法.下面我们来仔细分析下这个函数.函数原型在arch/i386/lib/usercopy.c中unsigned longcopy_from_user(void *to, const void _user *from, unsigned long n)might_sleep(); if (access_ok(VERIFY_READ, from, n)n = _copy_from_user(to, from, n);elsememset(to, 0, n);return n;首先这个函数是能够睡眠的,他调用might_sleep()来处理,他在include/linux/kernel.h中定义,本质也就是调用schedule(),转到其他进程.接下来就要验证用户空间地址的有效性.他在/include/asm-i386/uaccess.h中定义.#define access_ok(type,addr,size) (likely(_range_ok(addr,size) = 0),进一步调用_rang_ok函数来处理,他所做的测试很简单,就是比较addr+size这个地址的大小是否超出了用户进程空间的大小,也就是0xbfffffff.可能有读者会问,只做地址范围检查,怎么不做指针合法性的检查呢,假如出现前面提到过的问题怎么办?这个会在下面的函数中处理,我们慢慢看.在做完地址范围检查后,假如成功则调用_copy_from_user函数开始拷贝数据了,假如失败的话,就把从to指针指向的内核空间地址到to+size范围填充为0._copy_from_user也在uaceess.h中定义,static inline unsigned long_copy_from_user(void *to, const void _user *from, unsigned long n)might_sleep();return _copy_from_user_inatomic(to, from, n);这里继续调用_copy_from_user_inatomic.static inline unsigned long_copy_from_user_inatomic(void *to, const void _user *from, unsigned long n)if (_builtin_constant_p(n) unsigned long ret;switch (n) case 1:_get_user_size(*(u8 *)to, from, 1, ret, 1);return ret;case 2:_get_user_size(*(u16 *)to, from, 2, ret, 2);return ret;case 4:_get_user_size(*(u32 *)to, from, 4, ret, 4);return ret;return _copy_from_user_ll(to, from, n);这里先判断要拷贝的字节大小,假如是8,16,32大小的话,则调用_get_user_size来拷贝数据.这样做是一种程式设计上的优化了。#define _get_user_size(x,ptr,size,retval,errret) do retval = 0; _chk_user_ptr(ptr); switch (size) case 1: _get_user_asm(x,ptr,retval,b,b,=q,errret);break; case 2: _get_user_asm(x,ptr,retval,w,w,=r,errret);break; case 4: _get_user_asm(x,ptr,retval,l,=r,errret);break; default: (x) = _get_user_bad(); while (0)#define _get_user_asm(x, addr, err, itype, rtype, ltype, errret) _asm_ _volatile_( 1: movitype %2,%rtype1n 2:n .section .fixup,axn 3: movl %3,%0n xoritype %rtype1,%rtype1n jmp 2bn .previousn .section _ex_table,an .align 4n .long 1b,3bn .previous : =r(err), ltype (x) : m(_m(addr), i(errret), 0(err)实际上在完成一些宏的转换后,也就是利用movb,movw,movl指令传输数据了,对于内嵌汇编中的.section .fixup, .section _ex_table,我们呆会要仔细讲。假如不是那些特别大小时,则调用_copy_from_user_ll处理。unsigned long_copy_from_user_ll(void *to, const void _user *from, unsigned long n)if (movsl_is_ok(to, from, n)_copy_user_zeroing(to, from, n);elsen = _copy_user_zeroing_intel(to, from, n);return n;直接调用_copy_user_zeroing开始真正的拷贝数据了,绕了那么多弯,总算快看到出路了。copy_from_user函数的精华部分也就都在这了。#define _copy_user_zeroing(to,from,size) do int _d0, _d1, _d2; _asm_ _volatile_( cmp $7,%0n . : 3(size), 0(size), 1(to), 2(from) : memory); while (0)这个函数的前一部分比较简单,也就是拷贝数据.关于后一部分就会涉及到我们前面提到过的那些情况了,假如用户空间的地址没被映射怎么办呢?在一些老的内核版本中是用verify_area()来验证地址地址合法性的,比如在早期的linux 0.11内核.linux0.11/kenrel/fork.c/ 进程空间写前验证函数。在现代CPU中,其控制寄存器CR0有个写保护标志位(wp:16),内核能够通过配置/ 该位来禁止特权级0的代码向用户空间只读页面执行写数据,否则将导致写保护异常。/ addr为内存物理地址void verify_area(void * addr,int size)unsigned long start;start = (unsigned long) addr;size += start & 0xfff; / start & 0xfff为起始地址addr在页面中的偏移,212=4096start &= 0xfffff000; / start为页开始地址,即页面边界值。此时start为当前进程空间中的逻辑地址start += get_base(current-ldt2); / get_base(current-ldt2)为进程数据段在线性地址空间中的开始地址,在加上start,变为系统这个线性空间中的地址页边界 addr -size- 页边界+-+| . | start&0xfff | | | . |+-+| start |start-size-while (size0) size -= 4096;write_verify(start); / 以页为单位,进行写保护验证,假如页为只读,则将其变为可写start += 4096;linux0.11/mm/memory.c/ 验证线性地址是否可写void write_verify(unsigned long address)unsigned long page;/ 假如对应页表为空的话,直接返回if (!( (page = *(unsigned long *) (address20) & 0xffc) )&1)return;page &= 0xfffff000;page += (address10) & 0xffc);/ 经过运算后page为页表项的内容,指向实际的一页物理地址if (3 & *(unsigned long *) page) = 1) / 验证页面是否可写,不可写则执行un_wp_page,取消写保护.un_wp_page(unsigned long *) page);return;但是假如每次在用户空间复制数据时,都要做这种检查是很浪费时间的,毕竟坏指针是很少存在的,在新内核中的做法是,在从用户空间复制数据时,取消验证指针合法性的检查,只多地址范围的检查,就象access_ok()所做的那样,一但碰上了坏指针,就要页异常出错处理程式去处理他了.我们去看看do_page_fault函数.arch/asm-i386/mm/fault.c/do_page_falut()fastcall void do_page_fault(struct pt_regs *regs, unsigned long error_code).if (!down_read_trylock(&mm-mmap_sem) if (error_code & 4) = 0 &!search_exception_tables(regs-eip)goto bad_area_nosemaphore;down_read(&mm-mmap_sem);. if (fixup_exception(regs)return;.error_code保存的是出错码,(error_code & 4) = 0代表产生异常的原因是在内核中.他调用fixup_exception(regs)来处理这个问题.既然出错了,那么如何来修复他呢?先看下fixup_exception()函数的实现:arch/asm-i386/mm/extable.cint fixup_exception(struct pt_regs *regs)const struct exception_table_entry *fixup;.fixup = search_exception_tables(regs-eip);if (fixup) regs-eip = fixup-fixup;return 1;.kernel/extable.cconst struct exception_table_entry *search_exception_tables(unsigned long addr)const struct exception_table_entry *e;e = search_extable(_start_ex_table, _stop_ex_table-1, addr);if (!e)e = search_module_extables(addr);return e;/lib/extable.cconst struct exception_table_entry *search_extable(const struct exception_table_entry *first,const struct exception_table_entry *last,unsigned long value)while (first insn insn value)last = mid - 1;elsereturn mid;return NULL;在内核中有个异常出错地址表,在地址表中有个出错地址的修复地址也气对应,他结构如下:/include/asm-i386/uaccess.hstruct exception_table_entryunsigned long insn, fixup;insn是产生异常指令的地址,fixup用来修复出错地址的地址,也就是当异常发生后,用他的地址来替换异常指令发生的地址。_copy_user_zeroing中的.section _ex_table代表异常出错地址表的地址,.section .fixup代表修复的地址。他们都是elf文档格式中的2个特别节。.section _ex_table,an .align 4n .long 4b,5bn .long 0b,3bn .long 1b,6bn 4b,5b的意思是

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论