[Linux内核]软中断与硬中断

摘要:
为了实现这一特性,当中断发生时,硬中断处理可以在短时间内完成的工作,而那些长时间处理事件的工作则在中断之后完成,即软中断。硬中断和软中断的区别软中断是通过执行中断指令产生的,而硬中断是由外围设备引起的。硬中断的中断号由中断控制器提供,而软中断的中断编号由指令直接指示,而不使用中断控制器。软中断处理硬中断的未完成工作,这是延迟执行的机制,属于后半部分。

转自:http://blog.csdn.net/zhangskd/article/details/21992933

本文主要内容:硬中断 / 软中断的原理和实现

内核版本:2.6.37

Author:zhangskd @ csdn blog

概述

从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。

如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,

跳到中断处理程序的入口点,进行中断处理。

[Linux内核]软中断与硬中断第1张

(1) 硬中断

由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统系统外设状态的变化。比如当网卡收到数据包

的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)。

(2) 软中断

为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间

就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。

(3) 中断嵌套

Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但同种中断

除外。软中断不能嵌套,但相同类型的软中断可以在不同CPU上并行执行。

(4) 软中断指令

int是软中断指令。

中断向量表是中断号和中断处理函数地址的对应表。

int n - 触发软中断n。相应的中断处理函数的地址为:中断向量表地址 + 4 * n。

(5)硬中断和软中断的区别

软中断是执行中断指令产生的,而硬中断是由外设引发的。

硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。

硬中断是可屏蔽的,软中断不可屏蔽。

硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。

软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。 

开关

(1) 硬中断的开关

简单禁止和激活当前处理器上的本地中断:

local_irq_disable();

local_irq_enable();

保存本地中断系统状态下的禁止和激活:

unsigned long flags;

local_irq_save(flags);

local_irq_restore(flags);

(2) 软中断的开关

禁止下半部,如softirq、tasklet和workqueue等:

local_bh_disable();

local_bh_enable();

需要注意的是,禁止下半部时仍然可以被硬中断抢占。

(3) 判断中断状态

#define in_interrupt() (irq_count()) // 是否处于中断状态(硬中断或软中断)

#define in_irq() (hardirq_count()) // 是否处于硬中断

#define in_softirq() (softirq_count()) // 是否处于软中断

硬中断

(1) 注册中断处理函数

注册中断处理函数:

 1 /** 
 2  * irq: 要分配的中断号 
 3  * handler: 要注册的中断处理函数 
 4  * flags: 标志(一般为0) 
 5  * name: 设备名(dev->name) 
 6  * dev: 设备(struct net_device *dev),作为中断处理函数的参数 
 7  * 成功返回0 
 8  */  
 9   
10 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,   
11     const char *name, void *dev);  

中断处理函数本身:

 1 typedef irqreturn_t (*irq_handler_t) (int, void *);  
 2   
 3 /** 
 4  * enum irqreturn 
 5  * @IRQ_NONE: interrupt was not from this device 
 6  * @IRQ_HANDLED: interrupt was handled by this device 
 7  * @IRQ_WAKE_THREAD: handler requests to wake the handler thread 
 8  */  
 9 enum irqreturn {  
10     IRQ_NONE,  
11     IRQ_HANDLED,  
12     IRQ_WAKE_THREAD,  
13 };  
14 typedef enum irqreturn irqreturn_t;  
15 #define IRQ_RETVAL(x) ((x) != IRQ_NONE)  

(2) 注销中断处理函数

 1 /** 
 2  * free_irq - free an interrupt allocated with request_irq 
 3  * @irq: Interrupt line to free 
 4  * @dev_id: Device identity to free 
 5  * 
 6  * Remove an interrupt handler. The handler is removed and if the 
 7  * interrupt line is no longer in use by any driver it is disabled. 
 8  * On a shared IRQ the caller must ensure the interrupt is disabled 
 9  * on the card it drives before calling this function. The function does 
10  * not return until any executing interrupts for this IRQ have completed. 
11  * This function must not be called from interrupt context. 
12  */  
13   
14 void free_irq(unsigned int irq, void *dev_id);  

软中断

(1) 定义

软中断是一组静态定义的下半部接口,可以在所有处理器上同时执行,即使两个类型相同也可以。

但一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是硬中断。 

软中断由softirq_action结构体表示:

1 struct softirq_action {  
2     void (*action) (struct softirq_action *); /* 软中断的处理函数 */  
3 };   

目前已注册的软中断有10种,定义为一个全局数组:

 1 static struct softirq_action softirq_vec[NR_SOFTIRQS];  
 2   
 3 enum {  
 4     HI_SOFTIRQ = 0, /* 优先级高的tasklets */  
 5     TIMER_SOFTIRQ, /* 定时器的下半部 */  
 6     NET_TX_SOFTIRQ, /* 发送网络数据包 */  
 7     NET_RX_SOFTIRQ, /* 接收网络数据包 */  
 8     BLOCK_SOFTIRQ, /* BLOCK装置 */  
 9     BLOCK_IOPOLL_SOFTIRQ,  
10     TASKLET_SOFTIRQ, /* 正常优先级的tasklets */  
11     SCHED_SOFTIRQ, /* 调度程序 */  
12     HRTIMER_SOFTIRQ, /* 高分辨率定时器 */  
13     RCU_SOFTIRQ, /* RCU锁定 */  
14     NR_SOFTIRQS /* 10 */  
15 };  

(2) 注册软中断处理函数

1 /** 
2  * @nr: 软中断的索引号 
3  * @action: 软中断的处理函数 
4  */  
5   
6 void open_softirq(int nr, void (*action) (struct softirq_action *))  
7 {  
8     softirq_vec[nr].action = action;  
9 }   

例如:

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

(3) 触发软中断 

调用raise_softirq()来触发软中断。

 1 void raise_softirq(unsigned int nr)  
 2 {  
 3     unsigned long flags;  
 4     local_irq_save(flags);  
 5     raise_softirq_irqoff(nr);  
 6     local_irq_restore(flags);  
 7 }  
 8   
 9 /* This function must run with irqs disabled */  
10 inline void rasie_softirq_irqsoff(unsigned int nr)  
11 {  
12     __raise_softirq_irqoff(nr);  
13   
14     /* If we're in an interrupt or softirq, we're done 
15      * (this also catches softirq-disabled code). We will 
16      * actually run the softirq once we return from the irq 
17      * or softirq. 
18      * Otherwise we wake up ksoftirqd to make sure we 
19      * schedule the softirq soon. 
20      */  
21     if (! in_interrupt()) /* 如果不处于硬中断或软中断 */  
22         wakeup_softirqd(void); /* 唤醒ksoftirqd/n进程 */  
23 }  

Percpu变量irq_cpustat_t中的__softirq_pending是等待处理的软中断的位图,通过设置此变量

即可告诉内核该执行哪些软中断。

 1 static inline void __rasie_softirq_irqoff(unsigned int nr)  
 2 {  
 3     trace_softirq_raise(nr);  
 4     or_softirq_pending(1UL << nr);  
 5 }  
 6   
 7 typedef struct {  
 8     unsigned int __softirq_pending;  
 9     unsigned int __nmi_count; /* arch dependent */  
10 } irq_cpustat_t;  
11   
12 irq_cpustat_t irq_stat[];  
13 #define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)  
14 #define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))  
15 #define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)   

唤醒ksoftirqd内核线程处理软中断。

1 static void wakeup_softirqd(void)  
2 {  
3     /* Interrupts are disabled: no need to stop preemption */  
4     struct task_struct *tsk = __get_cpu_var(ksoftirqd);  
5   
6     if (tsk && tsk->state != TASK_RUNNING)  
7         wake_up_process(tsk);  
8 }  

在下列地方,待处理的软中断会被检查和执行:

1. 从一个硬件中断代码处返回时

2. 在ksoftirqd内核线程中

3. 在那些显示检查和执行待处理的软中断的代码中,如网络子系统中

而不管是用什么方法唤起,软中断都要在do_softirq()中执行。如果有待处理的软中断,

do_softirq()会循环遍历每一个,调用它们的相应的处理程序。

在中断处理程序中触发软中断是最常见的形式。中断处理程序执行硬件设备的相关操作,

然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用

do_softirq(),于是软中断开始执行中断处理程序完成剩余的任务。

下面来看下do_softirq()的具体实现。

 1 asmlinkage void do_softirq(void)  
 2 {  
 3     __u32 pending;  
 4     unsigned long flags;  
 5   
 6     /* 如果当前已处于硬中断或软中断中,直接返回 */  
 7     if (in_interrupt())   
 8         return;  
 9   
10     local_irq_save(flags);  
11     pending = local_softirq_pending();  
12     if (pending) /* 如果有激活的软中断 */  
13         __do_softirq(); /* 处理函数 */  
14     local_irq_restore(flags);  
15 }  
 1 /* We restart softirq processing MAX_SOFTIRQ_RESTART times, 
 2  * and we fall back to softirqd after that. 
 3  * This number has been established via experimentation. 
 4  * The two things to balance is latency against fairness - we want 
 5  * to handle softirqs as soon as possible, but they should not be 
 6  * able to lock up the box. 
 7  */  
 8 asmlinkage void __do_softirq(void)  
 9 {  
10     struct softirq_action *h;  
11     __u32 pending;  
12     /* 本函数能重复触发执行的次数,防止占用过多的cpu时间 */  
13     int max_restart = MAX_SOFTIRQ_RESTART;  
14     int cpu;  
15   
16     pending = local_softirq_pending(); /* 激活的软中断位图 */  
17     account_system_vtime(current);  
18     /* 本地禁止当前的软中断 */  
19     __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);  
20     lockdep_softirq_enter(); /* current->softirq_context++ */  
21     cpu = smp_processor_id(); /* 当前cpu编号 */  
22   
23 restart:  
24     /* Reset the pending bitmask before enabling irqs */  
25     set_softirq_pending(0); /* 重置位图 */  
26     local_irq_enable();  
27     h = softirq_vec;  
28     do {  
29         if (pending & 1) {  
30             unsigned int vec_nr = h - softirq_vec; /* 软中断索引 */  
31             int prev_count = preempt_count();  
32             kstat_incr_softirqs_this_cpu(vec_nr);  
33   
34             trace_softirq_entry(vec_nr);  
35             h->action(h); /* 调用软中断的处理函数 */  
36             trace_softirq_exit(vec_nr);  
37   
38             if (unlikely(prev_count != preempt_count())) {  
39                 printk(KERN_ERR "huh, entered softirq %u %s %p" "with preempt_count %08x,"  
40                     "exited with %08x?
", vec_nr, softirq_to_name[vec_nr], h->action, prev_count,  
41                     preempt_count());  
42             }  
43             rcu_bh_qs(cpu);  
44         }  
45         h++;  
46         pending >>= 1;  
47     } while(pending);  
48   
49     local_irq_disable();  
50     pending = local_softirq_pending();  
51     if (pending & --max_restart) /* 重复触发 */  
52         goto restart;  
53   
54     /* 如果重复触发了10次了,接下来唤醒ksoftirqd/n内核线程来处理 */  
55     if (pending)  
56         wakeup_softirqd();   
57   
58     lockdep_softirq_exit();  
59     account_system_vtime(current);  
60     __local_bh_enable(SOFTIRQ_OFFSET);  
61 }   

(4) ksoftirqd内核线程

内核不会立即处理重新触发的软中断。

当大量软中断出现的时候,内核会唤醒一组内核线程来处理。

这些线程的优先级最低(nice值为19),这能避免它们跟其它重要的任务抢夺资源。

但它们最终肯定会被执行,所以这个折中的方案能够保证在软中断很多时用户程序不会

因为得不到处理时间而处于饥饿状态,同时也保证过量的软中断最终会得到处理。

每个处理器都有一个这样的线程,名字为ksoftirqd/n,n为处理器的编号。

 1 static int run_ksoftirqd(void *__bind_cpu)  
 2 {  
 3     set_current_state(TASK_INTERRUPTIBLE);  
 4     current->flags |= PF_KSOFTIRQD; /* I am ksoftirqd */  
 5   
 6     while(! kthread_should_stop()) {  
 7         preempt_disable();  
 8   
 9         if (! local_softirq_pending()) { /* 如果没有要处理的软中断 */  
10             preempt_enable_no_resched();  
11             schedule();  
12             preempt_disable():  
13         }  
14   
15         __set_current_state(TASK_RUNNING);  
16   
17         while(local_softirq_pending()) {  
18             /* Preempt disable stops cpu going offline. 
19              * If already offline, we'll be on wrong CPU: don't process. 
20              */  
21              if (cpu_is_offline(long)__bind_cpu))/* 被要求释放cpu */  
22                  goto wait_to_die;  
23   
24             do_softirq(); /* 软中断的统一处理函数 */  
25   
26             preempt_enable_no_resched();  
27             cond_resched();  
28             preempt_disable();  
29             rcu_note_context_switch((long)__bind_cpu);  
30         }  
31   
32         preempt_enable();  
33         set_current_state(TASK_INTERRUPTIBLE);  
34     }  
35   
36     __set_current_state(TASK_RUNNING);  
37     return 0;  
38   
39 wait_to_die:  
40     preempt_enable();  
41     /* Wait for kthread_stop */  
42     set_current_state(TASK_INTERRUPTIBLE);  
43     while(! kthread_should_stop()) {  
44         schedule();  
45         set_current_state(TASK_INTERRUPTIBLE);  
46     }  
47   
48     __set_current_state(TASK_RUNNING);  
49     return 0;  
50 }  

免责声明:文章转载自《[Linux内核]软中断与硬中断》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ELK之从6.3.1升级至6.6.2Mysql数据按天分区,定期删除下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

linux简单命令与Shell脚本

说明1、#表示root用户登录,$表示普通用户登录2、|符号是管道符,用于把|前的输出作为后面命令的输入3、>表示追加并覆盖的意思4、>>两个大于符号,表示追加命令1、cd命令:切换目录cd /home 进入/home目录cd ../返回上一级目录cd  ./当前目录2、ls命令:产看文件及目录ls  ./ 查看当前目录所有的文件和目录l...

Deepin -Android Studio-Genymotion 之旅

Deepin -Android Studio-Genymotion 之旅 暑假无聊中在deepin系统下配置好了android的开发环境,并使用了比较好的一款模拟器–Genymotion;下面是我配置的非常详细的过程。 第一步 :安装deepin系统 为什么我会使用deepin系统呢,首先deepin的图形界面做的非常美观,其次从我装了7到8种linux...

Linux升级gcc到最新版本--gcc-9.2.0

1、下载安装包(可以用迅雷之类的下好再传到服务器,因为下载有点慢)wget http://ftp.gnu.org/gnu/gcc/gcc-9.2.0/gcc-9.2.0.tar.gztar -vxf gcc-9.2.0.tar.gz 2、下载依赖文件./contrib/download_prerequisites 如果连接不上服务器或者下载比较慢的话,可以...

linux如何在日志中查找关键字、前几行、结尾几行

查看日志前 n行: 1、  cat 文件名 | head -n 数量 demo cat test.log | head -n 100 # 查看test.log前100行 查看日志尾 n行: 2、 cat 文件名 | tail -n 数量 demo cat test.log |tail -n 100# 查看test.log倒数100行 3、根据关键词查看日...

免费学习的Linux主机

最近因为在学习Linux系统,所以需要找个地方练练手,在网上搜了大半天终于让我找到了一个免费供学员练习的主机,站点是http://www.unix-center.net,需要进行注册,因为人家是公益性的所以在使用上会有一些限制,但如果你捐100元钱的话,会让你成为标准会员拥有更大的空间,还会提供更多的功能,但你没特别需要的话,只需练习一下,就没必要捐那10...

linux下安装rabbitmq 集群

1.下载erlang官网地址 http://www.erlang.org/download 挑选合适的版本 然后 wget 比如目前18.3运行命令 wget http://erlang.org/download/otp_src_18.3.tar.gz 2.使用yum安装下必须的配件:  yum install gcc glibc-devel make n...