- 软中断守护进程
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, ¶m);
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);
}
- 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;
}
- 下面以网络收发流程为参考,举例说明软中断的处理过程。
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软中断标志。