uClinux下中断驱动的I/O方式_第1页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、uclinux下中断驱动的io方式在32位微处理器逐渐成为系统主流的同时,嵌入式应用也变得越来越复杂。许多嵌入式系统都不得不借助于专用的操作系统来支撑自己的应用。作为类unix操作系统,继承了的各种优秀的品质,成为首选的嵌入式系统的操作系统。为自己的设备在操作系统下添加驱动程序,是嵌入式设计必不行少的部分。针对不同的设备类型,挑选合适的驱动程序的模式,同样也是非常重要的。通常的设备驱动采纳挺直io的方式,如存储器、等;而对于象网络这样的数据流设备的驱动,则应当用到中断机制。本文以uclinux为背景,以一种数据流设备为目标,介绍中断驱动的io设备驱动的开发。1 应用背景1.1 硬件描述本文介绍

2、的驱动程序是应用在一种电信e1线路和以太网互联设备上的。它是旁路接收e1数据并将其发送到以太网的某一台服务器上,在服务器上对e1的话路和信令时隙分析。该设备中的处理器是采纳三星公司出品的网络型处理器s3c4510b。e1线路接口采纳dallas公司的专用el接口单元(liu)芯片ds2148,它完成波形收拾、时钟复原和hdb3解码。ds2148将收拾后的e1数据流送给一片公司的cyclone系列的(eplc3t144c8),它将串行的e1数据流存入到fifo,再通过arm的32位外部将数据传送给arm。arm将数据打包通过以太网发送到服务器上。图l所示是本系统的硬件框图。本文主要介绍接在arm

3、的外部总线上的fpga,在uclinux下的驱动程序中断机制的设计。12硬件衔接s3c4510b处理器和fpga的衔接2所示。1.3 fpga内fifo的结构在fpga内部设置了两个fifo。为了防止arm和fpga操作的矛盾,arm和fpga对两个fifo操作采纳乒乓方式,这样arm和fpga就可以同时操作不同的fifo,而不需要等待。fifo的大小是4096位,能容纳一个e1复帧的数据量。当fpga将一个fifo填满后,会用中断的方式通知arm来读fifo,同时fpga会置内部的f1fo状态寄存器。fifo)状态寄存器命名为fpga_imf,是一个32位的寄存器,用其中某几位置“l”,表示

4、对应的fifo需要读取。2 软件设计中断驱动的io是指,输人数据在中断期间被填充到缓冲区内,并由读取该设备的进程取走缓冲区内的数据;输出缓冲区由写设备的进程填充,并在中断期间取走数据。数据缓冲可以将数据的发送和接收与write及read系统调用分别开来,提高系统的整体性能。下面是ucllnux下的中断程序的设计。2.1 uclinux下的中断程序在uclinux系统中,通过调用下面这个函数向系统申请一个中断通道(或中断哀求irq),并在处理完以后释放掉它。mt reqliest_irq(unsigned int irq,void(*handler)(int,vold*,struct pt_re

5、gs*),unsigned 10ng flags,const chat*device,vold*dev_id);void free_irq(unstgned int lrq,void*dev_id);其中,irq是中断号。在本系统中它对应于s3c4510b的21个中断源。这里用的是中断源o。handler指向要安装的中断处理函数的指针。flags是一个与中断管理有关的各种选项的字节掩码。device传递给request_irq的字符串,在procinterrupts中用于显示中断的拥有者。dev_id指针用于分享的中断信号线。函数的返回值为o时表示胜利,或者返回一个负的错误码。函数返回一ebu

6、jsy通知另一个设备驱动程序已经用法了要申请的中断信号线。下面是fpga的设备中断申请函数。这个函数是在驱动中的fpga_open函数中被调用的。int fpga_open(struct inode*inocle,stuct_file*file)int result;result=request_irq(fpga_irq,fpga_isr,sa_inter-rupt,fpga,null);if(resuit!=o)printk(kern_info”can not register fpga isr!n”);elseprintk(kern_infofpga isr register succes

7、sfully!n”);在申请了中断通道后,系统会响应外部中断0,而进入中断处理程序。中断处理程序的第一步是要先清除s3c4510b的中断悬挂寄存器的外部中断o位。这是为了让fpga可以产生新的中断。在uclinux系统中是调用下面的宏来实现的。deflne clear_pend_int(n) intpend=(1(n)中断处理程序功能就是将有关中断接收的信息反馈给设备,并按照要服务的中断的不同含义相应地对数据举行读写。所以fpga的中断处理的主要任务是,读取fpga中fifo状态寄存器的值,猎取需要读取的fifo的信息并支配接收数据。在程序中用到了系统提供的inl函数。unmgned mt s

8、tatusstatus=inl(fpga_imf);中断处理程序的执行应尽可能的短,而从fpga中接收数据,一次必需读完一个fifo及128字。这是一个需要较长时光的外部io操作,所以把这个操作放到中断处理的底半部(bottom-haif)来完成。下面介绍中断处理的底半部的设计。2.2 bh机制底半部处理程序和上半部最大的不同就在于,在执行bh时全部的中断都是打开的,所以说它是在“更平安”时光内运行。24版本的uclinux内核有三种机制来实现底半部的处理:软中断、tasklet和bh。在这里选用了较为容易的bh机制。bh机制事实上是一个任务队列,中断处理程序将要处理的任务插到特定的任务队列中

9、等待内核执行。内核维护着多个任务队列,但驱动程序只能用前三种:tq_scheduler队列。当调度器被运行时,该队列就会被处理。由于此时调度器在被调度出的进程的上下文中运行,所以该队列中的任务几乎可以做任何事。它们不会在中断时运行。tq_timer队列。该队列由定时器队列处理程序(timertick)运行,由于该处理程序是在中断时问运行的。该队列中的全部任务就也是在中断时光内运行的。tu_lmmediate队列。立刻队列在系统调用返回时或调度器运行时尽快得处处理的(不管两种状况谁先发生了)。该队列是在中断时光内得处处理的。队列元素由下面的结构来描述:structtq_structstructq

10、_struct*mext*激活的bh的链接表*unsigned 1ong sync;*必需初始化为零*void(*outine)(vold*);*调用的函数*void*data; *传递给函数的参数*;上面的数据结构中最重要的字段是rotltine和data。将要延迟的任务插入队列,必需先设置好结构的这些字段,并把next和sync两个字段清零。结构中的sync标记位用于避开同一任务被插人多次,这会破坏next指针。一旦任务被排人队列,该数据结构就被认为是内核“拥有”了,不能再被修改。在fpga的驱动中,定义了一个任务队列元素用于完成底半部分:struct tq_struct el_task;

11、unsigned int el_line;el_line数组用来保存传递给任务的参数。在打开fpga时要对任务队列结构赋值:el_taskroutine=fpga_bh;e1 taskdata=e1_line:上面的fpga_bh是底半部分处理函数void fpga_bh(unsigned int*line)的函数名,el_line是传递给fpga_bh函数的实参。与任务队列有关的还有下面的函数:void queue_task(struct tq_struet*task,task_queue*list);正如该函数的名字,本函数用于将任务排进队列中。它关闭了中断,避开了竞争,因此可以被模块中任

12、一函数调用。fpga的任务被插入到tq_immediate队列中,所以,list被赋值为tq_immediate。当某段代码需要调度运行下半部处理时,只要调用mark_bh即可:void mark_bh(int nr);这里,nr是激活的bh的类型。这个数是在头文件中定义的一个符号常数。每个下半部bh相应的处理函数由拥有它的那个驱动程序提供。完成任务队列元素设置后,中断处理函数中就可以启用bh机制。在读得fpga_imf的值后将其赋给el_line,然后调用queue_task将任务插入到tq_immediate队列中,再调用mark_bh(immediate_bh),启动底半部分处理。到此,

13、中断处理程序就可以退出了。2.3底半部分处理程序和缓冲区uclinux操作系统退出中断处理程序后,会立刻将tq_immediate队列中任务投入运行,其中也有fpga_bh函数。在进入fpga_bh同时,系统会将el_line的地址作为实参传递给形参line。也就是将fifo状态寄存器(fpga_imf)的值间接传给了底半部处理程序。底半部分程序中会检查这个值的每一位,据此打算需要读的fifo。从fifo中读上来的数据都是存放在内核的缓冲区中的。由于每一个fifo的容量是一个e1的复帧,所以内核的缓冲也是以e1复帧的大小为一个缓冲块。缓冲块用链表串连起来。缓冲单元的数据结构如下:struct

14、buf_structstruct list_head list; *链表头*unsigned int buf_size; *数据块的大小*unsigned int*bulhead; *缓冲块的指针*unsigned int*bul_curl*缓冲块当前指针*;buf_size解释了数据块的大小。这是一个以“字”为单位的数值。缓冲块在内核堆区开拓,buf_head指向实际的缓冲块的首地址,而buf_cur指向缓冲块中正在操作的单元。为了用法链表机制,驱动必需包含头文件。其中定义了list_head类型结构:struct list_headstruct list_head*next*prev;为了

15、拜访缓冲块链表,还要建立一个链表头,在驱动 中定义全局变量:?struct list_head read_list;链表头必需是一个自立的list_head结构。在用法之前,必需用init_list_head宏来初始化链表头:init_list_head(&readlist); ilinux系统提供了链表的操作函数,在头文件中:list_add(struet list_head*new,struct list_head*head);*在链表头后插入一个新项*list_add_tail(stuot list_head*new,struet list_head*head); *在链表尾部添

16、加一个新项*list_del(struet_list_head*entry); *将给定项从链表中删除*list_empty(struct list_head*head)*推断链表是否为空*list_entry(struct list_head。ptr,type_of_struet,field_ name); *拜访包含链表头的结构*其中list_entry的作用是一个1ist_head结构指针映射回一个指向包含它的大结构的指针。ptr是指向structlist_head结构的指针,type_of_struct是包含ptr的结构类型,field_name是结构中链表字段的名字。如可以用这个宏将

17、指向数据缓冲块的链表指针(readl)映射为缓冲块结构指针(buf):struet buf_strcut*buf=list_entry(real,struct buf_struct,list);底半部分处理程序中,内核缓冲块是动态分配的。由于驱动程序是内核的一部分,所以在内核堆区开拓缓冲区就要用专用的函数,在头文件定义了如下函数:void*kmalloc(size t size,int flags);*在内核堆中分配size大小的空问*void kfree(void*obi/*释放kmalloc分配的空间*kmalloc函数的第1个参数是size(大小),第2个参数是优先权。最常用的优先权是g

18、fp_kernel,它的意思是该内存分配是由运行在内核态的进程调用的。有时kmalloc是在进程上下文之外调用的,比如在中断处理、任务队列处理和内核定时器处理时发生。这些状况下,current进程就不应当进入睡眠状态,这时应当就用法优先权gfp_atomic。不要过于频繁地用kmalloc在内核堆中分配空间,由于在分配空间时可能有中断到来,这样是担心全的。在驱动中建立另一个链表用于回收用法过的缓冲块。在驱动中用free_1ist作为回收缓冲块的链表头:struct list_head free_list;这样就存在两个链表:一个是装载着数据的链表,一个是已经用法过的缓冲块的链表(称为自由链表)

19、。那么只要自由链表中还有表项,在需要缓冲块时就可以挺直从自由链表中取出一个用法,而不用kmalloc再去分配。2.4 堵塞型io和自旋锁的用法在驱动程序中,read的工作是将内核缓冲区中拷贝到用户空间。在举行这种操作时有两种状况是应当注重的:当read时发觉读链表是空,也就是还没有数据可读。这种状况下,可以让read立刻返回一eagain,告诉用户进程没有读到数据;另一个方法就是实现堵塞型io,在没有数据可读时让用户进程进入睡眠状态并等待数据。有几种处理和唤醒的办法,都要处理同一个基本的数据类型等待队列(walt_queue_head_t),就是由正在等待某大事发生的进程组成的一个队列。用法之

20、前必需声明和初始化,在驱动程序中是如下声明的:wait_queue_head_t read_jqueue;init_waitqueue_head(read_queue);可以调用如下函数之一让进程进入睡眠状态:void wait_evet(wait_queue_head_ queue,int condition);int wait_evem_interruptible(walt_queue_hean_t queue,int condition);这两个函数把等待大事和测试大事是否发生合并起来。调用之后,进程会向来睡眠到c布尔表达式condition为真时为止。在驱动中的read函数中,推断读链

21、表为空,就调用它进入睡眠:while(1ist_efnpty(read_list)if(filp一f_flags o_nonblock)*假如设置成非堵塞io*returneagain;if(wait_evert_interruptible(read_queue,!list_empty(read_list) returnerestartsys;对应上面的函数,要唤醒进程可以调用下面的函数:wake_up(wait_queue_gead_t*queue);wake_up_jnterruptlbk(wait_queue_head_t*queue);驱动程序应当在数据到来后准时唤醒进程,也就是从fi

22、fo读取数据后,在退出底半部处理程序前执行:wake_up_mteriuptible(read_queue);要指出的是被唤醒并不保证等待的大事发生了,所以从睡眠态返回后,应当循环测试condition。当read操作正在拜访某一个链表时,底半程序也要拜访同一个链表。这样是比较危急的,应当避开。为了避开这种状况的发生,这里用法自旋锁。在read操作拜访链表前获得锁,拜访结束时解锁。底半部要拜访链表时先要检查自旋锁是否已上锁,假如有,则等待到锁可用。自旋锁用法类型spinlock_t来描述。自旋锁被声明和初始化为不加锁状态方式如下:spinlock_t1ist_10ck=spin_lock_unlocked;处理自旋锁的函数如下:spill_1ock_bh(spllalock-t*1ock);spin_unloek_bh(splnlock_t*lock);这里用法获得自旋锁并且阻挡底半部执行的函数,就可以彻低保证底半部程序不会在read操作拜访链表时来拜访链表。程序中如下实现:spln_lo

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论