进程的软中断通信_Linux内核浅析-软中断

前面谈到了硬中断,都是通过int指令发出,基本上都会关中断,更不可能存进程调度,如果ISR运行时间过长,则会造成明显的卡顿,所以规定中断中不能存在sleep、阻塞,也会尽量减少资源占用,但这还不够,linux将中断中的一部分逻辑推后执行,这就是软中断,软中断分多钟类型,执行时允许被中断,甚至有些软中断(work queue)可以允许被用户进程抢占。

软中断类型

软中断的实现难机制很多,比如bottom half(下半部)、task queue,不过这两者已经被废弃,下面三种实现机制是现在kernel还支持的:

软中断(softirq):内核2.3引入,是最基本、最优先的软中断处理形式,为了避免名字冲突,本文中将这种子类型的软中断叫softirq。

tasklet:其底层使用softirq机制实现,提供了一种用户方便使用的软中方式,为软中断提供了很好的扩展性。

work queue:前两种软中断执行时是禁止抢占的(softirq的ksoftirq除外),对于用户进程不友好。如果在softirq执行时间过长,会继续推后到work queue中执行,work queue执行处于进程上下文,其可被抢占,也可以被调度,如果软中断需要执行睡眠、阻塞,直接选择work queue。

软中断的数据结构

/* 用于描述一个软中断 */
struct softirq_action
{
    /* 软中断的处理函数 */        
    void    (*action)(struct softirq_action *);
};

/* 6个软中断描述符都保存在此数组 */
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

kernel2.6支持以下6个软中断,其优先级和在softirq_vec的下标相等。其中HI_SOFTIRQ和TASKLET_SOFTIRQ就是用于实现tasklet机制,其他的基本都是外设专用的软中断。

31ac2a11f2b38191a9a23bebe082e8ca.png

__softirq_pending:每个cpu的irq_cpustat_t中有__softirq_pending,是32位的位图,如果第n位为1,则代表对应类型的软中断待处理,通过local_softirq_pending()宏获取,下面软中断执行流程中会用到。

preemt_count:该字段在task_struct -> thread_info -> preemt_count,其每位表示的意义如下。这个字段在在宏in_interrupt宏中用到,用于避免软中断嵌套的硬中断(软中断时允许硬中断)中再调用软中断。如果preemt_couont软中断和硬中断计数器有一个不为0,in_interrupt则返回true,表示程序处于中断中。

56d1531285b390d057ee7a47acdc31eb.png

软中断的触发时机

1、irq_exit:在硬中断退出时,会检查local_softirq_pending和preemt_count,如果都符合条件,则执行软中断。

if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();

2、local_bh_enable:使用此函数开启软中断时,会检查local_softirq_pending,如果都符合条件,则执行软中断。调用链为local_bh_enable()->__local_bh_enable()->do_softirq()。

3、raise_softirq:主动唤起一个软中断,会首先设置__softirq_pending对应的软中断位为挂起,然后检查in_interrupt,如果不在中断中,则唤起ksoftirq线程执行软中断(ksoftirq是softirq的一种执行机制,在软中的运行流程中会提到)。

inline void raise_softirq_irqoff(unsigned int nr)
{
    __raise_softirq_irqoff(nr);
    if (!in_interrupt())
        wakeup_softirqd(); // 如果不处于中断上下文中,则尽快执行软中断处理。
}

软中断的运行流程

open_softirq:注册软中断,nr是softirq_vec的下标,action是软中断处理函数入口。

/* 开启软中断 */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}

open_softirq(TASKLET_SOFTIRQ, tasklet_action); // 开启tasklet软中断
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);  // 开启时钟相关软中断

__do_softirq:执行软中断,其调用链分别为irq_exit -> invoke_softirq -> __do_softirq和local_bh_enable()->__local_bh_enable()-> do_softirq -> __do_softirq,执行流程如下:

02ff2b12a8baab64982875d319faf246.png

ksoftirq:软中断处理进程,上图没有画完整的是在时间到期或者执行完10次后还有未执行的软中断时,会唤醒ksoftirq后台线程进行执行(ps:这不是work queue机制),其目的是为了处理剩余的软中断,避免cpu长时间不能被抢占的情况。ksoftirq就是一个普通进程,优先级是120,所以其是可以被抢占的。其处理函数是run_ksoftirqd()。所以单纯说softirq是否可以被抢占都不对,softirq在irq_exit中是不能被抢占的,但在ksoftirq中是可以被抢占的。

由上图可见HI_SOFTIRQ和TASKLET_SOFTIRQ是一个链条,这就是tasklet,其依赖于softirq机制实现,其保证了软中断机制的开放性和扩展性(由于其他软中断基本是专用类型的),使得用户可以自行丰富其所需要的软中断。

tasklet

softirq_init:软中断初始化,会将每个CPU的tasklet_vec和tasklet_hi_vec两个链表,分别代表HI_SOFTIRQ和TASKLET_SOFTIRQ。

struct tasklet_head {
    struct tasklet_struct *head;
    struct tasklet_struct **tail;
};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

tasklet_struct:tasklet链表中的元素,其中func就是执行该tasklet的方法。

struct tasklet_struct
{
    struct tasklet_struct *next;      /* 指向链表下一个tasklet */
    unsigned long state;              /* tasklet状态 TASKLET_STATE_SCHED表示处于链表中,TASKLET*/
                                      /* _STATE_RUN表示正在运行*/
    atomic_t count;                   /* 禁止计数器,调用tasklet_disable()会增加此数,tasklet*/
                                      /* enable()减少此数 */
    void (*func)(unsigned long);      /* 处理函数 */
    unsigned long data;               /* 处理函数使用的数据 */
};

tasklet_action:就是tasklet的处理函数,其会遍历tasklet_vec和tasklet_hi_vec链表,然后执行。

工作队列

感觉实现不太重要,请参加《深入Linux内核》182页。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值