現在我們還在關於機器代碼裡晃悠呢,昨天分析了arch/x86/kernel/head_32.S裡面的代碼,彙編的時代已經過去了,現在起,代碼都是C的,很爽啊。我們離大本營越來越近了,裝置模型就在前方不遠處,不過還沒繼續分析完,估計你還在迷糊,在哪裡呢?呵呵,很快的,這一切會在一瞬間就走完,不要太過於留戀太多無聊的東西。現在該是時候跳到昨天說的那裡了。
來自arch/x86/kernel/head32.c:
void __init i386_start_kernel(void)
{
#ifdef CONFIG_X86_TRAMPOLINE
/*
* But first pinch a few for the stack/trampoline stuff
* FIXME: Don't need the extra page at 4K, but need to fix
* trampoline before removing it. (see the GDT stuff)
*/
reserve_early_overlap_ok(PAGE_SIZE, PAGE_SIZE + PAGE_SIZE,
"EX TRAMPOLINE");
#endif
reserve_early(__pa_symbol(&_text), __pa_symbol(&__bss_stop), "TEXT DATA BSS");
#ifdef CONFIG_BLK_DEV_INITRD
/* Reserve INITRD */
if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) {
/* Assume only end is not page aligned */
u64 ramdisk_image = boot_params.hdr.ramdisk_image;
u64 ramdisk_size = boot_params.hdr.ramdisk_size;
u64 ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size);
reserve_early(ramdisk_image, ramdisk_end, "RAMDISK");
}
#endif
/* Call the subarch specific early setup function */
switch (boot_params.hdr.hardware_subarch) {
case X86_SUBARCH_MRST:
x86_mrst_early_setup();
break;
default:
i386_default_early_setup();
break;
}
/*
* At this point everything still needed from the boot loader
* or BIOS or kernel text should be early reserved or marked not
* RAM in e820. All other memory is free game.
*/
# 其實上面這部分看不懂沒關係,因為對太底層的東西,我們可以暫時不去動它,畢竟裡面太複雜,還不是我們這個年紀
# 可以啃得動的,我們還需要積累很多經驗才能從容應對,而且我們的大本營就在前方不遠了
start_kernel();
}
start_kernel函數,它預示著我們馬上就要飛出機器相關的部分,來到一個非常自由無拘無束的地方--機器無關代碼。
來自init/main.c:
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];
smp_setup_processor_id();
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_off();
early_init_irq_lock_class();
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE "%s", linux_banner);
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
build_all_zonelists(NULL);
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it/n");
local_irq_disable();
}
rcu_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
if (!irqs_disabled())
printk(KERN_CRIT "start_kernel(): bug: interrupts were "
"enabled early/n");
early_boot_irqs_on();
local_irq_enable();
/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic(panic_later, panic_param);
lockdep_info();
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
"disabling it./n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
enable_debug_pagealloc();
kmemleak_init();
debug_objects_mem_init();
idr_init_cache();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();
check_bugs();
acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
# 看到上面這些沒有,基本都是xxx_init(),我樂個操!當我傻逼啊,不會上你的當,去一個一個揭開看,還是剛才那句老話,
# 我們的年紀還不夠
rest_init();
}
跳到rest_init函數:
static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
# rcu這種東西實在噁心,關於多處理器平台同步、互斥的
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
# 這個函數估計大家顧名思義可以的,建立一個線程,說白了,就是核心線程
# 線程函數是kernel_init。
# 關於進程、線程的概念可以看看書籍,至於進程管理這部分,我們是不會涉及的
# 只需要知道函數用法即可
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
# 下面的可以直接忽略
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}
我們來看看kernel_init:
static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
/*
* Tell the world that we're going to be the grim
* reaper of innocent orphaned children.
*
* We don't want people to have to make incorrect
* assumptions about where in the task array this
* can be found.
*/
init_pid_ns.child_reaper = current;
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
smp_init();
sched_init_smp();
# 看到這裡了,這個函數就將揭開初始化裝置模型的面紗
do_basic_setup();
# 下面的飄過~~
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console./n");
(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
init_post();
return 0;
}
跟蹤do_basic_setup()進去:
# 哎喲,沒幾行嘛,找到我們關注的
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
init_tmpfs();
# 我們日思夜想,萬裡長征不怕難,終於找到了,我們的大本營,也就是我們的重點分析目標
# 現在我該用什麼樣的方式表達我的激動,真是無法表達,因為這一切來之不易
# 我們避開了多少的複雜代碼才來到這裡的,別讓我們失望
# 這個初始化函數建立了一個非常強大的裝置模型系統
# 為PC裡的裝置提供著源源不斷的溪流
# 它是支援這PC裡所有的裝置
# 這就是裝置模型!這也是它的魅力所在
driver_init(); /*build device module subsystem*/
init_irq_proc();
do_ctors();
# 這個初始化函數,可以調用核心裡存放在初始化節裡的初始化函數。
do_initcalls();/*init devices*/
}
現看看這個函數do_initcalls():
# 這些外部變數下面講
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
下面這點內容就是介紹初始化節,這個名字是我自己取的,聽起來形象點,如果編譯核心,我們就知道有個連結指令檔vmlinux.lds,這個檔案是由存放在arch/x86/kernel/vmlinux.lds.S的組合語言寫的檔案編譯出來的。在這個檔案裡面存放的資訊,把整個各個目錄裡編譯出來的核心目標檔案(一般為.o尾碼)按照vmlinux.lds的連結資訊進行連結組成一個完整的核心vmlinux,說白點,就是重組。也就是說核心代碼中的資料區段、bss段、data段等一些自訂的段都會按照連結指令碼的資訊進行連結。
這裡出現的初始化節,應該說是放一些核心初始化時需要調用的函數地址。在vmlinux.lds中(注意vmlinux.lds必須編譯後才能得到)
*(.initcallearly.init) __early_initcall_end = .;
*(.initcall0.init)
*(.initcall0s.init)
*(.initcall1.init)
*(.initcall1s.init)
*(.initcall2.init)
*(.initcall2s.init)
*(.initcall3.init)
*(.initcall3s.init)
*(.initcall4.init)
*(.initcall4s.init)
*(.initcall5.init)
*(.initcall5s.init)
*(.initcallrootfs.init)
*(.initcall6.init)
*(.initcall6s.init)
*(.initcall7.init)
*(.initcall7s.init)
__initcall_end = .;
對應這些節的還有一些宏,可以提供給那些需要核心在初始化時調用的模組。
來自include/linux/init.h:
#define __define_initcall(level,fn,id) /
static initcall_t __initcall_##fn##id __used /
__attribute__((__section__(".initcall" level ".init"))) = fn
/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall("early",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.
*/
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
明白了吧,上面這些define就是起到定義的作用,我們展開一個說說,比如:subsys_initcall(usb_init)。
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define __define_initcall(level,fn,id) /
static initcall_t __initcall_##fn##id __used /
__attribute__((__section__(".initcall" level ".init"))) = fn
可以得到==>
static initcall_t __initcallusb4__used __attribute__((__section__(".initcall4.init")))= usb_init。至於這樣的代碼為什麼會將usb_init這個初始化usb裝置模組的函數放在".initcall4.init"這個初始化節裡,恐怕得要學習一些gcc編譯、連結方面的書籍了,當然,這些我們不去過問的。哦!對咯!這裡還有個類型沒說initcall_t,這個是函數指標,不說也知道的,在檔案最上面有定義。
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);
好了,把知識點部分說完了,現在在來說那個do_initcalls函數也不是什麼難事情。
# 對照上面初始化節,就可以發現定義在哪裡了
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn); # 繼續看它是怎麼做的,其實不看我們也能猜到!
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
看do_one_initcall():
int __init_or_module do_one_initcall(initcall_t fn)
{
int count = preempt_count();
int ret;
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn(); # 肯定得這樣調用嘛,不看我們也知道,呵呵!
# 下面的直接忽略
msgbuf[0] = 0;
if (ret && ret != -ENODEV && initcall_debug)
sprintf(msgbuf, "error code %d ", ret);
if (preempt_count() != count) {
strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf));
preempt_count() = count;
}
if (irqs_disabled()) {
strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
local_irq_enable();
}
if (msgbuf[0]) {
printk("initcall %pF returned with %s/n", fn, msgbuf);
}
return ret;
}
好了,現在萬事具備,只差分析了。從上面的內容中,我們知道我們在幹什麼,必須得整理一遍思路,我們找到了分析裝置模型最重要的函數driver_init(),我們還分析核心初始化時會調用初始化節裡的函數,關於初始化節裡的函數一般是模組初始化函數,不過我也沒具體看過,因此也只能說說我知道。關於模組,是linux核心裡的一個非常重要的特點,模組的靜態編譯和動態載入使得linux非常靈活,特別是對於裝置驅動。建議如果你還對於核心模組沒有一個概念性的認識,找本書看看,然後順便知道怎麼進行核心驅動編寫,也看看,一般書裡都會講的,其實沒多少內容,幾天就可以搞定。
想到馬上我就可以分析日思夜想的裝置模型這塊內容了,還是比較興奮,不過裡面博大精深的內容,估計得花很多時間,因此,我不曉得後面的文章還會接得緊嗎,但是為了把裝置模型分析得更加透徹些,我覺得花多寫時間是物超所值的。
我們已經走完了核心啟動到初始化的全過程了,你學到了多少,我老實說,我寫的這些文章必須建立在對很多知識都有一定的瞭解的基礎上的,而且已經分析過代碼,只是有很多地方不是很懂,那麼看看這些文章還蠻管用的。對於很多想學習核心原始碼的你,但是還有許多條件都沒跟上,那麼千萬別著急著去看原始碼,這樣會事倍功半。下篇文章,如果要寫,我會寫些學習核心的基本條件和素養,為什麼還有素養,呵呵,自己的看法吧!