【Linux基础系列之】中断系统(2)-下半部

本文深入探讨Linux中断系统的下半部分,包括softirq、tasklet和workqueue机制。介绍了softirq的并发执行与同步、tasklet的动态分配与串行执行特性,以及workqueue在进程上下文中的休眠能力,阐述了它们如何提高系统性能。
摘要由CSDN通过智能技术生成

前面一篇文章分析了中断系统的框架,这篇文章主要讲中断的下半部份的机制;


(一)概述

linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情。在执行bottom half的时候,是开中断的。系统可以把实时性的操作或者硬件的一些ACK放在上半部分来做,其他数据处理,上报操作等不是太紧要的都可以放到下半部分来做,这样就大大减少了系统关中断的时间,提高了系统的性能;

常见处理下半部份的机制有softirq、tasklet、workqueue;workqueue运行在process context,而softirq和tasklet运行在interrupt context,所以workqueue和softirq、tasklet是有本质区别的;


(二)softirq

(1)softirq和tasklet的区别

soft和irq都用于中断的下半部处理,softirq更倾向于性能,而tasklet更倾向于易用性。但是tasklet是基于softirq机制来实现的;

同一个中断触发然后调用下半部的处理函数,并启用softirq机制,这样相同类型的softirq有可能在不同的CPU上并发执行,所以要考虑并发引入同步机制,当中断处理的softirq同时在Processor A和B上运行的时候,中断又来了。这时候,中断分发给processor A,这时候,processor A上的handler仍然会raise softirq,但是并不会调度该softirq。也就是说,softirq在一个CPU上是串行执行的。这种情况下,系统性能瓶颈是CPU资源,需要增加更多的CPU来解决该问题。

同样相同中断触发的tasklet在processor A上被调度执行,那么它永远也不会同时在processor B上执行,也就是说,tasklet是串行执行的(注:不同的tasklet还是会并发的),不需要考虑重入的问题。在检查tasklet的状态的时候,如果发现该tasklet在其他processor上已经正在运行,那么该tasklet不会被处理,一直等到在processor A上的tasklet处理完,在processor B上的这个tasklet才能被执行;

(2)irq context

一个task的thread info数据结构定义如下:

struct thread_info {  
    …… 
    int            preempt_count;    /* 0 => preemptable, <0 => bug */ 
    …… 
};

preempt_count这个成员被用来判断当前进程是否可以被抢占。如果preempt_count不等于0(可能是代码调用preempt_disable显式的禁止了抢占,也可能是处于中断上下文等),说明当前不能进行抢占,如果preempt_count等于0,说明已经具备了抢占的条件(当然具体是否要抢占当前进程还是要看看thread info中的flag成员是否设定了_TIF_NEED_RESCHED这个标记,可能是当前的进程的时间片用完了,也可能是由于中断唤醒了优先级更高的进程);

preempt字段格式如下:

这里写图片描述

IRQ context其实就是hard irq context,也就是说明当前正在执行中断handler(top half),只要preempt_count中的hardirq count大于0(=1是没有中断嵌套,如果大于1,说明有中断嵌套),那么就是IRQ context。

(3)softirq机制

a. 注册softirq,注册一个软中断,即在软中断向量数据中,增加软中断number为nr的处理函数:

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

softirq_vec为软中断向量表,针对不同的softirq number有不同的entry:

58 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

softirq number:

387 enum
388 {
389     HI_SOFTIRQ=0,//高优先级的tasklet;
390     TIMER_SOFTIRQ,//基于系统tick的software timer
391     NET_TX_SOFTIRQ,//网卡数据发送;
392     NET_RX_SOFTIRQ,//网卡数据接收;
393     BLOCK_SOFTIRQ,//block device
394     BLOCK_IOPOLL_SOFTIRQ,//block device
395     TASKLET_SOFTIRQ,//用于普通的tasklet;
396     SCHED_SOFTIRQ,
397     HRTIMER_SOFTIRQ,
398     RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
399 
400     NR_SOFTIRQS
401 };

b. 触发softirq :如果要启动某个软中断,则需要置位irq_stat[cpu].__softirq_pending中的相应位,而设置__softirq_pending的操作则由函数__raise_softirq来实现。_softirq_pending是per cpu的,因此不需要考虑多个CPU的并发,只要disable本地中断,就可以确保对__softirq_pending操作的原子性。

可以通过以下两种方法启动软中断:

1)在中断上下文中,通过调用函数raise_softirq,置位irq_stat[cpu].__softirq_pending中的相应软中断位;则会在中断结束后在函数irq_exit中调用invoke_softirq,实现软中断处理;

2)在非中断上下文中,通过调用raise_softirq_irqoff,置位irq_stat[cpu].__softirq_pending中的相应软中断位;并唤醒软中断守护进程,通过软中断守护进程实现软中断的处理;

//大多数情况下都是在中断handler被调用,是关cpu中断的,但是也有可能在其他上下文中调用,这个时候是打开cpu中断的,所以要用local_irq_save来保护softirq_pending的状态;
427 void raise_softirq(unsigned int nr)
428 {   
429     unsigned long flags;
430 
431     local_irq_save(flags);
432     raise_softirq_irqoff(nr);
433     local_irq_restore(flags);
434 }
410 inline void raise_softirq_irqoff(unsigned int nr)
411 {
412     __raise_softirq_irqoff(nr);//设定本CPU上的__softirq_pending的某个bit等于1,具体的bit是由soft irq number(nr参数)指定的。;
413 
423     if (!in_interrupt())
424         wakeup_softirqd();
    //如果在中断上下文,我们只要set __softirq_pending的某个bit就
    //OK了,在中断返回的时候自然会进行软中断的处理。但是,如果在
    //context上下文调用这个函数的时候,我们必须要调用
    //wakeup_softirqd函数用来唤醒本CPU上的softirqd这个内核线程。
425 }

c. 中断处理:softirq 的执行有两个时刻:在退出中断 irq_exit() 时或者在 softirqd 线程当中,在中断上下文的情况,通过退出中断的时候会调用softirq注册的处理函数,代码如下:

389 void irq_exit(void)
390 {
399     if (!in_interrupt() && local_softirq_pending())
        //确保没有在中断上下文,同时local_softirq_pending来判断是否触发了软中断执行;
400         invoke_softirq();
405 }

350 static inline void invoke_softirq(void)
351 {
352     if (!force_irqthreads) {
  //如果强制线程化,那么系统中所有的软中断都在sofirq的softirqd进程中被调度执行。
353 #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
359         __do_softirq();
360 #else
366         do_softirq_own_stack();
367 #endif
368     } else {
369         wakeup_softirqd();//内核专门开了一个softirq服务线程来处理软中断;
370     }
371 }
233 asmlinkage __visible void __do_softirq(void)
234 {
235     unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
236     unsigned long old_flags = current->flags;
237     int max_restart = MAX_SOFTIRQ_RESTART;
238     struct softirq_action *h;
239     bool in_hardirq;
240     __u32 pending;
241     int softirq_bit;
242 
248     current->flags &= ~PF_MEMALLOC;
249 
250     pending = local_softirq_pending();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值