參考:

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. Windows Azure HDInsight 支持预览版 Hadoop 2.2 群集

     Windows Azure HDInsight 支持预览版 Hadoop 2.2 群集 继去年 10 月推出 Windows Azure HDInsight 之后,我们宣布 Windows Az ...

  2. uva11536 Smallest Sub-Array

    Thinking about it: 我的思路跟sliding window有点类似.假设已经确定了一个区间[l, r],序列中从 l 到 r 恰好包含了[1, K]的各个元素,则从 r 开始继续迭代 ...

  3. 表格java代码的相关知识积累

    本文主要收集各大博客中的java表格 用JSP创建一个表格模板 . 项目中要用到一些展示信息的表格,表头不固定,表格内容是即时从后台取的:考虑到复用性,笔者用jsp编写了一个表格模板,可以从reque ...

  4. iOS开发 点击跳转到App Store 或者 点击按钮去评价

    //跳转到应用页面 NSString *str = [NSString stringWithFormat:@"http://itunes.apple.com/us/app/id%d" ...

  5. Node.js学习笔记1(简介)

            1.什么是Node.js?         Node.js,或者 Node,是一个可以让 JavaScript 运行在服务器端的平台.它可以让 JavaScript 脱离浏览器的束缚运 ...

  6. 设计模式值六大原则——设计模式之六大原则——单一职责原则(SRP)

    定义: 应该有且仅有一个原因引起类的变更. There should never be more than one reason for a class to change. 优点: 1.类的复杂性降 ...

  7. oracle去除字符串中间的空格

    update AC01 A set A.AAC003 = REGEXP_REPLACE(A.AAC003, '( ){1,}', '') WHERE A.AAC002 IN (SELECT AAC00 ...

  8. apache加载php模块失败

    LoadModule php5_module "G:/php54/php5apache2_2.dll" apache2 conf加入这个之后无法加载 解决办法 在这句之前加入PHP ...

  9. WebRTC Demo - getUserMedia()

    WebRTC介绍 WebRTC提供三类API: MediaStream,即getUserMedia RTCPeerConnection RTCDataChannel getUserMedia已经由Ch ...

  10. POJ-1003&1004

    这两题比较简单,就不做分析了,描述下题目,就上代码吧. [题目描述] 1003,其实就是求这个方程的最小n:1/2 + 1/3 + 1/4 + ... + 1/(n + 1) >= c: 100 ...