Linux内核put_user的用法

put_user 是 Linux 内核中用于将内核空间的数据安全复制到用户空间的关键函数。它在系统调用、设备驱动等场景中广泛使用,确保内核数据能安全传递给用户态程序。以下是详细用法指南:


函数原型

1
int put_user(type val, type __user *addr);
  • **val**:要写入的内核数据(标量类型:charintlong 等)。
  • **addr**:目标用户空间地址(需用 __user 标记,用于静态检查)。
  • 返回值:成功返回 0;失败返回 -EFAULT(地址无效)。

使用场景

在需要向用户空间传递数据的场景中使用,例如:

  • 系统调用实现(如 read 处理函数)。
  • 设备驱动中的 ioctlread 方法。
  • 内核模块与用户程序交互时。

使用步骤与示例

示例:将内核整型变量写入用户空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <linux/uaccess.h> // 必需头文件

long my_syscall(int __user *user_ptr) {
int kernel_data = 42; // 内核空间数据
int ret;

// 将 kernel_data 写入 user_ptr 指向的用户空间
ret = put_user(kernel_data, user_ptr);

if (ret) {
pr_err("Failed to write to user space!\n");
return -EFAULT; // 返回错误码
}
return 0; // 成功
}

关键注意事项

  1. 地址有效性检查

    • put_user 内部自动检查用户地址是否有效(可写且在用户空间)。
    • 无需手动调用 access_ok(),但复杂场景建议前置检查:
      1
      2
      if (!access_ok(VERIFY_WRITE, addr, sizeof(int)))
      return -EFAULT;
  2. 支持的数据类型

    • 支持基本标量类型(1/2/4/8 字节):
      1
      char, short, int, long, long long
    • 不支持结构体/数组!需改用 copy_to_user()
  3. 原子性

    • 可在中断上下文安全使用(不睡眠)。
  4. 错误处理

    • 必须检查返回值,失败时返回 -EFAULT

copy_to_user() 的区别

函数 适用场景 性能
put_user(x, ptr) 单个标量数据(如 int, char 高效(内联汇编)
copy_to_user() 结构体、数组等大数据块 相对较慢

常见错误

  1. 未检查返回值

    1
    put_user(data, addr); // 错误!未处理可能的失败
  2. 传递内核地址

    1
    2
    3
    int *kernel_ptr = kmalloc(...);
    put_user(*kernel_ptr, user_ptr); // 正确:解引用后传值
    put_user(kernel_ptr, user_ptr); // 错误!传递的是内核地址
  3. 误用结构体

    1
    2
    3
    struct my_struct s;
    put_user(s, user_ptr); // 错误!不支持结构体
    copy_to_user(user_ptr, &s, sizeof(s)); // 正确

内部机制

put_user 通过内联汇编实现,核心步骤:

  1. 检查用户地址是否属于用户空间(非内核地址)。
  2. 触发页面错误异常处理(若地址未映射)。
  3. 通过特殊指令(如 x86 的 mov)执行数据复制。
  4. 捕获错误并返回状态码。

总结

  • 何时使用:向用户空间传递单个标量值(整数、字符等)。
  • 必须操作
    1. 包含头文件 <linux/uaccess.h>
    2. 检查返回值并处理 -EFAULT
    3. 确保目标地址用 __user 标记。
  • 替代方案:复制大数据块时使用 copy_to_user()