分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
linux内核中的copy_to_user和copy_from_user(一)
Kernel version:2.6.14
CPU architecture:ARM920T
Author:ce123(http://blog.csdn.net/ce123)
1.copy_from_user
在学习Linux内核驱动的时候,经常会碰到copy_from_user和copy_to_user这两个函数,设备驱动程序中的ioctl函数就经常会用到。这两个函数负责在用户空间和内核空间传递数据。首先看看它们的定义(linux/include/asm-arm/uaccess.h),先看copy_from_user:
static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n){ if (access_ok(VERIFY_READ, from, n)) n = __arch_copy_from_user(to, from, n); else /* security hole - plug it */ memzero(to, n); return n;}
先看函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。
这个函数从结构上来分析,其实都可以分为两个部分:- 首先检查用户空间的地址指针是否有效;
- 调用__arch_copy_from_user函数。
1.1.access_ok
access_ok用来对用户空间的地址指针from作某种有效性检验,这个宏和体系结构相关,在arm平台上为(linux/include/asm-arm/uaccess.h):
#define __range_ok(addr,size) ({ \ unsigned long flag, sum; \ __chk_user_ptr(addr); \ __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \ : "=&r" (flag), "=&r" (sum) \ : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \ : "cc"); \ flag; })#define access_ok(type,addr,size) (__range_ok(addr,size) == 0)
可以看到access_ok中第一个参数type并没有用到,__range_ok的作用在于判断addr+size之后是否还在进程的用户空间范围之内。下面我们具体看一下。这段代码涉及到GCC内联汇编,不懂的朋友可以先看看这篇博客(http://blog.csdn.net/ce123/article/details/8209702)。(1)unsigned long flag, sum;\\定义两个变量
- flag:保存结果的变量:非零代表地址无效,零代表地址可以访问。初始存放非零值(current_thread_info()->addr_limit),也就是当前进程的地址上限值。
- sum:保存要访问的地址范围末端,用于和当前进程地址空间限制数据做比较。
这个函数涉及到__CHECKER__宏的判断,__CHECKER__宏在通过Sparse(Semantic Parser for C)工具对内核代码进行检查时会定义的。在使用make C=1或C=2时便会调用该工具,这个工具可以检查在代码中声明了sparse所能检查到的相关属性的内核函数和变量。
- 如果定义了__CHECKER__,__chk_user_ptr和__chk_io_ptr在这里只声明函数,没有函数体,目的就是在编译过程中Sparse能够捕捉到编译错误,检查参数的类型。
- 如果没有定义__CHECKER__,这就是一个空函数。
请看具体的定义(linux/compiler.h):
#ifdef __CHECKER__...extern void __chk_user_ptr(void __user *);extern void __chk_io_ptr(void __iomem *);#else...# define __chk_user_ptr(x) (void)0# define __chk_io_ptr(x) (void)0...#endif
(3)接下来是汇编:adds %1, %2, %3
sum = addr + size 这个操作影响状态位(目的是影响是进位标志C),以下的两个指令都带有条件CC,也就是当C=0的时候才执行。
如果上面的加法指令进位了(C=1),则以下的指令都不执行,flag就为初始值current_thread_info()->addr_limit(非0),并返回。
如果没有进位(C=0),就执行下面的指令:
sbcccs %1, %1, %0
sum = sum - flag - 1,也就是(addr + size) - (current_thread_info()->addr_limit) - 1,操作影响符号位。
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,则C=1
如果(addr + size) < (current_thread_info()->addr_limit) - 1,则C=0
当C=0的时候执行以下指令,否则跳过(flag非零)。
movcc %0, #0
flag = 0,给flag赋值0。
综上所述:__range_ok宏其实等价于:
- 如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值
- 如果(addr + size) < (current_thread_info()->addr_limit),返回零
从这里再次可以认识到,copy_from_user的使用是结合进程上下文的,因为他们要访问“user”的内存空间,这个“user”必须是某个特定的进程。通过上面的源码就知道,其中使用了current_thread_info()来检查空间是否可以访问。如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用。如果在中断上下文中使用了,那代码就很可能操作了根本不相关的进程地址空间。其次由于操作的页面可能被换出,这两个函数可能会休眠,所以同样不可在中断上下文中使用。
1.2.__arch_copy_from_user
#define USER(x...) \9999: x; \ .section __ex_table,"a"; \ .align 3; \ .long 9999b,9001f; \ .previous
.long 9999b,9001f;
其中9999b对应标号9999处的指令,9001f是9001处的指令,是9999b处指令的修复指令。这样,当标号9999处发生缺页异常时,系统将调用do_page_fault提交物理页面,然后跳到9001继续执行。
/* Prototype: unsigned long __arch_copy_from_user(void *to,const void *from,unsigned long n); * Purpose : copy a block from user memory to kernel memory * Params : to - kernel memory * : from - user memory * : n - number of bytes to copy * Returns : Number of bytes NOT copied. */.cfu_dest_not_aligned: rsb ip, ip, #4 cmp ip, #2USER( ldrbt r3, [r1], #1) @ May fault strb r3, [r0], #1USER( ldrgebt r3, [r1], #1) @ May fault strgeb r3, [r0], #1USER( ldrgtbt r3, [r1], #1) @ May fault strgtb r3, [r0], #1 sub r2, r2, ip b .cfu_dest_alignedENTRY(__arch_copy_from_user) stmfd sp!, {r0, r2, r4 - r7, lr} cmp r2, #4 blt .cfu_not_enough PLD( pld [r1, #0] ) PLD( pld [r0, #0] ) ands ip, r0, #3 bne .cfu_dest_not_aligned.cfu_dest_aligned: ands ip, r1, #3 bne .cfu_src_not_aligned/* * Seeing as there has to be at least 8 bytes to copy, we can * copy one word, and force a user-mode page fault... */.cfu_0fupi: subs r2, r2, #4 addmi ip, r2, #4 bmi .cfu_0nowordsUSER( ldrt r3, [r1], #4) str r3, [r0], #4 mov ip, r1, lsl #32 - PAGE_SHIFT @ On each page, use a ld/st??t instruction rsb ip, ip, #0 movs ip, ip, lsr #32 - PAGE_SHIFT beq .cfu_0fupi/* * ip = max no. of bytes to copy before needing another "strt" insn */ cmp r2, ip movlt ip, r2 sub r2, r2, ip subs ip, ip, #32 blt .cfu_0rem8lp PLD( pld [r1, #28] ) PLD( pld [r0, #28] ) PLD( subs ip, ip, #64 ) PLD( blt .cfu_0cpynopld ) PLD( pld [r1, #60] ) PLD( pld [r0, #60] ).cfu_0cpy8lp: PLD( pld [r1, #92] ) PLD( pld [r0, #92] ).cfu_0cpynopld: ldmia r1!, {r3 - r6} @ Shouldnt fault stmia r0!, {r3 - r6} ldmia r1!, {r3 - r6} @ Shouldnt fault subs ip, ip, #32 stmia r0!, {r3 - r6} bpl .cfu_0cpy8lp PLD( cmn ip, #64 ) PLD( bge .cfu_0cpynopld ) PLD( add ip, ip, #64 ).cfu_0rem8lp: cmn ip, #16 ldmgeia r1!, {r3 - r6} @ Shouldnt fault stmgeia r0!, {r3 - r6} tst ip, #8 ldmneia r1!, {r3 - r4} @ Shouldnt fault stmneia r0!, {r3 - r4} tst ip, #4 ldrnet r3, [r1], #4 @ Shouldnt fault strne r3, [r0], #4 ands ip, ip, #3 beq .cfu_0fupi.cfu_0nowords: teq ip, #0 beq .cfu_finished.cfu_nowords: cmp ip, #2USER( ldrbt r3, [r1], #1) @ May fault strb r3, [r0], #1USER( ldrgebt r3, [r1], #1) @ May fault strgeb r3, [r0], #1USER( ldrgtbt r3, [r1], #1) @ May fault strgtb r3, [r0], #1 b .cfu_finished.cfu_not_enough: movs ip, r2 bne .cfu_nowords.cfu_finished: mov r0, #0 add sp, sp, #8 LOADREGS(fd,sp!,{r4 - r7, pc}).cfu_src_not_aligned: bic r1, r1, #3USER( ldrt r7, [r1], #4) @ May fault cmp ip, #2 bgt .cfu_3fupi beq .cfu_2fupi.cfu_1fupi: subs r2, r2, #4 addmi ip, r2, #4 bmi .cfu_1nowords mov r3, r7, pull #8USER( ldrt r7, [r1], #4) @ May fault orr r3, r3, r7, push #24 str r3, [r0], #4 mov ip, r1, lsl #32 - PAGE_SHIFT rsb ip, ip, #0 movs ip, ip, lsr #32 - PAGE_SHIFT beq .cfu_1fupi cmp r2, ip movlt ip, r2 sub r2, r2, ip subs ip, ip, #16 blt .cfu_1rem8lp PLD( pld [r1, #12] ) PLD( pld [r0, #12] ) PLD( subs ip, ip, #32 ) PLD( blt .cfu_1cpynopld ) PLD( pld [r1, #28] ) PLD( pld [r0, #28] ).cfu_1cpy8lp: PLD( pld [r1, #44] ) PLD( pld [r0, #44] ).cfu_1cpynopld: mov r3, r7, pull #8 ldmia r1!, {r4 - r7} @ Shouldnt fault subs ip, ip, #16 orr r3, r3, r4, push #24 mov r4, r4, pull #8 orr r4, r4, r5, push #24 mov r5, r5, pull #8 orr r5, r5, r6, push #24 mov r6, r6, pull #8 orr r6, r6, r7, push #24 stmia r0!, {r3 - r6} bpl .cfu_1cpy8lp PLD( cmn ip, #32 ) PLD( bge .cfu_1cpynopld ) PLD( add ip, ip, #32 ).cfu_1rem8lp: tst ip, #8 movne r3, r7, pull #8 ldmneia r1!, {r4, r7} @ Shouldnt fault orrne r3, r3, r4, push #24 movne r4, r4, pull #8 orrne r4, r4, r7, push #24 stmneia r0!, {r3 - r4} tst ip, #4 movne r3, r7, pull #8USER( ldrnet r7, [r1], #4) @ May fault orrne r3, r3, r7, push #24 strne r3, [r0], #4 ands ip, ip, #3 beq .cfu_1fupi.cfu_1nowords: mov r3, r7, get_byte_1 teq ip, #0 beq .cfu_finished cmp ip, #2 strb r3, [r0], #1 movge r3, r7, get_byte_2 strgeb r3, [r0], #1 movgt r3, r7, get_byte_3 strgtb r3, [r0], #1 b .cfu_finished.cfu_2fupi: subs r2, r2, #4 addmi ip, r2, #4 bmi .cfu_2nowords mov r3, r7, pull #16USER( ldrt r7, [r1], #4) @ May fault orr r3, r3, r7, push #16 str r3, [r0], #4 mov ip, r1, lsl #32 - PAGE_SHIFT rsb ip, ip, #0 movs ip, ip, lsr #32 - PAGE_SHIFT beq .cfu_2fupi cmp r2, ip movlt ip, r2 sub r2, r2, ip subs ip, ip, #16 blt .cfu_2rem8lp PLD( pld [r1, #12] ) PLD( pld [r0, #12] ) PLD( subs ip, ip, #32 ) PLD( blt .cfu_2cpynopld ) PLD( pld [r1, #28] ) PLD( pld [r0, #28] ).cfu_2cpy8lp: PLD( pld [r1, #44] ) PLD( pld [r0, #44] ).cfu_2cpynopld: mov r3, r7, pull #16 ldmia r1!, {r4 - r7} @ Shouldnt fault subs ip, ip, #16 orr r3, r3, r4, push #16 mov r4, r4, pull #16 orr r4, r4, r5, push #16 mov r5, r5, pull #16 orr r5, r5, r6, push #16 mov r6, r6, pull #16 orr r6, r6, r7, push #16 stmia r0!, {r3 - r6} bpl .cfu_2cpy8lp PLD( cmn ip, #32 ) PLD( bge .cfu_2cpynopld ) PLD( add ip, ip, #32 ).cfu_2rem8lp: tst ip, #8 movne r3, r7, pull #16 ldmneia r1!, {r4, r7} @ Shouldnt fault orrne r3, r3, r4, push #16 movne r4, r4, pull #16 orrne r4, r4, r7, push #16 stmneia r0!, {r3 - r4} tst ip, #4 movne r3, r7, pull #16USER( ldrnet r7, [r1], #4) @ May fault orrne r3, r3, r7, push #16 strne r3, [r0], #4 ands ip, ip, #3 beq .cfu_2fupi.cfu_2nowords: mov r3, r7, get_byte_2 teq ip, #0 beq .cfu_finished cmp ip, #2 strb r3, [r0], #1 movge r3, r7, get_byte_3 strgeb r3, [r0], #1USER( ldrgtbt r3, [r1], #0) @ May fault strgtb r3, [r0], #1 b .cfu_finished.cfu_3fupi: subs r2, r2, #4 addmi ip, r2, #4 bmi .cfu_3nowords mov r3, r7, pull #24USER( ldrt r7, [r1], #4) @ May fault orr r3, r3, r7, push #8 str r3, [r0], #4 mov ip, r1, lsl #32 - PAGE_SHIFT rsb ip, ip, #0 movs ip, ip, lsr #32 - PAGE_SHIFT beq .cfu_3fupi cmp r2, ip movlt ip, r2 sub r2, r2, ip subs ip, ip, #16 blt .cfu_3rem8lp PLD( pld [r1, #12] ) PLD( pld [r0, #12] ) PLD( subs ip, ip, #32 ) PLD( blt .cfu_3cpynopld ) PLD( pld [r1, #28] ) PLD( pld [r0, #28] ).cfu_3cpy8lp: PLD( pld [r1, #44] ) PLD( pld [r0, #44] ).cfu_3cpynopld: mov r3, r7, pull #24 ldmia r1!, {r4 - r7} @ Shouldnt fault orr r3, r3, r4, push #8 mov r4, r4, pull #24 orr r4, r4, r5, push #8 mov r5, r5, pull #24 orr r5, r5, r6, push #8 mov r6, r6, pull #24 orr r6, r6, r7, push #8 stmia r0!, {r3 - r6} subs ip, ip, #16 bpl .cfu_3cpy8lp PLD( cmn ip, #32 ) PLD( bge .cfu_3cpynopld ) PLD( add ip, ip, #32 ).cfu_3rem8lp: tst ip, #8 movne r3, r7, pull #24 ldmneia r1!, {r4, r7} @ Shouldnt fault orrne r3, r3, r4, push #8 movne r4, r4, pull #24 orrne r4, r4, r7, push #8 stmneia r0!, {r3 - r4} tst ip, #4 movne r3, r7, pull #24USER( ldrnet r7, [r1], #4) @ May fault orrne r3, r3, r7, push #8 strne r3, [r0], #4 ands ip, ip, #3 beq .cfu_3fupi.cfu_3nowords: mov r3, r7, get_byte_3 teq ip, #0 beq .cfu_finished cmp ip, #2 strb r3, [r0], #1USER( ldrgebt r3, [r1], #1) @ May fault strgeb r3, [r0], #1USER( ldrgtbt r3, [r1], #1) @ May fault strgtb r3, [r0], #1 b .cfu_finished .section .fixup,"ax" .align 0 /* * We took an exception. r0 contains a pointer to * the byte not copied. */9001: ldr r2, [sp], #4 @ void *to sub r2, r0, r2 @ bytes copied ldr r1, [sp], #4 @ unsigned long count subs r4, r1, r2 @ bytes left to copy movne r1, r4 blne __memzero mov r0, r4 LOADREGS(fd,sp!, {r4 - r7, pc}) .previous
我们将在另一篇博文中详细分析该函数。