当前位置: 技术问答>linux和unix
新手第一次写驱动,发现一个关于数据传递的问题
来源: 互联网 发布时间:2016-07-07
本文导语: 刚开始学习LINUX字符设备驱动开发,写了一个小模块练手 以下是驱动程序中IOCTL部分的片段: static int uda_ioctl (struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg) int g...
刚开始学习LINUX字符设备驱动开发,写了一个小模块练手
以下是驱动程序中IOCTL部分的片段:
static int uda_ioctl (struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg)
int get_data;
case DATA_READ:
get_data = read_data();//read_data返回所读寄存器的值
copy_to_user (arg, &get_data, sizeof(get_data));
break;
相应的测试程序片段为:
int getdata = 0;
fd = open(DEV_NAME, O_RDWR);
ioctl(fd, DATA_READ, &getdata);
printf ("getdata = %dn", getdata);
以上程序运行后打印出的getdata即为所读寄存器的值。
本人的理解是, &getdata是用户空间的地址, 作为arg参数传入驱动程序; 而&get_data是内核空间中的地址, 所以需要使用copy_to_user函数. 这个本来没错, 但是实际试验中却发现将copy_to_user换成memcpy甚至copy_from_user, 参数不变, 所得结果依然正确, 这令我百思不得其解.
请问之所以有这样的结果, 是我对用户空间和内核空间的理解有误还是代码本身有问题?
以下是驱动程序中IOCTL部分的片段:
static int uda_ioctl (struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg)
int get_data;
case DATA_READ:
get_data = read_data();//read_data返回所读寄存器的值
copy_to_user (arg, &get_data, sizeof(get_data));
break;
相应的测试程序片段为:
int getdata = 0;
fd = open(DEV_NAME, O_RDWR);
ioctl(fd, DATA_READ, &getdata);
printf ("getdata = %dn", getdata);
以上程序运行后打印出的getdata即为所读寄存器的值。
本人的理解是, &getdata是用户空间的地址, 作为arg参数传入驱动程序; 而&get_data是内核空间中的地址, 所以需要使用copy_to_user函数. 这个本来没错, 但是实际试验中却发现将copy_to_user换成memcpy甚至copy_from_user, 参数不变, 所得结果依然正确, 这令我百思不得其解.
请问之所以有这样的结果, 是我对用户空间和内核空间的理解有误还是代码本身有问题?
|
static inline long copy_to_user(void *to,
const void __user * from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
memcpy(to, from, n);
else
return n;
return 0;
}
static inline long copy_from_user(void *to,
const void __user * from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
memcpy(to, from, n);
else
return n;
return 0;
}
kernel 中long copy_to_user ,copy_from_user,函数的实现。
从kernel 的意图来说, 始终是检查用户空间的地址是否可访问,如果你传反了,
自然也不会有错误,除非你传进来的内核地址本身就是错误的,或者是空地址。
|
进程的地址空间跟内核的地址空间有所不同。
正如你所见,进程的地址空间记录在VMA中。所以进程访问内存的方式是:
地址->VMA->MMU (page table和各种检查还有page fault)->物理内存。所以,进程要访问内核的地址的话,在MMU阶段会被检查,而且会发现进程没有访问内核地址的权限,不能直接访问。
相反,内核可以访问进程的空间,不过要保证地址合法,就像copy to from user那样(这样应该不能说是直接访问吧)。
总结就是,通常情况下,进程不能访问内核地址,内核要访问进程地址时要做相应检查。
通过向内核提交一些map的请求,进程可以访问某些设备或文件,但个人觉得,这并不完全算是内核的地址空间。
正如你所见,进程的地址空间记录在VMA中。所以进程访问内存的方式是:
地址->VMA->MMU (page table和各种检查还有page fault)->物理内存。所以,进程要访问内核的地址的话,在MMU阶段会被检查,而且会发现进程没有访问内核地址的权限,不能直接访问。
相反,内核可以访问进程的空间,不过要保证地址合法,就像copy to from user那样(这样应该不能说是直接访问吧)。
总结就是,通常情况下,进程不能访问内核地址,内核要访问进程地址时要做相应检查。
通过向内核提交一些map的请求,进程可以访问某些设备或文件,但个人觉得,这并不完全算是内核的地址空间。
|
楼主研究好细致。
你在驱动里把arg地址打出来看看? 然后直接对那个地址赋个值,看用户空间能不能得到这个值。如果可以的话,就明了了
你在驱动里把arg地址打出来看看? 然后直接对那个地址赋个值,看用户空间能不能得到这个值。如果可以的话,就明了了
|
copy_to_user检查了相应的地址是否可以访问,因为相应的页面可能会在内存吃紧的时候交换出去。不能依赖memcpy做正确的事的,因为相应的页面可能可能不在当前内存中。
copy_from_user也能用?这个你怎么用?
copy_from_user也能用?这个你怎么用?
|
copy_to_user最终也就是类似于memcpy功能,只是之前会进行地址的检查,所以在正常情况下两者一样
|
请问之所以有这样的结果, 是我对用户空间和内核空间的理解有误还是代码本身有问题?
===============================================
前面极几位说了, copy_to_user 出了 copy 数据以外,还会检查地址的合法性。
内核有最高的权限,内核态可以访问用户态的数据,而用户态的程序则不能访问内核态的数据。
|
我直接把那句换成copy_from_user (arg, &get_data, sizeof(get_data)); 最后在应用层依然能读到0x000101.
对于这点我还是没想明白,有没有高人能解释下?
==========================================================================
你两个地址填反了,
你这么填地址,当然能copy 出去了, arg 是用户空间地址, &get_data是内核空间地址。
还句话说,你把 copy_from_user 当 memcpy 使用了 。
|
没有看copy_from_user具体实现。你把两个参数(from to)位置对调,如果copy_from_user能够成功,那么似乎说明内核在检查地址合法性的时候没有检查访问权限。每个进程对于0xc0000000以后的地址映射跟内核中的page table是一样的,所以这个地址总会在进程的空间中,但进程没有它的访问权限。把from to调换以后,内核检查到的是内核自己的地址,所以肯定是在进程的地址空间中的,但似乎它没有检查访问权限,所以它认为地址是可访问的,然后还是memory copy操作,所以成功了。
内核会假设自己是bug free的,copy_from_user这种东西只会在内核空间使用,所以它认为程序员应该自己保证使用正确吧。
内核会假设自己是bug free的,copy_from_user这种东西只会在内核空间使用,所以它认为程序员应该自己保证使用正确吧。