内存设备驱动程序设计分析.doc
《内存设备驱动程序设计分析.doc》由会员分享,可在线阅读,更多相关《内存设备驱动程序设计分析.doc(14页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、【精品文档】如有侵权,请联系网站删除,仅供学习与交流内存设备驱动程序设计分析.精品文档.完整的内存设备驱动程序目录一、设备驱动中的并发控制21、并发22、自旋锁22.1、自旋锁的使用23、信号量23.1、信号量的相关操作33.2、信号量用于同步3二、设备驱动中的阻塞与非阻塞31、阻塞操作32、非阻塞操作33、等待队列33.1、等待队列的相关操作34、轮询操作4三、设备驱动中的异步通知41、异步通知42、信号的接收43、信号的释放43.1、异步通知编程用到一项数据结构和两个函数4四、设备I/O端口和I/O内存的访问51、I/O端口与I/O内存52、可以使用以下函数访问定位于I/O空间端口521、
2、I/O内存52.2、对设备内存映射的虚拟地址的读写53、申请与释放设备I/O端口和I/O内存54、设备I/O端口和I/O内存访问流程54.1、设备I/O端口访问流程54.2、I/O内存访问流程5五、globalfifo驱动涉及的结构体、操作及代码51、globalfifo设备结构体51.1、cdev结构体61.2、设备号的分配和释放61.3、struct file_operations结构体62、使globalfifo驱动实现异步通知73、文件打开函数于释放函数74、读写函数74.1读函数74.2、写函数85、ioctl设备控制函数106、轮询操作107、初始化并注册cdev118、文件操作结
3、构体119、设备驱动模块加载函数129.1自动创建设备文件1310、模块卸载函数1311、其他代码1311.1、必要的头文件1311.2模块的相关信息14六、Makefile14七、模块加载141、直接编译内核142、使用模块法15设备驱动最通俗的理解是“驱使硬件设备行动”。驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的轮询、中断处理、DMA通信,进行物理内存向虚拟内存的映射等,最终让通信设备能收发数据,让显示设备能显示文字和画面,让存储设备能记录文件和数据。现以globalfifo设备驱动为例介绍完整的内存设备驱动程序。首先描述一下该驱动中所涉及到的并发控
4、制、自旋锁、信号量、阻塞与非阻塞I/O、轮询操作、异步通知与异步I/O和I/O访问等。一、设备驱动中的并发控制1、并发指的是多个执行单元同时、并行被执行。而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问很容易导致竞态。处理并发的常用技术是加锁或者互斥,即确保在任何时候只有一个执行单元可以操作共享资源。在Linux内核中主要通过信号量机制和自旋锁机制实现。2、自旋锁可以从它的工作方式理解,即,在某CPU上运行的代码需要先执行一个原子操作,该操作测试并设置某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存变量。如果测试结果表明锁已经空闲,则
5、程序获得这个自旋锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋锁”。自旋锁最多只能被一个可执行单元持有。2.1、自旋锁的使用a定义自旋锁spinlock_t lock;b.初始化自旋锁spin_lock_init(lock)该宏用于动态初始化自旋锁lock。c获得自旋锁spin_lock(lock)该宏用于获得自旋锁lock,如果能立即获得锁,马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放。d释放自旋锁spin_unlock(lock)3、信号量它主要提供对进程间共享资源访问控制机制。与自旋锁类似,只有得到信号量的进程才
6、能执行临界区代码。与自旋锁不同的是,当获得不到信号量时,进程不会原地打转而是进入休眠等待状态。3.1、信号量的相关操作a定义信号量struct semaphore sem;定义名为sem的信号量。b初始化信号量void sema_init(struct semaphore *sem,int val);该函数初始化信号量,并设置信号量sem的值为val。c获得信号量void down(struct semaphore *sem);该函数用于获得信号量sem,它可能会导致睡眠。d释放信号量void up(struct semaphore *sem);该函数释放信号量sem,唤醒等待者。3.2、信号
7、量用于同步 如果信号量被初始化为0,则它可以用于同步,同步意味着一个执行单元的继续执行需等待另一个执行单元完成某事,保证执行的先后顺序。二、设备驱动中的阻塞与非阻塞1、阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足操作的条件后在进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。2、非阻塞操作是指进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。3、等待队列用来实现进程的阻塞。等待队列可看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待队列中取出进程。3.1、等待队列的相关操作a定义“
8、等待队列头”wait_queue_head_t mu_queue;b初始化“等待队列头”init_waitqueue_head(&my_queue);c定义等待队列DECLARE_WAITQUEUE(name,tsk)该宏用于定义并初始化一个名为name的等待队列。d添加/移除等待队列void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);e等待事件wait_even
9、t(queue,condition)wait_event_interruptible(queue,condition)f唤醒队列void wake_up(wait_queue_head_t *queue);void wake_up_interruptible(wait_queue_head_t *queue);上述操作会唤醒queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。g在等待队列上睡眠sleep_on(wait_queue_head_t *q);interruptible_sleep_on(wait_queue_head_t *q);sleep_on()函
10、数的作用就是将目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒。4、轮询操作在用户程序中,select()和poll()也是与设备阻塞与非阻塞访问息息相关的论题。select()系统调用用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程。应用程序常常调用select系统调用,它可能会阻塞进程。这个调用用驱动的poll()方法实现,其原型为:unsigned int (*poll)(struct file *filp,poll_table *wait)。poll()方法负责完成使用p
11、oll_wait将等待队列添加到poll_table中;返回描述设备是否可读或可写的掩码。三、设备驱动中的异步通知1、异步通知是指一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态。异步通知使用信号来实现。2、信号的接收在用户程序中,为了捕获信号,可以使用signal()函数来设置信号的处理函数:void(*signal(int signum,void(*handler)(int)(int);如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数handler值,失败则返回SIG_ERR。为了在用户空间中能处理一个设备释放的信号,它必须完成3项工作:
12、通过F_SETOWNI IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动法出的信号才能被本进程接收到。 通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式。 通过signal()函数连接信号和信号处理函数。3、信号的释放在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号的源头在设备驱动端。因此,应该在合适的时机让设备驱动释放信号。 为了使设备支持异步通知机制,驱动程序中应涉及3项工作: 支持F_SETOWN命令,能在这个控制命令处理中设置filp-f_owner为对应进程ID。 支持F_SETFL命令处理,每当FASYNC标志改变
13、时,驱动程序中的fasync()函数将得以执行。 在设备资源获得时,调用kill_fasync()函数激发相应的信号。3.1、异步通知编程用到一项数据结构和两个函数数据结构是fasync_struct结构体。两个函数分别是:a.处理FASYNC标识变更的:int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct *fa);b.释放信号用的函数:void kill_fasync(struct fasync_struct *fa,int sig,int band);四、设备I/O端口和I/O内存的访问1、I/O
14、端口与I/O内存设备通常会提供一组寄存器来用于控制设备、读写设备和获取设备状态,即控制寄存器、数据寄存器和状态寄存器。这些寄存器可能位于I/O空间,也可能位于内存空间。当位于I/O空间时,被称为I/O端口,位于内存空间时,被称为I/O内存。2、可以使用以下函数访问定位于I/O空间端口a读写字节端口(8位宽)unsigned inb(unsigned port);void outb(unsigned char byte,unsigned port);类似的还有读写字端口(16位宽)和读写长字端口(32位宽),b读写一串字节:void insb(unsigned port,void *addr,u
15、nsigned long count);void outsw(unsigned port,void *addr,unsigned long count);类似的还有读写一串字和读写一串字长。21、I/O内存使用ioremap()函数将设备所处的物理地址映射到虚拟地址,其原型为:void *ioremap(unsigned long offset,unsigned long size);通过ioremap()获得的虚拟地址应该被iounmap()函数释放,其原型为:void iounmap(void *addr);2.2、对设备内存映射的虚拟地址的读写可通过读I/O内存、写I/O内存、读一串I/
16、O内存、写一串I/O内存、复制I/O内存和设置I/O内存来完成。3、申请与释放设备I/O端口和I/O内存n I/O端口申请u struct resource *request_region(unsigned long first,unsigned long n,const char *name);n I/O端口释放u void release_region(unsigned long start,unsigned long n);n I/O内存申请u struct resource *request_mem_region(unsigned long start,unsigned long le
17、n,char *name);n I/O内存释放u void release_mem_region(unsigned long start,unsigned long len);4、设备I/O端口和I/O内存访问流程4.1、设备I/O端口访问流程在设备打开或驱动模块被加载时申请I/O端口区域,之后使用inb()、outb()等进行端口访问,最后,在设备关闭或驱动被卸载时释放I/O端口范围。4.2、I/O内存访问流程首先,调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间虚拟地址,之后通过Linux设备访问编程接口访问这些设备的寄存器。访问完
18、成后,应对ioremap()申请的虚拟地址进行释放,并释放release_mem_region()申请的I/O内存资源。五、globalfifo驱动涉及的结构体、操作及代码1、globalfifo设备结构体struct globalfifo_dev struct cdev cdev; /cdev结构体unsigned int current_len; /fifo有效数据长度unsigned char memGLOBALFIFO_SIZE; /全局内存struct semaphore sem; /并发控制用的信号量wait_queue_head_t r_wait; /阻塞读用的等待队列头wait
19、_queue_head_t w_wait; /阻塞写用的等待队列头struct fasync_struct *async_queue; /异步结构体指针,用于读 struct globalfifo_dev *globalfifo_devp; /设备结构体指针1.1、cdev结构体其中,cdev结构体描述一个字符设备,cdev结构体的定义如下所示:struct cdevstruct kobject kobj; /内嵌的kobject对象struct module *owner; /所属模块struct file_operations *ops; /文件操作结构体struct list_head
20、list; /内核链表结构体dev_t dev; /设备号unsigned int count;cdev结构体的dev_t成员定义了设备号,为32位,其中主设备号12位,次设备号20位。主设备号用来标识与设备文件相连的驱动程序。次设备号被驱动程序用来辨别操作的是哪个设备。1.2、设备号的分配和释放使用register_chrdev_trgion()或alloc_chrdev_region()函数向系统申请设备号其原型分别为:int register_chrdev_tegion(dev_t from,unsigned count,const char *name);int alloc_chrde
21、v_tegion(dev_t *dev,unsigned baseminor,unsigned count,const char *name);register_chrdev_tegion()函数用于已知起始设备的设备号的情况,而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功后,会把得到的设备号放入第一个参数dev中。使用unregister_chrdev_region()函数释放申请的设备号,其原型为:void unregister_chrdev_tegion(dev_t from,unsigned count);1.3、stru
22、ct file_operations结构体struct file_operations结构体是一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL。file_operations结构体主要成员有:l llseek()函数:用来修改一个文件的当前读写位置,并将新位置返回,出错时返回一个负值。l read()函数:用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。l write()函数:向设备发送数据,成功时函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EI
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 内存 设备 驱动 程序设计 分析
限制150内