免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1926 | 回复: 1
打印 上一主题 下一主题

ldd3 read note Capter 10 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-08-06 09:33 |只看该作者 |倒序浏览

                    
   
   
           
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).
*
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
unsigned long probe_irq_on(void); //返回一个未分配的中断位图
产生一个中断,(记着要关闭.)
int probe_irq_off(unsigned long); //传入未分配中断位图, 返回>0是探测到的irq, =0是没有中断,

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
禁止什么中断都极少用.特别是共享中断,这么做是不成的.
*
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;
}

               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/79526/showart_2019289.html

论坛徽章:
0
2 [报告]
发表于 2014-05-23 07:33 |只看该作者
请问你运行过这个程序吗? 能产生中断吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP