精华内容
下载资源
问答
  • copy_from_user
    千次阅读
    2021-12-29 15:29:36

    copy_to_user/copy_from_user解析

    copy_to_user和copy_from_user是在进行驱动相关程序设计的时候,要经常遇到的函数。由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成内核空间到用户空间的复制,函数copy_from_user()完成用户空间到内核空间的复制。

    首先,我们来看一下这个函数的在源码文件中是如何定义的:

    copy_to_user
    Name
    copy_to_user --  Copy a block of data into user space.
    Synopsis
    
    unsigned long copy_to_user (void __user * to, const void * from, unsigned long n);
    
    Arguments
    
    to
    
    Destination address, in user space.
    from
    
    Source address, in kernel space.
    n
    
    Number of bytes to copy.
    Context
    
    User context only. This function may sleep.
    Description
    
    Copy data from kernel space to user space.
    
    Returns number of bytes that could not be copied. On success, this will be zero.
    

    从注释中就可以看出,这个函数的主要作用就是从内核空间拷贝一块儿数据到用户空间,由于这个函数有可能睡眠,所以只能用于用户空间。它有如下三个参数,

       To 目标地址,这个地址是用户空间的地址;
    
       From 源地址,这个地址是内核空间的地址;
    
       N 将要拷贝的数据的字节数。
    

    如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。

    copy_from_user
    Name
    copy_from_user --  Copy a block of data from user space.
    Synopsis
    
    unsigned long copy_from_user (void * to, const void __user * from, unsigned long n);
    
    Arguments
    
    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.
    Description
    
    Copy data from user space to kernel space.
    
    Returns number of bytes that could not be copied. On success, this will be zero.
    
    If some data could not be copied, this function will pad the copied data to the requested size using zero bytes.
    

    从注释中就可以看出,这个函数的主要作用就是从用户空间拷贝一块儿数据到内核空间,由于这个函数有可能睡眠,所以只能用于内核空间。它有如下三个参数,

       To 目标地址,这个地址是内核空间的地址;
    
       From 源地址,这个地址是用户空间的地址;
    
       N 将要拷贝的数据的字节数。
    

    如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。

    更多相关内容
  • Kernel module for testing copy_to from_user infrastructure.
  • 网上很多人提问为什么一定要copy_from_user,也有人解答。比如百度一下:但是这里面很多的解答没有回答到点子上,不能真正回答这个问题。我决定写篇文章正式回答一下这个问题,消除读者的各种疑虑。这个问题,我认为...

    网上很多人提问为什么一定要copy_from_user,也有人解答。比如百度一下:

    285b2e9308d4d08091546f268be37dac.png

    但是这里面很多的解答没有回答到点子上,不能真正回答这个问题。我决定写篇文章正式回答一下这个问题,消除读者的各种疑虑。

    这个问题,我认为需要从2个层面回答

    第一个层次是为什么要拷贝,可不可以不拷贝?

    第二个层次是为什么要用copy_from_user而不是直接memcpy

    为什么要拷贝

    拷贝这个事情是必须的,这个事情甚至都跟Linux都没有什么关系。比如Linux有个kobject结构体,kobject结构体里面有个name指针:

    struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct kernfs_node *sd; /* sysfs directory entry */ struct kref kref;...};

    但我们设置一个设备的名字的时候,其实就是设置device的kobject的name:

    int dev_set_name(struct device *dev, const char *fmt, ...){ va_list vargs; int err; va_start(vargs, fmt); err = kobject_set_name_vargs(&dev->kobj, fmt, vargs); va_end(vargs); return err;}

    驱动里面经常要设置name,比如:

    dev_set_name(&chan->dev->device, "dma%dchan%d", device->dev_id, chan->chan_id);

    但是Linux没有傻到直接把name的指针这样赋值:

    struct device { struct kobject kobj; ...}; dev_set_name(struct device *dev, char *name){ dev->kobj.name = name_param; //假想的烂代码}

    如果它这样做了的话,那么它就完蛋了,因为驱动里面完全可以这样设置name:

    driver_func(){char name[100];....dev_set_name(dev, name);}

    传给dev_set_name()的根本是个stack区域的临时变量,是一个匆匆过客。而device的name对于这个device来讲,必须长期存在。所以你看内核真实的代码,是给kobject的name重新申请一份内存,然后把dev_set_name()传给它的name拷贝进来:

    int kobject_set_name_vargs(struct kobject *kobj, const char *fmt, va_list vargs){constchar*s; .. s = kvasprintf_const(GFP_KERNEL, fmt, vargs); ... if (strchr(s, '/')) { char *t; t = kstrdup(s, GFP_KERNEL); kfree_const(s); if (!t) return -ENOMEM; strreplace(t, '/', '!'); s = t; } kfree_const(kobj->name); kobj->name = s; return 0;}

    这个问题在用户空间和内核空间的交界点上是完全存在的。假设内核里面某个驱动的xxx_write()是这么写的:

    struct globalmem_dev { struct cdev cdev; unsigned char *mem; struct mutex mutex;}; static ssize_t globalmem_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos){ struct globalmem_dev *dev = filp->private_data; dev->mem=buf;//假想的烂代码 return ret;}

    这样的代码绝对是要完蛋的,因为dev->mem这个内核态的指针完全有可能被内核态的中断服务程序、被workqueue的callback函数、被内核线程,或者被用户空间的另外一个进程通过globalmem_read()去读,但是它却指向一个某个进程用户空间的buffer。

    在内核里面直接使用用户态传过来的const char __user * buf指针,是灾难性的,因为buf的虚拟地址,只在这个进程空间是有效的,跨进程是无效的。但是调度一直在发生,中断是存在的,workqueue是存在的,内核线程是存在的,其他进程是存在的,原先的用户进程的buffer地址,切了个进程之后就不知道是个什么鬼!换个进程,页表都特码变了,你这个buf地址还能找着人?进程1的buf地址,在下面的红框里面,什么都不是!

    213b2ade6a9e9b5c533aba3e88f63265.png

    所以内核的正确做法是,把buf拷贝到一个跨中断、跨进程、跨workqueue、跨内核线程的长期有效的内存里面:

    struct globalmem_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE];//长期有效 struct mutex mutex;}; static ssize_t globalmem_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos){ unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; .... if (copy_from_user(dev->mem + p, buf, count))//拷贝!! ret = -EFAULT; else { *ppos += count; ret = count; ...}

    记住,对于内核而言,用户态此刻传入的指针只是一个匆匆过客,只是个灿烂烟花,只是个昙花一现,瞬间即逝!它甚至都没有许诺你天长地久,随时可能劈腿!

    所以,如果一定要给个需要拷贝的理由,原因就是防止劈腿!别给我扯些有的没的。

    必须拷贝的第二个理由,可能与安全有关。比如用户态做类似pwritev, preadv这样的调用:

    ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

    用户传给内核一个iov的数组,数组每个成员描述一个buffer的基地址和长度:

    struct iovec{ void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ __kernel_size_t iov_len; /* Must be size_t (1003.1g) */};

    用户传过来的是一个iovec的数组,里面有每个iov的len和base(base也是指向用户态的buffer的),传进内核的时候,内核会对iovec的地址进行check,保证它确实每个buffer都在用户空间,并且会把整个iovec数组拷贝到内核空间:

    ssize_t import_iovec(int type, const struct iovec __user * uvector, unsigned nr_segs, unsigned fast_segs, struct iovec **iov, struct iov_iter *i){ ssize_t n; struct iovec *p; n = rw_copy_check_uvector(type, uvector, nr_segs, fast_segs, *iov, &p);... iov_iter_init(i, type, p, nr_segs, n); *iov = p == *iov ? NULL : p; return n;}

    这个过程是有严格的安全考量的,整个iov数组会被copy_from_user(),而数组里面的每个buf都要被access_ok的检查:

    ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector, unsigned long nr_segs, unsigned long fast_segs, struct iovec *fast_pointer, struct iovec **ret_pointer){ ... if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) { ret = -EFAULT; goto out; } ... ret = 0; for (seg = 0; seg < nr_segs; seg++) { void __user *buf = iov[seg].iov_base; ssize_t len = (ssize_t)iov[seg].iov_len; ... if (type >= 0 && unlikely(!access_ok(buf, len))) { ret = -EFAULT; goto out; } ... }out: *ret_pointer = iov; return ret;}

    access_ok(buf, len)是确保从buf开始的len长的区间,一定是位于用户空间的,应用程序不能传入一个内核空间的地址来传给系统调用,这样用户可以通过系统调用,让内核写坏内核本身,造成一系列内核安全漏洞。

    假设内核不把整个iov数组通过如下代码拷贝进内核:

    copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))

    而是直接访问用户态的iov,那个这个access_ok就完全失去价值了,因为,用户完全可以在你做access_ok检查的时候,传给你的是用户态buffer,之后把iov_base的内容改成指向一个内核态的buffer去。

    所以,从这个理由上来讲,最开始的拷贝也是必须的。但是这个理由远远没有最开始那个随时劈腿的理由充分!

    为什么不直接用memcpy?

    这个问题主要涉及到2个层面,一个是copy_from_user()有自带的access_ok检查,如果用户传进来的buffer不属于用户空间而是内核空间,根本不会拷贝;二是copy_from_user()有自带的page fault后exception修复机制。

    先看第一个问题,如果代码直接用memcpy():

    static ssize_t globalmem_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos){ struct globalmem_dev *dev = filp->private_data; .... memcpy(dev->mem + p, buf, count)) return ret;}

    memcpy是没有这个检查的,哪怕用户传入进来的这个buf,指向的是内核态的地址,这个拷贝也是要做的。试想,用户做系统调用的时候,随便可以把内核的指针传进来,那用户不是可以随便为所欲为?比如内核的这个commit,引起了著名的安全漏洞:

    CVE-2017-5123

    be8e41c823e3dd2f854a93a0aea0bcd4.png

    就是因为,作者把有access_ok的put_user改为了没有access_ok的unsafe_put_user。这样,用户如果把某个进程的uid地址传给内核,内核unsafe_put_user的时候,不是完全可以把它的uid改为0?

    所以,你看到内核修复这个CVE的时候,是对这些地址进行了一个access_ok的:

    c489369cbcc215669c2e3d2c60ad5d22.png

    下面我们看第二个问题,page fault的修复机制。假设用户程序随便胡乱传个用户态的地址给内核:

    void main(void){ int fd; fd = open("/dev/globalfifo", O_RDWR, S_IRUSR | S_IWUSR); if (fd != -1) {intret=write(fd,0x40000000,10);//假想的代码 if (ret < 0) perror("write error "); }}

    0x40000000这个地址是用户态的,所以access_ok是没有问题的。但是这个地址,根本什么有效的数据、heap、stack都不是。我特码就是瞎写的。

    如果内核驱动用memcpy会发生什么呢?我们会看到一段内核Oops:

    ec425533b728cc59e990271f37843c48.png

    用户进程也会被kill掉:

    # ./a.out Killed

    当然如果你设置了/proc/sys/kernel/panic_on_oops为1的话,内核就不是Opps这么简单了,而是直接panic了。

    但是如果内核用的是copy_from_user呢?内核是不会Oops的,用户态应用程序也是不会死的,它只是收到了bad address的错误:

    # ./a.out write error: Bad address

    内核只是友好地提示你用户闯进来的buffer地址0x40000000是个错误的地址,这个系统调用的参数是不对的,这显然更加符合系统调用的本质。

    内核针对copy_from_user,有exception fixup机制,而memcpy()是没有的。详细的exception修复机制见:

    https://www.kernel.org/doc/Documentation/x86/exception-tables.txt

    PAN

    如果我们想研究地更深,硬件和软件协同做了一个更加安全的机制,这个机制叫做PAN (Privileged Access Never)。它可以把内核对用户空间的buffer访问限制在特定的代码区间里面。PAN可以阻止kernel直接访问用户,它要求访问之前,必须在硬件上开启访问权限。根据ARM的spec文档

    https://static.docs.arm.com/ddi0557/ab/DDI0557A_b_armv8_1_supplement.pdf

    描述:

    64b45674302a342ca552e2a21e5bbb12.png

    所以,内核每次访问用户之前,需要修改PSATE寄存器开启访问权限,完事后应该再次修改PSTATE,关闭内核对用户的访问权限。

    根据补丁:

    https://patchwork.kernel.org/patch/6808781/

    28a64987dcf615a0862fa8cdb2189a93.png

    copy_from_user这样的代码,是有这个开启和关闭的过程的。

    所以,一旦你开启了内核的PAN支持,你是不能在一个随随便便的位置访问用户空间的buffer的。

    展开全文
  • 简单的linux驱动,copy_to_user, copy_from_user 测试
  • COPY_FROM_USER 详解

    千次阅读 2020-07-31 11:38:03
    copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0. 这么简单的一个函数却含盖了许多关于内核方面的知识,比如内核关于异常出错的处理.从用户空间拷贝 数据到内核中...

     

    copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.

    这么简单的一个函数却含盖了许多关于内核方面的知识,比如内核关于异常出错的处理.从用户空间拷贝
    数据到内核中时必须很小心,假如用户空间的数据地址是个非法的地址,或是超出用户空间的范围,或是
    那些地址还没有被映射到,都可能对内核产生很大的影响,如oops,或被造成系统安全的影响.所以
    copy_from_user函数的功能就不只是从用户空间拷贝数据那样简单了,他还要做一些指针检查连同处理这些
    问题的方法.下面我们来仔细分析下这个函数.函数原型在[arch/i386/lib/usercopy.c]中
    unsigned long
    copy_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);
    else
    memset(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: mov"itype" %2,%"rtype"1\n" \
    "2:\n" \
    ".section .fixup,\"ax\"\n" \
    "3: movl %3,%0\n" \
    " xor"itype" %"rtype"1,%"rtype"1\n" \
    " jmp 2b\n" \
    ".previous\n" \
    ".section __ex_table,\"a\"\n" \
    " .align 4\n" \
    " .long 1b,3b\n" \
    ".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);
    else
    n = __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,%0\n" \
          ...
          : "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在页面中的偏移,2^12=4096
    start &= 0xfffff000; // start为页开始地址,即页面边界值。此时start为当前进程空间中的逻辑地址
    start += get_base(current->ldt[2]); // get_base(current->ldt[2])为进程数据段在线性地址空间中的开始地址,在加上start,变为系统这个线性空间中的地址
    页边界 addr ----size----- 页边界
    +--------------------------------------------------------+
    | ... | start&0xfff | | | ... |
    +--------------------------------------------------------+
    | start |
    start-----------size-------------
    while (size>0) {
    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 *) ((address>>20) & 0xffc)) )&1))
    return;
    page &= 0xfffff000;
    page += ((address>>10) & 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.c]
    int 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.c]
    const 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.c]
    const 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;
    else
    return mid;
    }
    return NULL;
    }
    在内核中有个异常出错地址表,在地址表中有个出错地址的修复地址也气对应,他结构如下:
    [/include/asm-i386/uaccess.h]
    struct exception_table_entry
    {
    unsigned long insn, fixup;
    };
    insn是产生异常指令的地址,fixup用来修复出错地址的地址,也就是当异常发生后,用他的
    地址来替换异常指令发生的地址。__copy_user_zeroing中的.section __ex_table代表异常出错
    地址表的地址,.section .fixup代表修复的地址。他们都是elf文档格式中的2个特别节。
    ".section __ex_table,\"a\"\n" \
    " .align 4\n" \
    " .long 4b,5b\n" \
    " .long 0b,3b\n" \
    " .long 1b,6b\n" 
    4b,5b的意思是当出错地址在4b标号对应的地址上时,就转入5b标号对应的地址去接着运行,
    也就是修复的地址。依次类推。所以理解这一点后,fixup_exception()函数就很容易看明白了
    就是根据出错地址搜索异常地址表,找到对应的修复地址,跳转到那里去执行就ok了。
    ok,到这里copy_from_user函数也就分析完了,假如有什么不明白的话,能够通过阅读
    /usr/src/linux/Documentation/exception.txt来得到更多关于异常处理方面的知识。copy_from_user的使用,有一个前提:
    1) 当前进程必须未锁定from所在的page,
    或者,
    2)from所在的page已经up_to_data,并且page -> count多余一个引用。
    否则,如果from所在的page不在影射中,则缺页异常处理程序会搜索/新增这个page,在page未up_to_data时,要求锁定这个page,然后提交IO读page。
    如 ( 当前进程已锁定本page ) && (page未up_to_data)成立,则死锁。
    那么,在generic_file_write中,因to所在的page必须被当前进程锁定,则当(from所在page == to所在page)时,只能用第二种保证办法。
    kernel好象并没有这样做,而只是在锁定to所在page之前,另from所在page为up_to_data,但并没有增加任何多余引用

    展开全文
  • ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); 等函数中,filp...

    字符设备驱动驱动的读,写。如:
    ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
    ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
    等函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count是
    要读写的字节数,f_pos是读的位置相对于文件开头的偏移。

    copy_from_user()和copy_to_user()函数的作用:由于内核空间与用户空间的内存不能直接互访,因此借助
    函数copy_to_user()完成内核空间到用户空间的复制,函数copy_from_user()完成用户空间到内核空间的复制。

     源码:linux/arch/i386/lib/usercopy.c

     copy_to_user: - Copy a block of data into user space.
     @to:   Destination address, in user space.
     @from: Source address, in kernel space.
     @n:    Number of bytes to copy.
     unsigned long    copy_to_user(void __user *to, const void *from, unsigned long n)

     {

              if (access_ok(VERIFY_WRITE, to, n))

                      n = __copy_to_user(to, from, n);
              return n;
      }


     copy_from_user: - Copy a block of data from user space.
     @to:   Destination address, in kernel space.
     @from: Source address, in user space.
     @n:    Number of bytes to copy.

      unsigned long   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
                      memset(to, 0, n);
             return n;
      }
     


    其中access_ok(VERIFY_READ, from, n)检查当前需要访问的地址空间,有没有被其他空间访问,
    如果当前进程被允许访问地址addr处的内存,函数返回真(1),否则为假(0)。

     源码定义:linux/include/asm-m32r/uaccess.h
     #define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0))
     #else
     static inline int access_ok(int type, const void *addr, unsigned long size)
     {
             extern unsigned long memory_start, memory_end;
             unsigned long val = (unsigned long)addr;
     
             return ((val >= memory_start) && ((val + size) < memory_end));
     }
    

      
      其中__copy_to_user(to, from, n);__copy_from_user(to, from, n);才函数实现部分。
      

    源码:linux/include/asm-frv/uaccess.h
      static inline unsigned long __must_check
     __copy_to_user(void __user *to, const void *from, unsigned long n)
     {
            might_sleep();
             return __copy_to_user_inatomic(to, from, n);
     }
     #define __copy_to_user_inatomic(to, from, n)    (memcpy(____force(to), (from), (n)), 0)


     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);
      }
     #define __copy_from_user_inatomic(to, from, n)  (memcpy((to), ____force(from), (n)), 0)


     函数源码定义在:linux/arch/alpha/lib/memcpy.c
     原型:extern void *memcpy(void *dest, void *src, unsigned int count);
      功能:由src所指内存区域复制count个字节到dest所指内存区域。
      说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针
      
      注:上述系列函数均返回不能复制的字节数,因此,完全复制成功,返回值0。
      且__copy_to_user()和__copy_from_user()如果直接调用的时候,不会调用
      access_ok(VERIFY_READ, from, n)检查函数,也就是不会检查当前需要访问的空间,
      有没有被其他空间访问。

    而get_user(),put_user()的作用:
    复制的内存是简单类型,如char,int ,long等,则可以使用简单的put_user()和get_user()
    get_user --  Get a simple variable from user space.
    put_user --  Write a simple value into user space.

    源码定义:linux/include/asm-i386/uaccess.h
      #define put_user(x,ptr)                                         \
     ({      int __ret_pu;                                           \
              __typeof__(*(ptr)) __pu_val;                            \
              __chk_user_ptr(ptr);                                    \
              __pu_val = x;                                           \
              switch(sizeof(*(ptr))) {                                \
              case 1: __put_user_1(__pu_val, ptr); break;             \
              case 2: __put_user_2(__pu_val, ptr); break;             \
              case 4: __put_user_4(__pu_val, ptr); break;             \
             case 8: __put_user_8(__pu_val, ptr); break;             \
              default:__put_user_X(__pu_val, ptr); break;             \
              }                                                       \
              __ret_pu;                                               \
      })


     在put_user(x,ptr)定义如下:
     x:用户内存空间的整型变量
     ptr:用户空间的地址
     
     下面只对put_user()函数分析:
     

     # define __chk_user_ptr(x) (void)0   取得ptr指向的0地址,然后
     __pu_val = x;可以得到x的偏移地址,这样就可以指向x。

     #define container_of(ptr, type, member) ({                      \
              const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
              (type *)( (char *)__mptr - offsetof(type,member) );})

     和这个宏定义很类似。
     
     该代码switch()执行的代码:

       #define __put_user_1(x, ptr) __asm__ __volatile__("call __put_user_1":"=a" (__ret_pu):"0" ((typeof(*(ptr)))(x)), "c" (ptr))
      #define __put_user_2(x, ptr) __asm__ __volatile__("call __put_user_2":"=a" (__ret_pu):"0" ((typeof(*(ptr)))(x)), "c" (ptr))
      #define __put_user_4(x, ptr) __asm__ __volatile__("call __put_user_4":"=a" (__ret_pu):"0" ((typeof(*(ptr)))(x)), "c" (ptr))
      #define __put_user_8(x, ptr) __asm__ __volatile__("call __put_user_8":"=a" (__ret_pu):"A" ((typeof(*(ptr)))(x)), "c" (ptr))
     #define __put_user_X(x, ptr) __asm__ __volatile__("call __put_user_X":"=a" (__ret_pu):"c" (ptr))


     get_user()原理相同,请自己分析。
     
     而__get_user(),__put_user()函数与上面一样,只是不会调用
      access_ok(VERIFY_READ, from, n)检查函数,也就是不会检查当前需要访问的空间,
      有没有被其他空间访问。

    展开全文
  • 图解__arch_copy_from_user函数

    热门讨论 2013-01-08 16:10:56
    图解__arch_copy_from_user函数,可以很清楚的数据的拷贝过程。
  • copy_from_user的详细用法!

    千次阅读 2020-08-15 13:51:18
    copy_from_user函数的目的是从...copy_from_user(void *to, const void __user *from, unsigned long n) @*to 将数据拷贝到内核的地址 @*from 需要拷贝数据的地址 @n 拷贝数据的长度(字节) 也就是将@form地址中的数
  • copy_from_usercopy_to_user这两个函数相信做内核开发的人都非常熟悉,分别是将用户空间的数据拷贝到内核空间以及将内核空间中的数据拷贝到用户空间。 copy_from_user函数的功能就不只是从用户空间拷贝数据那样...
  • 了解linux内核中的copy_to_usercopy_from_user。 内核 2.6.12 1.copy_from_user   copy_from_usercopy_to_user函数负责在用户空间和内核空间传递数据。 copy_from_user:(linux/include/asm-arm/uaccess.h) ...
  • 在编写linux驱动程序的时候会用到copy_to_user()和copy_from_user()这两个函数。那这两个函数的作用是什么呢?为了便于理解,我们先来了解一下用户空间和内核空间。 在linux系统中,每个进程的运行空间分为内核...
  • get_user/put_usercopy_from_user/copy_to_user都是用于用户空间与内核空间的数据交换,区别在于: 1.get_user/put_user用于简单类型的拷贝,例如int、long;copy_from_user/copy_to_user用于拷贝大量数据,类似...
  • copy_to_user()和copy_from_user() 由于内核空间与用户空间的内存不能直接互访, 因此借助函数 copy_from_user()完成用户空间到内核空间的复制, 函数copy_to_user()完成内核空间到用户空间的复制。 copy_from_...
  • 在编译驱动模块的时候,使用copy_to_usercopy_from_user函数时,会出现下面的类似报错,原因是缺少相关的头文件。 现象 解决办法 添加如下头文件 #include <linux/uaccess.h> 在这里插入代码片 再次编译 ...
  • 函数原型:unsigned long copy_to_user(void __user *to, const void *from, unsigned long n); *to是用户空间的指针, *from是内核空间指针, n表示从内核空间向用户空间拷贝数据的字节数 如果数据拷贝成功,则...
  • linux内核中的copy_to_usercopy_from_user

    千次阅读 2018-11-12 10:19:06
    Kernel version:2.6.14 CPU architecture:ARM920T ...在学习Linux内核驱动的时候,经常会碰到copy_from_usercopy_to_user这两个函数,设备驱动程序中的ioctl函数就经常会用到。这两个函数负责在用户空间和内核...
  • 谈一谈copy_from_usercopy_to_user

    万次阅读 多人点赞 2018-12-23 23:36:28
    copy_from_usercopy_to_user这两个函数相信做内核开发的人都非常熟悉,分别是将用户空间的数据拷贝到内核空间以及将内核空间中的数据拷贝到用户空间。这两个函数一般用于系统调用中,前者将用户空间参数拷贝到内核...
  • 隐式声明函数‘copy_from_user’ 一、定义 copy_from_user(rwbuf, buf, count); 【作用】:从用户空间复制到内核空间 copy_to_user( buf, rwbuf, count); 【作用】:从内核空间复制到用户空间 【头文件】: #...
  • linux内核:copy_to_usercopy_from_user

    千次阅读 2021-12-15 23:32:13
    copy_to_usercopy_from_user 在linux内核中,我们将用户态数据拷贝到内核或者将用户态数据拷贝到内核,使用的是copy_from_usercopy_to_user。 但是在有些情况下,我们直接使用memcpy也不会出现错误,可以正常...
  • copy_to_usercopy_from_user函数

    千次阅读 2018-06-20 22:31:47
     copy_to_user:从内核空间复制数据到用户空间 copy_from_user:从用户空间复制数据到内核空间。 在进行驱动相关程序设计时,经常会用到这两个函数,因为底层驱动显然是内核空间,调用驱动的是用户空间。 具体函数...
  • copy_from_user 函数原型 long copy_to_user(void __user *to, const void *from, unsigned long n) long copy_from_user(void *to, const void __user * from, unsigned long n) (1)copy_to_user:从设备拷贝...
  • 非要使用copy_from_user才行吗? 在现代通用操作系统里面,cpu运行指令时,它的运级别分为用户态和内核态这两个态,内核要保护应用程序,不能让用户态的数据对内核进行污染。 那用户态要委托内核完成某个服务时...
  • static inline int copy_to_user(void __user *to, const void... return(CHOOSE_MODE_PROC(copy_to_user_tt, copy_to_user_skas, to, from, n)); } 从 上面可以推测把 to 是目标地址 from 是源地址 n 是大小 ...
  • linux内核中的copy_to_usercopy_from_user(一) Kernel version:2.6.14 CPU architecture:ARM920T Author:ce123(http://blog.csdn.net/ce123) 1.copy_from_user 在学习Linux内核驱动的时候,经常会...
  • copy_to_usercopy_from_user都有可能引起阻塞,当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存 ...
  • linux copy_from_user实现原理

    千次阅读 2018-11-06 01:00:37
    如果你顺着copy_from_user()向下找几层,会看到__get_user_asm宏,该宏展开后可读性太差,我们用下面的伪代码来描述它: 1: movb (%from),(%to) /* 这里访问用户态地址,当地址非法时会产生一个page fault*/ 2:...
  • linux中copy_to_user()和copy_from_user()函数

    千次阅读 2019-03-31 13:49:45
    因此借助函数 copy_from_user()完成用户空间到内核空间的复制, 函数copy_to_user()完成内核空间到用户空间的复制。 copy_from_user()和 copy_to_user()的原型如下所示: unsigned long copy_from_user(void *...
  • ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); 等函数中,f
  • 为什么要用copy_from_user/copy_to_user?

    千次阅读 2018-05-10 09:27:16
    如果应用程序传入了一个参数user_arg,指向的是用户空间的地址。那么我们在内核态里能否直接从这个地址读取数据呢?答案是肯定的,因为内核能够看到进程的整个地址空间,属于这个进程的所有page在此进程的page table...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 245,764
精华内容 98,305
关键字:

copy_from_user