Linux下字符设备驱动程序的结构

首先,希望可以明确一下概念:

1、 要说的是,驱动,必然是与内核紧密相连的底层程序;并且接口对用户是不可见的。所以你要想到的是如何把驱动和内核联系起来,第一个就想到了模块(modules),模块就像一个载体,一个容器,通过它,会把你写好的程序插入(装载)到内核可见的区域,从而使内核感知到你的驱动的存在,然后用户空间才能通过系统调用的形式联系到驱动,从而完成它的任务,所以你首要了解模块。

2、 驱动位于OS之下,为OS提供硬件操作(当然驱动也有可能不是针对硬件的)的逻辑和底层抽象的封装,所以他要上下兼顾,上边必须符合OS的驱动调用接口,下边要处理好硬件的操作。

3、 Linux比较特殊,把所有的设备都抽象成了文件,这样的话操作接口会比较统一,并且给开发也带来了很大的方便。Linux下设备文件有三种:字符设备、块设备、网络设备。

简单的介绍一下做一个驱动(字符设备驱动)需要做哪些:

1、 表征设备存在的结构体cdev:


struct cdev{
 		struct kobject kobj; 			 /* 内嵌的kobject对象*/
 		struct module *owner; 			 /*所属模块*/
struct file_operations *ops;	 /*文件操作结构体*/
struct list_head list;
dev_t dev; 		/*设备号,为32 位,其中高12 位为主设备号,低20 位为次设备号*/
 		unsigned int count;
};


整个过程都是围绕着这个结构体在进行。对他的各元素赋值并通过模块加载到内核即可。我们逐一的说明一下。

对于,kobject的初始化是在cdev_init里边完成的,它在Fs/Char_dev.c :


void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

这里连带着把kobject、list_head、file_operations也都做了赋值或初始化;这里做为参数你要有一个file_operations来用于赋值,,而file_operations包含了对该字符设备进行操作的所有可能用到的函数(接口)。从而就引出了file_operations,它是整个字符驱动的核心,也是要把所有处理逻辑绑定的对象:


static const struct file_operations globalmem_fops={  
    .owner = THIS_MODULE,  
    .read = globalmem_read,  
    .write = globalmem_write,  
    .open = globalmem_open,  
    .release = globalmem_release,  
};

这里是个简单的例子,我们要做的就是实现那些等号后边的函数,并对其赋值。下边是file_operations的原型,很完备的接口,只用实现你用到的就可以了(在include/linux/fs.h中):


struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

到这里,通过cdev的初始化,我们已经把你写的逻辑和cdev联系起来了(通过file_operations),然后通过:

    cdev.owner = THIS_MODULE;

直接复制把cdev和当前模块联系了起来。然后是通过:

    dev_tdevno =MKDEV(222,0); 

用MKDEV宏生成一个主设备号222,此设备号0的dev_t类型的设备号变量devno,之后是把设备号和设备结构体cdev关联起来,这样设备才能间接找到它的处理函数,用这个来完成关联:

    cdev_add(&dev->cdev,devno,1);

well,设备结构体准备好了,和具体的设备号也约定好了(这样属于该类型的设备会直接来找它进行具体的处理),另外,也和当前模块挂接好了,还差一步就是让内核知道它的存在,用下边的函数:

    register_chrdev_region(devno,1,"globalmem");

把当前设备注册到内核,这个和cdev没有关系,只是对虚拟设备globalmem进行内核的注册,而他们联系的纽带就是devno。当然对应的还有注销函数:

    unregister_chrdev_region(devno,1);

他们一般是成对出现的。

 

这样就差不多了,整个过程及原理也就是这样了,更详细的,可以好好品读一下Linux驱动开发详解的第六章。讲得非常详细。另外看一下另外一篇blog,【Linux下第一个驱动程序】,是一个实例,并附有大量说明,这样应该可以建立一个初步的概念。然后就是循循渐进了,哈哈。

 

【本文doc文档下载】



你可能感兴趣的:(linux,struct,list,File,Module,user)