上周在 MontaVista Linux 内核下写了一个如下的类似代码,通过 ioctl 命令将内核空间中一个unsigned int 类型的数据复制到用户空间,结果却返回错误;使用 put_user 或 copy_to_user 函数是一样的结果,然后发现如果去掉 13 ~ 16 行的 access_ok 检查代码,数据就能成功复制。将问题定位在内核的地址空间检查中。
然后后来同事在他的内核实现了一个同样的代码,却是可以顺利执行。当时以为是 MontaVista 内核的问题,因为在网上查了一下,其他人也有类似的问题(ioctl copy_from_user)
1 /*
2 * 参考了
<< linux device driver 3>>
3 *
6.1.6. The Implementation of the ioctl Commands
4 */
5
6 unsigned int phone_num; /* global variable */
7 int (*ioctl)
(struct inode *inode, struct file *filp, unsigned int cmd, \
8 unsigned long arg)
9 {
10 int retval
= 0;
11 switch (cmd)
{
12 case MT88L85_DTMF_CODE:
/* copy 4
Bytes (unsigned int) to user space */
13 if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(unsigned int)))
{
14 PDEBUG(KERN_INFO
"access_ok error.\n");
15 return -EFAULT;
16 }
17 /*
18 retval
= put_user(phone_num, (unsigned int __user *)arg);
19
20 retval
= copy_to_user((unsigned int *)arg, &phone_num, sizeof(unsigned
int));
21 */
22 retval =
__put_user(phone_num, (unsigned int __user *)arg);
23 break;
24 default:
25 retval = -EFAULT;
26 break;
27 }
28 return retval;
29 }
代码一再检查,后来确认无误。只好暂时去除之前的检查代码,只使用 __put_user() 来实现数据复制。说明一下,put_user 和 copy_to_user 都是有内核地址检查的。 put_user() 是有 access_ok() 代码的 __put_user(), 其执行小数据(1、2、4、8字节)的复制,效率比 copy_to_user 稍高。
后来一次偶然的机会,需要重新烧写开发板上的内核镜像,想到中间内核代码曾经做过修改,于是重新编译内核和上面的驱动模块,启动后测试了在 ioctl 中使用 put_user 或 copy_to_user() 的情况,发现不再像之前那样返回错误值了。问题解决!
总结: 驱动模块使用的内核代码一定要和使用的内核镜像编译的代码保持一致,就是说代码要纯净,不能污染代码。保证内核版本对应。
当时也简单看了一下这几个函数的具体实现,如下。
1
2 /*
3 *
taken from include/asm-generic/uaccess.h
4 */
5
6 #ifndef
__copy_to_user
7 static inline __must_check long __copy_to_user(void __user *to,
8 const void *from, unsigned long n)
__must_check 是提示 gcc 一定要做代码检查用的. 在内核中被定义为 __attribute__((warn_unused_result)) 。 如果没有检查函数返回值,在编译中会告警。这样的属性防止函数被误用。
此函数只进行内核空间到用户空间的数据复制。
9 {
10 if (__builtin_constant_p(n))
{
11 switch(n)
{
12 case 1:
13 *(u8 __force *)to =
*(u8 *)from;
14 return 0;
15 case 2:
16 *(u16 __force *)to =
*(u16 *)from;
17 return 0;
18 case 4:
19 *(u32 __force *)to =
*(u32 *)from;
20 return 0;
21 #ifdef
CONFIG_64BIT
22 case 8:
23 *(u64 __force *)to =
*(u64 *)from;
24 return 0;
25 #endif
26 default:
27 break;
28 }
29 }
30
31 memcpy((void __force *)to, from, n);
32 return 0;
33 }
34 #endif
35
36 /*
37 *
These are the main single-value transfer routines. They automatically
38 * use
the right size if we just have the right pointer type.
39 * This
version just falls back to copy_{from,to}_user, which should
40 *
provide a fast-path for small values.
41 */
42 #define
__put_user(x, ptr) \
43 ({ \
44 __typeof__(*(ptr))
__x = (x); \
45 int __pu_err = -EFAULT; \
46 __chk_user_ptr(ptr); \
47 switch (sizeof (*(ptr)))
{
\
复制数据只能为 1, 2, 4, 8 个字节。否则没有返回值
48 case 1: \
49 case 2: \
50 case 4: \
51 case 8: \
52 __pu_err
= __put_user_fn(sizeof (*(ptr)), \
53
ptr, &__x); \
54 break; \
55 default:
\
56 __put_user_bad();
\
57 break; \
58 } \
__pu_err 为返回值。这样写是因为这是一个宏,其返回结果为最后一个表达式的值。
59 __pu_err;
\
60 })
61
put_user() 就是有 access_ok() 的 __put_user()
62 #define
put_user(x, ptr) \
63 ({ \
64 might_sleep(); \
65 access_ok(VERIFY_WRITE,
ptr, sizeof(*ptr)) ? \
66 __put_user(x,
ptr) : \
67 -EFAULT; \
68 })
69
70 static inline int __put_user_fn(size_t size, void __user
*ptr, void *x)
71 {
72 size = __copy_to_user(ptr,
x, size);
73 return size
? -EFAULT : size;
74 }
75
76 extern int __put_user_bad(void)
__attribute__((noreturn)); // 无返回值
77
此问题花了有两天时间才得以解决,当时还以为是内核bug。随记,以防再次出现类似的粗心。
阅读(2256) | 评论(2) | 转发(0) |