Linux——中断下半部(深入代码理解)

这篇博客深入探讨Linux中断下半部,包括Softirq(软中断)、tasklet(小任务)和工作队列(work queues)的机制。软中断是一组静态接口,可在所有处理器上并行执行。tasklet利用软中断实现,是常用的下半部机制。工作队列则用于将任务推后到进程上下文执行,支持休眠。三种机制各有优缺点,适用于不同的场景。
摘要由CSDN通过智能技术生成

中断下半部分

中断分为上半部和下半部分,操作系统把一些紧急的任务,需要硬件参与的任务放在上半部去完成。而把一些不是很急切需要完成的任务放在下半部分来执行。

中断下半部的三种机制

Softirq(软中断)

软中断是一组静态定义的下半部接口,可以在所有处理器上同时执行(即使两个类型相同也可以)。

软中断的结构体

struct softirq_action { 

void (*action)(struct softirq_action*); 

};

softirq_action这个数组有32项,每个被注册的软中断都会占据其中的一项。

static struct softirq_action softirq_vec[NR_SOFTIRQS]
软中断流程

将软中断注册进softirq_vec数组中

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

对TASKLET_SOFTIRQ和HI_SOFTIRQ两种软中断进行初始化

void __init softirq_init(void)
{
   
    int cpu;

    for_each_possible_cpu(cpu) {
   
        per_cpu(tasklet_vec, cpu).tail =
            &per_cpu(tasklet_vec, cpu).head;
        per_cpu(tasklet_hi_vec, cpu).tail =
            &per_cpu(tasklet_hi_vec, cpu).head;
    }

    /* 开启常规tasklet */
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    /* 开启高优先级tasklet */
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

触发本地cpu上的软中断

void raise_softirq(unsigned int nr)
{
   
	unsigned long flags;

	local_irq_save(flags);//关中断
	raise_softirq_irqoff(nr);
	local_irq_restore(flags);//恢复
}

关中断之后调用raise_softirq_irqoff,通过in_interrupt判断现在是否在中断上下文中,或者软中断是否被禁止,如果都不成立,我们必须要调用wakeup_softirqd函数用来唤醒本CPU上的softirqd这个内核线程。

inline void raise_softirq_irqoff(unsigned int nr)
{
   
	__raise_softirq_irqoff(nr);
	if (!in_interrupt())
		wakeup_softirqd();
}

当触发软中断之后,我们就需要对软中断进行处理。

asmlinkage __visible void do_softirq(void)
{
   
	__u32 pending;
	unsigned long flags;

	if (in_interrupt())//在中断处理中,就返回
		return;

	local_irq_save(flags);//关中断

	pending = local_softirq_pending();//取得当前已注册软中断的位图,当第n位为1,则表示需要处理

	if (pending && !ksoftirqd_running())//如果ksoftirqd这个内核线程启动了,软中断就不需要现在就处理了,这里显然需要未启动
		do_softirq_own_stack();

	local_irq_restore(flags);//恢复
}

do_softirq_own_stack调用__do_softirq

static inline void do_softirq_own_stack(void)
{
   
	__do_softirq();
}

__do_softirq

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
   
	unsigned long end = jiffies + MAX_SOFTIRQ_TIME;//为了防止软中断执行时间太长,设置了一个软中断结束时间
	unsigned long old_flags = current->flags;//保存当前进程的标志 
	int max_restart = MAX_SOFTIRQ_RESTART;//软中断循环执行次数
	struct softirq_action *h;
	bool in_hardirq;
	__u32 pending;
	int softirq_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;//PF_MEMALLOC内存分配

	pending = local_softirq_pending();//获取此CPU的__softirq_pending变量值
	account_irq_enter_time(current);//用于统计进程被软中断使用时间

	__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);//增加preempt_count软中断计数器
	in_hardirq = lockdep_softirq_start();//避免死锁

    //循环的入口
restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);//把当前cpu上的__softirq_pending清零,保证了新的软中断会在下次循环中执行 

	local_irq_enable();//中断开启

	h = softirq_vec;//h指向软中断数组头

	while ((softirq_bit = ffs(pending))) {
   //每次获取pending第一个1的位置的软中断
		unsigned int vec_nr;
		int prev_count;

		h += softirq_bit - 1;//获取此软中断描述符地址

		vec_nr = h - softirq_vec;//减去软中断描述符数组首地址,获得软中断号
		prev_count = preempt_count();//获取preempt_count的值

		kstat_incr_softirqs_this_cpu(vec_nr);//增加统计该软中断发生次数

		trace_softirq_entry(vec_nr);
		h->action(h);//执行该软中断的action操作
		trace_softirq_exit(vec_nr);
		if (unlikely(prev_count != preempt_count())) {
   
            /*
            之前保存的preempt_count并不等于当前的preempt_count的情况处理,也是简单的把之前的复制到当
            前的preempt_count上,这样做是防止最后软中断计数不为0导致系统不能够执行调度 
            */
			pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
			       vec_nr, softirq_to_name[vec_nr], h
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值