苏州网站建设制作网站快速优化排名软件
2026/4/18 3:54:21 网站建设 项目流程
苏州网站建设制作,网站快速优化排名软件,湖南旅游十大必去景区,井研移动网站建设文章目录引言上半部和下半部软中断和tasklet软中断tasklet总结引言 我想先用一种不同于其他博客的方式来引入本篇文章的核心:软中断和tasklet 我们先来看下面这个代码#xff1a; 以上是我刚踏足嵌入式领域时#xff0c;接触到的一份代码。那时是从单片机开始入门的#…文章目录引言上半部和下半部软中断和tasklet软中断tasklet总结引言我想先用一种不同于其他博客的方式来引入本篇文章的核心:软中断和tasklet我们先来看下面这个代码以上是我刚踏足嵌入式领域时接触到的一份代码。那时是从单片机开始入门的上面的代码是我参加智能车竞赛时所用的单片机程序。可能看到这个单片机程序大家觉得我在扯淡其实我也有觉得咱们不是在说linux内核里的中断吗怎么扯到单片机上去了。其实当时我在模仿别人写这个代码时也不知道为什么这样写。几年后的我现在逐步理解了linux内核里的下半部机制后开始恍然大悟。那上面的程序和本文的主题到底有什么关系呢下面我就先来简单描述一下上面这个程序实现的思路。这个程序的目的是读取GPS发来的串口数据并完成数据解析得到经纬度航向角等诸多信息。好的假设我们现在要去从0开始写这个程序我们应该怎么做很简单先初始化一个串口硬件上接好线然后接收到数据之后就会进入串口中断服务函数UART3_IRQHandler在串口中断服务函数里我们读取串口数据然后对数据进行解析就得到了经纬度等信息。这个思路很简单但仔细想想这么做会带来什么问题解析数据是一个非常耗时的工作如果中断处理函数一直没有完成数据解析的工作此时GPS又发来了一次数据即又触发了一次中断这会导致什么问题呢。结果就是新的中断大概率会被丢弃这一次的GPS数据也就没法更新了。在更高频率的数据传输场景下比如网口就会丢失大量数据。因此聪明的你想到了一个解决方案正如上面的程序写的一样。在中断处理函数里只做数据接收将接收到的数据拷贝到缓冲区然后将标志位置1在主函数的循环里检查标志位是否为1如果为1则说明刚刚中断处理函数已经把数据拷贝到了缓冲区然后在这里调用gps_data_parse函数在里面对缓冲区的数据进行解析。如果你理解了上面的步骤那么恭喜你你已经理解了linux内核里的上半部和下半部机制。上半部和下半部上半部其实就是中断处理函数在上面的单片机程序中如此在linux内核里同样如此。 而下半部就好比上面的程序中在主函数循环里执行数据解析的那一部分。言归正传我们开始正式学习linux内核的中断下半部机制。在linux内核中中断处理函数必须遵循越快越好的原则并且不能睡眠。本文的核心是中断处理函数必须要快至于为什么有如下几点原因1.中断是打断进程代码异步执行的如果中断函数处理的很慢有可能会影响主程序的正常执行。2.linux内核中中断函数处理时是在本地中断关闭的状态下执行的也就是此时所处CPU无法响应其他中断。如果中断函数执行时间太长那么此期间触发的中断就都不会得到处理导致丢数据。因此linux内核为了让中断处理的越快越好引入了下半部机制。在中断处理函数里只做类似接收数据的一些简单工作此为上半部接着一般是在硬件中断退出时进行下半部处理在下半部中去做数据解析之类的比较复杂、耗时的工作。而下半部的特点就是它在执行前会使能中断即可以响应中断。这样一来在做很耗时的数据解析工作时也可以响应新的中断这就保证了不会出现丢数据的现象。上半部我们已经知道了就是中断处理函数。下半部的实现主要包括软中断、tasklet和工作队列。我们现在深入解析软中断和tasklet其中tasklet是基于软中断实现的所以放在一起讲比较合适。软中断和tasklet软中断我们先从软中断说起因为tasklet是基于软中断实现的所以要先理解软中断再来看tasklet的实现。本篇文章更侧重于内核源码中执行的流程而不对相关定义进行详细的说明这些书上网络上都有很多可以自行学习。但我们还是要先简单了解一下软中断在内核中的定义include/linux/interrupt.hstructsoftirq_action{void(*action)(structsoftirq_action*);};softirq_action就是表示软中断的结构体内部定义了一个void action函数这个就是软中断处理函数。那么思路就很清晰了上半部下半部处理流程就是硬件中断触发-中断处理函数执行-软中断触发-软中断处理函数执行。好了现在我们再来看问题硬件中断不用说是外部触发的比如上升沿信号下降沿信号当然这里也可以是定时器中断。然后就是到中断处理函数。随后就到了下半部软中断触发执行软中断处理函数问题在于软中断怎么触发在哪里触发软中断处理函数在哪里执行一个一个来说明。1.软中断触发软中断可以用raise_softirq和raise_softirq_irqoff这两个函数来触发定义如下kernel/softirq.cvoidraise_softirq(unsignedintnr){unsignedlongflags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);}inlinevoidraise_softirq_irqoff(unsignedintnr){__raise_softirq_irqoff(nr);if(!in_interrupt())wakeup_softirqd();}我们可以看到raise_softirq的内部调用的raise_softirq_irqoff不同的是raise_softirq多调用了一个local_irq_save这个函数是用来禁止并保存中断标志的也就是说raise_softirq会主动禁止本地中断而raise_softirq_irqoff不会主动禁止本地中断。这个是什么意思我们在后面来说明现在只要知道他们两个有这个区别就可以了。接着来看__raise_softirq_irqoff函数#definelocal_softirq_pending()\__IRQ_STAT(smp_processor_id(),__softirq_pending)#ifndef__ARCH_SET_SOFTIRQ_PENDING#defineset_softirq_pending(x)(local_softirq_pending()(x))#defineor_softirq_pending(x)(local_softirq_pending()|(x))#endifvoid__raise_softirq_irqoff(unsignedintnr){trace_softirq_raise(nr);or_softirq_pending(1ULnr);}这个函数是将__softirq_pending这个标志的第nr个比特位置1。nr也可看作是软中断的序号。在这里就要说说软中断的定义了我觉得在这里提是最合适。软中断是静态定义的相当于定义了一个全局变量。(kernel/softirq.c)staticstructsoftirq_actionsoftirq_vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;这里定义了一个软中断的结构体数据定义了NR_SOFTIRQS1个软中断。NR_SOFTIRQS被定义在这里enum{HI_SOFTIRQ0,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};这里枚举了全部的软中断类型TASKLET_SOFTIRQ就是表示tasklet的软中断。所以现在可以猜到上面触发软中断函数里的参数nr是什么意思了其实就是代表触发哪一个类型的软中断。现在问题又来了说是触发软中断但是最终只是去置位了__softirq_pending的一个比特位虽然我们现在知道了这个比特位正是对应了要触发的软中断那这也只是操作了这么一个变量软中断处理函数也没执行呀。不着急我们接着继续看。2.软中断在哪里触发现在我们知道了软中断怎么触发了那他在哪里触发呢一般来说我们会在中断处理函数结束之前去触发软中断。比如staticirqreturn_tkey0_handler(intirq,void*dev_id)// 中断处理函数{// 执行中断处理函数内容// ......// 执行中断处理函数内容raise_softirq_irqoff(nr);// 触发软中断returnIRQ_RETVAL(IRQ_HANDLED);}为什么在这里触发接下来会给出答案。3.软中断处理函数在哪里执行我们先来看一下中断处理函数在哪里被执行kernel/irq/irqdesc.cint__handle_domain_irq(structirq_domain*domain,unsignedinthwirq,bool lookup,structpt_regs*regs){structpt_regs*old_regsset_irq_regs(regs);unsignedintirqhwirq;intret0;irq_enter();#ifdefCONFIG_IRQ_DOMAINif(lookup)irqirq_find_mapping(domain,hwirq);#endif/* * Some hardware gives randomly wrong interrupts. Rather * than crashing, do something sensible. */if(unlikely(!irq||irqnr_irqs)){ack_bad_irq(irq);ret-EINVAL;}else{generic_handle_irq(irq);}irq_exit();set_irq_regs(old_regs);returnret;}irq_enter()从这里开始进入中断上下文。从generic_handle_irq函数进去一层一层嵌套会跳到handle_irq_event_percpu这个函数该函数内部执行action-handler这个就是执行了中断处理函数。这里我们就不一步一步看代码了毕竟主题是软中断和tasklet。好那么现在我们知道了在generic_handle_irq这里中断处理函数会被执行完毕中断处理函数里我们触发了软中断__softirq_pending被对应的软中断序号比特位被置1。现在我们进入irq_exit函数kernel/softirq.c这个是中断退出函数。前面我们说过一般是在硬件中断退出时执行下半部。voidirq_exit(void){#ifndef__ARCH_IRQ_EXIT_IRQS_DISABLEDlocal_irq_disable();#elseWARN_ON_ONCE(!irqs_disabled());#endifaccount_irq_exit_time(current);preempt_count_sub(HARDIRQ_OFFSET);if(!in_interrupt()local_softirq_pending())invoke_softirq();tick_irq_exit();rcu_irq_exit();trace_hardirq_exit();/* must be last! */}staticinlinevoidinvoke_softirq(void){if(!force_irqthreads){#ifdefCONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK/* * We can safely execute softirq on the current stack if * it is the irq stack, because it should be near empty * at this stage. */__do_softirq();#else/* * Otherwise, irq_exit() is called on the task stack that can * be potentially deep already. So call softirq in its own stack * to prevent from any overrun. */do_softirq_own_stack();#endif}else{wakeup_softirqd();}}irq_exit函数里执行invoke_softirqinvoke_softirq会执行__do_softirq。好了到这里其实已经接近最后的答案了__do_softirq函数里就是真正执行软中断的地方。asmlinkage __visiblevoid__do_softirq(void){unsignedlongendjiffiesMAX_SOFTIRQ_TIME;unsignedlongold_flagscurrent-flags;intmax_restartMAX_SOFTIRQ_RESTART;structsoftirq_action*h;bool in_hardirq;__u32 pending;intsoftirq_bit;/* * Mask out PF_MEMALLOC s current task context is borrowed for the * softirq. A softirq handled such as network RX might set PF_MEMALLOC * again if the socket is related to swap */current-flags~PF_MEMALLOC;pendinglocal_softirq_pending();account_irq_enter_time(current);__local_bh_disable_ip(_RET_IP_,SOFTIRQ_OFFSET);in_hardirqlockdep_softirq_start();restart:/* Reset the pending bitmask before enabling irqs */set_softirq_pending(0);local_irq_enable();hsoftirq_vec;while((softirq_bitffs(pending))){unsignedintvec_nr;intprev_count;hsoftirq_bit-1;vec_nrh-softirq_vec;prev_countpreempt_count();kstat_incr_softirqs_this_cpu(vec_nr);trace_softirq_entry(vec_nr);h-action(h);trace_softirq_exit(vec_nr);if(unlikely(prev_count!preempt_count())){pr_err(huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n,vec_nr,softirq_to_name[vec_nr],h-action,prev_count,preempt_count());preempt_count_set(prev_count);}h;pendingsoftirq_bit;}rcu_bh_qs();local_irq_disable();pendinglocal_softirq_pending();if(pending){if(time_before(jiffies,end)!need_resched()--max_restart)gotorestart;wakeup_softirqd();}lockdep_softirq_end(in_hardirq);account_irq_exit_time(current);__local_bh_enable(SOFTIRQ_OFFSET);WARN_ON_ONCE(in_interrupt());tsk_restore_flags(current,old_flags,PF_MEMALLOC);}重点来看一下这个函数。pending local_softirq_pending();实际上就是读取我们前面置位过的__softirq_pending#definelocal_softirq_pending()\__IRQ_STAT(smp_processor_id(),__softirq_pending)local_irq_enable();在软中断函数执行之前使能本地中断这呼应了前面我们所说的软中断的一个重要特点可以响应中断。h softirq_vec;让指针指向所定义的软中断向量softirq_vec的第一项。接下来就是这个while循环省略部分代码如下while((softirq_bitffs(pending))){hsoftirq_bit-1;h-action(h);// 执行软中断处理函数pendingsoftirq_bit;}ffs函数的功能是查找整数中最低位的那个1在第几位。这里就是循环遍历__softirq_pending中所有为1的位在循环里去执行这些被置位为1的软中断的处理函数action。到这里我们所有的问题都被解答了软中断的处理流程也基本清楚了接下来我们看tasklet的实现。最后用一张图来总结一下整体的函数调用流程加强理解tasklet如果理解了软中断再来看tasklet就很轻松了。同样先来看tasklet的定义structtasklet_struct{structtasklet_struct*next;unsignedlongstate;atomic_tcount;void(*func)(unsignedlong);unsignedlongdata;};可以看出其中的func就是tasklet的处理函数。tasklet在使用前需要初始化使用tasklet_init函数指定处理函数传入变量等参数。话不多说我们直接来看调用流程。这里的下半部处理函数就是tasklet的处理函数相同的思路硬件中断触发-中断处理函数执行-tasklet调度-tasklet处理函数执行。同样的问题tasklet如何调度在哪里调度处理函数在哪里执行首先tasklet在哪里调度和软中断触发是一样的一般就是在中断服务函数的最后调度taskletstaticirqreturn_tkey0_handler(intirq,void*dev_id)// 中断处理函数{// 执行中断处理函数内容// ......// 执行中断处理函数内容tasklet_schedule(tasklet);// 调度taskletreturnIRQ_RETVAL(IRQ_HANDLED);}很明显tasklet是使用tasklet_schedule函数进行调度include/linux/interrupt.hstaticinlinevoidtasklet_schedule(structtasklet_struct*t){if(!test_and_set_bit(TASKLET_STATE_SCHED,t-state))__tasklet_schedule(t);}其实还有一个tasklet_hi_schedule函数也是进行调度的只不过它是调度的更高优先级的tasklet这里我们只看tasklet_schedule。tasklet_schedule里面执行了__tasklet_schedule函数kernel/softirq.cvoid__tasklet_schedule(structtasklet_struct*t){unsignedlongflags;local_irq_save(flags);t-nextNULL;*__this_cpu_read(tasklet_vec.tail)t;__this_cpu_write(tasklet_vec.tail,(t-next));raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_restore(flags);}看这里我们发现了什么它里面执行了raise_softirq_irqoff这不就是触发软中断嘛。接下来的步骤就不用我说了最终会执行__do_softirq去执行软中断处理函数。到这里我们就又蒙了tasklet的处理函数怎么被执行的呢我们看下面的定义kernel/softirq.cvoid__initsoftirq_init(void){intcpu;for_each_possible_cpu(cpu){per_cpu(tasklet_vec,cpu).tailper_cpu(tasklet_vec,cpu).head;per_cpu(tasklet_hi_vec,cpu).tailper_cpu(tasklet_hi_vec,cpu).head;}open_softirq(TASKLET_SOFTIRQ,tasklet_action);open_softirq(HI_SOFTIRQ,tasklet_hi_action);}系统初始化时会标记tasklet的软中断TASKLET_SOFTIRQ的处理函数为tasklet_actiontasklet_action就是tasklet的中断处理函数。同样的tasklet_hi_action是更高优先级的tasklet的处理函数。而在前面__tasklet_schedule里执行了raise_softirq_irqoff(TASKLET_SOFTIRQ)触发了TASKLET_SOFTIRQ这个软中断那么接下来在__do_softirq里执行的软中断处理函数就是tasklet_action我们来看tasklet_action的代码kernel/softirq.cstaticvoidtasklet_action(structsoftirq_action*a){structtasklet_struct*list;local_irq_disable();list__this_cpu_read(tasklet_vec.head);__this_cpu_write(tasklet_vec.head,NULL);__this_cpu_write(tasklet_vec.tail,this_cpu_ptr(tasklet_vec.head));local_irq_enable();while(list){structtasklet_struct*tlist;listlist-next;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);continue;}tasklet_unlock(t);}local_irq_disable();t-nextNULL;*__this_cpu_read(tasklet_vec.tail)t;__this_cpu_write(tasklet_vec.tail,(t-next));__raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_enable();}}我们看到tasklet_action执行了t-func(t-data);这就是tasklet的处理函数。可能有点乱用一张图来梳理整个的调用流程。总结到这里就是本篇文章的全部内容这篇文章主要是记录自己学习linux内核中断里的下半部机制-软中断和tasklet的所感所想可能有很多不对的地方希望能够得到大家的批评与指正所有内容均参考自以下两本书《Linux内核设计与实现》 和 《奔跑吧Liunx内核-基于Linux 4.x内核源代码问题分析》个人认为这两本书结合起来看会非常收益前者了解大体的概念后者从源码出发一步步深入。希望能给大家带来帮助

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询