驱动程序中的copy_from_user

原理上,由于内核空间的优先级是高于用户空间的,内核态是可以直接访问用户态的虚拟地址空间的,所以如果需要在内核态获取用户态地址空间的数据的话,理论上应该是可以直接访问的,但为什么还需要使用copy_from_user接口呢?

直接访问的话,无法保证被访问的用户态虚拟地址是否有对应的页表项,即无法保证该虚拟地址已经分配了相应的物理内存,如果此时没有对应的页表项,那么此时将产生page fault,导致流程混乱,原则上如果没有页表项(即没有物理内存时),是不应该对其进行操作的。

所以直接操作有比较大的风险,而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;
}

从结构上来分析,其实都可以分为两个部分: 1.首先检查用户空间的地址指针是否有效; 2.调用__arch_copy_from_user函数。

access_ok用来对用户空间的地址指针from作某种有效性检验,这个宏和体系结构相关

#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之后是否还在进程的用户空间范围之内。

__range_ok宏其实等价于:

  • 如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值 //越界

  • 如果(addr + size) < (current_thread_info()->addr_limit),返回零 //没越界

而access_ok就是检验将要操作的用户空间的地址范围是否在当前进程的用户地址空间限制中

如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用。如果在中断上下文中使用了,那代码就很可能操作了根本不相关的进程地址空间。其次由于操作的页面可能被换出,这两个函数可能会休眠,所以同样不可在中断上下文中使用。

如果内核访问一个尚未被提交物理页面的空间,将产生缺页异常,内核会调用do_page_fault,因为异常发生在内核空间,do_page_fault将调用search_exception_tables在“ __ ex_table”中查找异常指令的修复指令,在__ arch_copy_from_user函数中经常使用USER宏,这个宏中了定义了“__ex_table” section。

#define USER(x...)              \
9999:   x;                  \
    .section __ex_table,"a";        \
    .align  3;              \
    .long   9999b,9001f;            \
    .previousc

其中9999b对应标号9999处的指令,9001f是9001处的指令,是9999b处指令的修复指令。这样,当标号9999处发生缺页异常时,系统将调用do_page_fault提交物理页面,然后跳到9001继续执行。

如果在驱动程序中不使用copy_from_user而用memcpy来代替,对于上述的情形会产生什么结果呢?

当标号9999出发生缺页异常时,系统在“__ ex_table”section总将找不到修复地址,因为memcpy没有像copy_from_user那样定义一个“__ex_table”section,此时do_page_fault将通过no_context函数产生Oops。


本文部分内容参考自网络,如有侵权请私信联系我删除

驱动程序中,`copy_from_user` 是 Linux 内核里用于从用户空间向内核空间安全复制数据的函数。该函数的原型如下: ```c unsigned long copy_from_user(void *to, const void __user *from, unsigned long n); ``` 参数解释: - `to`:指向内核空间的目标缓冲区,用于存储从用户空间复制过来的数据。 - `from`:指向用户空间的源缓冲区,是要复制数据的起始位置。 - `n`:要复制的字节数。 返回值:若复制成功,返回 0;若部分或全部复制失败,则返回未成功复制的字节数。 使用 `copy_from_user` 时,需要注意以下几点: - 该函数自带 `access_ok` 检查,若用户传入的缓冲区不属于用户空间而是内核空间,则不会进行拷贝操作 [^3]。 - 它还具备 `page fault` 后的异常修复机制,以此保证操作的安全性和稳定性 [^3]。 以下是一个简单的使用示例: ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #define BUFFER_SIZE 1024 static char kernel_buffer[BUFFER_SIZE]; ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { unsigned long remaining; remaining = copy_from_user(kernel_buffer, buf, count); if (remaining != 0) { printk(KERN_ERR "Failed to copy data from user space\n"); return -EFAULT; } // 处理从用户空间复制过来的数据 printk(KERN_INFO "Data copied from user space: %s\n", kernel_buffer); return count; } struct file_operations my_fops = { .read = my_read, }; static int __init my_module_init(void) { printk(KERN_INFO "My module initialized\n"); // 注册字符设备等操作 return 0; } static void __exit my_module_exit(void) { printk(KERN_INFO "My module exited\n"); // 注销字符设备等操作 } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); ``` 在这个示例中,`my_read` 函数使用 `copy_from_user` 从用户空间的缓冲区 `buf` 向内核空间的 `kernel_buffer` 复制数据。若复制失败,会打印错误信息并返回 `-EFAULT`;若复制成功,则处理数据并打印复制的内容。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值