嵌入式Linux開發必懂:基於ARM64的init用戶進程究竟如何啟動?

2020-12-20 電子工程專輯
[導讀] 前面的文章有提到linux啟動的第一個進程為init,那麼該進程究竟是如何從內核啟動入口一步一步運行起來的,而該進程又有些什麼作用呢?做嵌入式Linux開發,有必要對這些概念了解清楚。本文基於ARM體系的內核啟動做出解析。

跳轉內核前基本準備

參考./Documentation/arm64/booting.txt

Bootloader至少完成以下基本的初始化準備:

  • 設置並初始化RAM(必須),引導加載程序應找到並初始化內核將用於系統中易失性數據存儲的所有RAM。它以機器相關的方式執行此操作。(它可以使用內部算法來自動定位和調整所有RAM的大小,或者可以使用機器中RAM的知識或引導加載程序設計者認為合適的任何其他方法。)

  • 設置設備樹dtb(必須) , 設備樹blob(dtb)必須8位元組對齊,並且大小不能超過2兆字節。由於dtb將使用最大2 MB的塊進行映射以可緩存,因此它不能放置在必須使用任何特定屬性進行映射的任何2M區域內。注意:v4.2之前的版本還要求將DTB放置在512 MB區域內,從內核映像下方的text_offset字節開始計算。

  • 解壓縮內核映像(可選),AArch64內核當前不提供解壓縮器,因此如果使用壓縮的Image目標(例如Image.gz),則需要由引導加載程序執行解壓縮(gzip等)。對於未實現此要求的引導加載程序,可以使用未經壓縮內核編譯。

  • 調用內核映像(必須)。壓縮內核頭部如下:

    u32 code0;             /* 可執行code   */
    u32 code1;             /* 可執行code   */
    u64 text_offset;       /* 加載偏移,小端 */
    u64 image_size;        /* 有效映象尺寸,小端 */
    u64 flags;             /* 內核標誌, 小端    */
    u64 res2  = 0;         /* 保留 */
    u64 res3  = 0;         /* 保留 */
    u64 res4  = 0;         /* 保留 */
    u32 magic = 0x644d5241/* 幻數,小端, "ARM\x64"  */
    u32 res5;          /* 保留(用於PE COFF偏移量) */

進入內核之前,必須滿足以下條件:

  • 禁止所有具有DMA功能的設備,以免內存被虛假錯誤的網絡數據包或磁碟數據損壞。

  • 主CPU通用寄存器設置:

  • CPU模式

  • Caches, MMUs

    • MMU必須關閉。

    • 指令緩存可以打開或關閉。

    • 與加載的內核映像相對應的地址範圍必須清除到PoC。如果存在系統緩存或啟用了緩存的其他相關主伺服器,則通常需要通過VA而不是通過設置/方式操作來維護緩存。

    • 遵循VA對架構化緩存維護的系統緩存。必須配置並啟用操作。

    • 不遵循VA對架構化混存維護的系統緩存,必須配置和禁用操作(不推薦)。

  • 架構定時器

  • 連貫性

  • 系統寄存器

  • 主CPU必須直接跳轉到內核映像的第一條指令。此CPU傳遞的設備樹Blob必須為每個cpu節點包含一個「啟用方法」屬性。支持的啟用方法如下所述。引導加載程序將生成這些設備樹屬性,並將其插入內核入口之前的blob中。

  • 具有「旋轉表」啟用方法的CPU在其cpu節點中必須具有「 cpu-release-addr」屬性。此屬性標識自然對齊的64位零初始化內存位置。

  • 具有「 psci」啟用方法的CPU應該保留在內核之外(即,在內存節點中描述給內核的內存區域之外,或者在內核中通過/ memreserve /描述給內核描述的內存保留區域之外)。設備樹)。內核將按照ARM文檔編號ARM DEN 0022A(「 ARM處理器上的電源狀態協調接口系統軟體」)中的說明發出CPU_ON調用,以將CPU帶入內核。設備樹應包含一個「 psci」節點,參考/bindings/arm/psci.txt.

  • 第二CPU通用寄存器設置的x0/x1/x2/x3都為0,保留。

內核啟動init總過程

內核啟動有兩種方式,壓縮格式或不壓縮格式,壓縮模式所不同的就是其入口位於arch/ /boot/compressed/head.S,為與該路徑下的代碼主要負責執行執行前期的初始化為解壓內核做準備。當完成解壓內核後,就跳轉到./arm/kernel/head.S開始啟動內核。

本文僅分析不壓縮方式啟動內核,通過分析內核代碼,整理出內核啟動過程的部分順序如下:


內核的啟動與U-Boot一樣,前面一段是彙編代碼,然後跳轉到C代碼。彙編的入口在

./arm/kernel/head.S中,符號名為__HEAD,該文件包含了head-common.S。

所以從啟動用戶首進程init而言,我將其分成大致分為四大步:

  • head.S ,初始化通用部分環境,與晶片無關

  • start_kernel, head.S完成後,調準到start_kernel,進入C函數執行,該函數為於./init/main.c中

  • rest_init,創建init進程,以及kthredd進程,其中Init進程號為1,kthredd為內核進程。

  • 啟動調度器,執行kernel_init,該函數將調用根文件系統中的init執行文件,至此用戶空間的init進程就啟動起來了。

head.S/head-common.S作用

剖析彙編代碼比較枯燥,這裡就不進行描述了。僅就其作用進行總結:

start_kernel階段

該函數主要完成以下以下工作:

  • lockdep 死鎖檢測模塊初始化,

  • RCU機制初始化:RCU(Read-Copy Update),顧名思義就是讀-拷貝修改,它是基於其原理命名的。對於被RCU保護的共享數據結構,讀者不需要獲得任何鎖就可以訪問它,但寫者在訪問它時首先拷貝一個副本,然後對副本進行修改,最後使用一個回調(callback)機制在適當的時機把指向原來數據的指針重新指向新的被修改的數據。這個時機就是所有引用該數據的CPU都退出對共享數據的操作。

  • SMP初始化,對稱多處理"(Symmetrical Multi-Processing)簡稱SMP,完成CPU ID的創建。

  • debug_objects_early_init,負責調試對象初始化,以便於內核調試

  • lockdep死鎖檢測模塊初始化,lockdep的工作方式是在內核中的鎖定調用包起來。每次採用或釋放特定類型的鎖時,都會記錄該事實以及輔助詳細信息,例如處理器當時是否正在處理中斷。Lockdep還記錄了使用新鎖時還持有哪些其他鎖;這是lockdep能夠執行的許多檢查的關鍵。

  • 調用setup_arch(&command_line),該函數位於arch/

    /kernel/setup.c,用於解析從bootloader傳入的引導命令行。
  • 初始化控制臺,以列印啟動日誌。

  • 初始化其他各子系統,如VFS,trace,內存管理子系統,FORK子系統,cgroup,acpi,proc文件系統,內核服務,緩存等等。

  • ……

  • 調用rest_init,以創建init進程以及內核進程,並啟動內核調度器。

rest_init階段

代碼如下,其注釋如下,主要作用就是先創建init進程使其進程號為1,這是第一個用戶空間進程,該進程執行後在衍生出一系列的應用進程。具體取決於啟動腳本或者Init的具體實現。然後創建內核進程kthreadd,該進程用於管理內核進程。該進程進程號為2。所有內核進程都是kthreadd的後代, kthreadd枚舉其他內核線程;它提供了接口例程,內核服務可以在運行時動態生成其他內核進程。通過kthread_create_list維護其他內核進程。可以使用ps -ef命令從命令行查看內核線程-它們顯示在[方括號]中:

static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    smpboot_thread_init();

    /*創建init進程,第一個用戶空間進程我們
    *需要首先生成init,以便它獲得pid 1,但是
    *init任務最終將要創建kthread,如果在創建
    *kthreadd之前對其進行調度,則OOPS。*/

    kernel_thread(kernel_init, NULL, CLONE_FS);
    numa_default_policy();

    /*創建kthreadd用於管理內核線程*/
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

    /*RCU 鎖*/
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    /*讓內核進程kthreadd處於就緒態TASK_NORMAL*/
    complete(&kthreadd_done);

    /* 啟動調度器      */
    init_idle_bootup_task(current);
    schedule_preempt_disabled();
    /* 禁用搶佔的情況下調用cpu_idle */
    cpu_startup_entry(CPUHP_ONLINE);
}

kernel_init階段

當內核調度器運行後,就會執行kernel_init函數:

static int __ref kernel_init(void *unused)
{
    int ret;

    kernel_init_freeable();
    /* 同步完成所有初始化操作 */
    async_synchronize_full();
#ifndef CONFIG_INITCALLS_THREAD
    free_initmem();
#endif
    mark_readonly();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();

    flush_delayed_fput();

    /*如果使能了ramdisk執行命令啟動init*/
    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
               ramdisk_execute_command, ret);
    }

    /* 如果execute_command使能,則按命令啟動init*/
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
              execute_command, ret);
    }

    /*如果前面兩項都沒有使能,則依次在根文件系統下尋找並啟動Init*/
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}

從而init用戶進程就啟動起來了,至於最終執行的是哪一個Init可執行文件,取決於系統移植的配置,如前文描述,常見的有busybox init,systemV init,systemD init等等。

相關焦點

  • 基於S3C2440的嵌入式Linux根文件系統構建
    嵌入式Linux早已成為IT界家喻戶曉的一個名字,使用Linux進行嵌入式產品開發有一個很大的優勢,就是開發資源豐富,且成本低廉,嵌入式Linux作業系統越來越受到重視,其應用也越來越廣泛。而文件系統作為作業系統的重要組成部分,用於控制對數據文件及設備的存取,提供對文件和目錄的分層組織形式,數據緩衝以及對文件存取權限的控制。
  • 怎樣設置嵌入式Linux開機自動運行用戶程序?
    本文是Linux嵌入式開發日常記錄,今天和大家分享下如何讓嵌入式Linux系統開機後自動運行用戶編寫的程序?嵌入式系統的目標是要執行用戶編寫的、完成指定任務的程序,並且這個用戶程序應該在系統啟動後自動執行。
  • 基於busybox的嵌入式Linux根文件系統的的製作方法
    根文件系統一直是Linux系統不可或缺的組件,在嵌入式Lin-ux中,內核在啟動期間進行的最後操作之一就是安裝根文件系統。Busybox是構建嵌入式Linux根文件系統的軟體,用它製作根文件系統簡單、方便,而且設置靈活。
  • 2020徵文-開發板鴻蒙liteos-a如何啟動第一個用戶進程Init_lite
    第一個用戶態進程init_liteInit_lite的位置:涉及以下模塊:· init啟動引導支持使用LiteOS-A內核的平臺,當前包括:Hi3516DV300平臺和Hi3518EV300平臺。負責處理從內核加載第一個用戶態進程開始,到第一個應用程式啟動之間的系統服務進程啟動過程。
  • 嵌入式Linux內核啟動主要分為這三個階段
    【IT168 資訊】嵌入式linux內核的啟動全過程主要分為三個階段。第一階段為內核自解壓過程,第二階段主要工作是設置ARM處理器工作模式、使能MMU、設置一級頁表等,而第三階段則主要為C代碼,包括內核初始化的全部工作,下面是詳細介紹。
  • 嵌入式Linux啟動時間優化的秘密之一工具鏈/應用程式優化
    >   01   工具鏈/應用程式優化   導讀:嵌入式Linux在應用中往往希望系統能在儘量短的時間內啟動,以提高用戶體驗。而且在有的應用場合,對啟動時間具有嚴格的時間要求,尤其在工業或者醫療器械應用領域。此時如何加快Linux的啟動,將成為一個挑戰,對於大多數應用開發人員而言,由於Linux系統的複雜性,對於如何提高啟動速度,往往無從下手。那麼閱讀完本文,將獲得清晰完整的解決思路。
  • 採用busybox的嵌入式Linux根文件系統的的製作方法
    根文件系統一直是Linux系統不可或缺的組件,在嵌入式Lin-ux中,內核在啟動期間進行的最後操作之一就是安裝根文件系統。Busybox是構建嵌入式Linux根文件系統的軟體,用它製作根文件系統簡單、方便,而且設置靈活。
  • 工程師嵌入式Linux自學筆記及體會
    初始化完成後,計算機就準備運行嵌入式應用。也許一個,也許是多個應用程式組成了嵌入式應用,但通常首先調用的是 init(通過 loader 向核心傳入init=/program 可以定製首先運行的程序)。桌面 linux 中,init 會讀取/etc/inittab 文件,來決定執行級別和哪些腳本和命令。
  • 學嵌入式開發難嗎_學好嵌入式開發需要多久
    搞企業應用軟體的IT企業,這個用戶的系統搞完了,又得去搞下一個用戶的,而且每個用戶的需求和完成時間都得按客戶要求改變,往往疲於奔命,重複勞動。相比而言,搞嵌入式系統的公司,都有自己的產品計劃,按自己的節奏行事。所開發的產品通常是通用的,不會因客戶的不同而修改。一個產品型號開發完了,往往有較長一段空閒時間(或只是對軟體進行一些小修補),有時間進行充電和休整。
  • 基於嵌入式Linux的MapInfo格式地圖顯示
    而市場上實現這種功能的大部分平臺都是基於WINCE這種作業系統,可以採用EVC開發,開發流程明確,驅動支持較豐富,但總體開發成本較高。由於Linux其免費,可配置,網絡性能,MiniGUI也是輕型、高性能、高可靠性、可配置的GUI,本文採用MiniGUI在ARM-linux平臺上實現了簡單的地圖顯示功能。
  • 嵌入式Linux NFS 根文件系統的構建及研究
    摘要:在嵌入式Linux系統開發過程中,根文件系統是構建嵌入式Linux系統的重要組成部分。為了方便和簡化嵌入式Linux開發過程中的調試過程,主要研究了如何使用Busybox構建出基本的嵌入式Linux根文件系統,包括Busybox的配置、編譯和安裝。
  • 嵌入式軟體工程師一般都在開發什麼
    打開APP 嵌入式軟體工程師一般都在開發什麼 發表於 2019-04-10 15:22:51 在嵌入式這個行業,只要工作了幾年的同仁。
  • 學習嵌入式Linux,做底層還是應用?底層要掌握哪些技能?
    三、嵌入式系統開發:掌握Linux系統配置,精通處理器體系結構、編程環境、指令集、尋址方式、調試、彙編和混合編程等方面的內容;掌握Linux文件系統製作,熟悉各種文件系統格式(YAFFS2、JAFFS2、RAMDISK等);熟悉嵌入式Linux啟動流程,熟悉Linux配置文件的修改;掌握內核裁減、內核移植、交叉編譯、內核調試、啟動程序Bootloader
  • Linux嵌入式驅動開發——ioctl接口
    第三個分區 16-29 表示傳遞的數據大小第四個分區 30-31 代表讀寫的方向00:表示用戶程序和驅動數據沒有數據傳遞10:表示用戶程序從驅動裡面讀取數據01:表示用戶程序向驅動裡面寫入數據11:先寫數據到驅動,然後再從驅動把數據讀出來(不常用)命令的合成宏與分解宏合成宏
  • linux內核啟動流程
    start_kernel是所有Linux平臺進入系統內核初始化後的入口函數,它主要完成剩餘的與 硬體平臺相關的初始化工作,在進行一系列與內核相關的初始化後,調用第一個用戶進程- init 進程並等待用戶進程的執行,這樣整個 Linux內核便啟動完畢。該函數位於init/main.c文件中,主要工作流程如圖3所示:
  • Linux:運行級別與重新啟動
    通常這裡沒有聯網,沒有(或者非常少)守護進程運行。在一些系統上,您必須通過登錄來進行認證,但在其他系統上您可以直接以根用戶開始操作 shell 提示符。單用戶模式可能是一個救生圈,也可能是毀壞您的系統,因此,不管任何時候,當您使用根用戶權限時都應該小心注意。完成後立即重新啟動到一個正常多用戶模式。和切換到正常多用戶運行級別一樣,您也可以使用 telinit 1 切換到單用戶模式。
  • 基於Qt/E的嵌入式Linux中文輸入法的設計與實現
    Qt/E是Nokia公司發布的面向嵌人式系統的Qt版本,正日益成為嵌入式GUI開發的主流,被越來越多的國內外廠商用於可攜式電子設備的開發,然而它並沒有提供中文輸入法。目前國內在Qt/E平臺下開發的中文輸入法主要有2種方式:  1)移植現有的PC機系統下的基於Qt/X1 1的中文輸人法,移植了基於X Window下的XIM輸入協議的中文輸入法,但其需要X Window下Xlib庫的支持,這將增大系統開銷,減慢顯示的速度。
  • 基於Yocto Project的定製嵌入式Linux產品設計淺析
    基於Yocto Project的定製嵌入式Linux產品設計淺析 電子發燒友 發表於 2019-05-30 15:42:07 引言 Linux作為嵌入式系統的主要工具
  • Linux 系統內核的調試
    調試是軟體開發過程中一個必不可少的環節,在 Linux 內核開發的過程中也不可避免地會面對如何調試內核的問題。但是,Linux 系統的開發者出於保證內核代碼正確性的考慮,不願意在 Linux 內核原始碼樹中加入一個調試器。他們認為內核中的調試器會誤導開發者,從而引入不良的修正[1]。
  • 嵌入式Linux+Android學習路線圖
    怎麼學習嵌入式Linux作業系統本文假設您是零基礎,以實用為主,用最快的時間讓你入門;後面也會附上想深入學習時可以參考的資料。在實際工作中,我們從事的是「作業系統」周邊的開發,並不會太深入學習、修改作業系統本身。①作業系統具有進程管理、存儲管理、文件管理和設備管理等功能,這些核心功能非常穩定可靠,基本上不需要我們修改代碼。