下半部目前包括软中断,tasklet,工作队列。
软中断:
- 编译器静态分配的;
- 不互相抢占;
- 只有中断处理程序可以抢占它;
- 相同类型软中断可以在不同的CPU上同时运行;
- 大部分软中断处理程序都通过采取单处理器数据或其他技巧来避免加锁。
tasklet:
建立在软中断之上;
可以动态生成;
root:/etc:# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 04:33 ? 00:00:00 init [5]
root 2 1 0 04:33 ? 00:00:00 [ksoftirqd/0] -----------------软中断,其包括分很多类型:
(enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ, --------- 定时器
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ, --------- 网络系统,对时间要求严格,所以直接使用软中断。
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERS
HRTIMER_SOFTIRQ,
#endif
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
};)
root 3 1 0 04:33 ? 00:00:00 [watchdog/0]
root 4 1 0 04:33 ? 00:00:00 [events/0] ------------- 工作队列
root 5 1 0 04:33 ? 00:00:00 [khelper]
root 6 1 0 04:33 ? 00:00:00 [kthread]
root 35 6 0 04:33 ? 00:00:00 [kblockd/0]
root 49 6 0 04:33 ? 00:00:00 [pdflush]
root 50 6 0 04:33 ? 00:00:00 [pdflush]
root 51 6 0 04:33 ? 00:00:00 [kswapd0]
root 52 6 0 04:33 ? 00:00:00 [aio/0]
root 169 1 0 04:33 ? 00:00:00 [mtdblockd]
root 192 6 0 04:33 ? 00:00:00 [kjournald]
root 216 6 0 04:33 ? 00:00:00 [kjournald]
root 219 6 0 04:33 ? 00:00:00 [kjournald]
asmlinkage void __init start_kernel(void)
{
init_IRQ();
init_timers();
hrtimers_init();
softirq_init();
rest_init(); --------- 其中创建了initd和kthreadd两个内核线程,且kthreadd处于休眠状态。
}
1、
static __init int spawn_ksoftirqd(void)
{
void *cpu = (void *)(long)smp_processor_id();
int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu); --- 此时创建并阻塞内核线程ksoftirqd/0,ksoftirqd/1等
BUG_ON(err == NOTIFY_BAD);
cpu_callback(&cpu_nfb, CPU_ONLINE, cpu); --- 此时唤醒内核线程
register_cpu_notifier(&cpu_nfb);
return 0;
}
early_initcall(spawn_ksoftirqd); ---- 每个CPU都启动了一个"ksoftirqd/%d"内核线程。
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(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu);
if (IS_ERR(p)) {
printk("ksoftirqd for %i failed\n", hotcpu);
return NOTIFY_BAD;
}
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),
any_online_cpu(cpu_online_map));
case CPU_DEAD:
case CPU_DEAD_FROZEN: {
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;
}
2、kthreadd平时休眠,被唤醒创建对应的内核线程;
软中断softirq,工作队列workqueue等通过kthread_create_on_node接口,激活kthreadd线程,进而创建对应的内核线程。创建完后,继续休眠进程。
此内核线程的调度策略为SCHED_NORMAL,优先级为0。//不同内核版本实现不同。
struct task_struct *kthread_create( int (*threadfn)( void *data), |
137 |
struct kthread_create_info create; |
139 |
create.threadfn = threadfn; |
141 |
init_completion(&create.done); |
143 |
spin_lock(&kthread_create_lock); |
144 |
list_add_tail(&create.list, &kthread_create_list); |
145 |
spin_unlock(&kthread_create_lock); |
147 |
wake_up_process(kthreadd_task); |
148 |
wait_for_completion(&create.done); |
150 |
if (!IS_ERR(create.result)) { |
151 |
struct sched_param param = { .sched_priority = 0 }; |
154 |
va_start (args, namefmt); |
155 |
vsnprintf(create.result->comm, sizeof (create.result->comm), |
162 |
sched_setscheduler_nocheck(create.result, SCHED_NORMAL, ¶m); |
163 |
set_cpus_allowed_ptr(create.result, cpu_all_mask); |
167 |
EXPORT_SYMBOL(kthread_create); |
3、软中断直接执行位置
- local_bh_enable
- irq_exit
- netif_rx_ni,接收到网络报文,如果不是在中断状态下,则执行软中断;否则退出;
4、软中断内核线程被创建成功后,默认为休眠状态,有很多唤醒时机:
- 在中断退出irq_exit时,如果不是在中断状态下,则执行软中断;否则,唤醒ksoftirqd去执行软中断;
- 软中断过多,一次执行不完,则唤醒ksoftirqd;(分do_softirq一次执行不完,软中断内核线程一次执行不完两种情况。)
- tasklet_schedule,如果不是在中断状态下,则唤醒ksoftirqd;如果是中断状态,则不执行,认为退出中断时会执行软中断。
相同类型软中断可以在不同的CPU上同时运行;