Linux資料報文接收發送總結4
阿新 • • 發佈:2021-02-19
二、系統初始化
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.c static 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.c static 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.h
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,
};