最近在做原子IMX6ULL的实验,在第50章Linux内核定时器实验的时候遇到了一点问题记录一下。
这个实验的逻辑就是利用ioctl()给硬件发命令。ioctl()函数在#include<unistd.h>中定义。
int ioctl( int fd, int request, …/* void *arg / );
第三个参数总是一个指针,但指针的类型依赖于request 参数。
(一)在原子的视频里,APP文件中ioctl(fd,cmd,&arg)是这个形式,
在驱动中使用了copy_from_user(&value,int(arg),sizeof(int))来get数据到value中,因为传入的arg是一个指针,我们要读取int类型的数据所以对这个指针进行强制类型转换,&value是要存入的地址,sizeof(int)是要读取数据的字节数。这样就成功的把APP文件传入的数据读取到value中了。
(二)在原子的例程里ioctl(fd,cmd,arg)是这个形式的,注意这个arg不是地址而是具体的数值,所以在驱动中直接value = arg;来获得APP传入的参数。
(三)我在实验的时候APP里直接ioctl(fd,cmd,arg)传入的是值,但是我在驱动中用了copy_from_user()在这个函数的参数里面要读取的地址我用了这个形式(int )(&arg) 首先对arg取址,然后强制类型转换成int类型的地址,但是这个方式时钟显示copy_from_user fail!!!!读取数据失败。
错误分析:用户空间传送一个数据给内核,直接传数据的话不能使用copy_from_user ,可能是因为这个数据取址之后的地址已经变成了内核空间的地址,用copy_rom_user就直接出错了。如果ioctl传入的是地址的话那么在驱动层得到的就是用户空间的内存地址,那么就可以用copy_from_user。
现在进行实验如下,首先利用ioctl(fd,cmd,arg)直接传送数据给内核空间,首先在用户空间打印出arg的地址发现是0X7E9D1C98,然后在对应的驱动中打印出arg的地址为0x8842ff1c显然这两个地址是不同的,那么我们可以合理的猜想,如果利用ioctl直接传值的话那么传进来的值会赋给一个内核空间的地址,也就是驱动中的arg,这个时候的arg是在内核开辟出来的一个地址空间用来存放值,所以我们看到两个地址是不同的,并且用copy_from_user会出错,因为明显这个地址就不是用户空间的地址,而是内核空间的。
接着的实验在APP代码中ioctl()直接传递的是arg的地址,我们在用户空间中打印出arg的地址和我们在内核中接收到的地址是相同的都是7e852c98,因此我们在驱动中调用copy_from_user可以得到arg对应地址的内容。
结论:
最终的出来,利用ioctl传递数据给驱动,如果直接传递的是值,那么直接在驱动中引用这个值,而不要再用到copy_from_user,因为在内核中看到的值实际上并不是用户空间的那个变量本身了。如果使用地址传递内容那么驱动得到的是用户空间的地址,那么是可以用copy_from_user。