




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、设备驱动程序(二)1中断处理中断的控制:打开和关闭中断响应函数的注册中断源和中断号中断的处理:中断响应函数使用tasklet2中断的控制中断控制器:中断首先由中断控制器处理。中断控制器可以被编程,可以将中断分派到多处理器环境下不同的处理器上。cli和sti:用于关闭和打开中断,但Linux系统中并不使用这种方式打开和关闭中断。unsigned ling flags;.save_flags(flags);cli();/* 这里的代码在中断关闭状态下执行 */restore_flags(flags);Linux系统中一般使用如下方式关闭和打开中断:更安全的方法是使用“锁机制”,特别是在多处理器环境
2、下,上述方法不能通过关闭中断保护临界区代码。spin_lock_irqsave通过自旋锁提供锁机制和对中断的控制。这种方法也适用于单处理器环境。3S3C2410的中断控制器4中断控制器寄存器操作5中断源6注册和注销中断响应函数注册中断响应函数打开中断响应驱动程序工作关闭中断响应注销中断响应函数int register_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),unsigned long flags,const char *dev_name,void *dev_id);void free_irq(un
3、signed int irq, void *dev_id);request_irq()/free_irq()unsigned int irq 中断号,每个中断响应函数都对应一个中断号。中断号由使用的硬件决定。void (*handler)(int, void *, struct pt_regs *) 指向中断响应函数的指针。unsinged long flags 用于中断控制的变量,实际是一些选项的位掩码。const char *dev_name 字符串指针。这个字符串将显示在/proc/interrupts中,表示这个中断的所有者。void *dev_id 这个指针用于共享的中断线。这个指针
4、一般由驱动程序自己使用,指向它自己的私有数 据。在中断不共享时,这个指针可以是空指针,但一般将它指向设备自己。7flags中的位标志SA_INTERRUPT 当这个比特置位时表示注册的是一个“快速”中断,即在中断响应期间中断是被禁止的。 (相对于“慢速”中断,因为需要较长的处理时间,系统在处理“慢速”中断期间中断是 不被禁止的。这种中断一般很少被使用。)SA_SHIRQ 这个比特被置位时表示这个中断可以被其它设备共享。如果其他设备已经注册了这个中 断并且不是共享方式,则无法再注册这个中断的中断响应函数。SA_SAMPLE_RANDOM 这个比特被置位时表示这个中断可以对熵池做贡献。熵池是操作系
5、统用于产生真随机数 的机制。熵池中的随机数经常被用来生成用于安全或加密的密码。如果这个中断的发生 是完全随机的,比如键盘中断,则可以将这个比特置位,向熵池贡献;否则,比如固定 间隔的时钟中断,不包含随机性,这种中断就用于为熵池做贡献。8使用register_irq()注册一个中断也是对系统资源申请的一个过程。注册成功则响应的系统资源将被占用。注册一个中断可以在驱动程序初始化的时候,也可以在设备第一次被打开的时候。一般,中断的注册在设备第一次被打开、同时设备硬件的中断功能还没有被使能时进行。这种方式需要驱动程序本身对设备的打开次数进行管理。int result;result = register
6、_irq(SPIOC_IRQ, spioc_interrupt,SA_INTERRUPT, “spioc”, NULL);if(result) printk(KERN_INFO “spioc: cant get assigned irq %dn”,SPIOC_IRQ);return -1; else enable_irq();我们可以通过/proc界面查看系统上中断的使用情况:/proc/interrupts/proc/stat9中断号中断号是一个中断最根本的标志,每个中断源对应一个中断号。中断号是唯一的,并且是由硬件决定的。我们要使用一个中断,首先就需要知道它的中断号。静态设定中断号:在我们
7、写驱动程序时已经明确知道要使用的中断号。驱动程序模块加载时设定中断号:由用户在加载驱动程序时指定要使用的中断号。比如某些ISA卡通过跳线设定所使用的中断,这时用户可根据跳线的设置在加载驱动程序时指定中断号,如:insmod ./spioc.o spioc_irq=x自动探测中断号:由驱动程序自动决定或由操作系统决定要使用的中断号。如PCI设备的中断号是在系统启动时由系统分配的。 1 完全由驱动程序执行自动探测:根据一些设备的基本知识,驱动程序可以根据设备的某些参数判断应该使用哪个中断号。 2 借助内核的帮助:内核提供了一套底层工具用于探测中断号unsigned long probe_irq_o
8、n(void);int probe_irq_off(unsigned long);10关于中断响应函数中断响应函数需要满足如下一些限制: 中断响应函数不能向用户空间或从用户空间传送数据,因为中断并不在进程环境中执行。 中断响应函数不能做任何与进入睡眠有关的操作,如调用sleep_on()等。 中断响应函数不能使用除GFP_ATOMIC类型之外的内存分配功能。 中断响应函数不能对一个信号量加锁。 中断响应函数中不能调用schedule()函数进行进程调度。一般,中断中断响应函数完成的工作包括: 从设备读数据,或向设备写数据。 简单的数据处理或其他简单操作。 唤醒一个在这个设备上睡眠的进程。11使
9、用tasklettasklet是Linux中断处理中bottom-half机制的实现方式之一。在2.3版本的内核之前,Linux使用老式的bottom-half实现方式,称为BH。如果使用的是老版本的内核,则无法使用tasklet。tasklet实现了一个中断处理过程的下半部,因此它也要遵守中断响应函数应该遵守的限制。tasklet在一个中断的上半部运行完成后才能获得运行机会,并且由上半部进行调度。一个tasklet可以被多次调度,但只运行一次。要使用tasklet,需要首先使用DECLARE_TASKLET宏进行声明DECLARE_TASKLET(name, function, data);
10、name: tasklet的名字。function: tasklet运行的函数的名字。它的参数类型为unsigned long, 返回值类型为void。data: 作为传递给function的数据,是unsigned long类型。然后,在中断上半部中用tasklet_schedule()来调度运行一个tasklet。12void spioc_do_tasklet(unsigned long);.DECLARE_TASKLET(spioc_tasklet, spioc_do_tasklet, 0);./* The interrupt handler */void spioc_interrupt
11、(int irq, void *dev_id, struct pt_regs *regs)/* read/write data etc. */.tasklet_schedule(&spioc_tasklet);./* other operations */* The tasklet function */void spioc_do_tasklet(unsigned long somevar)/* Here should do the not so critic but time consumingdata processing */./* If some waiting queue is ne
12、ed to be waked up, do it here */wake_up_interruptible(&spioc_read_queue);13竞争情况由于中断出现的随机性,驱动程序在处理中断时要特别注意存在竞争条件的情况。避免竞争情况出现所采用的方法充满了各式各样的技巧,而且由于竞争情况本身的复杂性,避免竞争的方法难于全面分类和描述。一般,比较常用的方法有:1. 使用循环缓冲区,不使用共享变量。2. 使用自旋锁来实现互斥。3. 使用可以自动增加/减少的锁变量。由于信号量的使用可能导致一个过程进入睡眠,所以在中断响应函数中不能使用信号量。关于竞争情况和处理方法,详见Linux设备驱动程序
13、第九章的相关内容。14驱动程序的其它内容阻塞型输入输出:睡眠和唤醒内存使用:申请和分配时间控制:延迟和定时使用devfs自动获得主设备号驱动程序调式技术安全性15阻塞型输入输出当一个进程从设备读数据但还没有可用的数据时,或向设备写数据而设备还没准备好时,进程一般应该进入睡眠。当有可用数据或设备准备就绪时,再通过唤醒的方式将睡眠中的进程唤醒,使得操作可以进行下去。等待队列及其初始化:wait_queue_head_t spioc_wait_queue;init_waitqueue_head(&spioc_wait_queue);还可以用如下方式声明一个静态的等待队列:DECLARE_WAIT_Q
14、UEUE_HEAD(spioc_wait_queue);上述语句声明的等待队列在编译时被初始化。16睡眠和唤醒一旦一个等待队列被声明和初始化之后,进程就可以使用这个等待队列进入睡眠。如下函数用于使一个进程进入睡眠和唤醒一个睡眠中的进程:sleep_on(wait_queue_head_t *queue);interruptible_sleep_on(wait_queue_head_t *queue);sleep_on_timeout(wait_queue_head_t *queue, long timeout);interruptible_sleep_on_timeout(wait_queue
15、_head_t *queue, long timeout);sleep_on(wait_queue_head_t *queue);interruptible_sleep_on(wait_queue_head_t *queue);void wait_event(wait_queue_head_t queue, int condition);int wait_event_interruptible(wait_queue_head_t queue, int condition);wake_up(wait_queue_head_t *queue);wake_up_interruptible(wait_
16、queue_head_t *queue);wake_up_sync(wait_queue_head_t *queue);wake_up_interruptible_sync(wait_queue_head_t *queue);17时间管理:时间间隔、时间延迟Linux操作系统的时钟产生固定时间间隔的时钟中断。时间间隔由HZ值决定。HZ:这是一个与体系结构有关的值,在大多数的体系结构上,Linux操作系统定义这个值为100,表示每一秒种的时钟中断次数。jiffies:这个变量是一个操作系统的全局变量。系统启动时这个变量被初始化成0,并在随后的每次时钟中断时被加1。每次时钟中断也被称为一次“滴答(
17、tick)” 。另外,目前的大多数CPU都有一个计数寄存器,专门用于记录CPU的时钟脉冲,它每个时钟周期被加1。这个寄存器记录的数据不受任何CPU其它操作的影响,最真实地记录了时间的变化。这个寄存器的值可以通过函数或系统调用在内核空间或用户空间被读出使用,如使用void do_gettimeofday(struct timeval *tv);gettimeofday(struct timeval *tv);18时间延迟确定时间间隔的时间延迟:长间隔延迟短间隔延迟任务的延迟执行:任务队列(task queue)tasklet内核定时器(kernel timer) unsigned ling j
18、= jiffies + delay*HZ while(jiffies j) /* do nothing */; sleep_on_timeout(wait_queue_head_t *q, unsigned long t); interruptible_sleep_on_timeout(); void udelay(unsigned long usecs); void mdelay(unsigned long msecs);19内存的申请和分配驱动程序有时需要动态申请一些内存用于暂存数据等。驱动程序申请的内存空间在内核地址空间范围内,因此它们会被保持在内存中而不会被交换到磁盘的交换空间上。驱动
19、程序用于申请内存空间的函数一般为:void *kmalloc(size_t size, int flags);void kfree(const void *addr);get_free_page(int flags);get_free_pages(int flags, unsigned long order);void free_page(unsigned long addr);void free_pages(unsigned long addr, unsigned long order);size:申请的字节数flags:控制内存分配的标志参数GFP_KERNEL:常用的标志,但可能导致进程
20、睡眠,因此不能用于中断响应函数中。GFP_ATOMIC:不会导致进程睡眠,如果没有内存可用,则内存分配失败。GFP_BUFFER, GFP_USER, GFP_HIGHMEM, _GFP_DMA, _GFP_HIGHMEM按页面申请内存空间。flags与kmalloc中的含义相同,order为以2为底的指数,如order=3表示申请8个页面。void *vmalloc(unsigned long size);void vfree(void *addr);void *ioremap(unsigned long offset, unsigned long size);void iounmap(vo
21、id *addr);用于在虚拟地址空间中申请内存空间。20使用devfs2.4版本Linux提供了对Device 的支持。使用devfs可以在设备驱动程序初始化时自动在/dev目录中创建相应的设备文件,并在设备驱动程序注销时自动移除相应的设备文件。使用devfs要求内核编译时定义了符号CONFIG_DEVFS_FSdevfs_handle_t devfs_register(devfs_handle_t dir, const char *name, unsigned int flags, unsigned int major, unsigned int minor, umode_t mode,
22、void *ops, void *info);void devfs_unregister(devfs_handle_t deventry);这两个函数用于向文件系统添加和移除设备文件int devfs_register_chrdev(unsigned int major, const char *name, struct fops);int devfs_unregister_chrdev(unsigned int major, const char *name);这两个函数用于向内核注册和撤消一个字符设备21设备号的动态分配虽然每个设备都有一个设备号(实际包括主设备号和次设备号),但是当文件系
23、统建立之后,我们使用的是设备的文件名,并不直接使用设备号。因此从应用的角度看,一个设备被分配哪个设备号并不重要。Linux操作系统提供了设备号的动态分配机制。 int result; . result = register_chrdev(spioc_major, “spioc”, &spioc_fops); if(result 0) printk(KERN_WARNING “spioc: cant get major %dn”, spioc_major);return result; if(spioc_major = 0)spioc_major = result; . unregister_c
24、hrdev(spioc_major, “spioc”);上述方法同样适用于devfs_register_chrdev()函数。22驱动程序的调试使用printk(): printk(KERN_DEBUG “some messages here with variable %d.n”, value);共有8个记录级别(loglevel): KERN_EMERG, KERN_ALERT, KERN_CRIT, KERN_ERR, KERN_WARNING, KERN_NOTICE, KERN_INFO, KERN_DEBUG使用/proc文件系统: /proc文件系统是一个特殊的由软件建立的文件系
25、统,内核利用它导出信息。/proc下的每个文件都与内核的一个功能/函数相联系,当用户读取相应的文件时,这些功能/函数的信息就被输出了。使用/proc文件系统主要用于查询一些内核信息。 cat /proc/modules使用测试程序: 利用用户级的应用程序测试驱动程序,对驱动程序的运行进行观察。使用系统出错记录: 使用系统出错转储(dump)信息。使用gdb: 使用gdb可以对运行中的内核进行有限调试,主要是读取一些内核信息。使用kdb: 第三方提供的一个内核调试器()。使用kdb需要给内核打上相应的补丁,并重新编译和安装内核。目前kdb对不同体系结构的支持非常有限。23安全性安全性是操作系统非
26、常重要的一个方面,也是一个非常复杂的课题。驱动程序因为是作为内核的一部分而工作,所以驱动程序的安全性和内核的安全性同等重要,因而也是整个操作系统的安全性。对设备使用的控制:设备文件的使用;只能单次打开的设备;只能由一个用户打开的设备;.驱动程序内部数据的使用:对数据边界的检测;内存泄露;缓冲区溢出;内存空间的初始化;.驱动程序结构的合理性:中断的处理;竞争条件的避免;对错误的细致处理;.驱动程序代码的可重入性(reentrant code):状态变量和私有数据的处理。24实例:在spioc中使用中断和devfs中断源:键盘中断使用方式:数据的读(或写)由中断控制执行tasklet的使用使用de
27、vfs25NET-ARM2410开发板键盘电路原理图键盘产生的中断,使用S3C2410外部中断 EINT4IIC接口键盘及LED控制器26中断号我们使用的中断源是s3c2410的一个外部中断信号线,EINT4,它的中断号是固定的。在include/asm-arm/arch-s3c2410/目录下的hardware.h和irqs.h中有关于s3c2410的各种寄存器以及各种资源,如地址、中断号等的定义。为了使用这些量,我们要在引用头文件时包含这两个头文件,如下所示:#include #include .#define NORMAL_IRQ_OFFSET32#define IRQ_EINT4(0
28、+NORMAL_IRQ_OFFSET)#define IRQ_EINT5(1 +NORMAL_IRQ_OFFSET).在include/asm-arm/arch-s3c2410/irqs.h中有如下定义27中断响应函数我们使用的中断源是s3c2410的一个外部中断信号线,EINT4,它的中断号是固定的。在include/asm-arm/arch-s3c2410/目录下的hardware.h和irqs.h中有关于s3c2410的各种寄存器以及各种资源,如地址、中断号等的定义。为了使用这些量,我们要在引用头文件时包含这两个头文件,如下所示:#include #include 中断响应函数stati
29、c void spioc_interrupt(int irq, void *dev_id, struct pt_regs *regs)printk(kbd interrupt received.n);/* do something, such read/write data to I/O port */* left other things to tasklet */tasklet_schedule(&spioc_tasklet);return;28tasklet初始化taskletstatic void spioc_do_tasklet(unsigned long);DECLARE_TASK
30、LET(spioc_tasklet, spioc_do_tasklet, 0);taskletstatic void spioc_do_tasklet(unsigned long)/* do some data process here */int i;for(i=0; i count)cnt = count;copy_to_user(buf, rbuff, cnt);return cnt;假设“读”操作是中断驱动的,则读函数应该在等待队列上“睡眠”,直到中断的到来。30注册中断响应函数如果这个中断信号只由我们这个驱动程序模块单独使用,则我们可以在驱动程序初始化阶段注册中断响应函数。我们以不共
31、享的方式使用这个中断。static int _init spioc_init(void)int result;.result = request_irq(_IRQ_EINT4, spioc_interrupt, SA_INTERRUPT, spioc, NULL);if(result) printk(spioc: irq request failed.n);/* some furthertreatment for this case */.31初始化中断控制器和打开/关闭中断中断控制器的初始化应该在操作系统启动阶段的中断初始化阶段完成了。操作系统的移植者已经帮我们完成了这个工作。然而,对某些微
32、控制器,它的管脚可能是复用的,而缺省状态又不是作为中断输入引脚,这时如果打开中断,可能造成系统“死掉”,比如电平响应的中断,将不停地执行中断响应程序,导致系统其它代码没有机会运行。static int spioc_open(.).enable_irq(IRQ_EINT4);.static int spioc_close(.).disable_irq(IRQ_EINT4);.32使用devfs注意,是否能使用devfs与内核是否支持这个特性相关。内核在编译时应该设定CONFIG_DEVFS_FS符号。我们还可以使用动态获得的设备号。#include static devfs_handle_t d
33、evfs_spioc;static int _init spioc_init(.)int result;.result = devfs_register_chrdev(0, &spioc_fops);if(result 0) return result;spioc_major = result;devfs_spioc = devfs_register(NULL, spioc, DEVFS_FL_DEFAULT, spioc_major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &spioc_fops, NULL);.static void _exit spioc_exi
34、t(void).devfs_unregister(devfs_spioc);devfs_unregister_chrdev(spioc_major, spioc);.33块设备块设备基本概念:块操作为基础,速度是主要考虑因素,类型复杂。与字符设备相似之处:注册/撤消,设备号,block_device_operations。块设备的基本操作块设备的读写:request。其它特性:如可移动性,分区等。块设备的加载和卸载:文件系统。34面向块数据的操作,数据块的大小主要由经验值来确定,一般为2的整数幂次字节大小,如4kB, 16kB等。块设备是用于存储大量数据的设备,主要是各种数据存储介质设备,如硬
35、盘,软盘,光盘,以及U盘等。出于效率的要求,块设备的数据传输几乎都使用较大的缓冲区,并使用请求队列。块设备主要由文件系统使用,因此,块设备上几乎都要建立文件系统,要有磁盘分区。应用几乎不直接使用块设备,而是通过文件系统使用块设备,如各种应用程序从磁盘的文件中读数据和向磁盘文件写入数据。块设备的操作要比字符设备复杂许多,如磁盘电机的启动/停止操作,磁盘坏块的处理等。块设备在在/dev目录下有相应的设备文件,有主设备号和次设备号。每个磁盘有一个主设备号,一个磁盘上得不同分区使用不同的次设备号。速度和效率是块设备要考虑的主要因素,为提高效率,块设备驱动程序一般都实现一定程度的预读功能。由于使用了缓冲
36、区,磁盘中的数据需要经常与系统缓冲的数据保持同步。否则会导致文件系统崩溃。块设备基本概念35块设备的注册和注销#inclide int register_blkdev(unsigned int major, const char *name,struct block_device_operations *bdops);int unregister blkdev(unsigned int major, const char *name);struct block_device_operations int (*open)(struct inode *inode, struct file *fil
37、p); int (*release)(struct inode *inode, struct file *filp); int (*ioctl)(struct inode *inode, struct file *filp,unsigned command, unsigned long arg); int (*check_media_change)(kdev_t dev); int (*revalidate)(kdev_t dev);#include extern void register_disk(struct gendisk *dev, kdev_t first, unsigned mi
38、nors, struct block_device_operations *ops, long size);36块设备的打开和关闭用户的程序一般只打开和关闭磁盘上的文件,并不打开块设备本身。例外的情况是使用如fdisk一类的应用程序对磁盘进行分区、使用mount命令加载一个文件系统等。这些程序将执行块设备文件的打开/关闭操作。fdisk /dev/hdamke2fs /dev/hda3mount -t ext2 /dev/hda3 /my_mount_pointumount /my_mount_point37块设备的读写#inclide blk_init_queue(request_queue
39、_t *queue, request_fn_proc *request);blk_cleanup_queue(request_queue_t *queue);void request(request_queue_t *queue);每当文件系统想想磁盘写入数据或打算从磁盘读出数据时,它就调用驱动程序的rerquest函数,将读写请求放到一个请求队列中去。这是一个需要由驱动程序开发者完成的函数。这个函数的主要结构是一个无限的循环,不停地检查等待队列,处理等待队列中的请求。被放到等待队列中的元素是request struct类型的结构体。block_device_operations中没有read
40、和write这两个函数。实际块设备的读写不是使用read/write完成的。出于效率的考虑,块设备的读写使用了请求队列。3839可移动设备块设备驱动程序需要处理可移动介质的情况,如软盘、U盘等。struct block_device_operations int (*open)(struct inode *inode, struct file *filp); int (*release)(struct inode *inode, struct file *filp); int (*ioctl)(struct inode *inode, struct file *filp,unsigned command, unsigned long arg); int (*check_media_change)(kdev_t dev); int (*revalidate)(kdev_t dev);块设备驱动程序提供两个函数用于检测介质改变的情况以及介质改变后重新使之可用:check_media_change(kd
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025服装店铺面房屋租赁合同
- 定制工厂出品合同范本
- 预防医学(安徽中医药大学)知到课后答案智慧树章节测试答案2025年春安徽中医药大学
- 伐木机械租赁合同范本
- 2025标准委托设计合同
- 2025商业综合体中央空调系统投资合作合同
- 2024年四川阿坝州人民医院招聘紧缺卫生专业技术人员真题
- 新版个人租房合同范本
- 2024年佳木斯市郊区招聘公益性岗位人员真题
- 2024年北京协和医院后勤保障处宿舍管理人员招聘笔试真题
- 《档案编研工作》课件
- 《山水林田湖草生态保护修复工程指南(试行)》
- 初中英语牛津深圳版单词表(按单元顺序)七年级至九年级
- 枪支安全及使用指南
- 《肝衰竭诊治指南(2024版)》解读
- 国省道公路标志标线维护方案投标文件(技术方案)
- 【MOOC】科技英语写作-西安电子科技大学 中国大学慕课MOOC答案
- 电动汽车课件
- 原始点医学(201904第15版)
- 网络安全应急处置工作预案
- 住宅物业消防安全管理 XF1283-2015知识培训
评论
0/150
提交评论