Chinaunix首页 | 论坛 | 博客

分类: LINUX

2009-11-07 10:08:58

设备和驱动介绍
用户程序不能直接与硬件通信因为它不能承担诸如执行特定指令,处理中断的处理权限. 设备驱动呈现了与硬件交互的负担和导出应用程序和剩余内核要用来访问设备的接口.应用程序通过/dev中的结点对设备进行操作,使用/sys下面结点来收集设备信息. 你将会看到网络应用程序使用另外机制来路由他们的请求到顶层设备.
下图显示了一个典型的兼容PC系统的硬件块图.你可以看到系统支持各种设备和接口技术,如内存,视频,音频,USB,PCI,WiFi,PCMCIA,I2C,IDE,Ethernet,串口,键盘,鼠标,软驱,并口和红外线. 内存控制器和图形控制器在PC架构里面属于北桥芯片组.外围设备总线从南桥中引出.


下图描叙了假定的嵌入式设备的类似块图. 该图包含了一些不在PC中的接口,如flash内存,LCD,触摸屏和微型猫.

自然,访问外围设备的能力是一个系统功能的关键部分.设备驱动为达到这个提供了引擎.

中断处理
由于I/O的不确定特性和I/O设备与处理器直接速度不匹配的原因,设备通过异步发送确定的某个硬件信号来请求处理器的注意. 这些硬件信号称为中断. 每个中断设备被赋予一个相关的描述符称为中断请求(IRQ)号.当处理器检测到一个IRQ上的中断产生了,它就突然停止正在做的事情来调用一个为对应的IRQ注册的中断处理程序(ISR). 中断处理程序(ISR)在中断上下文中执行.


中断上下文

ISR是代码中的一个重要部分,它直接通硬件打交道. 它们在更好的系统性能下被赋予了即时执行权限. 但是如果ISR不快和非轻量级,就会与自身含义相抵触. 为补偿当前线程执行时的粗鲁中断,ISR不得不有礼貌的在一个受限的称为中断上下文(或称为原子上下文)的环境中执行.

下面是中断上下文中该做的和不该做的列表:
1. 如果你的中断上下文代码进入休眠就是个祸害. 中断处理程序不能通过调用睡眠函数如schedule_timeout()来放弃处理器. 在你的中断处理函数中调用内核API前请仔细刺探下它内部是否会触发一个阻塞等待.例如input_register_device()在表面上没有害处,但是用GFP_KERNEL作为参数的内部kmalloc()调用就不利了.
2. 为了保护中断处理函数礼貌的临界区,你不能使用信号量因为它们可能会进入休眠.使用自旋锁并在必须的情况下使用.
3. 中断处理函数不能直接和用户空间交换数据因为它们不能通过进程上下文连到用户区. 这是中断处理函数不能睡眠的另外一个原因: 调度器以进程的粒度工作,所以如果一个中断处理函数休眠了并被调度出去,那怎么把它拉回到运行队列中呢?
4. 中断处理函数用意是快速跳出执行但是不能指望工作就完成了. 为包围(circumvent) Catch-22,中断处理函数通常把自己的工作一分为二. 细小的前半部用来应答声明它已经服务了中断,但是实际上加载所有的繁重任务到后半部. 当所有中断被允许时,后半部的执行被推迟到一个稍后时间点.
5. 你不需设计一个汇总段处理函数为可重入的.当一个中断处理函数正在运行,对应的IRQ被禁止直到处理函数返回. 所以不像进程上下文代码,同一个处理函数的不同实例不能在一个多处理器上同时运行.
6. 中断处理函数可以被IRQ优先级更高的中断处理函数打断. 为防止这个嵌套中断你可以请求内核把你的中断处理函数看做是快速中断处理函数. 快速中断处理函数在本地处理器上所有中断禁止情况下运行. 在禁止中断或标识你的中断处理函数被快速前,请注意中断滞后(interrupt-off) 时间对系统性能有害. 中断滞后时间越多,潜在中断越多或一个产生的中断被服务前的延迟越长. 潜在中断反转了系统的实时响应时间.

一个函数可以通过检查in_interrupt()的返回值来判断它是否运行在中断上下文中。

同步到来的中断跟外部硬件产生的异步中断不同。同步中断之所以这样称呼是因为它们并不是毫无预料的发生而是处理器自身执行一条指令时产生的。外部中断和同步中断都是内核使用识别机制来处理的。

同步中断的例子包含下面这些:
1、异常,用来报告严重的运行时错误
2、软中断,例如int 0x80指令在x86架构上实现系统调用


指派IRQ

设备驱动把IRQ号码联系到中断处理函数。因此它们需要知道指派到要驱动设备的IRQ。 IRQ指派可以是直接的或需要复杂的探测。 在PC架构中,定时器中断指派IRQ 0,RTC中断回复IRQ 8。 猫总线技术如PCI足够老练的来响应自身的IRQ查询(当引导期间在总线上行进时由BIOS指派). PCI驱动可以深入到设备配置空间内的标记区域来断定出IRQ. 对于诸如基于ISA卡的老旧设备,驱动可能不得不根据硬件特定知识来测探破译出IRQ.

查看/proc/interrupts可以得到系统上激活的IRQ列表.

设备例子: 滚轮
现在你可以学习到基本的中断处理过程. 我们来实现一个滚轮的中断处理函数. 滚轮可以在电话和PDA上找到,来简化菜单导航和实现三种移动:顺时针滚动, 逆时针滚动, 点击. 处理器的中断处理在IRQ 7上发生. 处理器GPIO的D端口上的三个低比特位与滚动设备相连. 不同的滚轮移动在引脚上产生的波形如下图,中断处理函数的任务就是查看D端口GPIO数据寄存器解读出滚轮移动.


驱动首先请求IRQ并与一个中断处理函数关联起来:
#define ROLLER_IRQ  7
static irqreturn_t roller_interrupt(int irq, void *dev_id);

if (request_irq(ROLLER_IRQ, roller_interrupt, IRQF_DISABLED |
                IRQF_TRIGGER_RISING, "roll", NULL)) {
  printk(KERN_ERR "Roll: Can\\'t register IRQ %d\\\\n", ROLLER_IRQ);
  return -EIO;
}
来看下request_irq函数的参数, IRQ号在这种简单情形下不用查询或测探获得而仅仅是硬编码为ROLLER_IRQ. 第二个参数roller_interrupt()是中断处理函数,它的原型指明了一个irqreturn_t返回类型,如果中断成功被处理则返回类型是IRQ_HANDLED,否则就是IRQ_NONE.  返回值采用了更加重要如PCI的I/O技术,这样多设备可以共享同一个IRQ.
IRQF_DISABLED标志表明中断处理必须让内核以快速中断来对待,所以内核在调用中断处理函数时要先禁止中断。 IRQF_TRIGGER_RISING声明了当滚轮想发送一个中断信号时它将在中断线杀死那个产生一个上升沿. 换句话说滚轮是一个上下升沿敏感的设备. 一些设备是水平敏感的,它保持中断线直到CPU为它服务. 要标志中断是水平敏感的,使用IRQF_TRIGGER_HIGH标志. 这个参数的其他可能值包括IRQF_SAMPLE_RANDOM和IRQF_SHARD(用来指明该IRQ在多设备间共享使用).
下个参数"roll"用来在诸如/proc/interrupts文件中识别这个设备的数据. 最后的参数在这种情况下设置为NULL,它仅仅跟共享中断有关用来设别共享IRQ的设备.
自2.6.19内核以来,中断处理函数接口发生了一些变化. 中断处理函数曾经带一个第三参数(struct pt_regs *),它包含指向CPU寄存器的指针,但这在2.6.19内核以后移除掉了。中断标准的IRQF_xxx系列也是如此,取代了SA_xxx系列。例如在早期内核中,你得用SA_INTERRUPT而不是IRQF_DISABLED来标记中断处理函数为快速中断。

驱动初始化不是请求IRQ的好场所因为那样会拱起有价值资源尽管它们没有在使用。所以,设备驱动通常在应用程序打开设备时请求IRQ。 同样,IRQ在应用程序关闭设备时释放而不是在退出驱动模式时。 这样释放IRQ:
free_irq(int irq, void *dev_id);
下面代码展示了滚动中断处理程序的实现.
spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;
static DECLARE_WAIT_QUEUE_HEAD(roller_poll);

static irqreturn_t roller_interrupt(int irq, void *dev_id)
{
  int i, PA_t, PA_delta_t, movement = 0;

  /* Get the waveforms from bits 0, 1 and 2
     of Port D as shown in Figure 4.3 */
  PA_t = PORTD & 0x07;

  /* Wait until the state of the pins change.
     (Add some timeout to the loop) */
  for (i=0; (PA_t==PA_delta_t); i++){
    PA_delta_t = PORTD & 0x07;
  }

  movement = determine_movement(PA_t, PA_delta_t); /* See below */

  spin_lock(&roller_lock);

  /* Store the wheel movement in a buffer for
     later access by the read()/poll() entry points */
  store_movements(movement);

  spin_unlock(&roller_lock);

  /* Wake up the poll entry point that might have
     gone to sleep, waiting for a wheel movement */
  wake_up_interruptible(&roller_poll);

  return IRQ_HANDLED;
}
int determine_movement(int PA_t, int PA_delta_t)
{
  switch (PA_t){
    case 0:
      switch (PA_delta_t){
      case 1:
        movement = ANTICLOCKWISE;
        break;
      case 2:
        movement = CLOCKWISE;
        break;
      case 4:
        movement = KEYPRESSED;
        break;
      }
      break;
    case 1:
      switch (PA_delta_t){
      case 3:
        movement = ANTICLOCKWISE;
        break;
      case 0:
        movement = CLOCKWISE;
        break;
      }
      break;
    case 2:
      switch (PA_delta_t){
      case 0:
        movement = ANTICLOCKWISE;
        break;
      case 3:
        movement = CLOCKWISE;
        break;
      }
      break;
    case 3:
      switch (PA_delta_t){
      case 2:
        movement = ANTICLOCKWISE;
        break;
      case 1:
        movement = CLOCKWISE;
        break;
      }
    case 4:
      movement = KEYPRESSED;
      break;
  }
}
roller_interrupt()带两个参数:IRQ和作为关联的request_irq()的最后参数的设备描述符.
驱动的入口点如read()和poll()跟roller_interrupt()一起串联起来操作. 例如当处理函数解读滚轮移动时,为回应应用程序如X Windows发起的select()系统调用,它唤醒可能进入睡眠的任何等待的poll()线程.
enable_irq(ROLLER_IRQ)在滚轮移动时允许中断产生,而disable_irq(ROLLER_IRQ)则相反. disable_irq_nosync(ROLLER_IRQ)禁止滚动中断但是并不等待任何当前执行的roller_interrupt()实例返回.这个不同步的disable_irq()相对快些但是可以潜在的引起竞争条件. 仅仅在你知道没有竞争产生时使用这个. disable_irq_nosync()的一个使用例子是drivers/ide/ide-io.c, 初始化过程中发生块中断因为一些系统有那样的麻烦.

软中断和小任务
中断处理函数有两个冲突境地:它们负责大量的设备数据处理但是它们又不得不尽可能快的退出. 为摆脱这样的状况,中断处理函数设计了两个部分: 快速匆忙和硬件打交道的前半部和在所有中断运行条件下放松的处理大部分任务的后半部. 不像中断,后半部是同步的因为内核决定什么时候执行它们.内核中有这些机制来推迟工作到后半部执行:软中断,小任务和工作队列.

软中断是后半部的一个基本机制,它有强烈的锁需要,仅仅用在很少的性能敏感的子系统中如网络层,SCSI层和内核定时器. 小任务基于软中断构建兵器易于使用. 推荐你使用小任务除非你有重要的量度(scalability)或速度需要. 软中断和小任务直接的一个主要区别在于前者是可重入的而后者不能. 软中断的不同实例可以同时运行在不同的处理器上,但是小任务不能.

为阐述软中断和小任务的使用方法,假设前面例子中的滚轮有与生俱来的硬件问题(如轮子偶尔粘住了), 导致不合规范波形的产生. 一个粘住的轮子会持续产生欺骗性的中断并潜在的拖住了系统. 为解决这个问题,捕获波形流对它做一些分析, 如果轮子看起来粘住了就动态的从中断模式切换到polled模式,反之就反过来. 捕获中断处理函数中的波形流并在后半部中执行分析. 下面代码用软中断实现了,稍后的代码使用小任务实现了. 两者都是前面代码的简化, 这减少处理函数为两个函数: 从GPIO D端口中得到波形片段的roller_capture()和在波形上执行算法分析并在需要时切换到polled模式的roller_analyzer().
使用软中断减轻负载工作的中断处理函数:

void __init roller_init()
{
  /* ... */

  /* Open the softirq. Add an entry for ROLLER_SOFT_IRQ in
     the enum list in include/linux/interrupt.h */
  open_softirq(ROLLER_SOFT_IRQ, roller_analyze, NULL);
}


/* The bottom half */
void roller_analyze()
{
  /* Analyze the waveforms and switch to polled mode if required */
}
/* The interrupt handler */
static irqreturn_t roller_interrupt(int irq, void *dev_id)
{
  /* Capture the wave stream */
  roller_capture();

  /* Mark softirq as pending */
  raise_softirq(ROLLER_SOFT_IRQ);

  return IRQ_HANDLED;
}
要定义一个软中断,你得静态添加一个入口到include/linux/interrupt.h中. 不能动态定义. raise_softirq()声明了对应的软中断是悬挂执行的, 内核将在下个合适时候执行它, 这可能是在从中断处理函数退出的时候或通过ksoftirq内核线程执行.
使用小任务减轻中断处理函数的负载:
struct roller_device_struct { /* Device-specific structure */
  /* ... */
  struct tasklet_struct tsklt;
  /* ... */
}

void __init roller_init()
{
  struct roller_device_struct *dev_struct;
  /* ... */

  /* Initialize tasklet */
  tasklet_init(&dev_struct->tsklt, roller_analyze, dev);
}


/* The bottom half */
void roller_analyze()
{
/* Analyze the waveforms and switch to
   polled mode if required */
}
/* The interrupt handler */
static irqreturn_t roller_interrupt(int irq, void *dev_id)
{
  struct roller_device_struct *dev_struct;

  /* Capture the wave stream */
  roller_capture();

  /* Mark tasklet as pending */
  tasklet_schedule(&dev_struct->tsklt);

  return IRQ_HANDLED;
}
tasklet_init()动态初始化一个小任务. 该函数并不为tasklet_struct分配内存,而需要你传递一个分配好的地址给它. tasklet_schedule()声明对应的小任务是悬挂执行的. 和中断相同,内核提供很多函数来控制多处理器系统中小任务的执行状态:
tasklet_enable()允许小任务.
tasklet_disable()禁止小任务并等待直到任何当前执行的小任务实例退出.
tasklet_disable_nosync()在语义上和disable_irq_nosync()类似, 该函数不等待激活的小任务实例完成执行.

中断处理函数和小任务都不可重入,都不能进入睡眠. 另外,中断处理函数,小任务,软中断都不能被抢占且运行在中断上下文环境下.

工作队列是第三种方式来推迟中断处理函数中的工作. 它们在进程上下文中执行允许休眠,所以它们可以使用有睡眠功能的函数如信号量.

阅读(801) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~