do softirqq为什么是在中断上下来

2620人阅读
Linux(22)
Linux的中断宏观分为两种:软中断和硬中断。声明一下,这里的软和硬的意思是指和软件相关以及和硬件相关,而不是软件实现的中断或硬件实现的中断。
软中断就是&信号机制&。软中不是软件中断。Linux通过信号来产生对进程的各种中断操作,&在Linux中最多可以注册32个软中断,目前系统用了6个软中断,他们为:定时器处理、SCSI处理、网络收发处理以及Tasklet机制,一般来说,软中断是由内核机制的触发事件引起的(例如进程运行超时),但是不可忽视有大量的软中断也是由于和硬件有关的中断引起的,软中断的工作工程模拟了实际的中断处理过程,当某一软中断时间发生后,首先需要设置对应的中断标记位,触发中断事务,然后唤醒守护线程去检测中断状态寄存器,如果通过查询发现某一软中断事务发生之后,那么通过软中断向量表调用软中断服务程序action()。这就是软中断的过程,与硬件中断唯一不同的地方是从中断标记到中断服务程序的映射过程。在CPU的硬件中断发生之后,CPU需要将硬件中断请求通过向量表映射成具体的服务程序,这个过程是硬件自动完成的,但是软中断不是,其需要守护线程去实现这一过程。例如当打印机端口产生一个硬件中断时,会通知和硬件相关的硬中断,硬中断就会产生一个软中断并送到操作系统内核里,这样内核就会根据这个软中断唤醒睡眠在打印机任务队列中的处理进程。
下列27种软中断信号的注释部分即为它的功能说明(另外5种忽略,一种32种)
   #define SIGHUP 1 /* hangup
   #define SIGINT 2 /* interrupt
   #define SIGQUIT 3 /* quit
   #define SIGILL 4 /* illegal
instruction (not reset when caught ) */
   #define SIGTRAP 5 /* trace
trap (not reset when caught ) */
   #define SIGIOT 6 /* IOT instruction
   #define SIGEMT 7 /* EMT instr
   #define SIGFPE 8 /* floating
point exception */
   #define SIGKILL 9 /* kill
(cannot be caught or ignored) */
   #define SIGBUS 10 /* bus error
   #define SIGSEGV 11 /* segmentation
violation */
   #define SIGSYS 12 /* bad argument
to system call */
   #define SIGPIPE 13 /* write
on a pipe with no one to read it */
   #define SIGALAM 14 /* alarm
   #define SIGTERM 15 /* software
termination signal from kill */
   #define SIGSTOP 17 /* sendoble
stop signal not from tty */
   #define SIGTSTP 18 /* stop
signal from tty */
   #define SIGCONT 19 /* continue
a stopped process */
   #define SIGCHLD 20 /* to parent
on child stop or exit */
   #define SIGTTIN 21 /* to readers
pgrp upon background tty read */
   #define SIGTTOOU 22 /* like
TTIN for output it (tp→t-local&&LTOSTOP)
   #define SIGTINT 23 /* to pgrp
on every input character if LINTRUP */
   #define SIGXCPU 24 /* exceeded
CPU time limit */
   #define SIGXFSZ 25 /* exceeded
file size limit */
   #define SIGSTAT 26 /* status
update requsted */
   #define SIGMOUS 27 /* mouse
interrupt */
   #define NSIG 32
硬中断就是通常意义上的&中断处理程序&,它是直接处理由硬件发过来的中断信号的。当硬中断收到它应当处理的中断信号以后,就回去自己驱动的设备上去看看设备的状态寄存器以了解发生了什么事情,并进行相应的操作。对于软中断,我们不做讨论,那是进程调度里要考虑的事情。由于我们讨论的是设备驱动程序的中断问题,所以焦点集中在硬中断里。我们这里讨论的是硬中断,即和硬件相关的中断。
构成软中断机制的核心元素包括:
1、&&软中断状态寄存器soft interrupt state(irq_stat)
2、&&软中断向量表(softirq_vec)
3、&&软中断守护daemon
&2.4版本的内核定义了四个软中断:HI_SOFTIRQ、NET_TX_SOFTIRQ、 NET_RX_SOFTIRQ、TASKLET_SOFTIRQ;2.6版本的内核中定义了六个软中断:HI_SOFTIRQ、 TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、 TASKLET_SOFTIRQ。
向内核注册一个软中断,其实质是设置软中断向量表相应槽位,注册其处理函数:
void open_softirq(int nr, void (*action)(struct softirq_action *))
& & & & softirq_vec[nr].action =
softirq_vec是整个软中断的向量表:
struct softirq_action
& & & & void& & & & (*action)(struct softirq_action *);
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_
NR_SOFTIRQS是最大软中断向量数,内核支持的所有软中断如下:
& & & & 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
当需要调用软中断时,需要调用raise_softirq函数激活软中断,这里使用术语“激活”而非“调用”,是因为在很多情况下不能直接调用软中断。所以只能快速地将其标志为“可执行”,等待未来某一时刻调用。为什么“在很多情况下不能直接调用软中断”?试想一下下半部引入的理念,就是为了让上半部更快地执行。如果在中断程序代码中直接调用软中断函数,那么就失去了上半部与下半部的区别,也就是失去了其存在的意义。
内核使用一个名为__softirq_pending的位图来描述软中断,每一个位对应一个软中断,位图包含在结构irq_stat中:
typedef struct {
& & & & unsigned int __softirq_
& & & & ……
} ____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)
& & & & //所有操作,应该关闭中断,避免嵌套调用
& & & & local_irq_save(flags);
& & & & raise_softirq_irqoff(nr);
& & & & local_irq_restore(flags);
可见,激活的操作,主要是两点:
&1&、最重要的,就是置相应的位图,等待将来被处理;
&2&、如果此时已经没有在中断上下文中,则立即调用(其实是内核线程的唤醒操作),现在就是将来;
是的,除了raise_softirq在,可能会(嗯,重要的是“可能”)通过wakeup_softirqd唤醒ksoftirqd外,还得明白软中断的其它调用时机。
A、当do_IRQ完成了I/O中断时调用irq_exit:
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq()& & & & __do_softirq()
# define invoke_softirq()& & & & do_softirq()
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();
& & & & ……
不论是哪种调用方式,最终都会触发到软中断的核心处理函数do_softirq,它处理当前CPU上的所有软中断。
内核将软中断设计尽量与平台无关,但是在某些情况下,它们还是会有差异,先来看一个x86 32位的do_softirq版本:
asmlinkage void do_softirq(void)
& & & & struct thread_info *
& & & & union irq_ctx *
& & & & u32 *
& & & & //软中断不能在中断上下文内嵌套调用。中断处理程序或下半部采用的是&激活&方式。
& & & & if (in_interrupt())
& & & & & & & &
& & & & //禁止中断,保存中断标志
& & & & 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-&
& & & & & & & & irqctx-&tinfo.previous_esp = current_stack_
& & & & & & & & /* 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&);
所有的这些执行,应该都是在定义4K栈的基础上的:
#ifdef CONFIG_4KSTACKS
* per-CPU IRQ handling contexts (thread information and stack)
union irq_ctx {
& & & & struct thread_info& && &
& & & & 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)
是的,这个版本相对复杂,但是如果看了复杂的,再来看简单的,就容易多了,当平台没有定义do_softirq函数时(__ARCH_HAS_DO_SOFTIRQ),
内核提供了一个通用的:
#ifndef __ARCH_HAS_DO_SOFTIRQ
asmlinkage void do_softirq(void)
& & & & __u32
& & & & if (in_interrupt())
& & & & & & & &
& & & & local_irq_save(flags);
& & & & pending = local_softirq_pending();
& & & & if (pending)
& & & & & & & & __do_softirq();
& & & & local_irq_restore(flags);
不论是哪个版本,都将调用__do_softirq函数:
asmlinkage void __do_softirq(void)
& & & & struct softirq_action *h;
& & & & __u32
& & & & int max_restart = MAX_SOFTIRQ_RESTART;
& & & & //保存位图
& & & & 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();
& & & & /* Reset the pending bitmask before enabling irqs */
& & & & //清除位图
& & & & set_softirq_pending(0);
& & & & //锁中断,只是为了保持位图的互斥,位图处理完毕。后面的代码可以直接使用保存的pending,
& & & & //而中断处理程序在激活的时候,也可以放心地使用irq_stat.__softirq_pending。
& & & & //所以,可以开中断了
& & & & local_irq_enable();
& & & & //取得软中断向量
& & & & h = softirq_
& & & & //循环处理所有的软中断
& & & & 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_
& & & & & & & & & & & & }
& & & & & & & & & & & & //??qsctr,这个是啥东东
& & & & & & & & & & & & rcu_bh_qsctr_inc(cpu);
& & & & & & & & }
& & & & & & & & //指向下一个软中断槽位
& & & & & & & & h++;
& & & & & & & & //移位,取下一个软中断位
& & & & & & & & pending &&= 1;
& & & & } while (pending);
& & & & //当软中断处理完毕后,因为前面已经开了中断了,所以有可能新的软中断已经又被设置,
& & & & //软中断调度程序会尝试重新软中断,其最大重启次数由max_restart决定。
& & & & //所以,这里必须再次关闭中断,再来一次……
& & & & local_irq_disable();
& & & & //取位图
& & & & pending = local_softirq_pending();
& & & & //有软中断被设置,且没有超过最大重启次数,再来一次先
& & & & if (pending && --max_restart)
& & & & & & & &
& & & & //超过最大重启次数,还有软中断待处理,调用wakeup_softirqd。其任处是唤醒软中断守护进程ksoftirqd。
& & & & if (pending)
& & & & & & & & wakeup_softirqd();
& & & & lockdep_softirq_exit();
& & & & account_system_vtime(current);
& & & & //恢复下半部
& & & & _local_bh_enable();
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:140576次
积分:1513
积分:1513
排名:千里之外
原创:24篇
转载:32篇
(1)(1)(2)(2)(9)(1)(4)(7)(1)(4)(6)(4)(12)(2)软件中断(softIRQ)是内核提供的一种延迟执行机制,它完全由软件触发,虽然说是延迟机制,实际上,在大多数情况下,它与普通进程相比,能得到更快的响应时间。软中断也是其他一些内核机制的基础,比如tasklet,高分辨率timer等。
/*****************************************************************************************************/声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!/*****************************************************************************************************/
1. &软件中断的数据结构
1.1 &struct softirq_action
& & & & 内核用softirq_action结构管理软件中断的注册和激活等操作,它的定义如下:
struct&softirq_action&&
&&&&void&&&&(*action)(struct&softirq_action&*);&&
非常简单,只有一个用于回调的函数指针。软件中断的资源是有限的,内核目前只实现了10种类型的软件中断,它们是:
&&&&HI_SOFTIRQ=0,&&
&&&&TIMER_SOFTIRQ,&&
&&&&NET_TX_SOFTIRQ,&&
&&&&NET_RX_SOFTIRQ,&&
&&&&BLOCK_SOFTIRQ,&&
&&&&BLOCK_IOPOLL_SOFTIRQ,&&
&&&&TASKLET_SOFTIRQ,&&
&&&&SCHED_SOFTIRQ,&&
&&&&HRTIMER_SOFTIRQ,&&
&&&&RCU_SOFTIRQ,&&&&
&&&&NR_SOFTIRQS&&
内核的开发者们不建议我们擅自增加软件中断的数量,如果需要新的软件中断,尽可能把它们实现为基于软件中断的tasklet形式。与上面的枚举值相对应,内核定义了一个softirq_action的结构数组,每种软中断对应数组中的一项:
static&struct&softirq_action&softirq_vec[NR_SOFTIRQS]&__cacheline_aligned_in_&&
1.2 &irq_cpustat_t
& & & & 多个软中断可以同时在多个cpu运行,就算是同一种软中断,也有可能同时在多个cpu上运行。内核为每个cpu都管理着一个待决软中断变量(pending),它就是irq_cpustat_t:
typedef&struct&{&&
&&&&unsigned&int&__softirq_&&
}&____cacheline_aligned&irq_cpustat_t;&&
irq_cpustat_t&irq_stat[NR_CPUS]&____cacheline_&&
__softirq_pending字段中的每一个bit,对应着某一个软中断,某个bit被置位,说明有相应的软中断等待处理。
1.3 &软中断的守护进程ksoftirqd
& & & & 在cpu的热插拔阶段,内核为每个cpu创建了一个用于执行软件中断的守护进程ksoftirqd,同时定义了一个per_cpu变量用于保存每个守护进程的task_struct结构指针:
DEFINE_PER_CPU(struct&task_struct&*,&ksoftirqd);&&
大多数情况下,软中断都会在irq_exit阶段被执行,在irq_exit阶段没有处理完的软中断才有可能会在守护进程中执行。
2. &触发软中断
& & & & 要触发一个软中断,只要调用api:raise_softirq即可,它的实现很简单,先是关闭本地cpu中断,然后调用:raise_softirq_irqoff
void&raise_softirq(unsigned&int&nr)&&
&&&&unsigned&long&&&
&&&&local_irq_save(flags);&&
&&&&raise_softirq_irqoff(nr);&&
&&&&local_irq_restore(flags);&&
再看看raise_softirq_irqoff:
inline&void&raise_softirq_irqoff(unsigned&int&nr)&&
&&&&__raise_softirq_irqoff(nr);&&
&&&&&&&&......&&
&&&&if&(!in_interrupt())&&
&&&&&&&&wakeup_softirqd();&&
先是通过__raise_softirq_irqoff设置cpu的软中断pending标志位(irq_stat[NR_CPUS] ),然后通过in_interrupt判断现在是否在中断上下文中,或者软中断是否被禁止,如果都不成立,则唤醒软中断的守护进程,在守护进程中执行软中断的回调函数。否则什么也不做,软中断将会在中断的退出阶段被执行。
3. &软中断的执行
& & & & 基于上面所说,软中断的执行既可以守护进程中执行,也可以在中断的退出阶段执行。实际上,软中断更多的是在中断的退出阶段执行(irq_exit),以便达到更快的响应,加入守护进程机制,只是担心一旦有大量的软中断等待执行,会使得内核过长地留在中断上下文中。
3.1 &在irq_exit中执行
& & & & 看看irq_exit的部分:
void&irq_exit(void)&&
&&&&&&&&......&&
&&&&sub_preempt_count(IRQ_EXIT_OFFSET);&&
&&&&if&(!in_interrupt()&&&&local_softirq_pending())&&
&&&&&&&&invoke_softirq();&&
&&&&&&&&......&&
如果中断发生嵌套,in_interrupt()保证了只有在最外层的中断的irq_exit阶段,invoke_interrupt才会被调用,当然,local_softirq_pending也会实现判断当前cpu有无待决的软中断。代码最终会进入__do_softirq中,内核会保证调用__do_softirq时,本地cpu的中断处于关闭状态,进入__do_softirq:
asmlinkage&void&__do_softirq(void)&&
&&&&&&&&......&&
&&&&pending&=&local_softirq_pending();&&
&&&&__local_bh_disable((unsigned&long)__builtin_return_address(0),&&
&&&&&&&&&&&&&&&&SOFTIRQ_OFFSET);&&
restart:&&
&&&&set_softirq_pending(0);&&
&&&&local_irq_enable();&&
&&&&h&=&softirq_&&
&&&&do&{&&
&&&&&&&&if&(pending&&&1)&{&&
&&&&&&&&&&&&&&&&&&&&......&&
&&&&&&&&&&&&trace_softirq_entry(vec_nr);&&
&&&&&&&&&&&&h-&action(h);&&
&&&&&&&&&&&&trace_softirq_exit(vec_nr);&&
&&&&&&&&&&&&&&&&&&&&&&&&......&&
&&&&&&&&}&&
&&&&&&&&h++;&&
&&&&&&&&pending&&&=&1;&&
&&&&}&while&(pending);&&
&&&&local_irq_disable();&&
&&&&pending&=&local_softirq_pending();&&
&&&&if&(pending&&&&--max_restart)&&
&&&&&&&&goto&&&
&&&&if&(pending)&&
&&&&&&&&wakeup_softirqd();&&
&&&&lockdep_softirq_exit();&&
&&&&__local_bh_enable(SOFTIRQ_OFFSET);&&
首先取出pending的状态;
禁止软中断,主要是为了防止和软中断守护进程发生竞争;
清除所有的软中断待决标志;
打开本地cpu中断;
循环执行待决软中断的回调函数;
如果循环完毕,发现新的软中断被触发,则重新启动循环,直到以下条件满足,才退出:
没有新的软中断等待执行;
循环已经达到最大的循环次数MAX_SOFTIRQ_RESTART,目前的设定值时10次;
如果经过MAX_SOFTIRQ_RESTART次循环后还未处理完,则激活守护进程,处理剩下的软中断;
推出前恢复软中断;
3.2 &在ksoftirqd进程中执行
& & & & 从前面几节的讨论我们可以看出,软中断也可能由ksoftirqd守护进程执行,这要发生在以下两种情况下:
在irq_exit中执行软中断,但是在经过MAX_SOFTIRQ_RESTART次循环后,软中断还未处理完,这种情况虽然极少发生,但毕竟有可能;
内核的其它代码主动调用raise_softirq,而这时正好不是在中断上下文中,守护进程将被唤醒;
守护进程最终也会调用__do_softirq执行软中断的回调,具体的代码位于run_ksoftirqd函数中,内核会关闭抢占的情况下执行__do_softirq,具体的过程这里不做讨论。
4. &tasklet
& & & &因为内核已经定义好了10种软中断类型,并且不建议我们自行添加额外的软中断,所以对软中断的实现方式,我们主要是做一个简单的了解,对于驱动程序的开发者来说,无需实现自己的软中断。但是,对于某些情况下,我们不希望一些操作直接在中断的handler中执行,但是又希望在稍后的时间里得到快速地处理,这就需要使用tasklet机制。 tasklet是建立在软中断上的一种延迟执行机制,它的实现基于TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断类型。
4.1 &tasklet_struct & & & &
在软中断的初始化函数softirq_init的最后,内核注册了TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断:
void&__init&softirq_init(void)&&
&&&&&&&&......&&
&&&&open_softirq(TASKLET_SOFTIRQ,&tasklet_action);&&
&&&&open_softirq(HI_SOFTIRQ,&tasklet_hi_action);&&
& & & & 内核用一个tasklet_struct来表示一个tasklet,它的定义如下:
struct&tasklet_struct&&
&&&&struct&tasklet_struct&*&&
&&&&unsigned&long&&&
&&&&atomic_t&&&
&&&&void&(*func)(unsigned&long);&&
&&&&unsigned&long&&&
next用于把同一个cpu的tasklet链接成一个链表,state用于表示该tasklet的当前状态,目前只是用了最低的两个bit,分别用于表示已经准备被调度执行和已经在另一个cpu上执行:
&&&&TASKLET_STATE_SCHED,&&&&
&&&&TASKLET_STATE_RUN&&&
原子变量count用于tasklet对tasklet_disable和tasklet_enable的计数,count为0时表示允许tasklet执行,否则不允许执行,每次tasklet_disable时,该值加1,tasklet_enable时该值减1。func是tasklet被执行时的回调函数指针,data则用作回调函数func的参数。
4.2 &初始化一个tasklet
有两种办法初始化一个tasklet,第一种是静态初始化,使用以下两个宏,这两个宏定义一个tasklet_struct结构,并用相应的参数对结构中的字段进行初始化:
DECLARE_TASKLET(name, func, data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于0。
DECLARE_TASKLET_DISABLED(name, func, data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于1。
第二个是动态初始化方法:先定义一个tasklet_struct,然后用tasklet_init函数进行初始化,该方法默认tasklet处于enable状态:
struct&tasklet_struct&tasklet_&&
tasklet_init(&tasklet_xxx,&func,&data);&&
4.3 &tasklet的使用方法
使能和禁止tasklet,使用以下函数:
tasklet_disable() &通过给count字段加1来禁止一个tasklet,如果tasklet正在运行中,则等待运行完毕才返回(通过TASKLET_STATE_RUN标志)。
tasklet_disable_nosync() &tasklet_disable的异步版本,它不会等待tasklet运行完毕。
tasklet_enable() &使能tasklet,只是简单地给count字段减1。
调度tasklet的执行,使用以下函数:
tasklet_schedule(struct tasklet_struct *t) &如果TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等待执行的tasklet链表上,接着发出TASKLET_SOFTIRQ软件中断请求。
tasklet_hi_schedule(struct tasklet_struct *t) &效果同上,区别是它发出的是HI_SOFTIRQ软件中断请求。
销毁tasklet,使用以下函数:
tasklet_kill(struct tasklet_struct *t) &如果tasklet处于TASKLET_STATE_SCHED状态,或者tasklet正在执行,则会等待tasklet执行完毕,然后清除TASKLET_STATE_SCHED状态。
4.4 &tasklet的内部执行机制
内核为每个cpu用定义了一个tasklet_head结构,用于管理每个cpu上的tasklet的调度和执行:
struct&tasklet_head&&
&&&&struct&tasklet_struct&*&&
&&&&struct&tasklet_struct&**&&
static&DEFINE_PER_CPU(struct&tasklet_head,&tasklet_vec);&&
static&DEFINE_PER_CPU(struct&tasklet_head,&tasklet_hi_vec);&&
回到4.1节,我们知道,tasklet是利用TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断来实现的,两个软中断只是有优先级的差别,所以我们只讨论TASKLET_SOFTIRQ的实现,TASKLET_SOFTIRQ的中断回调函数是tasklet_action,我们看看它的代码:
static&void&tasklet_action(struct&softirq_action&*a)&&
&&&&struct&tasklet_struct&*&&
&&&&local_irq_disable();&&
&&&&list&=&__this_cpu_read(tasklet_vec.head);&&
&&&&__this_cpu_write(tasklet_vec.head,&NULL);&&
&&&&__this_cpu_write(tasklet_vec.tail,&&__get_cpu_var(tasklet_vec).head);&&
&&&&local_irq_enable();&&
&&&&while&(list)&{&&
&&&&&&&&struct&tasklet_struct&*t&=&&&
&&&&&&&&list&=&list-&&&
&&&&&&&&if&(tasklet_trylock(t))&{&&
&&&&&&&&&&&&if&(!atomic_read(&t-&count))&{&&
&&&&&&&&&&&&&&&&if&(!test_and_clear_bit(TASKLET_STATE_SCHED,&&t-&state))&&
&&&&&&&&&&&&&&&&&&&&BUG();&&
&&&&&&&&&&&&&&&&t-&func(t-&data);&&
&&&&&&&&&&&&&&&&tasklet_unlock(t);&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&tasklet_unlock(t);&&
&&&&&&&&}&&
&&&&&&&&local_irq_disable();&&
&&&&&&&&t-&next&=&NULL;&&
&&&&&&&&*__this_cpu_read(tasklet_vec.tail)&=&t;&&
&&&&&&&&__this_cpu_write(tasklet_vec.tail,&&(t-&next));&&
&&&&&&&&__raise_softirq_irqoff(TASKLET_SOFTIRQ);&&
&&&&&&&&local_irq_enable();&&
解析如下:
关闭本地中断的前提下,移出当前cpu的待处理tasklet链表到一个临时链表后,清除当前cpu的tasklet链表,之所以这样处理,是为了处理当前tasklet链表的时候,允许新的tasklet被调度进待处理链表中。
遍历临时链表,用tasklet_trylock判断当前tasklet是否已经在其他cpu上运行,而且tasklet没有被禁止:
如果没有运行,也没有禁止,则清除TASKLET_STATE_SCHED状态位,执行tasklet的回调函数。
如果已经在运行,或者被禁止,则把该tasklet重新添加会当前cpu的待处理tasklet链表上,然后触发TASKLET_SOFTIRQ软中断,等待下一次软中断时再次执行。
分析到这了我有个疑问,看了上面的代码,如果一个tasklet被tasklet_schedule后,在没有被执行前被tasklet_disable了,岂不是会无穷无尽地引发TASKLET_SOFTIRQ软中断?
通过以上的分析,我们需要注意的是,tasklet有以下几个特征:
同一个tasklet只能同时在一个cpu上执行,但不同的tasklet可以同时在不同的cpu上执行;
一旦tasklet_schedule被调用,内核会保证tasklet一定会在某个cpu上执行一次;
如果tasklet_schedule被调用时,tasklet不是出于正在执行状态,则它只会执行一次;
如果tasklet_schedule被调用时,tasklet已经正在执行,则它会在稍后被调度再次被执行;
两个tasklet之间如果有资源冲突,应该要用自旋锁进行同步保护;
阅读(...) 评论()}

我要回帖

更多关于 softirq tasklet 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信