在硬件上,中断源可以通过中断控制器向CPU提交中断,进而引发中断处理程序的执行,不过这种硬件中断体系每一种CPU都不一样,而Linux作为操作系统,需要同时支持这些中断体系,如此一来,Linux中就提出了软中断的概念,也有人叫内核中断,其本质就是使用统一的方式对不同硬件中断体系中的中断号进行再映射,在操作系统中操作的中断号都是这些映射过的软中断号。就是下图中的irq_num

Linux内核中定义了名为irq_desc的中断例程描述符表来描述中断服务例程,每一个irq_desc对象数组中的下标就是软中断号。其中的struct irqaction对象描述了这个中断服务例程的中断的具体信息。

//include/linux/irqdesc.h
40 struct irq_desc {
41 struct irq_data irq_data;
42 unsigned int __percpu *kstat_irqs;
43 irq_flow_handler_t handle_irq;
44 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
45 irq_preflow_handler_t preflow_handler;
46 #endif
47 struct irqaction *action; /* IRQ action list */
48 unsigned int status_use_accessors;
49 unsigned int core_internal_state__do_not_mess_with_it;
50 unsigned int depth; /* nested irq disables */
51 unsigned int wake_depth; /* nested wake enables */
52 unsigned int irq_count; /* For detecting broken IRQs */
53 unsigned long last_unhandled; /* Aging timer for unhandled count */
54 unsigned int irqs_unhandled;
55 raw_spinlock_t lock;
56 struct cpumask *percpu_enabled;
57 #ifdef CONFIG_SMP
58 const struct cpumask *affinity_hint;
59 struct irq_affinity_notify *affinity_notify;
60 #ifdef CONFIG_GENERIC_PENDING_IRQ
61 cpumask_var_t pending_mask;
62 #endif
63 #endif
64 unsigned long threads_oneshot;
65 atomic_t threads_active;
66 wait_queue_head_t wait_for_threads;
67 #ifdef CONFIG_PROC_FS
68 struct proc_dir_entry *dir;
69 #endif
70 int parent_irq;
71 struct module *owner;
72 const char *name;
73 } ____cacheline_internodealigned_in_smp;
74
 76 extern struct irq_desc irq_desc[NR_IRQS];  //NR_IRQS表示中断源的数目
//linux/interrupt.h
104 //一个irq action的描述结构
105 struct irqaction {
106 irq_handler_t handler;
107 void *dev_id;
108 void __percpu *percpu_dev_id;
109 struct irqaction *next;
110 irq_handler_t thread_fn;
111 struct task_struct *thread;
112 unsigned int irq;
113 unsigned int flags;
114 unsigned long thread_flags;
115 unsigned long thread_mask;
116 const char *name;
117 struct proc_dir_entry *dir;
118 } ____cacheline_internodealigned_in_smp;

struct irqaction

--105-->handler: 中断处理函数

--106-->name: 设备名

--107-->dev_id: 设备识别id

--109-->next: 指向下一个irqaction的指针

--110-->irq: 硬件中断号!!!

--113-->flags:IRQF_DISABLED .etc

--110-->thread_fn: 线程中断的中断处理函数

--111-->thread: 线程中断的线程指针

--114-->thread_flags: 与@thread关联的flags

--115-->thread_mask: 追踪@thread activity的位掩码

--116-->dir: 指向proc/irq/NN/name的入口指针

raw_local_irq_save(x) //禁止所有中断
raw_local_irq_enable //取消禁止中断

写中断处理程序

1. 编写中断处理函数

下面这个就是中断处理程序的原型,中断发生后,内核会将软中断号和注册时的data作为参数传入。中断处理程序ISR是在中断发生时被调用的用来处理中断的函数,不是进程上下文,在中断期间运行,不能执行可能休眠的操作,不能同用户空间交换数据,不能调用schedule()函数放弃调度

实现中断处理有一个原则:尽可能快的处理并返回,冗长的计算处理工作应该交给tasklet或任务队列在安全的时间内进行。此外,硬件系统中常使用共享中断,即多个设备共享一根线。即该(软硬)中断号可以被多个设备共享,这在实际的硬件连接中经常见到,可以节约很多资源,但是如此一来,就给软件的编写的提出了要求,内核给出的方案是一旦接收到来自一条中断线的中断,它就会循环执行所有注册到该中断线(->硬中断号->软中断号)的handler,这样,每一个handler就有义务判断到底是不是自己负责的设备来的中断,handler原型的dev_id也正是为了这个目的而存在,我们可以将该handler负责的设备的中断状态寄存器的地址作为dev_id和handler一起注册,当内核遍历执行所有的handler的时候,会将每一个中断的dev_id作为参数依次传入每一个handler,在每一个handler内部首次通过读取这个寄存器来快速判断是否是自己负责的设备发出的。这也就是共享中断必须设置dev_id参数的原因之一。至此,就可以实现多个设备对这一中断线的"shared"。注意,对于这个"shared",并不是在时间上允许多个中断同时发生,而是一种空间上的、中断号上的"shared",此外,这个"shared"和三星芯片中常见的Shared Peripheral Interrupts(SPI)不是一回事,后者表示这个中断可以被GIC router到任意一个CPU中。

 88 typedef irqreturn_t (*irq_handler_t)(int, void *);
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
int status = read_irq_status(); /* 读取相应的寄存器获取中断源 */
if(!is_myirq(dev_id,status)){ /* 判断是否是本设备中断 */
return IRQ_NONE;
}
/* 中断处理程序 */
return IRQ_HANDLED
}

2. 注册中断处理函数

下面这个就是注册中断的API,flags取值在"interrupt.h"有定义,常用的有IRQF_DISABLEDIRQF_SHARED,前者表示中断程序调用时屏蔽所有中断,"所有"指的是屏蔽所有中断线的中断,本中断线的中断本来就是屏蔽的,内核默认不允许中断嵌套IRQF_SHARED表示多个设备共享中断,即该中断线上连接了多个相同或不同的设备。

除了中断类型,flags还需要"位或"上触发方式,eg:IRQF_DISABLED|IRQF_TRIGGER_RISING

/**
* @flags:中断标志位。
* @dev_id用于共享中断,用来标识是哪个设备触发了中断,通常传入相应设备的中断状态寄存器的地址
* @name 是中断名称,可以在/proc/interrupt中看到
*/
int requst_irq(unsigned int irq,irq_handler_t handler, unsigned long flags, const char *name,void * dev_id);

3. 释放中断处理函数

中断号也是一种系统资源,使用完毕后要释放,注意,free_irq的第二个参数应当与request_irq()中最后一个参数相同,这样才能将这个handler从这个中断线中的handler链表中删除。

/**
* free_irq - 释放irq
*/
void free_irq(unsigned int irqno,void * dev_id);

底半部

中断不属于进程上下文,所以不能被内核调度,如果进入了中断处理函数,就只能将其执行完毕,不能被打断,这样带来的一个问题是如果在中断处理函数中执行耗时操作,就会极大的影响系统性能,为了解决这个问题,Linux内核中提出了中断顶半部和`底半部(bottom half)的概念,对于耗时的中断处理程序,将重要的、必要的操作放在顶半部执行,这部分和传统的中断概念一样,一旦开始就必须执行完毕;将中断处理程序中耗时的,不那么紧要的操作放在底半部,防止整个中断处理程序过多的占用系统资源。

Linux内核提供的3种中断底半部机制:工作队列,tasklet,软中断。其中软中断机制是内核底层的机制,包括定时器,异步通知等很多机制都是基于软中断,开发者不应该直接操作,所以这里仅讨论前两种

Tasklet

每个tasklet都和一个函数相关联,当tasklet被运行时,该函数就被调用,并且tasklet可以调度自己。

//定义一个处理函数
void my_tasklet_fcn(unsigned long){} //定义一个tasklet结构my_tasklet,并与处理函数相关联,
DECLARE_TASKLET(my_tasklet,my_tasklet_fcn,data); //调度tasklet
tasklet_schedule(&my_tasklet);

工作队列

工作队列和tasklet类型,tasklet多运行于中断上下文,而工作队列多运行与进程上下文

此外,tasklet中不能睡眠,而工作队列处理函数允许睡眠(正是由于它被当作内核线程被调度)

//定义一个工作队列
struct work_queue my_wq; //定义一个处理函数
void my_wq_fcn(unsigned long){} //初始化工作队列并将其与处理函数绑定
INIT_WORK(&my_wq,my_wq_fcn); //调度工作队列执行
schedule_work(&my_wq);
static irqreturn_t handler_t(int irq, void *dev)
{
//top half
schedule_work(&ws);
return IRQ_HANDLED;
} void work_func(struct work_struct *work)
{
//bottom half
ssleep(3);
} static int __init demo_init(void)
{
...
INIT_WORK(&ws, work_func);
request_irq(irq, handler_t, IRQF_TRIGGER_RISING, "demo", NULL);
...
}

其他API

除了上述API,内核还提供了其他的中断操作API,在内核代码中被广泛使用。

raw_local_irq_save(x)   //禁止所有中断
raw_local_irq_enable //取消禁止中断 //下面三个函数用于可编程中断控制器,对所有CPU都有效
//屏蔽一个中断
void disable_irq(int irq); //立即返回
void disable_irq_nosync(); //等待目前中断处理返程 //使能一个中断
void enable_irq()

Linux驱动技术(六) _内核中断的更多相关文章

  1. Linux驱动技术(七) _内核定时器与延迟工作

    内核定时器 软件上的定时器最终要依靠硬件时钟来实现,简单的说,内核会在时钟中断发生后检测各个注册到内核的定时器是否到期,如果到期,就回调相应的注册函数,将其作为中断底半部来执行.实际上,时钟中断处理程 ...

  2. Linux驱动技术(四) _异步通知技术

    异步通知的全称是"信号驱动的异步IO",通过"信号"的方式,放期望获取的资源可用时,驱动会主动通知指定的应用程序,和应用层的"信号"相对应, ...

  3. Linux驱动技术(四) _异步通知技术【转】

    转自:https://www.cnblogs.com/xiaojiang1025/p/6376561.html 异步通知的全称是"信号驱动的异步IO",通过"信号&quo ...

  4. Linux驱动技术(八) _并发控制技术

    为了实现对临界资源的有效管理,应用层的程序有原子变量,条件变量,信号量来控制并发,同样的问题也存在与驱动开发中,比如一个驱动同时被多个应用层程序调用,此时驱动中的全局变量会同时属于多个应用层进程的进程 ...

  5. Linux驱动技术(五) _设备阻塞/非阻塞读写

    等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节点都是一个PCB(进程控制块),内核会将PCB挂在等待队列中的所有进程都调度为睡眠状态,直到某个唤醒的条件发生 ...

  6. Linux驱动技术(五) _设备阻塞/非阻塞读写【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6377925.html 等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节 ...

  7. Linux驱动技术(二) _访问I/O内存

    ARM是对内存空间和IO空间统一编址的,所以,通过读写SFR来控制硬件也就变成了通过读写相应的SFR地址来控制硬件.这部分地址也被称为I/O内存.x86中对I/O地址和内存地址是分开编址的,这样的IO ...

  8. Linux驱动技术(一) _内存申请

    先上基础,下图是Linux的内存映射模型,其中体现了Linux内存映射的几个特点: 每一个进程都有自己的进程空间,进程空间的0-3G是用户空间,3G-4G是内核空间 每个进程的用户空间不在同一个物理内 ...

  9. linux 驱动学习笔记01--Linux 内核的编译

    由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...

随机推荐

  1. strcpy和memcpy的差别

    strcpy和memcpy都是标准C库函数.它们有以下的特点. strcpy提供了字符串的复制. 即strcpy仅仅用于字符串复制.而且它不仅复制字符串内容之外,还会复制字符串的结束符,strcpy_ ...

  2. FDMEMTABLE将修改后的数据序列为JSON

    FDMEMTABLE将修改后的数据序列为JSON procedure TForm1.Button3Click(Sender: TObject); var memtable: TFDMemTable; ...

  3. Asp.Net Core异常处理整理

    目前版本是Asp.Net Core v1.1,这个版本的感觉对Http请求中的错误处理方便不是很完善. 没有HttpException异常类,不能在任何的地方自由的抛出对应的异常状态. 一.默认的异常 ...

  4. C#调试心经

    我们在做程序开发时,难免会遇到错误异常.如何快速地找到出错的地方.分析错误的原因以及找到解决问题的方案,是许多初级程序员困扰的问题,这也正是经验的宝贵之处.下面我将简单介绍在Visual Studio ...

  5. install pymongo,mysql

    yum install pymongo yum install MySQL-python

  6. loopback文件系统

    回环设备(loop-back devices) 实验环境 centos7.2 回环设备( 'loopback device')允许用户以一个普通磁盘文件虚拟一个块设备.(磁盘文件 --> 块设备 ...

  7. 我的Nginx配置文件

    #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #erro ...

  8. Background Media Recovery terminated with ORA-1274 after adding a Datafile (Doc ID 739618.1)

    APPLIES TO: Oracle Database - Enterprise Edition - Version 9.2.0.1 to 12.1.0.2 [Release 9.2 to 12.1] ...

  9. 使用ExpandableListView以及如何优化view的显示减少内存占用

    上篇博客讲到如何获取手机中所有歌曲的信息.本文就把上篇获取到的歌曲按照歌手名字分类.用一个ExpandableListView显示出来. MainActivity .java   public cla ...

  10. Python--Redis实战:第四章:数据安全与性能保障:第7节:非事务型流水线

    之前章节首次介绍multi和exec的时候讨论过它们的”事务“性质:被multi和exec包裹的命令在执行时不会被其他客户端打扰.而使用事务的其中一个好处就是底层的客户端会通过使用流水线来提高事务执行 ...