问题描述
测试一个驱动demo的时候,执行了写设备文件的操作,然后出现错误,错误信息中提示Unhandled fault: page domain fault。
原因分析
驱动层write函数大体如下:
ssize_t xxx_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
......
/* 访问user_buf */
xxx
......
return count;
}
在内核空间不能直接访问(读/写)用户空间的数据,必须通过专门的接口进行访问,这些接口是copy_to_user
、copy_from_user
。可以注意到函数的形参const char __user *user_buf
使用了__user
做修饰,这其实已经提示user_buf
来自于用户空间。
解决方案
如果需要从用户空间读数据则先使用copy_from_user将数据拷贝到内核空间;如果需要将数据写到用户空间则需要使用copy_to_user。比如我们可以将上例中的write函数修改如下:
ssize_t xxx_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
......
/* 将用户空间的缓存内容拷贝到内核空间再访问 */
if (copy_from_user(kernel_buf, user_buf, len))
return -EFAULT;
/* 访问kernel_buf */
xxx
......
return count;
}
当然,也可以在编译时使用sparse
工具来实现在编译时进行一些检查。如果没有安装该工具则先进行安装(在Ubuntu环境下):
apt install sparse
然后再编译驱动模块时使用make C=1
,其中C=1用于开启sparse检查。假如我们驱动是这么写的:
ssize_t xxx_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
......
memcpy(kernel_buf, user_buf, len);
/* 访问kernel_buf */
xxx
......
return count;
}
直接访问用户空间(实际上user_buf可能没有指向用户空间),没有使用copy_from_user,那么sparse工具就会检查出来:
pathname:行号:列号: warning: incorrect type in argument 2 (different address spaces)
pathname:行号:列号: expected void const *<noident>
pathname:行号:列号: got char const [noderef] <asn:1>*user_buf
当然,sparse工具还能检查处其它一些程序中的隐患,哪怕程序能够通过编译生成.ko文件,但是sparse输出的信息仍然值得关注。其实,编译驱动模块时总是开启sparse检查,倒不失为一个好习惯,最大程度的消除驱动程序中的隐患,才能确保安全稳定。