[置顶] 《Linux中断编程》中断处理底半部

Android2.3及Linux2.6.29内核模拟器版本编译与调试

一、Linux内核中断机制

1.同步中断:当一条指令执行完毕后,由CPU控制单元产生、而不是发生在代码指令执行期间的中断。也叫异常,例如系统调用。(注意:它运行在进程上下文!!!

2.异步中断:由其他硬件设备依照CPU时钟信号随机产生、能够发生在指令执行过程中的中断。也叫硬件中断,例如键盘中断,这是狭义上的中断概念。(注意:异步中断内核都运行在中断上下文中。

3.软中断:Linux2.6内核中软中断还是由函数do_softirq调用,调用时机如下:

硬件处理完一个中断之后(irq_exit函数):一定在中断上下文中。

内核线程ksoftirqd:一般在进程上下文中。

显式调用do_softirq的地方(比如net子系统中):一般在进程上下文中。

因此,软中断属于同步中断,但其可能运行在中断上下文、也可能运行在进程上下文。

广义上的中断应该包含上述两个部分。

二、中断上下文

  在Linux系统中,异步中断发生,内核在进入中断之前都会保存当前的进程上下文、然后CPU的寄存器值变为当前发出中断信息的硬件设备的值,内核运行与中断上下文中;由于此时已经没有进程上下文,因此,需要避免使用会引起调度的函数(如msleep、信号量获取等)、因为这些函数都与具体进程相关。

三、中断底半部的实现方式

中断处理的矛盾:它需要完成大量的设备数据处理,同时、又不得不快速的退出。

解决方法:中断处理分成2部分,顶半部和底半部;顶半部是异步的,底半部是同步的。

下半部和上半部最大的不同:下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!

实现中断处理底半部的方式如下:

1.softirq和tasklet:这两种方式中断底半部可能运行与中断上下文,因此、不允许有会引起调度的函数使用(如msleep、信号量获取等);这时的中断底半部可以被其他irq打断。

2.workqueue:这种方式中断底半部运行与events/0内核线程的进程上下文,因此、允许有引起调度的函数使用(如msleep、信号量获取等);这时的中断底半部并没有在中断上下文中运行。

四、中断底半部的实现例子

1.tasklet方式

interrupt.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
//add by tankai
#include <linux/delay.h>
//end tankai

static int irq;
static char *devname;
static struct tasklet_struct mytasklet;
static struct work_struct mywork;

module_param(irq, int, 0644);
module_param(devname, charp, 0644);
struct myirq
{
  int devid;
};
struct myirq mydev={1119};

static void mytasklet_handler(unsigned long data)
{
  printk("current1111111->pid is %d\n",current->pid);
  printk("current->pid is %d\n",current->pid);
  printk("in_interrupt() is %d\n",in_interrupt());
  printk("in_irq() is %d\n",in_irq());
  printk("in_softirq() is %d\n",in_softirq()); 
  printk("tasklet is working..\n");
  
  //情况一:使用不带进程调度的睡眠函数
  mdelay(10000);
  
  /*
  //情况二:可以使用带进程调度的睡眠函数  
  msleep(10000);
  */
}
static irqreturn_t myirq_handler(int irq, void *dev)
{
  struct myirq mydev;
  static int count =0;
  mydev=*(struct myirq*)dev;
  printk("##################\n");
  //msleep(10000); //中断顶半部不能调用可能会导致睡眠的函数
  printk("key:%d..\n",count+1);
  //printk("devid:%d ISR is working..\n",mydev.devid);
  //printk("Bottom half will be working..\n");
  printk("current->pid is %d\n",current->pid);
  printk("in_interrupt() is %d\n",in_interrupt());
  printk("in_irq() is %d\n",in_irq());
  printk("in_softirq() is %d\n",in_softirq());   
   
  //底半部实现一:tasklet(由内核线程——软中断(ksoftirqd/0)调度执行),不能睡眠
  tasklet_init(&mytasklet, mytasklet_handler,0); //中断底半部,tasklet实现方式中、底半部也不能睡眠
  tasklet_schedule(&mytasklet); //调度底半部
  //此时,底半部在合适时机运行与软中断上下文
      
  /*
  //底半部实现二:workqueue(由内核线程——events/0调度执行),可以睡眠
  INIT_WORK(&mywork, mytasklet_handler);
  schedule_work(&mywork);
  //此时,底半部在合适时机运行与events/0的进程上下文
  */
  printk("ISR is leaving\n");
  printk("##################\n");
  count++;
  return IRQ_HANDLED;
}

static int __init myirq_init(void)
{
  printk("Module is working..\n");
  if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0)
  {
    printk("%s request IRQ:%d failed..\n",devname,irq);
    return -1;	
  }

  printk("%s rquest IRQ:%d success..\n",devname,irq);
  return 0;
}

static void __exit myirq_exit(void)
{
  printk("Module is leaving");
  /*
  //移除底半部一:
  tasklet_kill(&mytasklet);
  */  
  
  /*
  //移除底半部二:
  destroy_workqueue(&mywork);
  */

  free_irq(irq, &mydev);
  printk("%s request IRQ :%d success...\n",devname ,irq);
}

module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m:=interrupt.o
PWD:=$(shell pwd)
CUR_PATH:=$(shell uname -r)
KERNELPATH:= /home/android2.3/android2.3_kernel/

all:
	make -C $(KERNELPATH) M=$(PWD) modules
clean:
	make -C $(KERNELPATH) M=$(PWD) clean

查看系统当前irq中断:

cat /proc/interrupts
           CPU0
  1:          1    goldfish  goldfish_pdev_bus
  3:       8965    goldfish  Goldfish Timer Tick
  4:          0    goldfish  goldfish_tty
 10:          0    goldfish  goldfish_rtc
 11:        149    goldfish  goldfish_tty
 12:        364    goldfish  eth0
 13:        326    goldfish  goldfish_fb
 14:          1    goldfish  goldfish_audio
 15:       1241    goldfish  goldfish_mmc
 16:          0    goldfish  goldfish-battery
 17:        116    goldfish  goldfish-events-keypad
 18:          1    goldfish  goldfish_switch
 19:          0    goldfish  goldfish_switch
Err:          0

运行,与tty共享中断向量:

insmod interrupt.ko irq="11"

结果:
current1111111->pid is 0
current->pid is 0
in_interrupt() is 256 //在中断上下文
in_irq() is 0
in_softirq() is 256 //而且是软中断、同步中断
tasklet is working..

2.workqueue方式

interrupt.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
//add by tankai
#include <linux/delay.h>
//end tankai

static int irq;
static char *devname;
static struct tasklet_struct mytasklet;
static struct work_struct mywork;

module_param(irq, int, 0644);
module_param(devname, charp, 0644);
struct myirq
{
  int devid;
};
struct myirq mydev={1119};

static void mytasklet_handler(unsigned long data)
{
  printk("current1111111->pid is %d\n",current->pid);
  printk("current->pid is %d\n",current->pid);
  printk("in_interrupt() is %d\n",in_interrupt());
  printk("in_irq() is %d\n",in_irq());
  printk("in_softirq() is %d\n",in_softirq()); 
  printk("tasklet is working..\n");
  /*
  //情况一:使用不带进程调度的睡眠函数
  mdelay(10000);
  */
  
  //情况二:可以使用带进程调度的睡眠函数  
  msleep(10000);
  
}
static irqreturn_t myirq_handler(int irq, void *dev)
{
  struct myirq mydev;
  static int count =0;
  mydev=*(struct myirq*)dev;
  printk("##################\n");
  //msleep(10000); //中断顶半部不能调用可能会导致睡眠的函数
  printk("key:%d..\n",count+1);
  //printk("devid:%d ISR is working..\n",mydev.devid);
  //printk("Bottom half will be working..\n");
  printk("current->pid is %d\n",current->pid);
  printk("in_interrupt() is %d\n",in_interrupt());
  printk("in_irq() is %d\n",in_irq());
  printk("in_softirq() is %d\n",in_softirq());   
  /*  
  //底半部实现一:tasklet(由内核线程——软中断(ksoftirqd/0)调度执行),不能睡眠
  tasklet_init(&mytasklet, mytasklet_handler,0); //中断底半部,tasklet实现方式中、底半部也不能睡眠
  tasklet_schedule(&mytasklet); //调度底半部
  //此时,底半部在合适时机运行与软中断上下文
  */    
  
  //底半部实现二:workqueue(由内核线程——events/0调度执行),可以睡眠
  INIT_WORK(&mywork, mytasklet_handler);
  schedule_work(&mywork);
  //此时,底半部在合适时机运行与events/0的进程上下文
  
  printk("ISR is leaving\n");
  printk("##################\n");
  count++;
  return IRQ_HANDLED;
}

static int __init myirq_init(void)
{
  printk("Module is working..\n");
  if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0)
  {
    printk("%s request IRQ:%d failed..\n",devname,irq);
    return -1;	
  }

  printk("%s rquest IRQ:%d success..\n",devname,irq);
  return 0;
}

static void __exit myirq_exit(void)
{
  printk("Module is leaving");
  /*
  //移除底半部一:
  tasklet_kill(&mytasklet);
  */  
  
  /*
  //移除底半部二:
  destroy_workqueue(&mywork);
  */

  free_irq(irq, &mydev);
  printk("%s request IRQ :%d success...\n",devname ,irq);
}

module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");

Makefile等同上,

运行,与tty共享中断向量:

insmod interrupt.ko irq="11"

结果:

current1111111->pid is 4
current->pid is 4
in_interrupt() is 0 //不在中断上下文
in_irq() is 0
in_softirq() is 0
tasklet is working..

你可能感兴趣的:([置顶] 《Linux中断编程》中断处理底半部)