cat /proc/interrupts
第一列:IRQ序号
第二、三列:CPU0/CPU1分别是当前CPU上发生中断的次数
第四列:中断控制器名称,比如IO-APIC
最后一列:设备名称,比如timer
Linux将中断处理分为上下两部分:
**Tasklet机制(小任务机制)**是指对要推迟执行的函数进行组织的一种机制,也就是说,推迟处理的事情是由相关处理函数实现,何时执行,由小任务机制封装后交给内核去处理。
中断程序 interrupt.c:
# include
# include
# include
# include
static int irq; //irq号
static char * devname; //设备名称
//这两个是用来让我们在命令行传入参数
module_param(irq,int,0644);
module_param(devname,charp,0644); //这里charp相当于char*,是字符指针
struct myirq
{
int devid; //这个主要用在共享irq中
};
struct myirq mydev={1119};
//中断处理函数
static irqreturn_t myirq_handler(int irq,void * dev)
{
struct myirq mydev;
static int count=1;
mydev = *(struct myirq*)dev;
printk("key: %d..\n",count);
printk("devid:%d ISR is working..\n",mydev.devid);
printk("ISR is leaving......\n");
count++; //进行中断计数
return IRQ_HANDLED; //返回的值代表接收到了准确的中断信号并且做了相应的正确处理
}
//内核模块初始化函数
static int __init myirq_init(void) //最重要的工作是注册中断线,并将自己写的中断服务例程注册进去,用request_irq完成
{
printk("Module is working...\n");
if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0) //IRQ_SHARED,即允许共享
{
printk("%s request IRQ:%d failed..\n",devname,irq);
return -1;
}
printk("%s request IRQ:%d success...\n",devname,irq);
return 0;
}
//内核模块退出函数
static void __exit myirq_exit(void)
{
printk("Module is leaving...\n");
free_irq(irq,&mydev); //注销函数
printk("Free the irq:%d..\n",irq);
}
MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);
执行指令 sudo insmod interrupt.ko irq=1 devname=myirq ,插入内核模块
执行指令cat /proc/interrupts ,查看中断情况:
查看一下日志信息dmesg:
执行指令 sudo rmmod interrupt 退出模块,dmesg查看:
interrupt.c代码如下:
#include
#include
#include
#include
static int irq;
static char * devname;
module_param(irq,int,0644);
module_param(devname,charp,0644);
struct myirq
{
int devid;
};
struct myirq mydev={1119};
static struct tasklet_struct mytasklet;
static void mytasklet_handler(unsigned long data) //下半部分处理函数
{
printk("I am mytasklet_handler");
}
static irqreturn_t myirq_handler(int irq,void * dev)
{
// struct myirq mydev;
// static int count=1;
static int count=0;
// mydev = *(struct myirq*)dev;
// printk("key: %d..\n",count);
// printk("devid:%d ISR is working..\n",mydev.devid);
// printk("ISR is leaving......\n");
printk("count:%d\n",count+1);
printk("I am myirq_handler\n");
printk("The most of the interrupt work will be done by folling tasks\n");
tasklet_init(&mytasklet,mytasklet_handler,0); //将下半部分函数进行注册。最主要的是要将下半部分处理函数挂载上去
tasklet_schedule(&mytasklet); //调度
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 request IRQ:%d success...\n",devname,irq);
return 0;
}
static void __exit myirq_exit(void)
{
printk("Module is leaving...\n");
free_irq(irq,&mydev);
tasklet_kill(&mytasklet); //注销掉tasklet
printk("Free the irq: %d..\n",irq);
}
MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);
运行结果:
执行指令cat /proc/interrupts ,查看中断情况:
执行指令sudo rmmod interrupt ,退出模块:
request_irq():
/*注册中断线,并可以将自己的中断服务例程注册进去
第一个参数irq就是中断号,对应的就是中断控制器上IRQ的编号
第二个参数 handler,即我们要注册的中断服务例程,需要我们自己去实现,它有一个特定类型的返回值irqreturn_t
第三参数参数flags,它指定了快速中断或者中断共享中中断的处理属性
第四个参数name,它是设备名称,即cat /proc/interrupt命令来查看的最后一列
第五个参数dev,指dev_id,主要用于共享中断线。注意该参数的类型是void,即可以使用强制转换为任意类型,可以作为共享中断时中断区别的一个参数。
*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
/*中断服务例程返回值类型*/
typedef enum irqreturn irqreturn_t;
/**
* enum irqreturn
* @IRQ_NONE 中断不是来自此设备或未被处理
* @IRQ_HANDLED 此设备处理中断
* @IRQ_WAKE_THREAD 处理程序请求来唤醒处理程序线程
*/
enum irqreturn {
IRQ_NONE = (0 << 0), //0,0 << 0 是把0按2进制左移0位,结果还是0
IRQ_HANDLED = (1 << 0), //1
IRQ_WAKE_THREAD = (1 << 1), //2
};
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*
* Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
* it cannot be set along with IRQF_NO_SUSPEND.
*/
if (((irqflags & IRQF_SHARED) && !dev_id) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
desc = irq_to_desc(irq); //根据中断号irq,在irq_desc数组中,返回一个具体的irq_desc
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
//实际上我们的注册就是要将我们传进来的参数生成一个个的action,再添加到irq_desc上,具体是通过“retval = __setup_irq(irq, desc, action);"这个函数进行的。
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}
retval = __setup_irq(irq, desc, action);
......
return retval;
}
/* free_irq - 释放用request_irq分配的中断
* @irq: Interrupt line to free
* @dev_id: Device identity to free
*/
const void *free_irq(unsigned int irq, void *dev_id)
{
......
action = __free_irq(desc, dev_id);
......
devname = action->name;
kfree(action);
return devname;
}
do_IRQ():
每次进入do_IRQ函数都是从汇编的代码中跳入,可以说发生了中断之后,do_IRQ是我们执行的第一个C语言函数,其与体系结构息息相关,我们的do_IRQ函数位于根目录下的arch/x86/kernel/irq.c
/*
* do_IRQ处理所有普通设备的中断
* 这个函数最核心的部分是执行handle_irq函数,从handle_irq进入,执行完后返回到exiting_irq()
*/
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
......
if (likely(!IS_ERR_OR_NULL(desc))) {
if (IS_ENABLED(CONFIG_X86_32))
handle_irq(desc, regs);
else
generic_handle_irq_desc(desc);
} else {
ack_APIC_irq();
if (desc == VECTOR_UNUSED) {
pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
__func__, smp_processor_id(),
vector);
} else {
__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
}
}
exiting_irq();
set_irq_regs(old_regs);
return 1;
}
bool handle_irq(struct irq_desc *desc, struct pt_regs *regs)
{
stack_overflow_check(regs);
if (IS_ERR_OR_NULL(desc))
return false;
generic_handle_irq_desc(desc);
return true;
}
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc); //handel_irq函数,它是在irq_desc结构中
}
handel_irq函数,它是在irq_desc结构中
hadle_irq是一个回调函数,负责处理底层的细节,比如中断确认,边沿触发以及电平触发的处理。最后再由这个函数去调用irqaction中的handler,实际上,handler才是最后我们想要的中断服务例程。
handle的挂载过程:
先是注册handle,然后在 __irq_do_set_handler 函数中 desc->handle_irq = handle
完成挂载
static void mp_register_handler(unsigned int irq, unsigned long trigger)
{
......
//进行一个选择,是哪种方式。后面将会以handle_edge_irq的方式为例来分析,即边沿触发的方式来分析。
hdl = fasteoi ? handle_fasteoi_irq : handle_edge_irq;
__irq_set_handler(irq, hdl, 0, fasteoi ? "fasteoi" : "edge");
}
void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
......
__irq_do_set_handler(desc, handle, is_chained, name);
irq_put_desc_busunlock(desc, flags);
}
static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
int is_chained, const char *name)
{
......
desc->handle_irq = handle; //将handle挂载到了handle_irq上
......
}
这里以handle_edge_irq为例来分析:
void handle_edge_irq(struct irq_desc *desc)
{
......
handle_irq_event(desc);
......
}
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
......
ret = handle_irq_event_percpu(desc);
......
}
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
......
retval = __handle_irq_event_percpu(desc, &flags);
......
}
/*该函数就实现了依次执行这条中断线上的所有中断服务例程*/
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) { //通过for_each_action_of_desc 这个宏来实现
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id); //将对应的返回值给了res,在后面可以在switch语句中可以看到,res的值是IRQ_WAKE_THREAD或IRQ_HANDLED
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action);
/* Fall through - to add to randomness */
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
#define for_each_action_of_desc(desc, act) \
for (act = desc->action; act; act = act->next)
tasklet_init():
/*内容基本上是字段的赋值,以及函数的挂载*/
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
tasklet_schedule():
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) 此处进行了字段的赋值
__tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
__tasklet_schedule_common(t, &tasklet_vec,
TASKLET_SOFTIRQ);
}
static void __tasklet_schedule_common(struct tasklet_struct *t,
struct tasklet_head __percpu *headp,
unsigned int softirq_nr)
{
struct tasklet_head *head;
unsigned long flags;
local_irq_save(flags);
head = this_cpu_ptr(headp);
t->next = NULL;
*head->tail = t; //把传入的tasklet挂入到了链表的尾部
head->tail = &(t->next);
raise_softirq_irqoff(softirq_nr); //调度不代表执行,真正的执行softirq_nr中断是tasklet_action函数,
local_irq_restore(flags);
}
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
static void tasklet_action_common(struct softirq_action *a,
struct tasklet_head *tl_head,
unsigned int softirq_nr)
{
......
while (list) { //遍历tasklet链表
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
t->func(t->data); //这里就是tasklet这个结构体的最后一个字段,其实是func的一个参数
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*tl_head->tail = t;
tl_head->tail = &t->next;
__raise_softirq_irqoff(softirq_nr);
local_irq_enable();
}
}
内核源码中驱动代码应用tasklet:
struct ieee802154_hw *
ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
{
......
//初始化
tasklet_init(&local->tasklet,
ieee802154_tasklet_handler, //中断的下部分函数
(unsigned long)local);
......
}
void
ieee802154_rx_irqsafe(struct ieee802154_hw *hw, struct sk_buff *skb, u8 lqi)
{
......
tasklet_schedule(&local->tasklet); //调度
}
void ieee802154_unregister_hw(struct ieee802154_hw *hw)
{
......
tasklet_kill(&local->tasklet); //注销
......
}
在/Documentation/x86/boot.rst中可以找到关于启动时的一些说明
在/Documentation/process/changes.rst中可以找到编译内核时最少需要的工具有哪些
在/Documentation/IRQ.txt中介绍了,什么是IRQ,这里是开发人员的解释
在/Documentation/core-api/genericirq.rst中,介绍中断API,到这个文件的最后就可以看到前面涉及到的manage.c, chip.c, handle.c
在/Documentation/IRQ-affinity.txt中,有一个特殊功能。 如果一个机器中有多个CPU,我们可以指定我们产生的中断在哪个CPU上进行处理,即绑定CPU。关于这里,应该根据生成环境的特点与应用的特点来平衡IRQ中断,这有助于提高整个系统的吞吐能力与性能,不同的场景下我们需要不同的设置,不是说我们修改了某些参数就叫做性能优化,还需要通过大量的测试进行测试/观察/改进
1.中断和异常的定义
中断也称外中断,指来自CPU执行指令以外的事件发生(这里的中断是狭义上的中断)
异常也称内中断、例外或陷入,指源自CPU执行指令内部的事件
2.Linux为什么将中断处理分为上下两部分
3.Tasklet机制是什么?
Tasklet机制(小任务机制)是指对要推迟执行的函数进行组织的一种机制,也就是说,推迟处理的事情是由相关处理函数实现,何时执行,由小任务机制封装后交给内核去处理。
小任务的数据结构为tasklet_struct,每个结构代表一个独立的小任务,其定义如下:
struct tasklet_struct
{
struct tasklet_struct *next; //指向链表中的下一个结构
unsigned long state; //小任务的状态
atomic_t count; //引用的计数器
void (*func)(unsigned long); //要调用的函数
unsigned long data; //传递给函数的参数
};
4.时钟中断的作用
在所有的外部中断中,时钟中断起着特殊的作用。因为计算机是以精确的时间进行数值运算和数据处理的,最基本的时间单元是时钟周期,例如取指令、执行指令、存取内存等,时间系统是整个操作系统活动的动力。
5.实时时钟、系统时钟和CPU时钟的区别
实时时钟:RTC(Real Time Clock)时钟,用于提供年、月、日、时、分、秒和星期等的实时时间信息,由后备电池供电,当你晚上关闭系统和早上开启系统时,RTC仍然会保持正确的时间和日期。
系统时钟:是一个存储于系统内存中的逻辑时钟。用于系统的计算,比如超时产生的中断异常,超时计算就是由系统时钟计算的。这种时钟在系统掉电或重新启动时每次会被清除。
CPU时钟:即CPU的频率,当然这里的时钟频率指的是工作频率,即外频,还有什么主频=外频×倍频,
6.为什么中断上下文不能睡眠?
(1)中断处理的时候,不应该发生进程切换,因为在中断context中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断,如果在中断context中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没有一个task_struct(这点对于softirq和tasklet一样),因此真的休眠了,比如调用了会导致block的例程,内核几乎肯定会死。
(2)schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了,所以不可以在中断处理程序中调用schedule()。
(3)内核中schedule()函数本身在进来的时候判断是否处于中断上下文:
if(unlikely(in_interrupt()))
BUG();
因此,强行调用schedule()的结果就是内核BUG。
(4)中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌。
(5)处于中断context时候,内核是不可抢占的。因此,如果休眠,则内核一定挂起。