linux 中断机制的处理过程

一、中断的概念

中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。

在实地址模式中,CPU把内存中从0开始的1KB空间作为一个中断向量表。表中的每一项占4个字节。但是在保护模式中,有这4个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量表的表项由8个字节组成,而中断向量表也叫做了中断描述符表(IDT)。在CPU中增加了一个用来描述中断描述符表寄存器(IDTR),用来保存中断描述符表的起始地址。

二、中断的请求过程

外部设备当需要操作系统做相关的事情的时候,会产生相应的中断。设备通过相应的中断线向中断控制器发送高电平以产生中断信号,而操作系统则会从中断控制器的状态位取得那根中断线上产生的中断。而且只有在设备在对某一条中断线拥有控制权,才可以向这条中断线上发送信号。也由于现在的外设越来越多,中断线又是很宝贵的资源不可能被一一对应。因此在使用中断线前,就得对相应的中断线进行申请。无论采用共享中断方式还是独占一个中断,申请过程都是先讲所有的中断线进行扫描,得出哪些没有别占用,从其中选择一个作为该设备的IRQ。其次,通过中断申请函数申请相应的IRQ。最后,根据申请结果查看中断是否能够被执行。

中断机制的核心数据结构是irq_desc,它完整地描述了一条中断线(或称为 “中断通道”)。以下程序源码版本为linux-2.6.32.2。

其中irq_desc结构在include/linux/irq.h中定义:

typedef   void(*irq_flow_handler_t)(unsigned int irq,

struct irq_desc *desc);

struct irq_desc {

unsignedint      irq;

structtimer_rand_state *timer_rand_state;

unsignedint           *kstat_irqs;

#ifdef CONFIG_INTR_REMAP

structirq_2_iommu     *irq_2_iommu;

#endif

irq_flow_handler_t  handle_irq;/*高层次的中断事件处理函数*/

structirq_chip      *chip; /*低层次的硬件操作 */

structmsi_desc      *msi_desc;

void         *handler_data; /*chip方法使用的数据*/

void         *chip_data; /* chip私有数据 */

structirqaction  *action;   /*行为链表(action list) */

unsignedint      status;      /*状态 */

unsignedint      depth;     /*关中断次数 */

unsignedint      wake_depth;   /*唤醒次数 */

unsignedint      irq_count; /*发生的中断次数 */

unsignedlong    last_unhandled;   /*滞留时间 */

unsignedint      irqs_unhandled;

spinlock_t   lock; /*自选锁*/

#ifdef CONFIG_SMP

cpumask_var_t    affinity;

unsignedint      node;

#ifdefCONFIG_GENERIC_PENDING_IRQ

cpumask_var_t    pending_mask;

#endif

#endif

atomic_t     threads_active;

wait_queue_head_t  wait_for_threads;

#ifdef CONFIG_PROC_FS

struct proc_dir_entry    *dir; /*在 proc文件系统中的目录 */

#endif

const char    *name;/*名称*/

} ____cacheline_internodealigned_in_smp;

I、Linux中断的申请与释放:在<linux/interrupt.h>,,实现中断申请接口:

request_irq(unsigned intirq, irq_handler_thandler, unsigned long flags,const char *name, void *dev);

函数参数说明

unsigned int irq:所要申请的硬件中断号

irq_handler_t handler:中断服务程序的入口地址,中断发生时,系统调用handler这个函数。irq_handler_t为自定义类型,其原型为:

typedef irqreturn_t(*irq_handler_t)(int,void *);

而irqreturn_t的原型为:typedef enumirqreturnirqreturn_t;

enum irqreturn {

IRQ_NONE,/*此设备没有产生中断*/

IRQ_HANDLED,/*中断被处理*/

IRQ_WAKE_THREAD,/*唤醒中断*/

};

在枚举类型irqreturn定义在include/linux/irqreturn.h文件中。

unsigned long flags:中断处理的属性,与中断管理有关的位掩码选项,有一下几组值:

#defineIRQF_DISABLED       0x00000020   /*中断禁止*/

#define IRQF_SAMPLE_RANDOM 0x00000040   /*供系统产生随机数使用*/

#defineIRQF_SHARED      0x00000080 /*在设备之间可共享*/

#defineIRQF_PROBE_SHARED   0x00000100/*探测共享中断*/

#defineIRQF_TIMER       0x00000200/*专用于时钟中断*/

#defineIRQF_PERCPU      0x00000400/*每CPU周期执行中断*/

#define IRQF_NOBALANCING 0x00000800/*复位中断*/

#define IRQF_IRQPOLL     0x00001000/*共享中断中根据注册时间判断*/

#define IRQF_ONESHOT     0x00002000/*硬件中断处理完后触发*/

#define IRQF_TRIGGER_NONE   0x00000000/*无触发中断*/

#define IRQF_TRIGGER_RISING 0x00000001/*指定中断触发类型:上升沿有效*/

#define IRQF_TRIGGER_FALLING 0x00000002/*中断触发类型:下降沿有效*/

#define IRQF_TRIGGER_HIGH   0x00000004/*指定中断触发类型:高电平有效*/

#define IRQF_TRIGGER_LOW 0x00000008/*指定中断触发类型:低电平有效*/

#define IRQF_TRIGGER_MASK   (IRQF_TRIGGER_HIGH |IRQF_TRIGGER_LOW | \

IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING)

#define IRQF_TRIGGER_PROBE  0x00000010/*触发式检测中断*/

const char *dev_name:设备描述,表示那一个设备在使用这个中断。

void *dev_id:用作共享中断线的指针.。一般设置为这个设备的设备结构体或者NULL。它是一个独特的标识,用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区,来标识哪个设备在中断。这个参数在真正的驱动程序中一般是指向设备数据结构的指针.在调用中断处理程序的时候它就会传递给中断处理程序的void*dev_id。如果中断没有被共享,dev_id可以设置为 NULL。

II、释放IRQ

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

III、中断线共享的数据结构

struct irqaction {

irq_handler_t handler; /*具体的中断处理程序 */

unsigned long flags;/*中断处理属性*/

const char *name; /*名称,会显示在/proc/interreupts中 */

void *dev_id; /* 设备ID,用于区分共享一条中断线的多个处理程序 */

struct irqaction *next; /*指向下一个irq_action结构 */

int irq;  /* 中断通道号 */

struct proc_dir_entry *dir; /*指向proc/irq/NN/name的入口*/

irq_handler_t thread_fn;/*线程中断处理函数*/

struct task_struct *thread;/*线程中断指针*/

unsigned long thread_flags;/*与线程有关的中断标记属性*/

};

thread_flags参见枚举

enum {

IRQTF_RUNTHREAD,/*线程中断处理*/

IRQTF_DIED,/*线程中断死亡*/

IRQTF_WARNED,/*警告信息*/

IRQTF_AFFINITY,/*调整线程中断的关系*/

};

多个中断处理程序可以共享同一条中断线,irqaction结构中的 next成员用来把共享同一条中断线的所有中断处理程序组成一个单向链表,dev_id成员用于区分各个中断处理程序。

根据以上内容可以得出中断机制各个数据结构之间的联系如下图所示:

三.中断的处理过程

Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

中断号的查看可以使用下面的命令:“cat/proc/interrupts”。

Linux实现下半部的机制主要有tasklet和工作队列。

小任务tasklet的实现

其数据结构为structtasklet_struct,每一个结构体代表一个独立的小任务,定义如下

struct tasklet_struct

{

struct tasklet_struct *next;/*指向下一个链表结构*/

unsigned long state;/*小任务状态*/

atomic_t count;/*引用计数器*/

void (*func)(unsigned long);/*小任务的处理函数*/

unsigned long data;/*传递小任务函数的参数*/

};

state的取值参照下边的枚举型:

enum

{

TASKLET_STATE_SCHED,    /*小任务已被调用执行*/

TASKLET_STATE_RUN   /*仅在多处理器上使用*/

};

count域是小任务的引用计数器。只有当它的值为0的时候才能被激活,并其被设置为挂起状态时,才能够被执行,否则为禁止状态。

I、声明和使用小任务tasklet

静态的创建一个小任务的宏有一下两个:

#define DECLARE_TASKLET(name, func,data)  \

struct tasklet_struct name = { NULL, 0,ATOMIC_INIT(0), func, data}

#define DECLARE_TASKLET_DISABLED(name,func, data) \

struct tasklet_struct name = { NULL, 0,ATOMIC_INIT(1), func, data}

这两个宏的区别在于计数器设置的初始值不同,前者可以看出为0,后者为1。为0的表示激活状态,为1的表示禁止状态。其中ATOMIC_INIT宏为:

#define ATOMIC_INIT(i)   { (i) }

即可看出就是设置的数字。此宏在include/asm-generic/atomic.h中定义。这样就创建了一个名为name的小任务,其处理函数为func。当该函数被调用的时候,data参数就被传递给它。

II、小任务处理函数程序

处理函数的的形式为:voidmy_tasklet_func(unsignedlong data)。这样DECLARE_TASKLET(my_tasklet,my_tasklet_func, data)实现了小任务名和处理函数的绑定,而data就是函数参数。

III、调度编写的tasklet

调度小任务时引用tasklet_schedule(&my_tasklet)函数就能使系统在合适的时候进行调度。函数原型为:

static inline void tasklet_schedule(structtasklet_struct *t)

{

if (!test_and_set_bit(TASKLET_STATE_SCHED,&t->state))

__tasklet_schedule(t);

}

这个调度函数放在中断处理的上半部处理函数中,这样中断申请的时候调用处理函数(即irq_handler_thandler)后,转去执行下半部的小任务。

如果希望使用DECLARE_TASKLET_DISABLED(name,function,data)创建小任务,那么在激活的时候也得调用相应的函数被使能

tasklet_enable(struct tasklet_struct *); //使能tasklet

tasklet_disble(struct tasklet_struct *); //禁用tasklet

tasklet_init(struct tasklet_struct *,void(*func)(unsignedlong),unsigned long);

当然也可以调用tasklet_kill(structtasklet_struct*)从挂起队列中删除一个小任务。清除指定tasklet的可调度位,即不允许调度该tasklet。

使用tasklet作为下半部的处理中断的设备驱动程序模板如下:

/*定义tasklet和下半部函数并关联*/

void my_do_tasklet(unsigned long);

DECLARE_TASKLET(my_tasklet,my_tasklet_func, 0);

/*中断处理下半部*/

void my_do_tasklet(unsigned long)

{

……/*编写自己的处理事件内容*/

}

/*中断处理上半部*/

irpreturn_t my_interrupt(unsigned intirq,void *dev_id)

{

……

tasklet_schedule(&my_tasklet)/*调度my_tasklet函数,根据声明将去执行my_tasklet_func函数*/

……

}

/*设备驱动的加载函数*/

int __init xxx_init(void)

{

……

/*申请中断,转去执行my_interrupt函数并传入参数*/

result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);

……

}

/*设备驱动模块的卸载函数*/

void __exit xxx_exit(void)

{

……

/*释放中断*/

free_irq(my_irq,my_interrupt);

……

}

工作队列的实现

工作队列work_struct结构体,位于/include/linux/workqueue.h

typedef void (*work_func_t)(structwork_struct *work);

struct work_struct {

atomic_long_t data; /*传递给处理函数的参数*/

#define WORK_STRUCT_PENDING 0/*这个工作是否正在等待处理标志*/

#define WORK_STRUCT_FLAG_MASK (3UL)

#define WORK_STRUCT_WQ_DATA_MASK(~WORK_STRUCT_FLAG_MASK)

struct list_head entry;  /*连接所有工作的链表*/

work_func_t func; /*要执行的函数*/

#ifdef CONFIG_LOCKDEP

struct lockdep_map lockdep_map;

#endif

};

这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。可以通过DECLARE_WORK在编译时静态地创建该结构,以完成推后的工作。

#define DECLARE_WORK(n,f)                                \

struct work_struct n =__WORK_INITIALIZER(n, f)

而后边这个宏为一下内容:

#define __WORK_INITIALIZER(n, f){                     \

.data =WORK_DATA_INIT(),                           \

.entry     = { &(n).entry,&(n).entry},                   \

.func =(f),                                       \

__WORK_INIT_LOCKDEP_MAP(#n,&(n))                  \

}

其为参数data赋值的宏定义为:

#define WORK_DATA_INIT()      ATOMIC_LONG_INIT(0)

这样就会静态地创建一个名为n,待执行函数为f,参数为data的work_struct结构。同样,也可以在运行时通过指针创建一个工作:

INIT_WORK(struct work_struct *work,void(*func) (void *));

这会动态地初始化一个由work指向的工作队列,并将其与处理函数绑定。宏原型为:

#define INIT_WORK(_work,_func)                                       \

do{                                                       \

staticstruct lock_class_key__key;                \

\

(_work)->data= (atomic_long_t) WORK_DATA_INIT();  \

lockdep_init_map(&(_work)->lockdep_map,#_work, &__key, 0);\

INIT_LIST_HEAD(&(_work)->entry);                \

PREPARE_WORK((_work),(_func));                        \

} while (0)

在需要调度的时候引用类似tasklet_schedule()函数的相应调度工作队列执行的函数schedule_work(),如:

schedule_work(&work);/*调度工作队列执行*/

如果有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度指定的时间后执行函数:

schedule_delayed_work(&work,delay);函数原型为:

int schedule_delayed_work(structdelayed_work *work, unsigned longdelay);

其中是以delayed_work为结构体的指针,而这个结构体的定义是在work_struct结构体的基础上增加了一项timer_list结构体。

struct delayed_work {

struct work_struct work;

struct timer_list timer; /*延迟的工作队列所用到的定时器,当不需要延迟时初始化为NULL*/

};

这样,便使预设的工作队列直到delay指定的时钟节拍用完以后才会执行。

使用工作队列处理中断下半部的设备驱动程序模板如下:

/*定义工作队列和下半部函数并关联*/

struct work_struct my_wq;

void my_do_work(unsigned long);

/*中断处理下半部*/

void my_do_work(unsigned long)

{

……/*编写自己的处理事件内容*/

}

/*中断处理上半部*/

irpreturn_t my_interrupt(unsigned intirq,void *dev_id)

{

……

schedule_work(&my_wq)/*调度my_wq函数,根据工作队列初始化函数将去执行my_do_work函数*/

……

}

/*设备驱动的加载函数*/

int __init xxx_init(void)

{

……

/*申请中断,转去执行my_interrupt函数并传入参数*/

result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);

……

/*初始化工作队列函数,并与自定义处理函数关联*/

INIT_WORK(&my_irq,(void (*)(void*))my_do_work);

……

}

/*设备驱动模块的卸载函数*/

void __exit xxx_exit(void)

{

……

/*释放中断*/

free_irq(my_irq,my_interrupt);

……

}

linux 中断机制的处理过程

时间: 2024-10-11 00:12:47

linux 中断机制的处理过程的相关文章

FFmpeg在Linux下安装编译过程

转载请把头部出处链接和尾部二维码一起转载,本文出自:http://blog.csdn.net/hejjunlin/article/details/52402759 今天介绍下FFmpeg在Linux下安装编译过程,用的是CentOS, 总体过程比较顺利,就是在ffmpeg等的时间稍长点.没什么技术难点.仅当记录. 关于FFmpeg FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件).它提供了录制.转换以及流化音视频的完整解决方案.它包

(作业3)Linux内核的启动过程(从start_kernel到init进程启动)

作业题目: 详细分析从start_kernel到init进程启动的过程并结合实验截图撰写一篇署名博客,并在博客文章中注明“真实姓名(与最后申请证书的姓名务必一致) + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,博客内容的具体要求如下: 题目自拟,内容围绕Linux内核的启动过程,即从start_kernel到init进程启动: 博客中需要使用实验截图 博客内容中需要仔细分析

Linux内核分析之跟踪分析Linux内核的启动过程

一.实验过程 使用实验楼虚拟机打开shell cd LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img 内核启动进入 menu 程序.下面是用 gbd 来跟踪内核的启动过程: gdb (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表 (gdb)target remote:1234 # 建立gdb和gdbserver之间的

mysql for linux 数据库的安装过程

mysql for linux 数据库的安装过程 l  安装版本:mysql-advanced-5.6.12-linux-glibc2.5-x86_64.tar.gz ,此版本是绿色版本,只需要将其解压到相关目录即可. PS:此安装方法为不针对特定平台的通用安装方法,使用的二进制文件是后缀为.tar.gz的压缩文件 PS:Mysql下载地址:http://pan.baidu.com/s/1eQJhoLO 1.创建mysql用户/账号 [[email protected] ~]#  groupad

linux手动安装sbt过程

ubuntu14 手动安装sbt 参见官网配置说明http://www.scala-sbt.org/release/tutorial/Manual-Installation.html 1.下载sbt通用平台压缩包:sbt-0.13.5.tgz http://www.scala-sbt.org/download.html 2.建立目录,解压文件到所建立目录 $ sudo mkdir /opt/scala/sbt $ sudo tar zxvf sbt-0.13.5.tgz -C /opt/scal

20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程

回顾 1.中断上下文的切换——保存现场&恢复现场 本节主要课程内容 Linux内核源代码简介 1.打开内核源代码页面 arch/目录:支持不同CPU的源代码:其中的X86是重点 init/目录:内核启动相关的代码基本都在该目录中(比如main.c等) start_kernel函数就相当于普通C程序的main函数 kernel/目录:Linux内核核心代码在kernel目录中 README 介绍了什么是Linux,Linux能够在哪些硬件上运行,如何安装内核源代码等 构造一个简单的linux系统m

minicom在虚拟机(linux)安装配置过程

1. minicom需要ncurses库的支持,否则安装会有问题. A. 下载ncurses.我选择是ncurses-5.6.tar.gz 下载地址:http://directory.fsf.org/project/ncurses/ B. 解压缩到/opt/ncurses目录下.# tar zxvf ncurses-5.5.tar.gz –C /opt/ncurses C. 到ncurses目录下,配置编译ncurses. 源码安装的三个步骤(configure.make.make instal

linux内核启动引导过程

linux内核(uImage格式镜像,uImage = zImage_压缩的内核镜像 + 0x40字节大小的uboot格式信息头)的启动过程大体可以分为三个阶段: 第一:内核的自解压过程(汇编+C语言实现) 主要由.arch/arm/boot/compressed对zImage完成解压,并调用call_kernel跳转到下阶段代码. 第二:板级引导阶段(汇编实现) 主要进行cpu和体系结构的检查.cpu本身的初始化以及页表的建立等 第三:通用内核启动阶段(C语言实现:重点分析) 1. 进入ini

Linux系统管理09——引导过程与服务控制

Linux系统管理09--引导过程与服务控制 一.引导过程总览 1.init进程 ·由linux内核加载运行/sbin/init程序 ·是系统中的第一个进程,所有进程的父进程 ·PID(进程标记)号永远为1 2.Upstart启动方式 初始化配置分散存放,响应不同的启动事件 参数 说明 /etc/inittab 配置默认运行级别 /etc/sysconfig/init 控制tty终端的开启数量.终端颜色方案 /etc/init/rcS.conf 加载rc.sysinit脚本,完成系统初始化任务