參考:

http://bbs.chinaunix.net/thread-2333484-1-1.html

http://liu1227787871.blog.163.com/blog/static/20536319720129210112658/

1、软中断

一般来说,一次中断服务的过程通常能够分为两个部分。

开头的 部分往往必须在关中断的条件下运行,这样才干在不受干扰的条件下“原子”地完毕一些关键性操作。同一时候这部分操作的时间性又往往非常强。必须在中断请求发生后马上或至少在一定时间限制中运行,并且相继的多次中断请求也不能合并在一起来处理。

而后半部分,通常能够并且应该在开中断的条件下运行。这样才不至于因中断关闭过久而造成其它中断的丢失,同一时候,这些操作经常同意延时到稍后才来运行。并且有可能多次中断的相关部分合并在一起处理。

这些不同的性质经常使中断服务的前后两半明显地区分开来,能够并且应该分别加以不同的实现。这里的后半部分就称为"bottom
half",在内核代码中往往写成bf 。而bf的这部分就能够通过软件中断来实现。

由于软件中断的激活是通过代码来实现的,而不是硬件,所以就能够自己或由系统来决定激活的时机!

1.1 注冊

还是以我最熟悉的两个老朋友做为开篇:





        open_softirq(NET_TX_SOFTIRQ, net_tx_action);

        open_softirq(NET_RX_SOFTIRQ, net_rx_action);





open_softirq向内核注冊一个软中断。事实上质是设置软中断向量表对应槽位。注冊其处理函数:

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

softirq_vec是整个软中断的向量表:

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

NR_SOFTIRQS是最大软中断向量数。内核支持的全部软中断例如以下:

enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS
};

好像后为为RPS新增了一个。只是这我的内核版本号偏低。





1.2 激活 





当须要调用软中断时,须要调用raise_softirq函数激活软中断。这里使用术语“激活”而非“调用”。

是由于在非常多情况下不能直接调用软中断。

所以仅仅能高速地将其标志为“可运行”。等待未来某一时刻调用。

为什么“在非常多情况下不能直接调用软中断”?试想一下下半部引入的理念。就是为了让上半部更快地运行。

假设在中断程序代码中直接调用软中断函数,那么就失去了上半部与下半部的差别,也就是失去了其存在的意义。





内核使用一个名为__softirq_pending的位图来描写叙述软中断,每个位相应一个软中断,位图包括在结构irq_stat中:

typedef struct {
unsigned int __softirq_pending;
……
} ____cacheline_aligned irq_cpustat_t; DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);

宏or_softirq_pending用于设置对应的位(位或操作):

#define or_softirq_pending(x)        percpu_or(irq_stat.__softirq_pending, (x))

local_softirq_pending用于取得整个位图(而非某一位):

#define local_softirq_pending()        percpu_read(irq_stat.__softirq_pending)

宏__raise_softirq_irqoff是or_softirq_pending的包裹:

#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

raise_softirq_irqoff通过调用__raise_softirq_irqoff实现激活软中断。它的參数nr即位软中断相应的位图槽位:

/*
* This function must run with irqs disabled!
*/
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.
*/
//设置了位图后。能够推断是否已经没有在中断上下文中了,假设没有,则是一个马上调用软中断的好时机。 //in_interrupt还有一个作用是推断软中断是否被禁用。
//wakeup_softirqd唤醒软中断的守护进程ksoftirq。 if (!in_interrupt())
wakeup_softirqd();
}
复制代码
如今能够来看"激活"软中断的全部含义了,raise_softirq函数完毕这一操作:
void raise_softirq(unsigned int nr)
{
unsigned long flags; //全部操作,应该关闭中断,避免嵌套调用
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

可见,激活的操作,主要是两点:

<1>、最重要的,就是置对应的位图。等待将来被处理;

<2>、假设此时已经没有在中断上下文中,则马上调用(事实上是内核线程的唤醒操作),如今就是将来;





2、调度时机

是的。除了raise_softirq在,可能会(嗯,重要的是“可能”)通过wakeup_softirqd唤醒ksoftirqd外,还得明确软中断的其他调用时机。

A、当do_IRQ完毕了I/O中断时调用irq_exit:

#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(); //调用软中断

B、假设系统使用I/O APIC,在处理完本地时钟中断时:

void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)
{
……
irq_exit();
……
}

C、local_bh_enable

local_bh_enable就是打开下半部。当然重中之中就是软中断了:
void local_bh_enable(void)
{
_local_bh_enable_ip((unsigned long)__builtin_return_address(0));
} static inline void _local_bh_enable_ip(unsigned long ip)
{
…… if (unlikely(!in_interrupt() && local_softirq_pending()))
do_softirq(); ……
}

D、在SMP中,当CPU处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时:

唔。对多核中CPU的之间的通信不熟。不太清楚这个机制……

3、do_softirq





不论是哪种调用方式,终于都会触发到软中断的核心处理函数do_softirq,它处理当前CPU上的全部软中断。

内核将软中断设计尽量与平台无关。可是在某些情况下。它们还是会有差异,先来看一个x86 32位的do_softirq版本号:

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);
//内核使用一个CPU位图,确实几个软中断能够同一时候在不同的CPU上运行,包含同样的软中断。比如,
//NET_RX_SOFTIRQ能够同一时候跑在多个处理器上。
//local_softirq_pending用于确定当前CPU的全部位图是否被设置。 即是否有软中断等待处理。 //回忆一下常常发生的网卡接收数据处理:当网卡中断落在哪一个CPU上时,与之对应的软中断函数就会在其上运行。
//从这里来看,实质就是哪个网卡中断落在对应的CPU上,CPU置其软中断位图,这里做对应的检測(这里local_softirq_pending仅仅
//是一个总的推断。后面还有按位的推断)。检測到有对应的位,运行之
if (local_softirq_pending()) {
//取得线程描写叙述符
curctx = current_thread_info();
//构造中断上下文结构,softirq_ctx是每一个CPU的软中断上下文
//static DEFINE_PER_CPU(union irq_ctx *, softirq_ctx);
//这里先取得当前CPU的软中断上下文,然后为其赋初始值——保存当前进程和栈指针
irqctx = __get_cpu_var(softirq_ctx);
irqctx->tinfo.task = curctx->task;
irqctx->tinfo.previous_esp = current_stack_pointer; /* build the stack frame on the softirq stack */
//构造中断栈帧
isp = (u32 *) ((char *)irqctx + sizeof(*irqctx)); //call_on_stack切换内核栈,并在中断上下文上运行函数__do_softirq
call_on_stack(__do_softirq, isp);
/*
* Shouldnt happen, we returned above if in_interrupt():
*/
WARN_ON_ONCE(softirq_count());
} //恢复之
local_irq_restore(flags);
}

当配置了CONFIG_4KSTACKS。每一个进程的thread_union仅仅有4K,而非8K。发生中断时,内核栈将不使用进程的内核栈。而使用每一个 cpu的中断请求栈。

内核栈将使用每一个 cpu的中断请求栈。而非进程的内核栈来运行软中断函数:

static void call_on_stack(void *func, void *stack)
{
asm volatile("xchgl %%ebx,%%esp \n" //交换栈指针,中断栈帧的指针stack做为传入參数(%ebx)。交换后esp是irq_ctx的栈顶,ebx是进程内核栈的栈
"call *%%edi \n" //调用软中断函数
"movl %%ebx,%%esp \n" //恢复之,直接使用movl,而非xchgl是由于函数运行完成,中断的栈帧指针已经没实用处了
: "=b" (stack)
: "0" (stack),
"D"(func)
: "memory", "cc", "edx", "ecx", "eax");
}

PS:全部的这些运行,应该都是在定义4K栈的基础上的:

#ifdef CONFIG_4KSTACKS
/*
* per-CPU IRQ handling contexts (thread information and stack)
*/
union irq_ctx {
struct thread_info tinfo;
u32 stack[THREAD_SIZE/sizeof(u32)];
} __attribute__((aligned(PAGE_SIZE))); static DEFINE_PER_CPU(union irq_ctx *, hardirq_ctx);
static DEFINE_PER_CPU(union irq_ctx *, softirq_ctx);
…… static void call_on_stack(void *func, void *stack)
……

是的,这个版本号相对复杂,可是假设看了复杂的,再来看简单的,就easy多了,当平台未定义do_softirq函数时(__ARCH_HAS_DO_SOFTIRQ)。

内核提供了一个通用的:

#ifndef __ARCH_HAS_DO_SOFTIRQ

asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags; if (in_interrupt())
return; local_irq_save(flags); pending = local_softirq_pending(); if (pending)
__do_softirq(); local_irq_restore(flags);
} #endif

无需很多其它的解释,它很的简洁。





不论是哪个版本号,都将调用__do_softirq函数:

asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu; //保存位图
pending = local_softirq_pending();
//进程记帐
account_system_vtime(current); //关闭本地CPU下半部。 为了保证同一个CPU上的软中断以串行方式运行。
__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter(); //获取本地CPU
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
//清除位图
set_softirq_pending(0); //锁中断,仅仅是为了保持位图的相互排斥,位图处理完成。后面的代码能够直接使用保存的pending,
//而中断处理程序在激活的时候,也能够放心地使用irq_stat.__softirq_pending。 //所以。能够开中断了
local_irq_enable(); //取得软中断向量
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;
}
//??qsctr,这个是啥东东
rcu_bh_qsctr_inc(cpu);
}
//指向下一个软中断槽位
h++;
//移位,取下一个软中断位
pending >>= 1;
} while (pending); //当软中断处理完成后,由于前面已经开了中断了。所以有可能新的软中断已经又被设置。
//软中断调度程序会尝试又一次软中断。其最大重新启动次数由max_restart决定。
//所以,这里必须再次关闭中断。再来一次……
local_irq_disable(); //取位图
pending = local_softirq_pending();
//有软中断被设置,且没有超过最大重新启动次数,再来一次先
if (pending && --max_restart)
goto restart; //超过最大重新启动次数。还有软中断待处理。调用wakeup_softirqd。其任处是唤醒软中断守护进程ksoftirqd。
if (pending)
wakeup_softirqd(); lockdep_softirq_exit(); account_system_vtime(current);
//恢复下半部
_local_bh_enable();
}

中断跟踪

假设中断跟踪CONFIG_TRACE_IRQFLAGS被定义。lockdep_softirq_enter/lockdep_softirq_exit用于递增/递减当前进程的软中断上下文计数器softirq_context:

# define lockdep_softirq_enter()        do { current->softirq_context++; } while (0)

# define lockdep_softirq_exit()        do { current->softirq_context--; } while (0)

复制代码

trace_softirq_entry与trace_softirq_exit配合使用。能够用于推断软中断的延迟。





好像软中断不太难,没有很多其它的内容了。欢迎大家回贴补充。

浅析Linux的软中断的实现的更多相关文章

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

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

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

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

  3. 浅析Linux操作系统工作的基础

    环境:lubuntu 13.04   kernel 3.9.7 作者:SA12226265 katao 简介: 本文根据 Linux™ 系统工作基础的分析,对存储程序计算机.堆栈(函数调用堆栈)机制和 ...

  4. 浅析 Linux 初始化 init 系统

    近年来,Linux 系统的 init 进程经历了两次重大的演进,传统的 sysvinit 已经逐渐淡出历史舞台,新的 UpStart 和 systemd 各有特点,越来越多的 Linux 发行版采纳了 ...

  5. 浅析Linux下进程间通信:共享内存

    浅析Linux下进程间通信:共享内存 共享内存允许两个或多个进程共享一给定的存储区.因为数据不需要在客户进程和服务器进程之间复制,所以它是最快的一种IPC.使用共享内存要注意的是,多个进程之间对一给定 ...

  6. 浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 第 2 部分: UpStart 第 3 部分: Systemd

    浅析 Linux 初始化 init 系统,第 1 部分: sysvinit  第 2 部分: UpStart 第 3 部分: Systemd http://www.ibm.com/developerw ...

  7. 浅析Linux服务器集群系统技术

    浅析Linux服务器集群系统技术 目录 前言 常用的服务器集群 集群系统的优势 LVS集群的通用体系结构 为什么使用层次的体系结构 为什么是共享存储 可伸缩Web服务 前言 总结两篇技术文章,努力学习 ...

  8. 浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程【转】

    本文转载自:http://www.cnblogs.com/qingchen1984/p/7007631.html 本篇文章主要介绍了"浅析 Linux 中的时间编程和实现原理一—— Linu ...

  9. 【转】浅析Linux中的零拷贝技术

    本文探讨Linux中主要的几种零拷贝技术以及零拷贝技术适用的场景.为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入: 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件 ...

随机推荐

  1. 高性能Mysql主从架构的复制原理及配置详解(转)

    温习<高性能MySQL>的复制篇. 1 复制概述 Mysql内建的复制功能是构建大型,高性能应用程序的基础.将Mysql的数据分布到多个系统上去,这种分布的机制,是通过将Mysql的某一台 ...

  2. 在Spring Boot启动后执行指定代码

    在开发时有时候需要在整个应用开始运行时执行一些特定代码,比如初始化环境,准备测试数据等等. 在Spring中可以通过ApplicationListener来实现相关的功能,不过在配合Spring Bo ...

  3. Java GUI图形界面开发工具

    Applet 应用程序     一种可以在 Web 浏览器中执行的小程序,扩展了浏览器中的网页功能. 缺: 1.需要下载 Applet 及其相关文件 2.Applet 的功能是受限制的 优: 3.无需 ...

  4. 宣布在 Azure 镜像库中正式推出 Windows Server 2012 R2 并降低 Windows Azure 的实例定价

    我们今天将宣布两条消息,为使用基础结构服务的客户提供更多选择和成本节约:在镜像库中推出 Windows Server 2012 R2 以及降低 Memory Intensive 计算实例定价. 虚拟机 ...

  5. mfc删除标题和边框

    //删除标题和边框WS_CAPTION和WS_BORDER风格 ModifyStyle(WS_CAPTION, 0);ModifyStyle(WS_BORDER, 0);

  6. ACM—循环小数转变成分数知识点_C++实现

    在小学的时候,我们的学生都能把“整数表示成分母是1的分数”,而且大多数学生也都能把有限小数和循环小数表示成分数的形式.这样,整数.分数.有限小数.循环小数都属于有理数.教科书中说“整数和分数统称有理数 ...

  7. Flex 事件机制

    使用ActionScript的单击事件示例 <?xml version="1.0" encoding="utf-8"?> <s:Applica ...

  8. [置顶] js操作iframe兼容各种浏览器

    在做项目时,遇到了操作iframe的相关问题.业务很简单,其实就是在操作iframe内部某个窗体时,调用父窗体的一个函数.于是就写了两个很简单的htm页面用来测试,使用网上流行的方法在谷歌浏览器中始终 ...

  9. 初次接触VC++载入自己定义LIB 即静态链接

    分为两部分 第一部分  LIBproject 用来生成LIB文件 #ifndef _myfun #define _myfun int myfun(int x,int y) { return x+y; ...

  10. JS于,子类调用父类的函数

    概要 JS虽然没有直接有面向对象的特性,但还是能prototype为了模拟面向对象的特性,如继承和多态.而大多数面向对象的语言(例如C++.Java等一下)相比,JS为了实现面向对象还是有点繁琐,抽象 ...