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内核实现透视---软中断&Tasklet

    软中断 首先明确一个概念软中断(不是软件中断int n).总来来说软中断就是内核在启动时为每一个内核创建了一个特殊的进程,这个进程会不停的poll检查是否有软中断需要执行,如果需要执行则调用注册的接口 ...

随机推荐

  1. 看完我的笔记不懂也会懂----bootstrap

    目录 Bootstrap笔记 知识点扫盲 容器 栅格系统 源码分析部分 外部容器 栅格系统(盒模型)设计的精妙之处 Bootstrap笔记 写在开始: 由于我对AngulaJS的学习只是一个最浅显的过 ...

  2. ISC BIND9 - 最详细、最认真的从零开始的BIND 9 服务讲解

    DNS and BIND 服务的搭建说明 目录 目录 DNS and BIND 服务的搭建说明 1. 背景 1.1 DNS 1.2 FQDN 1.3 BIND 1.4 本文中搭建模拟DNS服务网络虚拟 ...

  3. CF482E ELCA

    一.题目 点此看题 二.解法 题目的提示已经足够明显了吧,肯定是要写一个 \(\tt link-cut-tree\) .我们只需要求出总和,再除以方案数就是期望.然后可以算每个点为 \(\tt lca ...

  4. Linux普通用户安装配置mysql(非root权限)

    Linux普通用户安装配置mysql(非root权限) 说明:在实际工作中,公司内网的机器我们一般没有root权限,也没有连网,最近参考网上的资料使用一般的账户成功安装mysql,记录如下 Linux ...

  5. linux安装mysql8.0

    linux 上安装mysql8.0 mysql版本8.0.16 MySQL Community 操作系统centos7 准备工作: mysql8.0 rpm文件 安装步骤: 1. 下载mysql的re ...

  6. 2018年11月16日SQL Server实验内容(触发器实验)

    --注意:先把studentmanager数据库中的所有表用select into命令复制一份, --然后用复制后新表完成下面的实验,同时,对每个触发器都要进行验证. select *into dep ...

  7. Java 给PPT添加动画效果(预设动画/自定义动画)

    PPT幻灯片中对形状可设置动画效果,常见的动画效果为内置的固定类型,即动画效果和路径是预先设定好的固定模板,但在设计动画效果时,用户也可以按照自己的喜好自定义动画动作路径.下面,通过Java后端程序代 ...

  8. Oment++ 初学者教程 第4节-将其转变为真实网络

    4.1两个以上的节点 现在,我们将迈出一大步:创建几个tic模块并将它们连接到网络中.现在,我们将使它们的工作变得简单:一个节点生成一条消息,其他节点继续沿随机方向扔消息,直到它到达预定的目标节点为止 ...

  9. HTML总结篇

    一.HTML基本结构标签 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...

  10. 全网最详细的Linux命令系列-cp命令

    cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一.一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数.但是如果是 ...