二、系统初始化
Linux驱动,内核协议栈等等模块在具备接收网卡数据包之前,要做很多的准备工作才行。比如要提前创建好ksoftirqd内核线程,要注册好各个协议对应的处理函数,网络设备子系统要提前初始化好,网卡要启动好。只有这些都Ready之后,我们才能真正开始接收数据包。那么我们现在来看看这些准备工作都是怎么做的。
Linux的子系统、模块均定义了一定的启动级别,在start_kernel函数中,按顺序启动
/* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined* by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection.** The `id' arg to __define_initcall() is needed so that multiple initcalls* can point at the same handler without causing duplicate-symbol build errors.*/#define __define_initcall(fn, id) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(".initcall" #id ".init"))) = fn; \LTO_REFERENCE_INITCALL(__initcall_##fn##id)/** Early initcalls run before initializing SMP.** Only for built-in code, not modules.*/#define early_initcall(fn)__define_initcall(fn, early)/** A "pure" initcall has no dependencies on anything else, and purely* initializes variables that couldn't be statically initialized.** This only exists for built-in code, not for modules.* Keep main.c:initcall_level_names[] in sync.*/#define pure_initcall(fn)__define_initcall(fn, 0)#define core_initcall(fn)__define_initcall(fn, 1)#define core_initcall_sync(fn)__define_initcall(fn, 1s)#define postcore_initcall(fn)__define_initcall(fn, 2)#define postcore_initcall_sync(fn)__define_initcall(fn, 2s)#define arch_initcall(fn)__define_initcall(fn, 3)#define arch_initcall_sync(fn)__define_initcall(fn, 3s)#define subsys_initcall(fn)__define_initcall(fn, 4)#define subsys_initcall_sync(fn)__define_initcall(fn, 4s)#define fs_initcall(fn)__define_initcall(fn, 5)#define fs_initcall_sync(fn)__define_initcall(fn, 5s)#define rootfs_initcall(fn)__define_initcall(fn, rootfs)#define device_initcall(fn)__define_initcall(fn, 6)#define device_initcall_sync(fn)__define_initcall(fn, 6s)#define late_initcall(fn)__define_initcall(fn, 7)#define late_initcall_sync(fn)__define_initcall(fn, 7s)#define __initcall(fn) device_initcall(fn)
2.1 创建ksoftirqd内核线程
Linux的软中断都是在专门的内核线程(ksoftirqd)中进行的,因此我们非常有必要看一下这些进程是怎么初始化的,这样我们才能在后面更准确地了解收包过程。该进程数量不是1个,而是N个,其中N等于你的机器的核数。
系统初始化的时候执行spawn_ksoftirq -> smpboot_register_percpu_thread->smpboot_register_percpu_thread_cpumask->__smpboot_create_thread,
该函数创建出softirqd内核线程(位于kernel/softirq.c, 线程主函数smpboot_thread_fn)。
相关代码如下:
//file: kernel/softirq.cstatic struct smp_hotplug_thread softirq_threads = {.store= &ksoftirqd,.thread_should_run = ksoftirqd_should_run,.thread_fn= run_ksoftirqd,.thread_comm = "ksoftirqd/%u",};static __init int spawn_ksoftirqd(void){register_cpu_notifier(&cpu_nfb); // 为每个CPU创建一个处理软件中断的线程BUG_ON(smpboot_register_percpu_thread(&softirq_threads));return 0;}early_initcall(spawn_ksoftirqd); // 将函数放至对应级别的初始化位置//file : kernel/smp_boot.cstatic int smpboot_thread_fn(void *data){struct smpboot_thread_data *td = data;struct smp_hotplug_thread *ht = td->ht;while (1) {set_current_state(TASK_INTERRUPTIBLE);preempt_disable();if (kthread_should_stop()) {__set_current_state(TASK_RUNNING);preempt_enable();/* cleanup must mirror setup */if (ht->cleanup && td->status != HP_THREAD_NONE)ht->cleanup(td->cpu, cpu_online(td->cpu));kfree(td);return 0;}if (kthread_should_park()) {__set_current_state(TASK_RUNNING);preempt_enable();if (ht->park && td->status == HP_THREAD_ACTIVE) {BUG_ON(td->cpu != smp_processor_id());ht->park(td->cpu);td->status = HP_THREAD_PARKED;}kthread_parkme();/* We might have been woken for stop */continue;}BUG_ON(td->cpu != smp_processor_id());/* Check for state change setup */switch (td->status) {case HP_THREAD_NONE:__set_current_state(TASK_RUNNING);preempt_enable();if (ht->setup)ht->setup(td->cpu);td->status = HP_THREAD_ACTIVE;continue;case HP_THREAD_PARKED:__set_current_state(TASK_RUNNING);preempt_enable();if (ht->unpark)ht->unpark(td->cpu);td->status = HP_THREAD_ACTIVE;continue;}if (!ht->thread_should_run(td->cpu)) { // 检测软件是否有可运行软中断preempt_enable_no_resched();schedule();} else {__set_current_state(TASK_RUNNING);preempt_enable();ht->thread_fn(td->cpu); // 执行注册的软件中断函数}}}
当ksoftirqd被创建出来以后,它就会进入自己的线程循环函数ksoftirqd_should_run和run_ksoftirqd了。不停地判断有没有软中断需要被处理。这里需要注意的一点是,软中断不仅仅只有网络软中断,还有其它类型。
//file: include/linux/interrupt.henum{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ, };