Linux内核的启动过程分析
秦鼎濤 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一、實驗目的及要求:
-
使用gdb跟蹤調試內核從start_kernel到init進程啟動
-
詳細分析從start_kernel到init進程啟動的過程并結合實驗截圖撰寫一篇署名博客,并在博客文章中注明“真實姓名(與最后申請證書的姓名務必一致) + 原創作品轉載請注明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000?”,博客內容的具體要求如下:
-
題目自擬,內容圍繞Linux內核的啟動過程,即從start_kernel到init進程啟動;
-
博客中需要使用實驗截圖
-
博客內容中需要仔細分析start_kernel函數的執行過程
-
總結部分需要闡明自己對“Linux系統啟動過程”的理解,尤其是idle進程、1號進程是怎么來的。
-
-
3)請提交博客文章URL到網易云課堂MOOC平臺Linux內核分析MOOC課程,編輯成一個鏈接可以直接點擊打開。?
二、實驗步驟(含實驗樓截圖):
1、登陸實驗樓虛擬機
打開shell終端,執行以下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img
(系統支持三個命令:help、version、quit)
2、使用gdb跟蹤調試內核
執行以下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img -s -S
關于-s和-S選項的說明:
-S freeze CPU at startup (use ’c’ to start execution) 在系統啟動的時候凍結CPU,使用c鍵繼續執行后續操作
-s shorthand for -gdb tcp::1234 打開遠程調試端口,默認使用tcp協議1234端口,若不想使用1234端口,則可以使用-gdb tcp:xxxx來取代-s選項
?
打開另外一個shell終端,執行以下命令
gdb
(gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加載符號表
(gdb)target remote:1234 # 建立gdb和gdbserver之間的連接,按c 讓qemu上的Linux繼續運行
(gdb)break start_kernel # 斷點的設置可以在target remote之前,也可以在之后
(不知道為什么沒有找到文件目錄。。)
?
三、start_kernel函數的執行過程分析:
asmlinkage void __init start_kernel(void)
{?char * command_line;
?extern struct kernel_param __start___param[], __stop___param[];
/*
?* Interrupts are still disabled. Do necessary setups, then
?* enable them
?*/
?lock_kernel(); 如果內核配置成支持搶占,那么在這里禁止搶占,將0號進程的init_thread_info.preempt_count加1; 如果配置成不支持搶占,那么內核全局自選鎖kernel_flag上鎖。
?page_address_init(); 10版本的ARM部分,沒有支持高端內存相關代碼,空函數。
?printk(linux_banner); 將linux_banner的內容打印到log_buf緩沖區中。
?setup_arch(&command_line); 函數原型在arch/arm/kernel/setup.c中 根據處理器、硬件平臺具體型號設置系統。解析Linux系統命令行,設置0號進程(swapper進程)的內存描述結構init_mm,系統內存管理初始化,統計并注冊系統各種資源,其他項目的初始化。
?setup_per_cpu_areas(); 為系統中每個處理器的per_cpu變量申請空間。 /*
? * Mark the boot cpu "online" so that it can call console drivers in
? * printk() and can access its per-cpu storage.
? */
?smp_prepare_boot_cpu(); /*
? * 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(); 初始化每個處理器的可運行進程隊列,設置系統初始化進程即0號進程。
?build_all_zonelists(); 建立系統內存頁區(zone)鏈表。
?page_alloc_init();
?printk("Kernel command line: %s\n", saved_command_line);
?parse_early_param(); 解析早期格式內核參數。
?parse_args("Booting kernel", command_line, __start___param,
???? __stop___param - __start___param,
???? &unknown_bootoption); 解析新格式內核參數。
?sort_main_extable(); 將放在__start__ex_table到__stop__ex_table之間的*(__ex_table)區中的struct exception_table_entry型全局結構變量按insn成員變量值從小到大排序,即將可能導致缺頁異常的指令按其指令二進制代碼值從小到大排序。
?trap_init(); 把放在.Lcvectors處的系統8個意外的入口跳轉指令搬到高端中斷向量0xffff0000處,再將從__stubs_start到__stubs_end之間的各種意外初始處理代碼搬到0xffff0200處。刷新0xffff0000處1頁范圍的指令cache,將DOMAIN_USER的訪問權限由DOMAIN_MANAGER權限改設置成DOMAIN_CLIENT權限。
?rcu_init(); 初始化當前CPU的讀、復制、更新數據結構(struct rcu_data)全局變量per_cpu_rcu_data和per_cpu_rcu_bh_data。
?init_IRQ(); 初始化系統中支持的最大可能中斷數的中斷描述結構struct irqdesc變量數組irq_desc[NR_IRQS],把每個結構變量irq_desc[n]都初始化為預先定義好的壞中斷描述結構變量bad_irq_desc,并初始化該中斷的連表表頭成員結構變量pend.
?pidhash_init(); 設置系統中每種pid hash表中的hash鏈表數的移位值全局變量pidhash_shift,將pidhash_shift設置為min(12);分別為每種hash表的連續hash鏈表表頭結構空間申請內存,把申請到的內存虛擬基址分別傳給pid_hash[n](n=0~3),并將每種hash表中的每個hash鏈表表頭結構struct hlist_head中的first成員指針設置成NULL
?init_timers(); 初始化當前出處理器的時間向量基本結構struct tvec_t_base_s全局變量per_cpu_tvec_bases,初始化per_cpu_tvec_bases的自旋鎖成員變量lock。
?softirq_init(); 設置系統小任務軟件中斷行為函數描述結構變量softirq_vec[TASKLET_SOFTIRQ(=6)],將softirq_vec[6]的行動函數指針action指向tasklet_action()函數,參數指針設置為NULL.
?time_init(); 檢查系統定時器描述結構struct sys_timer全局變量system_timer是否為空,如果是將其指向dummy_gettimeoffset()函數。 /*
? * 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(); 初始化系統的控制臺結構,該函數執行后調用printk()函數將log_buf中符合打印級別要求的系統信息打印到控制臺上。
?if (panic_later)
??panic(panic_later, panic_param);
?profile_init(); 對系統剖析作相關初始化,系統剖析用于系統調用。
?local_irq_enable(); 將處理器的當前系統狀態寄存器CPSR的第7位清0,使能IRQ中斷。
#ifdef CONFIG_BLK_DEV_INITRD
?if (initrd_start && !initrd_below_start_ok &&
???initrd_start < min_low_pfn << PAGE_SHIFT) {
??printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
????? "disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
??initrd_start = 0;
?}
#endif
?vfs_caches_init_early();
?mem_init(); 該函數執行完后不能再用像alloc_bootmem()、alloc_bootmem_low()、alloc_bootmem_pages()等申請低端內存的函數來申請內存,也就不能申請大塊的連續物理內存了。
?kmem_cache_init(); 執行高速緩存內存管理即slab分配器相關初始化。
?numa_policy_init();
?if (late_time_init)
??late_time_init();
?calibrate_delay(); 計算機系統的BogMIPS數值,即處理器每秒鐘執行的指令數。
?pidmap_init();
?pgtable_cache_init();
?prio_tree_init(); 初始化無符號長整型全局數組index_bits_to_maxindex[BITS_PER_LONG]的每個組員,將每個組員index_bits_to_maxindex[n]設置成-1,將最后的index_bits_to_maxindex[BITS_PER_LONG-1]設置成~0UL。
?anon_vma_init(); 該函數調用kmem_cache_create()函數,為匿名虛擬內存區域鏈表結構struct anon_vma創建高速緩存內存描述結構kmem_cache_t變量,為該變量命名為“anon_vma",其對象的構造函數指針指向void anon_vma_ctor(void *data,kmem_cache_t *cachep,unsigned long flags)函數,析構函數指針空,將創建的kmem_cache_t結構變量地址傳給全局指針anon_vma_chachep。
#ifdef CONFIG_X86
?if (efi_enabled)
??efi_enter_virtual_mode();
#endif
?fork_init(num_physpages); 執行進程創建相關初始化。
?proc_caches_init();
?buffer_init(); 調用 kmem_cache_create("buffer_head",?sizeof(struct buffer_head), 0,?SLAB_PANIC, init_buffer_head, NULL)函數為緩沖區描述結構struct buffer_head創建高速緩存內存描述結構kmem_cache_t變量。
?unnamed_dev_init(); 調用并返回idr_init(&unnamed_dev_idr)函數。
?security_init(); 打印”安全架構v1.0.0被初始化“。如果沒有定義系統啞元安全操作函數組結構全局變量dummy_security_ops,打印錯誤信息,返回I/O錯誤。
?vfs_caches_init(num_physpages);
?radix_tree_init();
?signals_init(); 調用kmem_cache_create("sigqueue",?sizeof(struct sigqueue),?__alignof__(struct sigqueue),?SLAB_PANIC, NULL, NULL)函數為信號隊列結構struct sigqueue創建高速緩存內存描述結構kmem_cache_t變量,名字叫”sigqueue“,不要求其對象按處理器硬件cache line大小對齊,沒有定義其對象的構造和析構函數,將創建號的kmem_cache_t結構變量的地址傳給全局指針sigqueue_cachep。
?/* rootfs populating might need page-writeback */
?page_writeback_init(); 統計系統中所有內存節點的通用(NORMAL)內存頁區中高頁數水印值頁數之外的額外內存總頁數之和傳給buffer_pages。
#ifdef CONFIG_PROC_FS
?proc_root_init(); 只有在系統支持proc文件系統即配置了CONFIG_PROC_FS選項時才被調用。
#endif
?check_bugs(); acpi_early_init(); /* before LAPIC and SMP init */ /* Do the rest non-__init'ed, we're now alive */
?rest_init(); 該函數創建init()內核進程即1號進程,然后是系統啟動進程即0號進程空閑。
}
?
四、實驗總結:
當計算機電源啟動,BIOS代碼被調用執行,然后開始調用執行Linux內核初始化代碼,在平臺相關的匯編代碼
執行完畢后會跳轉到start_kernel()函數,開始真正的內核初始化,其中init_task創建了0號進程,即最終的idle進程,
隨后rest_init()函數創建了init進程,即1號進程,以及kthreadd進程,即2號進程,系統開始正式對外工作了。
?
轉載于:https://www.cnblogs.com/qindingtao/p/5268143.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Linux内核的启动过程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DP接口与HDMI接口各有什么优势?哪个
- 下一篇: 四则运算2+psp0