文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>ldd3 read note Capter 10

ldd3 read note Capter 10

时间:2009-08-06  来源:hylpro

           
2009.7.2


CHAPTER 10   Interrupt Handling

并口中断

并 口中断的作用是让打印机可以通知lp驱动, 已经做好接受下一个字符的准备.port2的bit 4  (0x37a,0x27a)是中断使能. 位. 一但中断使能后, 当pin10 (status port bit 6) (ACK bit) 从low变成high, 就会触发一个中断.
最简单的办法就是吧pin9 和 pin 10 链接起来. 然后可以向 /dev/short0写入二进制数据来触发中断(注意,ascii不成, ascii最高bit都是0).


*<linux/interrupt.h>
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long flags, const char *dev_name, void *dev_id); //返回0代表成功, 负值代表失败
//dev_id是共享中断的时候使用的,建议无论共享与否,都把它指向device structure
flag:
SA_INTERRUPT : 中断过程中禁止其他中断
SA_SHIRQ : 中断可以被共享
SA_SAMPLE_RANDOM : 可以给/dev/random and /dev/urandom贡献熵.(中断时间不确定)

void free_irq(unsigned int irq, void *dev_id);

如 果不能共享中断, 在模块初始化的时候安装中断并不是一个好注意, 这样有时候会浪费中断资源. 最好在第一次打开的时候(在中断产生前)挂接中断, 在最后一次释放后释放中断. 但是自己要有统计计数. (short呢,不在open内申请中断, 而是在初始化的时候)

if (short_irq >= 0) {
   result = request_irq(short_irq, short_interrupt,
   SA_INTERRUPT, "short", NULL);
   if (result) {
       printk(KERN_INFO "short: can't get assigned irq %i\n",short_irq);
       short_irq = -1;
   }
   else { /* actually enable it -- assume this *is* a parallel port */
            outb(0x10,short_base+2);
   }
}

int can_request_irq(unsigned int irq, unsigned long flags); //是否被占用

一般情况下kernel总是尝试cpu0处理中断(一般情况下是cpu0处理中断),保证cach locality.

/proc/interrupts   #体系结构无关
cat /proc/stat     #特定体系结构
intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0
/proc/stat 这个intr的统计是统计所有中断历史次数. 而/proc/interrupts则没有当前未使用的中断的统计.

Autodetecting the IRQ Number

*short操控的LPT还是有默认值可以用的.
if (short_irq < 0) /* not yet specified: force the default on */
switch(short_base) {
   case 0x378: short_irq = 7; break;
   case 0x278: short_irq = 2; break;
   case 0x3bc: short_irq = 5; break;
}

*有些设备知道自己的irq, 可以从寄存器或者PCI内读出来 (PCI是bios或者linux分配好的)

*最后的自动检测就是让设备产生中断,看看那条线active了

Kernel-assisted probing

仅对非共享中断有效.
<linux/interrupt.h>

unsigned long probe_irq_on(void); //返回一个未分配的中断位图
产生一个中断,(记着要关闭.)
int probe_irq_off(unsigned long); //传入未分配中断位图, 返回>0是探测到的irq, =0是没有中断, <0是多个中断
记着处理处于pending状态的设备

int count = 0;
do {
    unsigned long mask;
    mask = probe_irq_on( );
    outb_p(0x10,short_base+2); /* enable reporting */
    outb_p(0x00,short_base); /* clear the bit */
    outb_p(0xFF,short_base); /* set the bit: interrupt! */
    outb_p(0x00,short_base+2); /* disable reporting */
    udelay(5); /* give it some time */
    short_irq = probe_irq_off(mask);
    if (short_irq = = 0) { /* none of them? */
            printk(KERN_INFO "short: no irq reported by probe\n");
            short_irq = -1;
    }
   
 /*
  * if more than one line has been activated, the result is
  * negative. We should service the interrupt (no need for lpt port)
  * and loop over again. Loop at most five times, then give up
  */
} while (short_irq < 0 && count++ < 5);
if (short_irq < 0)
printk("short: probe failed %i times, giving up\n", count);

建议就是模块加载的时候probe一下就成了.

Do-it-yourself probing

也就3,5,7.9几个.
irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)
{
   if (short_irq = = 0) short_irq = irq; /* found */
   if (short_irq != irq) short_irq = -irq; /* ambiguous */
   return IRQ_HANDLED;
}

Fast and Slow Handlers

SA_INTERRUPT: 禁止其他中断. 建议快速处理的中断使用.(这时,其他cpu还是可以处理其他中断的). 除非理由充分,不要用这个标记.


Implementing a Handler

*不能访问user space
*不能睡眠(不能lock, 不能kmalloc without GFP_ATOMIC)
*不能调用schedule

中断任务,开始是处理..., 最后如果设备有 interrupt pending bit, 要clear掉,否则不会产生下一个中断. 但是并口是没有pending的不用ack设备.

一个中断的典型任务就是唤醒在队列上等待数据的进程. 如果需要大量计算, 还是推迟到tasklet或者workqueue吧.

没有barrier, 编译器会把new直接优化掉, 直接更新index,然后再根据条件调整index. 这样就有一个窗口, index的值是错误的. 这个也算是lock free了.

short提供/dev/shortprint可以驱动一个真正的打印机.
Handler Arguments and Return Value

irq, reg就不提了. dev_id就是request的时候传入的. 如果共享中断的设备, 这个dev_id可以指向device结构. 中断里就可以直接访问这个个结构了.

static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs
*regs)
{
   struct sample_dev *dev = dev_id;
   /* now `dev' points to the right hardware item */
   /* .... */
}

static void sample_open(struct inode *inode, struct file *filp)
{
  struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev);
  request_irq(dev->irq, sample_interrupt,
   0  /* flags */, "sample", dev /* dev_id */);
  /*....*/
  return 0;
}

如果确实是设备发生中断, 需要返回IRQ_HANDLED,否则返回IRQ_NONE. 有个宏可以利用: IRQ_RETVAL(handled)

如果不知道是否是自己设备产生的,就返回IRQ_HANDLED.

Enabling and Disabling Interrupts
禁止中断不是互斥手段,极少使用. driver用spinlock的时候一般要禁止中断,以免造成死锁(肯定是和进程/softirq用同一个spin了...).

Disabling a single interrupt
禁止什么中断都极少用.特别是共享中断,这么做是不成的.
*<asm/irq.h>
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

可 以嵌套使用,两次disable需要两次enable...., 这些函数操作PIC/APIC来禁止单个中断pin. disable irq会等待当前正在运行的中断. 如果进程在持有资源的的情况下(如spin), 调用这个函数,可能引起死锁. disable_irq_nosync 不会等待, 但是仍有部分竞争窗口存在.

plip 的例子
plip device 使用串口模拟网卡.  在接受报文的时候禁止中断.  有个模式的plip最多一次接受5bit(因为打印机电缆的原因),有一个bit作为handshake. 下图是这种NULL电缆.

接受数据和组合niblle参考plip_receive() (当然从status寄存器中读取数据了). 在NB0和NB1的数据有效bit是不同的,一切为了写程序方便.

发送的时候第一次触发对方的ack, 然后发送nb的时候中断被禁止, ack作为数据的一个bit.

Disabling all interrupts

void local_irq_save(unsigned long flags);
void local_irq_disable(void);
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);

在2.6中全系统的中断什么时候都不能关闭....

Top and Bottom Halves

典型的top half 将数据存入设备的buffer,然后调度后半部分就结束了. bottom部分做唤醒进程, 开始下次io的各种工作. bottom有两种技术, tasklet和workqueu. 前者必须全程atomic,后者容许睡眠.

Tasklets
*多次调度一次运行(在运行前)
*在一个软中断环境中运行
*同一个tasklet不会同时运行
*可以和另一个tasklet同时运行
*和调度的cpu在同一个cpu运行: 中断不会被自己的tasklet打断, 但是tasklet可以被自己的中断打断


void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);


irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */
    short_incr_tv(&tv_head);
    tasklet_schedule(&short_tasklet);
    short_wq_count++; /* record that an interrupt arrived */
    return IRQ_HANDLED;
}

void short_do_tasklet (unsigned long unused)
{
   int savecount = short_wq_count, written;
   short_wq_count = 0; /* we have already been removed from the queue */
  /*
   * The bottom half reads the tv array, filled by the top half,
   * and prints it to the circular text buffer, which is then consumed
   * by reading processes
   */
   /* First write the number of interrupts that occurred before this bh */
   written = sprintf((char *)short_head,"bh after %6i\n",savecount);
   short_incr_bp(&short_head, written);
   /*
   * Then, write the time values. Write exactly 16 bytes at a time,
   * so it aligns with PAGE_SIZE
   */
   do {
      written = sprintf((char *)short_head,"%08u.%06u\n",
     (int)(tv_tail->tv_sec % 100000000),
      (int)(tv_tail->tv_usec));
      short_incr_bp(&short_head, written);
      short_incr_tv(&tv_tail); //
   } while (tv_tail != tv_head);
   wake_up_interruptible(&short_queue); /* awake any reading process */
}


Workqueues

*运行在一个特殊内核进程
*不能访问user space

static struct work_struct short_wq;
/* this line is in short_init( ) */
INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);

irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
  /* Grab the current time information. */
   do_gettimeofday((struct timeval *) tv_head);
   short_incr_tv(&tv_head);
  /* Queue the bh. Don't worry about multiple enqueueing */
   schedule_work(&short_wq);
   short_wq_count++; /* record that an interrupt arrived */
   return IRQ_HANDLED;
}


Interrupt Sharing

PCI需要共享中断, linux提供了在所有bus上共享中断的手段,在ISA上也提供了共享.

*必须传递SA_SHIRQ
*dev_id必须唯一, 在共享的情况下为NULL显然不合适
*在中断是无人使用或者大家都是共享的情况下可以注册成功
*需要快速判断是不是自己设备产生了中断, 不是则返回IRQ_NONE(每个共享的handler都得到调用)
*共享中断不能使用内核提供的机制进行probe, 不过这种不能提供irq却要共享的硬件似乎没有
*不要禁止irq, 这个irq不是你自己的了

irqreturn_t short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
  int value, written;
  struct timeval tv;
   /* If it wasn't short, return immediately */
   value = inb(short_base);
   if (!(value & 0x80))
       return IRQ_NONE;
   /* clear the interrupting bit */
    outb(value & 0x7F, short_base); //假定9,10pin被跳线短接
   /* the rest is unchanged */
   do_gettimeofday(&tv);
   written = sprintf((char *)short_head,"%08u.%06u\n",
   (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
   short_incr_bp(&short_head, written);
   wake_up_interruptible(&short_queue); /* awake any reading process */
   return IRQ_HANDLED;
}

注意: 真正的打印机不能辨识是否自己发出了中断, 不支持中断共享.

Interrupt-Driven I/O

driver buffer 可以隔离read/write和真正的数据接受.一个中断驱动的io,设备应该有如下能力:
*在数据到达可以被cpu接受的时候发出中断, 然后使用io/内存映射或者DMA来进行数据传输.
*发送成功或者可以接受新的数据的时候发出中断.

shortprint driver

*shortprint driver maintains a one-page circular output buffer
*write 函数不进行任何I/O, 只是吧数据写入上述buffer

shortp_write core:
*有sem 保护这段代码
*使用workqueue向设备发送数据, 访问shortp_output_active 用spinlock来保护.


static void shortp_start_output(void)
{
   if (shortp_output_active) /* Should never happen */
     return;
   /* Set up our 'missed interrupt' timer */
   shortp_output_active = 1;
   shortp_timer.expires = jiffies + TIMEOUT;
   add_timer(&shortp_timer);
   /* And get the process going. */
   queue_work(shortp_workqueue, &shortp_work);
}

*中断有可能丢失, 用了一个保活timer

下面代码是workqueue core:

下面是写一个字符到打印机

static irqreturn_t shortp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
  if (! shortp_output_active)
       return IRQ_NONE;
   /* Remember the time, and farm off the rest to the workqueue function */
   do_gettimeofday(&shortp_tv);
   queue_work(shortp_workqueue, &shortp_work);
   return IRQ_HANDLED;
}



相关阅读 更多 +
排行榜 更多 +
金属狂怒

金属狂怒

赛车竞速 下载
大家来挖矿

大家来挖矿

赛车竞速 下载
越野泥跑者

越野泥跑者

赛车竞速 下载