1 软中断概述

软中断是实现中断下半部的一种手段,与2.5以前版本的下半段机制不同。软中断可以同时运行在不同的CPU上。

1.1 软中断的表示

内核中用结构体softirq_action表示一个软中断。软中断是一组静态定义的接口,有32个。但是内核(2.6.34)中只实现了10个。可用的软中断的个数用NR_SOFTIRQ表示,NR_SOFTIRQ=10,软中断的类型用一个枚举体表示。这里需要注意的是,32个软中断体现在位掩码是unsigned int 类型。

static struct softirq_action softirq_vec[NR_SOFTIRQS] ;
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS
};
struct softirq_action
{
void (*action)(struct softirq_action *);
};

2 软中断相关的数据结构

2.1 thread_info的preempt_count字段

preempt_count 是一个32位的int型,共分为5个字段



宏in_interrupt检测软中断计数器 硬中断计数器 和 NMI掩码,只要这三个字段任意一个字段不为0,就表示进程处于中断上下文。

#define in_irq()		(hardirq_count())
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())

2.2 pending位掩码

每个CPU上都有一个irq_stat结构,irq_stat中的__softirq_pending是一个32位的掩码,为1表示该软中断已经激活,正等待处理。为0表示软中断被禁止。在do_irq中被使用。

内核使用local_softirq_pending得到当前CPU上的位掩码

#define local_softirq_pending()	percpu_read(irq_stat.__softirq_pending)
#define set_softirq_pending(x) percpu_write(irq_stat.__softirq_pending, (x))
#define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x)) irq_cpustat_t irq_stat[NR_CPUS]
typedef struct {
...
unsigned int __softirq_pending;
...
}irq_cpustat_t;

2.3 软中断栈

进程的内核栈的大小根据编译时选项不同,可以是4K或者8K。如果是8K堆栈,中断,异常和软中断(softirq) 共享这个堆栈。如果选择4K堆栈,则内核堆栈 硬中断堆栈 软中断堆栈各自使用一个4K空间。关于软中断堆栈,后面在软中断处理时再详细说明。

#ifdef CONFIG_4KSTACKS

static DEFINE_PER_CPU_PAGE_ALIGNED(union irq_ctx, softirq_stack);

union irq_ctx {
struct thread_info tinfo;
u32 stack[THREAD_SIZE/sizeof(u32)];
} __attribute__((aligned(PAGE_SIZE)));

3 软中断的初始化

内核使用open_softirq初始化一个软中断,nr是代表软中断类型的常量,action指向一个软中断处理函数

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}

4 软中断的触发(raise softirq)

触发就是将位掩码pending的相应位 置1的过程。内核使用raise_softirq完成触发软中断,nr是要触发的软中断类型。值的注意的是,中断的触发 发生关闭硬中断的情况下。

触发软中断的过程中,如果该进程未处于中断上下文,说明当前进程处于进程上下文中,那么我们直接调用wakeup_softirqd调度ksoftirqd即可。

反之,如果当前处于中断上下文中或软中断被禁止使用,那么就不必调度内核线程,在中断处理后期irq_exit中,会调用invoke_softirq()处理软中断。

实际的工作是交给or_softirq_pending(1UL << (nr)); 完成的,该函数通过位操作将指定为和pending相加。

void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
} inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr); /*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
* 如果不在中断上下文中
*/
if (!in_interrupt())
wakeup_softirqd();
} #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); }

5. 软中断的处理

5.1 处理软中断的时机

1 在do_irq的末期(irq_exit)

如果当前进程没有处于中断上下文中并且本地CPU上还有没有处理的软中断,那么就调用invoke_softirq()处理软中断。

#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq()
#else
# define invoke_softirq() do_softirq()
#endif
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq(); rcu_irq_exit();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
} #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq()
#else
# define invoke_softirq() do_softirq()
#endif

程序5-1 irq_exit

2 当软中断被重复触发超过10次时,内核会调用wakeup_softirqd()唤醒内核线程ksoftirqd去处理软中断。

5.2 软中断的处理

1 do_softirq

根据内核堆栈大小,有两种do_softirq,一种是通用do_irq,另一种是架构相关的do_irq(arch/x86/kernel/irq_32.c)。

通用的do_irq的工作流程

1 判断当前是否处于硬中断上下文中或者软中断被禁用,如果是那么直接返回

2 保存Eflags 然后关闭本地硬件中断

3 获取本地CPU的位掩码pending 如果有待处理的软中断就调用__do_irq

4 从__do_irq返回恢复Eflags

asmlinkage void do_softirq(void)
{
//用来保存位掩码的局部变量
__u32 pending;
//保存Eflags寄存器的局部变量
unsigned long flags;
//如果do_softirq在中断上下文中被调用 或 软中断被禁止使用 那么不处理软中断
//直接返回
if (in_interrupt())
return;
//将Eflags保存到flags中 然后关硬件中断
local_irq_save(flags);
//获取本地CPU上的位掩码
pending = local_softirq_pending(); if (pending)
__do_softirq();
//将flags
local_irq_restore(flags);
}

x86_32架构下使用4K软中断堆栈的do_softirq处理流程

1 类似于通用的do_softirq,如果在中断上下文中或者软中断被禁止使用就立即返回。然后关外部中断

2 如果本地CPU上存在待处理的软中断就开始对软中断堆栈的处理,关键是令isp指向软中断堆栈的栈底。然后在软中断栈上调用call_on_stack。call_on_stack是一段内联汇编,其主要目的是完成从内核栈到软中断栈的切换。先将esp保存到ebx中,使用call指令跳转到__do_softirq子例程,子例程返回时再恢复esp。

asmlinkage void do_softirq(void)
{
unsigned long flags;
struct thread_info *curctx;
union irq_ctx *irqctx;
u32 *isp; if (in_interrupt())
return; local_irq_save(flags); if (local_softirq_pending()) {
//curctx指向当前进程的thread_info结构
curctx = current_thread_info();
//irqctx包含一个软中断堆和thread_info结构
irqctx = __get_cpu_var(softirq_ctx);
//触发硬中断和软中断是同一个进程所以将threadinfo的task指针统一
irqctx->tinfo.task = curctx->task;
//从内核堆栈切换到软中断堆栈 需要保存内核堆栈的栈指针寄存器内容
irqctx->tinfo.previous_esp = current_stack_pointer; /* build the stack frame on the softirq stack */
//isp指向软中断栈底
isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
//在软中断堆栈上调用__do_softirq
call_on_stack(__do_softirq, isp);
}
local_irq_restore(flags);
} static void call_on_stack(void *func, void *stack)
{
//call *%%edi 间接绝对近调用 偏移地址存储在edi寄存器中
//指令执行时 先将eip入栈 然后将edi-->eip
//先交换ebx and esp
//然后调用__do_softirq
//然后将ebx-->esp 恢复xchgl之前的esp
//输出约束将ebx --> stack
asm volatile("xchgl %%ebx,%%esp \n"
"call *%%edi \n"
"movl %%ebx,%%esp \n"
: "=b" (stack)
: "0" (stack),
"D"(func)
: "memory", "cc", "edx", "ecx", "eax");
}

2 __do_softirq

软中断的处理实际上是由 __do_softirq完成,整体的思路是遍历pending,如果某一位不为空表示本地CPU上有待处理的软中断,然出调用软中断的处理函数。

开始处理软中断前,内核要调用__local_bh_disable(通过将preempt_count的软中断计数器加1)关闭下半部。如前所说,处理软中断的时机不止一个,内核要保证在本地CPU上软中断的处理是串行的。

另外在处理软中断的循环结束时,内核还要检测是否有重复触发的软中断。先调用local_softirq_pending()获取位掩码pending,然后根据pending继续处理软中断,不过这种重复处理不能超过10次(MAX_SOFTIRQ_RESTART),一旦超过10次,内核就会唤醒ksoftirqd

#define MAX_SOFTIRQ_RESTART 10

asmlinkage void __do_softirq(void)
{
// softirq_action表示一个软中断
struct softirq_action *h;
// 局部变量pending 保存待处理软中断位图
__u32 pending;
// 软中断的重启次数
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
//获取本地CPU上所有待处理的软中断
pending = local_softirq_pending();
account_system_vtime(current); //关闭下半部中断
__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter(); cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
//pending已经保存了所有带出软中断的状态 所以将pending bitmask clear
set_softirq_pending(0);
// 开中断
local_irq_enable();
//h指向第一类软中断
h = softirq_vec;
do {
//先处理第一类软中断
if (pending & 1) {
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(h - softirq_vec); trace_softirq_entry(h, softirq_vec);
//调用软中断处理程序
h->action(h);
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
} rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending); //关闭本地CPU上的硬中断
local_irq_disable();
//获取位掩码
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart; if (pending)
wakeup_softirqd(); lockdep_softirq_exit(); account_system_vtime(current);
//将软中断计数器加1,激活软中断
_local_bh_enable();
}

6 ksoftirqd内核线程

6.1 ksoftirqd

在内核处理类似NET_RX_SOFTIRQ的软中断时,如果有大量等待处理的数据包。就会不断的调用

__raise_softirq_irqoff(NET_RX_SOFTIRQ)重复触发软中断NET_RX_SOFTIRQ。这样做会导致用户进程的” 饥饿问题 “ (长时间无法获得CPU)。

针对这种问题内核使用内核线程ksoftirqd去处理自行触发次数超过10次的软中断。

6.2 ksoftirqd的实现

static int ksoftirqd(void * __bind_cpu)
{
//ksoftirqd的优先级最低(nice = 19)
set_user_nice(current, 19);
//将ksoftirqd设置为不可冻结
current->flags |= PF_NOFREEZE;
//设置ksoftirqd为可中断状态
set_current_state(TASK_INTERRUPTIBLE); while (!kthread_should_stop()) {
//如果没有待处理的软中断则 调度别的进程
if (!local_softirq_pending())
schedule(); __set_current_state(TASK_RUNNING); while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
//关闭内核抢占
preempt_disable();
//处理软中断
do_softirq();
//开启内核抢占
preempt_enable();
//cond_resched()的目的是提高系统实时性, 主动放弃cpu供优先级更高的任务使用
cond_resched();
} set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}

程序6-1 ksoftirqd主功能函数

out:
local_irq_enable();
return; softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;

程序6-2 net_rx_action

void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __get_cpu_var(ksoftirqd); if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}

程序6-3 wakeup_softirqd

还未解决的问题

set_current_state和 __set_current_state的区别

PF_NOFREEZE

cond_resched()

为什么在do_softirq开始处理软中断时要关闭硬件中断

参考

ULK

http://blog.csdn.net/hardy_2009/article/details/7383729 关于内核栈和中断栈的说明

Linux内核软中断的更多相关文章

  1. linux内核--软中断与tasklet

    硬件中断通常都需要在最短的时间内执行完毕,如果将所有硬件中断相关的处理都放在硬件中断处理程序中,那么就达不到这个目的. 通过linux提供的软中断和tasklet,可以将硬件中断处理程序中可以延迟处理 ...

  2. [Linux内核]软中断、tasklet、工作队列

    转自:http://www.cnblogs.com/li-hao/archive/2012/01/12/2321084.html 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制, ...

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

    转自:http://blog.csdn.net/zhangskd/article/details/21992933 本文主要内容:硬中断 / 软中断的原理和实现 内核版本:2.6.37 Author: ...

  4. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

  5. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

  6. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】

    转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_time ...

  7. 《深入理解Linux内核》软中断/tasklet/工作队列

    软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来.下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任 ...

  8. 把握linux内核设计思想(三):下半部机制之软中断

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途]         中断处理程序以异步方式执行,其会打断其它重要代码,其执行时该中 ...

  9. linux内核分析作业8:理解进程调度时机跟踪分析进程调度与进程切换的过程

    1. 实验目的 选择一个系统调用(13号系统调用time除外),系统调用列表,使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 分析汇编代码调用系统调用的工作过程,特别是参数的传递的方 ...

  10. linux内核分析作业5:分析system_call中断处理过程

    1.增加 Menu 内核命令行 调试系统调用. 步骤:删除menu git clone        (tab) make rootfs 这就是我们将 fork 函数写入 Menu 系统内核后的效果, ...

随机推荐

  1. C++回溯法走迷宫

    #include <iostream> #include <iomanip> #include <cstdlib> using namespace std; #de ...

  2. ajenti试用感受

    ajenti试用感受_展现技术动态_百度空间 ajenti试用感受   今天看开源中国介绍一款名为"服务器管理系统"的开源软件发布了,名为ajenti,页面感觉不错,对机器的采集信 ...

  3. 数据结构《21》----2014 WAP 第一个问题----Immutable queue

    2014 WAP第一个问题----实现一个不可改变的队列: 看似非常easy.. 其实,不同的版本号之间的效率差距可能是巨大的.. 甚至难以想象. . 使用前STL图书馆queue我们进行了比较.大差 ...

  4. 《java入门第一季》之面向对象(代码块一网打尽)

    上一篇里面对代码块做出介绍,这里给出一个面试题,加深印象. 如有毁三观的地方,请见谅.拒绝黄赌毒 写程序的执行结果. class Student { static { System.out.print ...

  5. 分布式系列九: kafka

    分布式系列九: kafka概念 官网上的介绍是kafka是apache的一种分布式流处理平台. 最初由Linkedin开发, 使用Scala编写. 具有高性能,高吞吐量的特定. 包含三个关键能力: 发 ...

  6. 1、了解计算机与操作系统发展阶段 2、选择一个具体的操作系统,结合计算机与操作系统的发展阶段,详细了解其渊源、发展过程、趋势,整理成简洁美观的图文博客发布。 Windows Mac os x Unix Linux Android 等。

    1.了解计算机与操作系统发展阶段 操作系统并不是与计算机硬件一起诞生的,它是在人们使用计算机的过程中,为了满足两大需求:提高资源利用率.增强计算机系统性能,伴随着计算机技术本身及其应用的日益发展,而逐 ...

  7. Spark SQL / Catalyst 内部原理 与 RBO

    原创文章,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/spark/rbo/ 本文所述内容均基于 2018年9月10日 Spark ...

  8. Runtime详解(下)

    Runtime应用 1.Runtime 交换方法 应用场景:当第三方框架或者系统原生方法功能不能满足我们的时候,我们可以在保持系统原有功能的基础上,添加额外的功能. 需求:加载一张图片直接用系统的[U ...

  9. 使用MegaCli监控Linux硬盘

    1.首先查看机器是否使用的是MegaRAID卡 dmesg | grep RAID [ 6.932741] scsi host0: Avago SAS based MegaRAID driver 2. ...

  10. Spring Cloud Ribbon负载均衡配置类放在Spring boot主类同级增加Exclude过滤后报Field config in com.cloud.web.controller.RibbonConfiguration required a bean of type &#39;com.netflix.client.config.IClientConfig&#39; that could not b

    环境: Spring Cloud:Finchley.M8 Spring Boot:2.0.0.RELEASE 目录结构: 可以看到代码第13行的注释,我已经在@ComponentScan注解中添加了E ...