內核啓動過程總結
之前配置編譯過內核源代碼,在交叉編譯源代碼後產生了三個文件(還有其他文件)分別是vmlinuz、vmlinux、vmlinux32,其中vmlinuz是可引導的、壓縮了的內核,將該內核拷貝到系統文件/boot目錄下,再配置下/boot/boot.cfg文件,將啓動時選擇內核的信息和加載內核的地方寫入就可以實現內核的移植。其實移植過程和正常內核啓動過程的原理是一樣的。
系統加電啓動後,MIPS處理器默認的程序入口時0xBFC00000,此地址在無緩存的KSEG1的地址區域內,對應的物理地址是0x1FC00000,即CPU從0x1FC00000開始取第一條指令,內核是系統引導程序把內核加載到內存中的,如果內核是經過壓縮的,那麼首先執行/arch/mips/boot/compressed的head.S文件去建立堆棧並解壓內核映像文件,然後去執行/arch/mips/kernel下的head.S,如果是沒有壓縮的內核則直接去執行該head.S。linux內核啓動的第一階段就是從kernel文件夾下的head.S開始的,kernel_entry()函數就是內核啓動的入口函數,這個函數是與體系結構相關的彙編語言編寫的,它首先初始化內核堆棧段,來爲創建系統的第一個進程0進程作準備,接着用一段循環將內核映像的未初始化數據段bss段清零,最後跳轉到/init/main.c中的start_kernel()初始化硬件平臺相關的代碼。
kernel_entry() - arch/mips/kernel/head.S
TLB初始化,Cache初始化
清除BSS段
準備參數 argc/argp/envp
設置棧
jal start_kernel (init/main.c)
怎麼爲第一個進程0進程作準備?
第一個進程涉及到init_thread_union,這個結構體在include/linux/sched.h得到定義
extern union thread_union init_thread_union;
union thread_union{
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
THREAD_SIZE是一個宏定義#define THREAD_SIZE (2*PAGE_SIZE)
#define PAGE_SIZE (_AC(1,UL)<<PAGE_SHIFT) PAGE_SHIFT=13 算出PAGE_SIZE=2^12=4096;則THREAD_SIZE=8192
內核把進程存放在任務隊列的雙向循環鏈表中,鏈表中的每一項都是類型爲task_struct、稱爲進程描述符的結構,進程描述符中包含一個具體進程的所有信息,linux通過slab分配器分配task_struct結構,每個任務都有一個thread_info結構,它在內核棧的尾部分配,結構中的task域中存放的是指向該任務實際task_struct的指針,thread_info結構在文件<asm/thread_info.h>中定義。
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long tp_value; /* thread pointer */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread */
struct restart_block restart_block;
struct pt_regs *regs;
};
/*
* Initial thread structure.
*
* We need to make sure that this is 8192-byte aligned due to the
* way process stacks are handled. This is done by making sure
* the linker maps this in the .text segment right after head.S,
* and making head.S ensure the proper alignment.
*
* The things we do for performance..
*/
union thread_union init_thread_union __init_task_data
__attribute__((__aligned__(THREAD_SIZE))) =
{ INIT_THREAD_INFO(init_task) };
__init_task_data是一個宏,在"include/linux/init_task.h"中這樣定義的
#define __init_task_data __attribute__((__section__(“.data..init_task”)))
這是一條賦值語句,對init_thread_union賦初值,並且把數據放在指定的數據段.data..init_task中。具體如何賦值,看看init_task這個全局變量
/*
* Initial task structure.
*
* All other task structs will be allocated on slabs in fork.c
*/
struct task_struct init_task = INIT_TASK(init_task);
0號進程的task_struct出現了,就是init_task。在對init_thread_union賦初值的時候,同時也通過調用INIT_TASK宏對init_task賦初值:
#define INIT_TASK(tsk) \
{ \
.state = 0, \
.stack = &init_thread_info, \
.usage = ATOMIC_INIT(2), \
.flags = PF_KTHREAD, \
.lock_depth = -1, \
.prio = MAX_PRIO-20, \
.static_prio = MAX_PRIO-20, \
.normal_prio = MAX_PRIO-20, \
.policy = SCHED_NORMAL, \
.cpus_allowed = CPU_MASK_ALL, \
.mm = NULL, \
.active_mm = &init_mm, \
.se = { \
.group_node = LIST_HEAD_INIT(tsk.se.group_node), \
}, \
.rt = { \
.run_list = LIST_HEAD_INIT(tsk.rt.run_list), \
.time_slice = HZ, \
.nr_cpus_allowed = NR_CPUS, \
}, \
.tasks = LIST_HEAD_INIT(tsk.tasks), \
.pushable_tasks = PLIST_NODE_INIT(tsk.pushable_tasks, MAX_PRIO), \
.ptraced = LIST_HEAD_INIT(tsk.ptraced), \
.ptrace_entry = LIST_HEAD_INIT(tsk.ptrace_entry), \
.real_parent = &tsk, \
.parent = &tsk, \
.children = LIST_HEAD_INIT(tsk.children), \
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
.real_cred = &init_cred, \
.cred = &init_cred, \
.cred_guard_mutex = \
__MUTEX_INITIALIZER(tsk.cred_guard_mutex), \
.comm = "swapper", \
.thread = INIT_THREAD, \
.fs = &init_fs, \
.files = &init_files, \
.signal = &init_signals, \
.sighand = &init_sighand, \
.nsproxy = &init_nsproxy, \
.pending = { \
.list = LIST_HEAD_INIT(tsk.pending.list), \
.signal = {{0}}}, \
.blocked = {{0}}, \
.alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock), \
.journal_info = NULL, \
.cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), \
.fs_excl = ATOMIC_INIT(0), \
.pi_lock = __RAW_SPIN_LOCK_UNLOCKED(tsk.pi_lock), \
.timer_slack_ns = 50000, /* 50 usec default slack */ \
.pids = { \
[PIDTYPE_PID] = INIT_PID_LINK(PIDTYPE_PID), \
[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID), \
[PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \
}, \
.thread_group = LIST_HEAD_INIT(tsk.thread_group), \
.dirties = INIT_PROP_LOCAL_SINGLE(dirties), \
INIT_IDS \
INIT_PERF_EVENTS(tsk) \
INIT_TRACE_IRQFLAGS \
INIT_LOCKDEP \
INIT_FTRACE_GRAPH \
INIT_TRACE_RECURSION \
INIT_TASK_RCU_PREEMPT(tsk) \
}
可以看到0號進程的stack就是剛纔init_thread_info的地址;parent是他自己;thread是INIT_THREAD。附上進程描述符的task_struct結構圖
0號進程task_struct有了,下面就該來講講INIT_THREAD_INFO宏了,在arch/mips/include/asm/thread_info.h:
/*
* macros/functions for gaining access to the thread information structure
*/
#define INIT_THREAD_INFO(tsk) \
{ \
.task = &tsk, \
.exec_domain = &default_exec_domain, \
.flags = _TIF_FIXADE, \
.cpu = 0, \
.preempt_count = INIT_PREEMPT_COUNT, \
.addr_limit = KERNEL_DS, \
.restart_block = { \
.fn = do_no_restart_syscall, \
}, \
}
執行完這個宏以後,init_thread_union就被初始化成以上內容了,至此,0號進程的task_struct和thread_info就初始化完畢了。
怎麼將未初始化數據段bss段清零呢?看源代碼
PTR_LA t0, __bss_start # clear .bss
LONG_S zero, (t0)
PTR_LA t1, __bss_stop - LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
最後跳轉到 /init/main.c中的start_kernel()初始化硬件平臺相關的代碼。
*********************************************************************
附上/kernel/head.S源代碼及部分註釋:
.macro setup_c0_status set clr
#ifdef CONFIG_MIPS_MT_SMTC
...........
#else
mfc0 t0, CP0_STATUS #取CP0 status寄存器的值到臨時寄存器to中
or t0, ST0_CU0|\set|0x1f|\clr
xor t0, 0x1f|\clr
mtc0 t0, CP0_STATUS #將臨時寄存器to中的值賦給CP0狀態寄存器
.set noreorder #表示禁止爲了填充加載指令和分支指令的延遲槽而對代 #碼重新排序
sll zero,3 # ehb (exception hazard barrier)
.macro setup_c0_status_pri
-----------------------------------------------------
#ifdef CONFIG_64BIT
setup_c0_status ST0_KX 0
#else
setup_c0_status 0 0
#endif
.endm
------------------------------------------------------------------------------------------
NESTED(kernel_entry, 16, sp) # kernel entry point聲明函數 kernel_entry,函數的堆棧爲 16 byte,返回地址保存在 $sp 寄存器中。
kernel_entry_setup #cpu specific setup,某些MIPS CPU需要額外的設置一些
控制寄存器,和具體的平臺相關,一般爲空宏;某些多
核MIPS,啓動時所有的core的入口一起指向kernel_entry,
然後在該宏裏分叉,boot core 繼續往下,其它的則不停
的判斷循環,直到boot core 喚醒之
setup_c0_status_pri #設置cp0_status 寄存器
PTR_LA t0, 0f
jr t0
PTR_LA t0, __bss_start # clear .bss
LONG_S zero, (t0) #變量 __bss_start 和 __bss_stop 在連接文件arch/mips/kernel/vmlinux.lds 中定義。
PTR_LA t1, __bss_stop - LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
LONG_S a0, fw_arg0 # firmware arguments
LONG_S a1, fw_arg1 #bootloader會將要傳給內核的參數寫在
LONG_S a2, fw_arg2 a0~a3裏,此處爲將參數保存在fw_arg0-fw_arg3中
LONG_S a3, fw_arg3
MTC0 zero, CP0_CONTEXT # clear context register
PTR_LA $28, init_thread_union #初始化gp,指向一個 union,THREAD_SIZE大小,最低處是一個thread_info結構體
PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE #_THERAD_SIZE=16384
PT_SIZE=304(sizeof(struct pt_regs))
PTR_ADDU sp, $28
back_to_back_c0_hazard
set_saved_sp sp, t0, t1 #把這個CPU核的堆棧地址 $sp保存到 kernelsp[NR_CPUS]數組。
----------------------------------------------------------------
如果定義了 CONFIG_SMP 宏,即多 CPU 核。
.macro set_saved_sp stackp temp temp2
#ifdef CONFIG_MIPS_MT_SMTC
mfc0 \temp, CP0_TCBIND
#else
MFC0 \temp, CP0_CONTEXT
#endif
LONG_SRL \temp, PTEBASE_SHIFT
LONG_S \stackp, kernelsp(\temp)
.endm
如果沒有定義 CONFIG_SMP 宏,單 CPU 核。
.macro set_saved_sp stackp temp temp2
LONG_S \stackp, kernelsp
.endm
變量 kernelsp 的定義,在 arch/mips/kernel/setup.c 文件中。
unsigned long kernelsp[NR_CPUS];
-------------------------------------------------------------------------------------------------------
PTR_SUBU sp, 4 * SZREG # init stack pointer SZREG=8
j start_kernel #最後跳轉到 /init/main.c中的start_kernel()初始化硬件平臺相關的代碼。
END(kernel_entry)
*********************************************************************
#define asmlinkage __attribute__((regparm(0)))
_attribute__是關鍵字,是gcc的C語言擴展,regparm(0)表示不從寄存器傳遞參數。如果是__attribute__((regparm(3))),
那麼調用函數的時候參數不是通過棧傳遞,而是直接放到寄存器裏,被調用函數直接從寄存器取參數
函數定義前加宏asmlinkage ,表示這些函數通過堆棧而不是通過寄存器傳遞參數。gcc編譯器在彙編過程中調用c語言函數時
傳遞參數有兩種方法:一種是通過堆棧,另一種是通過寄存器。缺省時採用寄存器,假如你要在你的彙編過程中調用c語言函數,
並且想通過堆棧傳遞參數,你定義的c函數時要在函數前加上宏asmlinkage
*********************************************************************
看看init/main.c源代碼
asmlinkage void __init start_kernel(void){
char *command_line;
extern const struct kernel_param __start__param[],__stop__param[];
//來自外部的/include/linux/moduleparam.h中的kernel_param結構體 這兩個變量爲地址指針,指向內核啓動參數處理相關結構體段在內存中的位置(虛擬地址)。
smp_setup_processor_id();//當只有一個CPU的時候這個函數什麼都不做,但是如果有多個CPU的時候它就返回啓動的時候的那個CPU的id號
unwind_init();
在MIPS體系結構中,這個函數是個空函數(可能調用setup_arch,配置核的相關函數)
lockdep_init();初始化核依賴關係哈希表
lockdep是一個內核調試模塊,用來檢查內核互斥機制(尤其是自旋鎖)潛在的死鎖問題。由於自旋鎖以查詢方式等待,不釋放處理器,比一般互斥機制更容易死鎖,故引入lockdep檢查以下幾種可能的死鎖情況:
1.同一個進程遞歸地加鎖同一把鎖;
2.一把鎖既在中斷(或中斷下半部)使能的情況下執行過加鎖操作, 又在中斷(或中斷下半部)裏執行過加鎖操作。這樣該鎖有可能在鎖定時由於中斷髮生又試圖在同一處理器上加鎖;
3.加鎖後導致依賴圖產生成閉環,這是典型的死鎖現象。
啓動Lock Dependency Validator(內核依賴的關係表),本質上就是建立兩個散列表calsshash_table和chainhash_table,並初始化全局變量lockdep_initialized,標誌已初始化完成
debug_objects_early_init();//在啓動早期初始化hash buckets 和鏈接靜態的 pool objects對象到 poll 列表. 在這個調用完成後 object tracker 已經開始完全運作了.
boot_init_stack_canary();canary值的是用於防止棧溢出攻擊的堆棧的保護字 。參考資料: GCC 中的編譯器堆棧保護技術
cgroup_init_early();cgroup: 它的全稱爲control group.即一組進程的行爲控制.該函數主要是做數據結構和其中鏈表的初始化
local_irp_disable(); //關閉系統總中斷(底層調用匯編指令)
early_boot_irqs_off();//設置系統中斷的關閉標誌(bool全局變量)
early_init_irp_lock_class();每個中斷都有一箇中斷描述符(struct irq_desc)來進行描述,這個函數的作用就是設置所有中斷描述符的鎖
tick_init();如果沒有定義 CONFIG_GENERIC_CLOCKEVENTS宏定義,則這個函數爲空函數,如果定義了這個宏,這執行初始化 tick控制功能,註冊clockevents的框架。
boot_cpu_init();對於 CPU 核的系統來說,設置第一個 CPU 核爲活躍 CPU 核。對於單 CPU 核系統來說,設置 CPU 核爲活躍 CPU 核
page_address_init();當定義了CONFIG_HIGHMEM 宏,並且沒有定義 WANT_PAGE_VIRTUAL 宏時,非空函數。其他情況爲空函數。
printk("KERN_NOTICE "%s",linux_banner);
setup_arch(&command_line);內核構架相關初始化函數,可以說是非常重要的一個初始化步驟。其中包含了處理器相關參數的初始化、內核啓動參數(tagged list)的獲取和前期處理、內存子系統的早期的初始化(bootmem分配器)。
mm_init_owner(&init_mm,&init_task);初始化代表內核本身內存使用的管理結構體init_mm。ps:每一個任務都有一個mm_struct結構以管理內存空間,init_mm是內核的mm_struct,其中:設置成員變量* mmap指向自己,意味着內核只有一個內存管理結構;
setup_command_line(command_line);
保存未改變的 comand_line 到字符數組 static_command_line[] 中。保存 boot_command_line到字符數組 saved_command_line[]中。
setup_nr_cpu_ids();
setup_per_cpu_areas();如果沒有定義 CONFIG_SMP宏,則這個函數爲空函數。如果定義了CONFIG_SMP宏,則這個 setup_per_cpu_areas()函數給每個CPU分配內存,並拷貝.data.percpu段的數據。
smp_prepare_boot_cpu();
build_all_zonelists(NULL);建立各個節點的管理區的 zonelist,便於分配內存的 fallback使用。
這個鏈表的作用: 這個鏈表是爲了在一個分配不能夠滿足時可以考察下一個管理區來設置了。在考察結束時,分配將從 ZONE_HIGHMEM回退到 ZONE_NORMAL,在分配時從 ZONE_NORMAL退回到 ZONE_DMA就不會回退了。
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);
。。。。。。。。此處省略很多行
rest_init();
}
*********************************************************************
其中有個函數是與自己體系結構相關的函數setup_arch()
在kernel/setup.c中在裏面看到
unsigned long kernelsp[NR_CPUS];
unsigned long fw_arg0, fw_arg1, fw_arg2, fw_arg3;
void __init setup_arch(char **cmdline_p)
{
cpu_probe();
調用函數cpu_probe(),該函數通過MIPS CPU的PRID寄存器來確定CPU類型,
從而確定使用的指令集和其他一些CPU參數,如TLB等
prom_init();
prom_init() 函數是和硬件相關的,做一些低層的初始化,接受引導裝載程序傳給內核的參數,確定 mips_machgroup,mips_machtype 這兩個變量,這兩個變量分別對應着相應的芯片組合開發板;
打印cpu_probe() 函數檢測到的CPU 的Processor ID。
如果有浮點處理器,也打印浮點處理器的Processor ID。
cpu_report();
應用程序通過終端接口設備使用特定的接口規程與終端進行交互,與操作系統內核本身交互的終端稱爲控制檯,
它可以是內核本身的內部顯示終端,也可以是通過串口連接的外部啞終端。
由於大多數情況下控制檯都是內核顯示終端,因此內核顯示終端也常常直接稱爲控制檯。
內核終端對用戶來說具有若干個虛擬終端子設備,它們共享同一物理終端,
但同一時刻只能有一個虛擬終端操作硬件屏幕。
宏 CONFIG_VT 的意思是否支持虛擬終端。
當配置了宏 CONFIG_VGA_CONSOLE 時爲內核本身的內部顯示終端。
當配置了宏 CONFIG_DUMMY_CONSOLE 時爲通過串口連接的外部啞終端。
用變量 conswitchp 來進行指定。
#if defined(CONFIG_VT)
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
對內存進行初始化。
arch_mem_init(cmdline_p);
這個函數遍歷每一個內存空間範圍(物理地址),在資源管理器中進行資源申請,並對內核代碼和數據段進行資源申請。
resource_init();
#ifdef CONFIG_SMP
plat_smp_setup();
#endif
}
在內核初始化函數start_kernel執行到最後,就是調用rest_init函數,這個函數的主要使命就是創建並啓動內核線程init
static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
rcu_scheduler_starting();#內核RCU鎖機制調度啓動
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
#創建kernel_init內核線程,內核的1號進程!!!!kernel_init 是要執行的函數的指針, NULL 表示傳遞給該函數的參數爲空, CLONE_FS | CLONE_SIGHAND 爲 do_fork 產生線程時的標誌,表示進程間的 fs 信息共享,信號處理和塊信號共享
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
1 創建kthreadd內核線程,它的作用是管理和調度其它內核線程。
2 它循環運行一個叫做kthreadd的函數,該函數的作用是運行kthread_create_list全局鏈表中維護的內核線程。
3 調用kthread_create創建一個kthread,它會被加入到kthread_create_list 鏈表中;
4 被執行過的kthread會從kthread_create_list鏈表中刪除;
5 且kthreadd會不斷調用scheduler函數讓出CPU。此線程不可關閉。
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
獲取kthreadd的線程信息,獲取完成說明kthreadd已經創建成功。並通過一個complete變量(kthreadd_done)來通知kernel_init線程。
/*
* 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();
此時內核本體進入了idle狀態,用循環消耗空閒的CPU時間片,該函數從不返回。在有其他進程需要工作的時候,該函數就會被搶佔!
}
0號進程和1號進程我們現在都知道是怎麼產生的,但是有個疑問就是start_kernel()中的rest_init()執行完畢後,內核就怎樣啓動了呢。
kernel_init()函數中do_basic_setup()函數主要是初始化設備驅動,完成其他驅動程序(直接編譯進內核的模塊)的初始化。內核中大部分的啓動數據輸出(都是個設備的驅動模塊輸出)都是這裏產生的。
接下來是打開根文件系統中的/dev/console,此處不可失敗
/* 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");
這是kernel_init打開的第一個文件,它也成爲了標準輸入,這裏需要打開/dev/console,如果沒有這個節點,系統就出錯,這個錯誤是經常碰到的,可能的原因是:
1、製作文件系統的時候忘記創建/dev/console節點
2、文件系統掛載問題,掛載上的文件系統不是什麼都沒有就是掛錯了節點
(void)sys_dup(0);
(void)sys_dup(0);
複製兩次標準輸入(0)的文件描述符(它是上面打開的/dev/console,也就是系統控制檯:
一個作爲標準輸出(1) 一個作爲標準出錯(2)
現在是標準輸入標準輸出標準出錯都是/dev/console了
· /*
· * 檢查是否有早期用戶空間的init程序。如果有,讓其執行
· *
· */
·
· 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, 我們已經完成了啓動初始化, and
· * 且我們本質上已經在運行。釋放初始化用的內存(initmem)段
· * 並開始用戶空間的程序..
· */
·
· init_post();
· return 0;
· }
在內核線程的最後執行了init_post函數,在這個函數中真正啓動了用戶空間進程init,這是一個非__init函數,強制讓它成爲非內聯函數,以防gcc讓它內斂到init()中併成爲init.text段的一部分,從此函數名可知,這個函數是運行在用戶控件的init程序之前
static noinline int init_post(void) __releases(kernel_lock){ }
current->signal->flags |= SIGNAL_UNKILLABLE;
設置當前進程(init)爲不可殺進程(忽略致命的信號)
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
如果ramdisk_execute_command有指定的init程序,就執行它
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
在檢查完ramdisk_execute_command和execute_command爲空的情況下,順序執行以下初始化程序:如果都沒有找到就打印錯誤信息。在做系統移植的時候經常碰到這樣的錯誤信息,出現這個信息很可能是因爲:
1.啓動參數配置有問題,指定了init程序,但是沒有找到,且默認的那四個程序文件也不在文件系統中。
2.文件系統掛載有問題,文件不存在
3.init程序沒有執行權限
至此內核的初始化結束,正式進入用戶空間的初始化過程。
這篇總結中大致講了下內核啓動的過程、源代碼的功能作用以及某些函數存在的意義,從中可以初步瞭解到內核被加載後做的工作以及怎麼讓內核啓動從而從內核空間轉向用戶空間,主要知識都是查資料總結的,裏面還有很多函數的功能以及代碼的實現都沒有總結,我會通過以後的學習慢慢積累完善。