内核软中断处理流程

10 篇文章 0 订阅
  1. 软中断守护进程

static __init int spawn_ksoftirqd(void)
{
	void *cpu = (void *)(long)smp_processor_id();
	int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);//创建守护进程

	BUG_ON(err != NOTIFY_OK);
	cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
	register_cpu_notifier(&cpu_nfb);
	return 0;
}
early_initcall(spawn_ksoftirqd);
static int __cpuinit cpu_callback(struct notifier_block *nfb,
				  unsigned long action,
				  void *hcpu)
{
	int hotcpu = (unsigned long)hcpu;
	struct task_struct *p;

	switch (action) {
	case CPU_UP_PREPARE:
	case CPU_UP_PREPARE_FROZEN:
		p = kthread_create_on_node(run_ksoftirqd,
					   hcpu,
					   cpu_to_node(hotcpu),
					   "ksoftirqd/%d", hotcpu);
		if (IS_ERR(p)) {
			printk("ksoftirqd for %i failed\n", hotcpu);
			return notifier_from_errno(PTR_ERR(p));
		}
		kthread_bind(p, hotcpu);
  		per_cpu(ksoftirqd, hotcpu) = p;
 		break;
	case CPU_ONLINE:
	case CPU_ONLINE_FROZEN:
		wake_up_process(per_cpu(ksoftirqd, hotcpu));
		break;
#ifdef CONFIG_HOTPLUG_CPU
	case CPU_UP_CANCELED:
	case CPU_UP_CANCELED_FROZEN:
		if (!per_cpu(ksoftirqd, hotcpu))
			break;
		/* Unbind so it can run.  Fall thru. */
		kthread_bind(per_cpu(ksoftirqd, hotcpu),
			     cpumask_any(cpu_online_mask));
	case CPU_DEAD:
	case CPU_DEAD_FROZEN: {
		static const struct sched_param param = {
			.sched_priority = MAX_RT_PRIO-1
		};

		p = per_cpu(ksoftirqd, hotcpu);
		per_cpu(ksoftirqd, hotcpu) = NULL;
		sched_setscheduler_nocheck(p, SCHED_FIFO, &param);
		kthread_stop(p);
		takeover_tasklets(hotcpu);
		break;
	}
#endif /* CONFIG_HOTPLUG_CPU */
 	}
	return NOTIFY_OK;
}

//软中断守护进程

static int run_ksoftirqd(void * __bind_cpu)
{
	set_current_state(TASK_INTERRUPTIBLE);

	while (!kthread_should_stop()) {
		preempt_disable();
		if (!local_softirq_pending()) {
			schedule_preempt_disabled();
		}

		__set_current_state(TASK_RUNNING);

		while (local_softirq_pending()) {
			/* Preempt disable stops cpu going offline.
			   If already offline, we'll be on wrong CPU:
			   don't process */
			if (cpu_is_offline((long)__bind_cpu))
				goto wait_to_die;
			local_irq_disable();//禁止本地中断
			if (local_softirq_pending())
				__do_softirq();//执行__do_softirq处理软中断。
			local_irq_enable();//开启中断。
			sched_preempt_enable_no_resched();
			cond_resched();
			preempt_disable();
			rcu_note_context_switch((long)__bind_cpu);
		}
		preempt_enable();
		set_current_state(TASK_INTERRUPTIBLE);
	}
	__set_current_state(TASK_RUNNING);
	return 0;

wait_to_die:
	preempt_enable();
	/* Wait for kthread_stop */
	set_current_state(TASK_INTERRUPTIBLE);
	while (!kthread_should_stop()) {
		schedule();
		set_current_state(TASK_INTERRUPTIBLE);
	}
	__set_current_state(TASK_RUNNING);
	return 0;
}

2.大多数情况下,硬中断退出后会执行irq_exit

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();

#ifdef CONFIG_NO_HZ
	/* Make sure that timer wheel updates are propagated */
	if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
		tick_nohz_irq_exit();
#endif
	rcu_irq_exit();
	sched_preempt_enable_no_resched();
}

static inline void invoke_softirq(void)
{
	if (!force_irqthreads) {
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
		__do_softirq();//被定义了这个宏,会执行到这里,处理软中断。
#else
		do_softirq();//为什么还要再执行一次?
#endif
	} else {
		__local_bh_disable((unsigned long)__builtin_return_address(0),
				SOFTIRQ_OFFSET);
		wakeup_softirqd();
		__local_bh_enable(SOFTIRQ_OFFSET);
	}
}
具体什么时候执行irq_exit呢,下面以GIC中断处理函数为例说明一下:
asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	do {
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		irqnr = irqstat & ~0x1c00;

		if (likely(irqnr > 15 && irqnr < 1021)) {
			irqnr = irq_find_mapping(gic->domain, irqnr);
			handle_IRQ(irqnr, regs);//会执行irq_exit
			continue;
		}
		if (irqnr < 16) {
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
			handle_IPI(irqnr, regs);//会执行irq_exit.
#endif
			continue;
		}
		break;
	} while (1);
}

  1. do_softirq函数:
    无论是守护进程,还是硬中断退出执行的irq_exit,都会通过do_softirq函数来扫描CPU上已经被置为pending状态的软中断
#define MAX_SOFTIRQ_RESTART 10

asmlinkage void __do_softirq(void)
{
	struct softirq_action *h;
	__u32 pending;
	int max_restart = MAX_SOFTIRQ_RESTART;
	int cpu;

	pending = local_softirq_pending();
	account_system_vtime(current);

	__local_bh_disable((unsigned long)__builtin_return_address(0),
				SOFTIRQ_OFFSET);
	lockdep_softirq_enter();

	cpu = smp_processor_id();
restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);

	local_irq_enable();

	h = softirq_vec;

	do {
		if (pending & 1) {
			unsigned int vec_nr = h - softirq_vec;
			int prev_count = preempt_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())) {
				printk(KERN_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() = prev_count;
			}
			

			rcu_bh_qs(cpu);
		}
		h++;
		pending >>= 1;
	} while (pending);

	local_irq_disable();

	pending = local_softirq_pending();
	if (pending && --max_restart)
		goto restart;

	if (pending)
		wakeup_softirqd();

	lockdep_softirq_exit();

	account_system_vtime(current);
	__local_bh_enable(SOFTIRQ_OFFSET);
}

#ifndef __ARCH_HAS_DO_SOFTIRQ

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

	if (in_interrupt())
		return;

	local_irq_save(flags);

	pending = local_softirq_pending();

	if (pending)
		__do_softirq();

	local_irq_restore(flags);
}

#endif

4.由上面的函数可以知道,只有被设置为pending状态的软中断才会被处理。
看几个比较关键的变量和宏

//include/linux/irq_cpustat.h
#define CONFIG_NR_CPUS 2	//3535 是双核CPU。

#define NR_CPUS		CONFIG_NR_CPUS


rq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
#ifndef __ARCH_IRQ_STAT
extern irq_cpustat_t irq_stat[];		/* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member)	(irq_stat[cpu].member)
#endif

  /* arch independent irq_stat fields */
#define local_softirq_pending() \
	__IRQ_STAT(smp_processor_id(), __softirq_pending)

  /* arch dependent irq_stat fields */
  
#define nmi_count(cpu)		__IRQ_STAT((cpu), __nmi_count)	/* i386 */
//include/linux/interrup.h
#define set_softirq_pending(x) (local_softirq_pending() = (x))
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

就是通过上面的宏来设置irq_stat[cpu].softirq_pending的标志位的。

5.处理软中断的时候,执行的是什么函数
答:open_softirq

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}
  1. 下面以网络收发流程为参考,举例说明软中断的处理过程。
static int stmmac_dvr_probe(struct platform_device *pdev)
{
	netif_napi_add(dev, &priv->napi, stmmac_poll, 64);
		ret = request_irq(SYNOP_GMAC_IRQNUM, stmmac_interrupt,
			IRQF_SHARED, STMMAC_RESOURCE_NAME, pdev);
}

static irqreturn_t stmmac_interrupt(int irq, void *arg)
{
	struct net_device *dev;
	struct stmmac_priv *priv;
	uint32_t int_status = readl(stmmac_base_ioaddr + TNK_REG_INTR_STAT);
	stm_proc.interrupts++;
	/*  GMAC 0 */
	if (int_status & TNK_INTR_GROUP_GMAC0) {
		stm_proc.gmac[0].interrupts++;
		dev = stmmac_device_list[TNK_GMAC0_ID];
		if (!dev)
			pr_err("GMAC0 dev not yet initialised\n");
		else {
			priv = netdev_priv(dev);
			if (!priv)
				pr_err("GMAC0 priv not yet initialised\n");
			else {
				if (int_status & TNK_INTR_STAT_GMAC0_DATA)
					stmmac_dma_interrupt(priv);

				if ((int_status & TNK_INTR_STAT_GMAC0_CTRL)
				    && likely(priv->plat->has_gmac))
					priv->hw->mac->
					    host_irq_status((void __iomem *)
							    dev->base_addr);
			}
		}
	}

static void stmmac_dma_interrupt(struct stmmac_priv *priv)
{
int status;

status =
    priv->hw->dma->dma_interrupt(priv->dma_ioaddr, priv->dma_channel,
				 &priv->xstats);
if (likely(status == handle_tx_rx))
	_stmmac_schedule(priv);//
	...................

}
//_stmmac_schedule最终会执行__napi_schedule
void __napi_schedule(struct napi_struct *n)
{
unsigned long flags;

local_irq_save(flags);
____napi_schedule(&__get_cpu_var(softnet_data), n);
local_irq_restore(flags);

}

static inline void ____napi_schedule(struct softnet_data *sd,
struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);//这里设置软中断的softirq_pending的NET_RX_SOFTIRQ位。
}

void __raise_softirq_irqoff(unsigned int nr)
{
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}

在net/core/dev.c中定义:

static int __init net_dev_init(void)
{
	open_softirq(NET_TX_SOFTIRQ, net_tx_action);
	open_softirq(NET_RX_SOFTIRQ, net_rx_action);
}

以网络数据接收为例。
GMAC硬中断—>napi_schedule,设置NRT_RX_SOFTIRQ pending标准位---->执行net_rx_action---->执行netdev->napi_list中的poll函数.这里就是stmmac_poll—>netif_receive_skb—>__netif_receive_skb,在这里遍历ptype_all、ptype_base[],把数据发送给上层。如ip_recv、arp_recv、packet_rcv—>…->应用层。

7.定时器中断是什么时候执行的呢?
init_timer
setup_timer
add_timer
mod_timer

什么时候会执行TIMER_SOFTIRQ呢

在start_kernel中会调用
void __init init_timers(void)
{
	int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
				(void *)(long)smp_processor_id());

	init_timer_stats();

	BUG_ON(err != NOTIFY_OK);
	register_cpu_notifier(&timers_nb);
	open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}
static int __cpuinit timer_cpu_notify(struct notifier_block *self,
				unsigned long action, void *hcpu)
{
	long cpu = (long)hcpu;
	int err;

	switch(action) {
	case CPU_UP_PREPARE:
	case CPU_UP_PREPARE_FROZEN:
		err = init_timers_cpu(cpu);//这个跟具体的CPU有关系。
		if (err < 0)
			return notifier_from_errno(err);
		break;
		..........
}

static void run_timer_softirq(struct softirq_action *h)
{
	struct tvec_base *base = __this_cpu_read(tvec_bases);

	hrtimer_run_pending();

	if (time_after_eq(jiffies, base->timer_jiffies))
		__run_timers(base);
}

static void __init hi3535_timer_init(void)
{
	/* set the bus clock for all timer */
	writel(readl(IO_ADDRESS(SYS_CTRL_BASE)) |
		(1<<16) | (1<<18) | (1<<20) | (1<<22) |
		(1<<25) | (1<<27) | (1<<29) | (1<<31),
		IO_ADDRESS(SYS_CTRL_BASE));

#ifdef CONFIG_HAVE_ARM_LOCAL_TIMER
	twd_local_timer_register(&twd_localtimer);
#endif

#ifdef CONFIG_HAVE_SP804_LOCAL_TIMER
	hi3535_local_timer_init();
#endif
	sp804_clocksource_and_sched_clock_init((void *)TIMER(0)->addr,
		TIMER(0)->name);
	sp804_clockevents_init((void *)TIMER(1)->addr,
		TIMER(1)->irq.irq, TIMER(1)->name);
}

定时器中断也依赖于用中断。

 下面说明下常用的tasklet_schudle的执行过程:
 

void __init softirq_init(void)
{
int cpu;

for_each_possible_cpu(cpu) {
	int i;

	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;
	for (i = 0; i < NR_SOFTIRQS; i++)
		INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
}

register_hotcpu_notifier(&remote_softirq_cpu_notifier);

open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);

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

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

enum
{
HI_SOFTIRQ=0,
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

};


 上面的	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
的作用就是给softirq_vec的【TASKLET_SOFTIRQ】的action赋值为tasklet_action.

struct softirq_action
{
void (*action)(struct softirq_action *);
};

可见,当我们执行tasklet_schedule来设置TASKLETIRQ为pendding标志的时候,就会在软中断中执行tasklet_action,来遍历tasklet_vec表
如果tasklet_struct的TASKLET_STATE_SCHED设置了就会执行对应的func函数,这个func函数就是驱动中在tasklet_init的时候定义的func函数。
TASKLET_STATE_SCHED标志是什么时候设置的呢?看

static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;

local_irq_save(flags);
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_restore(flags);

}


tasklet_schedule的时候先是设置struct tasklet_struct的t->state | =TASKLET_STATE_SCHED。
然后设置TASKLET_SOFTIRQ软中断标志。




 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值