这两天闲暇,突然想再把LDD3书上的例子细过一遍,就着手看了,说实话,这个上面SCULL驱动有点晦涩难懂,真是看了大半天。
开发板被搞得现在还没起来,没办法只能在x86 ubuntu上实验驱动了。
驱动其实本身并不难写尤其是很多常见的芯片,网上有很多可用的驱动程序,即便没有也可以着手芯片手册尝试写出来,而linux驱动难得地方不在于驱动本身,而在于你要知道怎么把一个驱动程序放进linux 驱动的框架中,这个时候你就不仅要了解芯片的工作初始化原理,更重要的是你要知道linux驱动框架的模型。
驱动的本质不变,还是对芯片进行初始化,然后让芯片进行工作。
首先结合书本说一下scull这个驱动的功能(后面会举例子说明), 站在用户角度,该驱动的功能就是用户可以使用open打开设备然后使用write吧自己的数据(该数据可以无限大小,当然也不是无限,会受到实际物理内存的限制)存进ram中,然后close,存储的数据会常驻ram,你可以在任何事或重新打开(该例子规定,必须以只读打开才能读取到之前存储的数据,如果以只写打开所有的数据会被清除,重新写新东西)读取到之前存储的数据。该“设备”的主要功能是往RAM中存储数据数据的大小不限,当然实际的功能没有什么优势,因为该例子主要是为了讲字符设备的驱动模型。
结构体中 quantum为量子的大小等于4000,qset为上图char*指针数组的大小等于1000,这样计算所得一个scull_qset可以存储的字节大小约等于4m,long为数据总大小,为啥定义为long?因为此“设备”可以存储无限制大小的数据,long就是这个数据的总大小字节数,cdev使用来注册核心。
再大概说一下linux字符驱动的模型:
简单的流程就是,在insmod的时候会执行module_init 用来给scull_dev分配空间并注册你的设备号,之后向核心注册cdev设备并把file_operation 和申请设备号的设备文件关联起来,做好使用的准备,之后用户就可以使用open,read,write 去打开并操作设备功能以达到目的。
为了测试加强理解 特别的写了一个测试程序:
我在驱动中读写都加了item,rest,q_pos,quantum的打印。
#include <stdio.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd;
char buf[4000] = {0};
char buf1[100] = {0};
char buf2[4000] = {0};
char buf3[4000] = {2};
char buf4[3333] = {2};
memset(buf,48,sizeof(buf));
memset(buf1,49,sizeof(buf1));
memset(buf2,50,sizeof(buf2));
memset(buf3,51,sizeof(buf3));
memset(buf4,52,sizeof(buf4));
char buf_read[4096];
int i = 0;
fd = open("/dev/scull1",O_WRONLY);/* 用只写方式打开 */
write(fd,buf,sizeof(buf)); //把buf中的内容写入memdev设备
write(fd,buf1,sizeof(buf1)); //把buf中的内容写入memdev设备
write(fd,buf2,sizeof(buf2)); //把buf中的内容写入memdev设备
for(i = 0;i < 1000;i++) {
write(fd,buf3,sizeof(buf3)); //把buf中的内容写入memdev设备
}
write(fd,buf4,sizeof(buf4)); //把buf中的内容写入memdev设备
close(fd);
return 0;
}
计算一下写入的总大小为 4000+4000+4000*1000+3333=4011333字节
此函数执行写函数我们在scull.h中定义SCULL_DEBUG宏之后,直接读取/proc/scullmen 得到如下结果
可以看到总共写入的大小为4011333字节这与我们计算的值相同,在最后一次写入的时候也就是驱动程序中写入3333字节的时候计算索引值,一个item为4000000字节,此时ram中已经写入了408000字节,所以item肯定=1,一个item=4000000 一个rest=总大小对item求余,所以rest=8000,spos = 2 ,q_pos = 0,所以
item=1,rest=8000,spos=2,qpos=0这确定了要写入3333字节数据的位置,我们用dmesg查看内核驱动的打印结果如下:
结果与我们计算相吻合,这是我们使用写打印的结果,因为数据均一次写入4000个,最后一次写入3333个,如果我们使用4000的buf去读出数据,最后一次输出的item,rest,spos,qpos值将与写入一致,
这次我们以1字节为单位去读取数据并打印item,rest,spos,qpos的结果,我们先预测一下:
如果一次只读一个字节(比较耗时,效率低),现在有4011333个字节要读取,当读取到最后一个字节的时候,进入read函数,此时计算的指针(item,rest,spos,qpos)势必指向4011333-1的位置此时:
item=1,rest=11332,spos=11332/4000=2,qpos=11332%4000=3332
#include <stdio.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd;
char buf4[4000] = {2};
memset(buf4,0,sizeof(buf4));
int ret = 1;
int i = 0;
fd = open("/dev/scull1",O_RDONLY);/* 用只写方式打开 */
while(ret > 0){
ret = read(fd,buf4,1);
}
close(fd);
return 0;
}
程序执行后用dmesg查看内核打印为:
结果与我们计算结果一致。
回归主题:这个例子重要的是字符设备驱动模型,可能只有我这样的闲人才看例子的逻辑- -。