linux驱动程序的编写一、实验目的1.掌握linux驱动程序的编写方法2.掌握驱动程序动态模块的调试方法3.掌握驱动程序填加到内核的方法二、实验内容1. 学习linux驱动程序的编写流程2. 学习驱动程序动态模块的调试方法3. 学习驱动程序填加到内核的流程三、实验设备PentiumII以上的PC机,LINUX操作系统,EL-ARM860实验箱四、linux的驱动程序的编写嵌入式应用对成本和实时性比较敏感,而对linux的应用主要体现在对硬件的驱动程序的编写和上层应用程序的开发上。
嵌入式linux驱动程序的基本结构和标准Linux的结构基本一致,也支持模块化模式,所以,大部分驱动程序编成模块化形式,而且,要求可以在不同的体系结构上安装。
linux是可以支持模块化模式的,但由于嵌入式应用是针对具体的应用,所以,一般不采用该模式,而是把驱动程序直接编译进内核之中。
但是这种模式是调试驱动模块的极佳方法。
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。
设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。
同时,设备驱动程序是内核的一部分,它完成以下的功能:对设备初始化和释放;把数据从内核传送到硬件和从硬件读取数据;读取应用程序传送给设备文件的数据和回送应用程序请求的数据;检测和处理设备出现的错误。
在linux操作系统下有字符设备和块设备,网络设备三类主要的设备文件类型。
字符设备和块设备的主要区别是:在对字符设备发出读写请求时,实际的硬件I/O一般就紧接着发生了;块设备利用一块系统内存作为缓冲区,当用户进程对设备请求满足用户要求时,就返回请求的数据。
块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
1 字符设备驱动结构Linux字符设备驱动的关键数据结构是cdev和file_operations结构体。
1)cdev结构体在linux2.6内核中,使用cdev结构体描述一个字符设备,cdev结构体的定义如下:struct cdev{struct kobject kobj;/*内嵌的kobject对象*/struct module *owner;/*所属模块*/struct file_operations *ops;/*文件操作结构体*/struct list_head list;dev_t dev;/*设备号*/unsigned int count;};cdev结构体的dev_t成员定义了设备号,为32位,其中12位主设备号,20位次设备号。
使用下列宏可以从dev_t获得主设备号和次设备号。
*dev_t这个不是structure,是简单变量,只用于保存一组major number和minor number.Linux提供一组mactor对其进行读写:MAJOR(dev_t dev);//读取设备的major numberMINOR(dev_t dev);//读取设备的minor number而使用下列宏则可以通过主设备号和次设备号生成dev_t;MKDEV(int major,int minor);//从一组指定的major number和minor number创建一个dev_tcdev结构体的另一个重要成员file_operations定义了字符设备提供给虚拟文件系统的接口函数。
Linux 2.6内核提供了一组函数用于操作cdev结构体:void cdev_init(struct cdev *,struct file_operations *);struct cdev *cdev_alloc(void);/*动态申请一个cdev空间内存*/void cdev_put(struct cdev *p);int cdev_add(struct cdev *,dev_t,unsigned);/*向系统添加、注册一个cdev*/void cdev_del(struct cdev *);/*向系统注销一个cdev*/cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接,其源代码如下所示:void cdev_init(struct cdev *cdev,struct file_operations *fops){memset(cdev,0,sizeof *cdev);INIT_LIST_HEAD(&cdev->list);kobject_init(&cdev->kobj,&ktype_cdev_default);cdev->ops=fops;/*将传入的文件操作结构体指针赋值给cdev的ops*/}cdev_alloc()函数用于动态申请一个cdev内存,其源代码清单如下:struct cdev *cdev_alloc(void){struct cdev *p=kzalloc(sizeof(struct cdev),GFP_KERNEL);if(p){INIT_LIST_HEAD(&p->list);kobject_init(&p->kobj,&ktype_cdev_dynamic);}return p;}cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。
对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用通常发生在字符设备驱动模块卸载函数中。
2)分配和释放设备号在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号,这两个函数的原型为:int register_chrdev_region(dev_t from,unsigned count,const char *name);int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);register_chrdev_region()函数用于已知起始设备的设备号的情况,而alloc_chrdev_regione用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功之后,会把得到的设备号放入第一个参数dev中。
后者的优点在于它会自动避开设备号重复的冲突。
相反地,在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型为:void unregister_chrdev_region(dev_t from,unsigned count);from:要分配设备编号范围的起始值,经常设置为0.count:所请求的连续设备编号的个数。
Name:是和该设备范围关联的设备名称,它将出现在/proc/devices和sysfs中。
3)file_operations结构体file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,是这符设备驱动与内核的接口,是用户空间对linux进行系统调用的最终落实者。
这些函数实际会在程序进行linux的open()、write()、read()、close()等系统调用时最终被调用。
字符设备驱动程序中,具体实现这些函数,通常,比如file_operation中的read这个函数指针将指向这个具体的驱动程序中的函数xxx_read().通常,一个设备驱动程序包括两个基本的任务:驱动设备的某些函数作为系统调用执行;而某些函数则负责处理中断(即中断处理函数)。
而file_operations 结构的每一个成员的名称都对应着一个系统调用。
用户程序利用系统调用,比如在对一个设备文件进行诸如read操作时,这时对应于该设备文件的驱动程序就会执行相关的ssize_t (*read) (struct file *, char *, size_t, loff_t *);函数。
在操作系统内部,外部设备的存取是通过一组固定入口点进行的,这些入口点由每个外设的驱动程序提供,由file_operations结构向系统进行说明,因此,编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。
file_operations结构在kernel/include/linux/fs.h中可以找到。
struct file_operations {struct module *owner;/*拥有该结构的模块的指针,一般为THIS_MODULES*/loff_t (*llseek) (struct file *, loff_t, int);/*用来修改文件当前的读写位置*/ssize_t (*read) (struct file *, char *, size_t, loff_t *);/*从设备中同步读取数据*/ssize_t (*write) (struct file *, const char *, size_t, loff_t *);/*向设备发送数据*/ssize_t (*aio_read) (struct file *, char *, size_t, loff_t *);/*初始化一个异步的读取操作*/ssize_t (*aio_write) (struct file *, const char *, size_t, loff_t *);/*初始化一个异步的写入操作*/int (*readdir) (struct file *, void *, filldir_t);/*仅用于读取目录,对于设备文件,该字符为null*/unsigned int (*poll) (struct file *, struct poll_table_struct *);/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);/*执行设备I/O的控制命令*/int (*mmap) (struct file *, struct vm_area_struct *);/*用于请求将设备内存映射到进程地址空间*/int (*open) (struct inode *, struct file *);/*打开*/int (*flush) (struct file *);int (*release) (struct inode *, struct file *);/*关闭*/int (*fsync) (struct file *, struct dentry *, int datasync);/*刷新待处理的数据*/int (*fasync) (int, struct file *, int);/*通知设备FASYNC标志发生变化*/int (*lock) (struct file *, int, struct file_lock *);ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); /*通常为NULL*/unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);#ifdef MAGIC_ROM_PTRint (*romptr) (struct file *, struct vm_area_struct *);#endif /* MAGIC_ROM_PTR */};File_operations结构中的成员全部是函数指针,所以实质上就是函数的跳转表。