拖了这么久才开始设备驱动程序进阶的第二篇文章,有点汗颜,最近忙于毕业和工作的事情,使得自己无法专心于技术的进阶,现在学校的事情总算是忙完了,所以文章的写作也要开始了。
这篇文章主要是结合Scull设备的代码谈谈如何构建一个简单的字符设备。然后编写一些测试代码讲讲如何测试Scull设备。
我们设计字符设备最基本的操作就是打开一个字符设备(open),和向这个字符设备读取数据(read),以及向这个字符设备写数据(write)。所以要设计一个数据结构能够关联这些操作,统一调度。对于每一个打开的文件都有一个file结构表示,然后它的操作则关联一个file_operations的结果。
static struct file_operations scull_fops=
{
.owner = THIS_MODULE,
// .llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
// .ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
对于上述的结果,.owner字段表明指向拥有该模块的指针,接下来的字段都是函数指针,表明对于Scull设备我们可以有那些操作,这个有点像对象编程里面对于对象行为的说明。这里我们实现了scull_read,scull_write,scull_open,和scull_release函数这些基本的操作。还有一些scull_ioctl等就是一些高级的话题,留待后续文章交代。
所谓万事开头难,首先要学会初始化一个设备。而要初始化一个设备首先要定义好它的设备结构。
struct scull_dev {
struct scull_qset* data;
int quantum;
int qset;
unsigned long size;
unsigned int access_key;
struct semaphore sem;
struct cdev cdev;
};
这个设备结构中重要的就是struct cdev这个系统里定义的结构,在这里,cdev.ops可以设置为一个file_operations的数据结构。struct scull_qset 是一个链表的数据结构,用来存储数据的,其实可以用其他的数据结构来替代,但是内核里面的内存空间实际上要比用户空间来的紧张,所以一般采用动态的内存分配方法,所以这里采用链表的数据结构是合适的。
scull_init_module函数,这是一个初始化设备的函数,主要的目的是注册设备,然后分配在Linux系统中的设备号。一个设备在Linux中总是有主,次设备编号的,dev_t这样的数据结构在<linux/types.h>中定义,实际上是一个32位的数,其中12位用来表示主设备号,而其余20位用来表示次设备号。要获得主次设备号要使用以下的宏:
MAJOR(dev_t dev);
MINOR(dev_t dev);
反之,如果要把主次设备号转换成dev_t数据结构,要使用MKDEV(int major,int minor)的宏。
int scull_init_module(void)
{
int result,i;
dev_t dev =0;
if(scull_major)
{
dev = MKDEV(scull_major,scull_minor);
result = register_chrdev_region(dev,scull_nr_devs,"scull"); //静态分配一个设备号
}
else
{
result = alloc_chrdev_region(&dev,scull_minor,scull_nr_devs,"scull");
scull_major = MAJOR(dev); //动态分配设备号。
}
if(result<0)
{
printk(KERN_WARNING "scull: can not get major %d/n",scull_major);
return result;
}
scull_devices = kmalloc(scull_nr_devs*sizeof(struct scull_dev),GFP_KERNEL);
if(!scull_devices)
{
result = -ENOMEM;
goto fail;
}
memset(scull_devices,0,scull_nr_devs*sizeof(struct scull_dev));
for(i=0;i<scull_nr_devs;i++)
{
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i],i);
}
dev = MKDEV(scull_major,scull_minor+scull_nr_devs);
printk(KERN_ALERT "Scuccessful load scull device!");
return 0;
fail:
scull_cleanup_module();
return result;
}
主要的scull_init_module代码在这里。我们可以用module_init(scull_init_module)加载这样的模块。