您当前的位置:五五电子网电子知识单片机-工控设备嵌入式系统-技术uClinux下中断驱动的I/O方式 正文
uClinux下中断驱动的I/O方式

uClinux下中断驱动的I/O方式

点击数:7731 次   录入时间:03-04 11:40:29   整理:http://www.55dianzi.com   嵌入式系统-技术


www.55dianzi.com

  2.3底半部分处理程序和缓冲区

   uClinux 操作系统 退出中断处理程序后,会立即将tq_immediate队列中任务投入运行,其中也有FPGA_bh函数。在进入fPGA_bh同时,系统会将el_line的地址作为实参传递给形参line。也就是将FIFO状态寄存器(FPga_imf)的值间接传给了底半部处理程序。底半部分程序中会检查这个值的每一位,据此决定需要读的FIFO。

  从FIFO中读上来的数据都是存放在内核的缓冲区中的。因为每一个FIFO的容量是一个E1的复帧,所以内核的缓冲也是以E1复帧的大小为一个缓冲块。缓冲块用链表串连起来。缓冲单元的数据结构如下:

  struct buf_struct{
       struct list_head list;?? /*链表头*/
       unsigned int buf_size;?? /*数据块的大小*/
       unsigned int*buLhead;??? /*缓冲块的指针*/
       unsigned int*buL_curl???? /*缓冲块当前指针*/
};

  buf_size说明了数据块的大小。这是一个以“字”为单位的数值。缓冲块在内核堆区开辟,buf_head指向实际的缓冲块的首地址,而buf_cur指向缓冲块中正在操作的单元。为了使用链表机制,驱动必须包含头文件。其中定义了list_head类型结构:

  struct list_head{

  struct list_head*next.*prev;

  为了访问缓冲块链表,还要建立一个链表头,在驱动 中定义全局变量:?

  struct list_head read_list;

  链表头必须是一个独立的list_head结构。在使用之前,必须用INIT_LIST_HEAD宏来初始化链表头:

  INIT_LIST_HEAD(&readlist); I
        Linux系统提供了链表的操作函数,在头文件中:?
       list_add(struet list_head*new,struct list_head*head);???? /*在链表头后插入一个新项*/?
       list_add_tail(stuot list_head*new,struet list_head*head); /*在链表尾部添加一个新项*/?  nbsp;  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是结构中链表字段的名字。如可以用这个宏将指向数据缓冲块的链表指针 (readl)映射为缓冲块结构指针(buf):?

  struetbuf_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个参数是优先权。最常用的优先权是GFP_KERNEL,它的意思是该内存 分配是由运行在内核态的进程调用的。有时kmalloc是在进程上下文之外调用的,比如在中断处理、任务队列处理和内核定时器处理时发生。这些情况下,current进程就不应该进入睡眠状态,这时应该就使用优先权GFP_ATOMIC。

  不要过于频繁地用kmalloc在内核堆中分配空间,因为在分配空间时可能有中断到来,这样是不安全的。在驱动中建立另一个链表用于回收使用过的缓冲块。在驱动中用free_1ist作为回收缓冲块的链表头:

  struct list_head free_list;

  这样就存在两个链表:一个是装载着数据的链表,一个是已经使用过的缓冲块的链表(称为自由链表)。那么只要自由链表中还有表项,在需要缓冲块时就可以直接从自由链表中取出一个使用,而不用kmalloc再去分配。

  2.4 阻塞型I/O和自旋锁的使用

  在驱动程序中,read的工作是将内核缓冲区中拷贝到用户空间。在进行这种操作时有两种情况是应该注意的:

  ①当read时发现读链表是空,也就是还没有数据可读。

  这种情况下,可以让read立即返回一EAGAIN,告知用户进程没有读到数据;另一个办法就是实现阻塞型I/O,在没有数据可读时让用户进程进入睡眠状态并等待数据。



www.55dianzi.com

  有几种处理和唤醒的方法,都要处理同一个基本的数据类型——等待队列(walt_queue_head_t),就是由正在等待某事件发生的进程组成的一个队列。使用之前必须声明和初始化,在驱动程序中是如下声明的:

  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函数中,判断读链表为空,就调用它进入睡眠:

  while(1ist_efnpty(&read_list)){
        If(filp一>f_flags δO_NoNBLOCK)/*如果设置成非阻塞I/o*/
       return—EAGAIN; 
    ;    if(wait_evert_interruptible(read_queue,!list_empty(δread_list))) return—ERESTARTSYS;

  }

  对应上面的函数,要唤醒进程可以调用下面的函数:

  wake_up(wait_queue_gead_t*queue);

  wake_up_jnterruptlbk(wait_queue_head_t*queue);

  驱动程序应该在数据到来后及时唤醒进程,也就是从FIFO读取数据后,在退出底半部处理程序前执行:

  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_lock_bh(&list_lock);
        list_del(readl); /*将使用后的缓冲块从读链表中删除*/
       list_add_tail(readI,&free_list);/*将使用后的缓冲块插入自由链表中*/
       spin_unlock_bh(&list_lock);

  2.5 中断驱动 的I/O

  至此,可以完整地描述ARM与FPGA之间数据流动的过程:当FPGA的一个FIFO满后,向ARM发出中断,ARM进入中断处理程序后,读取FPGA中的FlFO状态寄存器(fpga_imf)的值,然后把一个任务插到立即队列(tq_imrnediate)中,启动底半部分(BH),同时将FIFO) 状态寄存器的值传递给底半部分处理程序(fpga_bh),完成这些工作后退出中断处理程序。进入底半部分处理程序后,根据FIFO状态寄存器的值确定要处理的F1F0。从FIFO中将数据读出存人到内核缓冲块中,这个缓冲块可能是从自由队列(free_list)中取出来的一个。如果自由队列中是空的,就新分配一个缓冲块。接下来将填好的缓冲块加到读队列(read-list)中,并唤醒睡眠的进程,这样底半部分的工作也完成了。当用户进程对FPGA设备进行读操作时,驱动中的read函数检查读链表。如果读链表为空,则进入睡眠并等待数据到来。有数据后将从读队列中取出的缓冲块的数据拷贝到用户空间,然后将使用过的缓冲块插到自由队列中,等待以后再次使用。内核缓冲区的操作过程如图3所示。图3上半部分是在底半部分程序中,下半部分是在read函数中。

内核缓冲区的操作过程

  结语

  连续数据流设备在 uClinux 下的驱动,通常会用到中断机制。本文讨论的中断驱动的I/O式为这种应用提供了一种实用的方法。文中所涉及的链表、阻塞型I/O、自旋锁等技术在驱动程序的开发中也经常得到使用。



上一页  [1] [2] 


本文关键字:暂无联系方式嵌入式系统-技术单片机-工控设备 - 嵌入式系统-技术