轉自:https://zhuanlan.zhihu.com/p/105284305
什么是kexec?
可從當前正在運行的內核直接跳轉到新內核
為什么需要kexec?
跳過boot階段,減少重啟時間
kexec整體思路如下
1)新的kernel鏡像和initrd鏡像連續存儲在內存中,initrd的位置記錄在boot_params中
2)切換到新內核就是跳轉到新的kernel鏡像所在內存位置,CPU執行其entry的代碼即可,新的內核通過boot_params記錄的initrd位置完成根文件系統內容的加載
原理不復雜,但受到一些實際情況的限制,所以在實現上會略復雜一些。
首先,kernel鏡像有指定的入口地址,kernel鏡像要加載到入口地址位置才能正常啟動,而這塊內存正在被當前內核使用,所以kernel鏡像需要臨時存放的內存,在跳轉前要將內容搬移到入口地址。
其次,initrd的體積可能較大,找不到用於存儲initrd的連續大段物理內存,因此需要分散存儲在能申請到的內存頁面中,在kernel跳轉前搬移拼接到目的地址。
簡單講,鏡像先臨時存放,切換時再搬移到目標地址。
以kexec -s uImage --ramdisk=./ramdisk.bin為例進行說明。
uImage和ramdisk.bin都是要使用的內容,抽象為segment對象進行管理,那么這里存在2個segment,分別對應uImage和ramdisk.bin
內核使用alloc_pages來申請頁面,如果碎片化嚴重,很難申請到高階物理內存,所以kexec的做法是循環申請,每次申請一個頁面。這會帶來一個問題,屬於同一個鏡像的頁面彼此不連續,需要對這些臨時承載鏡像內容的頁面進行管理。
為此,kexec引入了entry的概念,其實就是一個unsigned long對象,記錄申請到的page的物理地址,並利用低位的4bit來對entry進行分類,共計有如下4類entry。
1)IND_DESTINATION,用於記錄segment要搬移到的目標地址
2)IND_SOURCE,用於記錄segment內容所在的物理地址
3)IND_INDIRECTION
4)IND_DONE
entry本身也需要內存來存儲,為此,kexec首先申請一個page用於存儲entry,如果page存滿entry時,需要再新分配一個page,並使用之前page內最后一個entry記錄新分配page的物理地址,這個entry就是3)IND_INDIRECTION類型entry。
那么uImage作為第一個segment,會對應一個IND_DESTINATION類型entry和若干IND_SOURCE類型entry,ramdisk.bin作為第二個segment同樣會對應一個IND_DESTINATION類型entry和若干IND_SOURCE類型entry,當全部segment存儲完成,kexec會加入4)IND_DONE類型entry,表示全部segment的內容到此記錄完成。
結合下圖會有較直觀的認識,IND_DESTINATION類型entry表示一個segment的開始,直到下一個IND_DESTINATION類型entry或IND_DONE類型entry之前的IND_SOURCE類型entry都記錄了該segment內容存儲的物理地址。
接下來的事情相對簡單,在用戶執行kexec -e后,會進入跳轉流程,將segment的內容依次搬移到IND_DESTINATION類型entry記錄的物理地址對應位置,結合下圖可有較直觀的認識。
全部segment內容搬移完成,跳轉到入口地址,就切換到了新的kernel。