Linux内核源码分析—从用户空间复制数据到内核空间

时间:2021-01-06 12:31:48

Linux内核源码分析—从用户空间复制数据到内核空间

本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析从用户空间复制数据到内核空间函数。

1、不描述内核同步、错误处理、参数合法性验证相关的内容

2、源码摘自Linux内核2.6.11.1版

3、阅读本文请结合《深入理解Linux内核》第三版相关章节

4、本文会不定时更新

1、copy_from_user

函数功能:

从用户空间向内核空间复制数据

函数源码:

/**

 * copy_from_user: - Copy a block of data fromuser space.

 * @to:  Destination address, in kernel space.

 * @from: Source address, in user space.

 * @n:   Number of bytes to copy.

 *

 * Context: User context only.  This function may sleep.

 *

 * Copy data from user space to kernel space.

 *

 * Returns number of bytes that could not becopied.

 * On success, this will be zero.

 *

 * If some data could not be copied, thisfunction will pad the copied

 * data to the requested size using zero bytes.

 */

unsigned long

copy_from_user(void *to, const void__user *from, unsigned long n)

{

    might_sleep();

    BUG_ON((long)n < 0);

    if(access_ok(VERIFY_READ, from, n))

       n= __copy_from_user(to, from, n);

    else

       memset(to,0, n);

    returnn;

}

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从用户空间复制数据到内核空间,__copy_from_user函数是__copy_from_user_inatomic函数的封装函数,__copy_from_user_inatomic的具体分析见本文;没有则把内核空间置0

2、__copy_from_user_inatomic

函数源码:

/**

 * __copy_from_user: - Copy a block of datafrom user space, with less checking.

 * @to:  Destination address, in kernel space.

 * @from: Source address, in user space.

 * @n:   Number of bytes to copy.

 *

 * Context: User context only.  This function may sleep.

 *

 * Copy data from user space to kernelspace.  Caller must check

 * the specified block with access_ok() beforecalling this function.

 *

 * Returns number of bytes that could not becopied.

 * On success, this will be zero.

 *

 * If some data could not be copied, thisfunction will pad the copied

 * data to the requested size using zero bytes.

 */

static inline unsigned long

__copy_from_user_inatomic(void *to,const void __user *from, unsigned long n)

{

    if(__builtin_constant_p(n)) {

       unsignedlong ret;

 

       switch(n) {

       case1:

           __get_user_size(*(u8*)to, from, 1, ret, 1);

           returnret;

       case2:

           __get_user_size(*(u16*)to, from, 2, ret, 2);

           returnret;

       case4:

           __get_user_size(*(u32*)to, from, 4, ret, 4);

           returnret;

       }

    }

    return__copy_from_user_ll(to, from, n);

}

函数处理流程:

1、__builtin_constant_p 是编译器gcc内置函数,用于判断一个值是否为编译时常量,如果是常数,函数返回1 ,否则返回0。

2、如果n是常量,这里先判断要拷贝的字节大小,如果是1,2,4字节的话,则调用函数__get_user_size来拷贝数据,具体分析参见本文

3、如果n不是常量,调用函数__copy_from_user_ll来拷贝数据,具体分析参见本文

4、__get_user_size

函数源码:

#define __get_user_size(x,ptr,size,retval,errret)           \

do {                               \

    retval= 0;                        \

    __chk_user_ptr(ptr);                   \

    switch(size) {                        \

    case1:__get_user_asm(x,ptr,retval,"b","b","=q",errret);break;    \

    case2:__get_user_asm(x,ptr,retval,"w","w","=r",errret);break;    \

    case4: __get_user_asm(x,ptr,retval,"l","","=r",errret);break; \

    default:(x) = __get_user_bad();              \

    }                           \

} while (0)

函数处理流程:

根据size是1、2、4字节,用不同的参数调用函数__get_user_asm来拷贝数据,具体分析参见本文

5、__get_user_asm

函数参数:

x=to

addr=from

err=4

itype=”b”/”w”/”l”

rtype=”b”/”w”/””

ltype=”=q”/”=r”/”=r”

errret=4

函数源码:

#define __get_user_asm(x, addr, err,itype, rtype, ltype, errret)  \

    __asm____volatile__(                     \

      /*movb/movw/movl addr, %b/w/“”1*/

       "1: mov"itype"%2,%"rtype"1\n"         \

      /**/

       "2:\n"                       \

      /*把下面的代码放入.fixup 节中*/

       ".section.fixup,\"ax\"\n"             \

      /*err= errret */

       "3: movl %3,%0\n"               \

      /*把reg1即x置0*/

       "   xor"itype"%"rtype"1,%"rtype"1\n"      \

      /**/

       "   jmp 2b\n"                \

      /*恢复编译到前面保存的节中*/

       ".previous\n"                   \

      /*这里指定异常表项,参见“参考文章3”*/

       ".section__ex_table,\"a\"\n"             \

      /**/

       "   .align 4\n"                 \

      /*1b地址的指令引起异常,就跳转到3b*/

       "   .long 1b,3b\n"                  \

      /**/

       ".previous"                     \

       :"=r"(err), ltype (x)                 \

       :"m"(__m(addr)), "i"(errret), "0"(err))

函数处理流程:

输出部:

代码:: "=r"(err), ltype (x)

解释:

%0: err=r0

%1: x=寄存器 (ltype(=q/=r/=r))

输入部:

代码::"m"(__m(addr)), "i"(errret), "0"(err))

解释:

    %2: addr内存单元

    %3: errret直接操作数

    %4: r0=err

损坏部:: 无

指令部:

1、具体指令含义参见代码注释

2、处理流程:执行1处的指令,成功则结束;失败,根据异常表项《.section __ex_table,\"a\"\n"》,跳转到3b处执行,设置寄存器值后跳转到2b处结束

注:关于异常表的介绍参见:

Linux异常表

http://www.cnblogs.com/chengxuyuancc/p/3428944.html

6、__copy_from_user_ll

函数源码:

unsigned long

__copy_from_user_ll(void *to, constvoid __user *from, unsigned long n)

{

    BUG_ON((long)n< 0);

    if(movsl_is_ok(to, from, n))

       __copy_user_zeroing(to,from, n);

    else

       n= __copy_user_zeroing_intel(to, from, n);

    returnn;

}

 

#define movsl_is_ok(a1,a2,n) \

    __movsl_is_ok((unsignedlong)(a1),(unsigned long)(a2),(n))

 

static inline int__movsl_is_ok(unsigned long a1, unsigned long a2, unsigned long n)

{

#ifdef CONFIG_X86_INTEL_USERCOPY

    if(n >= 64 && ((a1 ^ a2) & movsl_mask.mask))

       return0;

#endif

    return1;

}

函数处理流程:

这里没理解清楚CONFIG_X86_INTEL_USERCOPY和movsl_mask.mask的含义,待理解后补上