0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

RK3576 Linux 内核 init 目录深度解析:从内核入口到用户空间启动

jf_44130326 来源:Linux1024 作者:Linux1024 2026-04-06 10:38 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

文章导读:本文深入剖析RK3576平台Linux 6.1内核的init目录代码,从内核入口到用户空间启动,完整拆解嵌入式Linux的启动底层逻辑,涵盖目录架构、核心文件、启动流程、配置调试等全维度内容,适合内核开发者、嵌入式工程师以及对系统启动流程感兴趣的技术人员。

一、引言:为什么init目录如此重要?

当按下电源键,RK3576开发板从“无响应”到进入Linux命令行,这一过程的核心调度者就是init目录。它是Linux内核启动的“市政厅”,承接从汇编代码到C代码的跳转,完成系统基础环境搭建,最终启动第一个用户进程,是内核空间到用户空间的关键桥梁。

其核心职责可概括为5点:

系统初始化:汇编跳转到C代码的第一个落脚点,统筹内核各子系统启动

•内存管理:建立内核内存分配体系,初始化页表、分配器等核心模块

•文件系统:解析启动参数,挂载根文件系统,支持initramfs/initrd等临时文件系统

驱动加载:通过initcall机制按优先级初始化所有硬件驱动

进程创建:创建0号、1号、2号核心进程,启动第一个用户空间init进程

二、init目录架构全景图

RK3576所基于的Linux 6.1内核中,init目录包含13个核心文件,各文件分工明确、相互配合,构成内核启动的核心框架,文件结构及核心功能如下:

kernel-6.1/init/├── main.c        #  核心:内核启动主流程,包含start_kernel()入口├── init_task.c      #  初始任务结构体定义,定义PID 0的idle进程├── calibrate.c      #  延迟校准,计算BogoMIPS值和loops_per_jiffy├── version.c       #  内核版本信息,定义版本字符串、编译信息├── version-timestamp.c  #  构建时间戳,自动生成编译时间(Makefile调用)├── do_mounts.c      #  根文件系统挂载核心,解析root=参数├── do_mounts.h      #  挂载相关头文件,定义挂载函数声明和宏├── do_mounts_rd.c    #  RAM磁盘基础处理,解析rd=参数,初始化ramdisk├── do_mounts_initrd.c  #  initrd专用处理,挂载并执行initrd内的/linuxrc├── initramfs.c      #  initramfs解包与加载,处理临时根文件系统├── noinitramfs.c     #  无initramfs时的兜底,创建最小化rootfs├── Kconfig        #  内核配置选项,定义init目录相关编译开关└── Makefile       #  编译规则,指定文件编译依赖和生成逻辑
wKgZO2nTHQGACEnfAAD3Ew4WPUI440.jpg

三、核心概念:Linux内核启动三层架构与关键数据结构

RK3576的Linux内核启动分为三个核心阶段,从汇编启动到C代码初始化,再到用户空间启动,各阶段衔接紧密,同时依赖三大核心数据结构支撑,构成启动的基础框架。

3.1启动三层架构与核心函数

┌─────────────────────────────────────────────────────────────────┐│         Linux内核启动流程核心框架          │├─────────────────────────────────────────────────────────────────┤│┌──────────────┐ ┌──────────────┐ ┌──────────────┐  │││ 第一阶段  │───│ 第二阶段  │───│ 第三阶段  │  │││ 汇编启动  │ │C代码初始化│ │ 用户空间启动│  ││└──────────────┘ └──────────────┘ └──────────────┘  ││    │         │         │      ││    ▼         ▼         ▼      ││┌──────────────┐ ┌──────────────┐ ┌──────────────┐  │││setup_arch()│ │start_kernel()│ │kernel_init()│  │││ 架构相关  │ │ init/main.c│ │ 第一个内核 │  │││ 硬件初始化 │ │ 核心初始化 │ │ 线程启动  │  ││└──────────────┘ └──────────────┘ └──────────────┘  ││               │         │      ││               ▼         ▼      ││           ┌──────────────┐ ┌──────────────┐  ││           │ rest_init()│──│ 运行/sbin/init│  ││           │创建init线程 │ │ 进入用户空间 │  ││           └──────────────┘ └──────────────┘  │└─────────────────────────────────────────────────────────────────┘

第一阶段:架构相关汇编代码执行,调用setup_arch()完成RK3576硬件初始化、设备树解析、内存布局设置;

第二阶段:进入start_kernel()(main.c),统筹初始化内存、调度、中断、VFS等核心子系统,执行所有initcall初始化函数;

第三阶段:通过rest_init()创建内核线程,最终调用/sbin/init进入用户空间,完成启动。

3.2三大核心数据结构

所有进程、内存、文件系统的初始化都基于以下三个基础结构,是内核启动的“基石”:

init_task:初始进程结构体(PID 0),也叫idle/swapper进程,是所有进程的祖先,内核线程的模板;

init_mm:初始内存描述符,建立内核内存管理的基础框架,供内核线程借用;

init_fs:初始文件系统结构,定义根文件系统的基础属性,支撑后续文件系统挂载。

四、逐文件深度解析:init目录核心逻辑

4.1 main.c -内核启动的“心脏”

文件定位:kernel-6.1/init/main.c(约900行)

核心功能

作为Linux内核启动的核心编排器,定义了start_kernel()这一核心入口,统筹所有子系统初始化,是C代码层面内核启动的总控中心

start_kernel()核心执行流程

start_kernel()是内核启动的核心函数,执行顺序严格遵循“基础环境→子系统→初始化→进程创建”逻辑,RK3576的架构特定代码也在该流程中执行:

wKgZO2nTHQKAYxqCAAEANh27rl4418.jpg

1.set_task_stack_end_magic(&init_task):设置初始任务栈结束魔数,用于栈溢出检测

2.smp_setup_processor_id():设置当前CPU ID,标识SMP系统中的启动CPU

3.cgroup_init_early():早期cgroup初始化,为资源管理打下基础

4.local_irq_disable():关闭本地中断,进入临界区,保证初始化原子性

5.boot_cpu_init():标记启动CPU为在线状态,完成CPU基础初始化

6.setup_arch(&command_line):RK3576架构专属初始化,解析设备树、设置内存布局、初始化页表、注册平台设备

7.setup_boot_config():解析bootconfig,处理额外的启动配置参数

8.mm_init():内存管理子系统初始化,初始化页面分配器、SLAB/SLUB分配器、vmalloc

9.sched_init():进程调度器初始化,建立内核进程调度框架

10.init_IRQ() / time_init():初始化中断控制器和系统时钟,建立中断处理机制

11.console_init():控制台初始化,此后内核可通过printk输出日志

12.vfs_caches_init():虚拟文件系统缓存初始化,为文件系统挂载做准备

13.do_initcalls():执行所有__init修饰的初始化函数,按8级优先级执行

14.rest_init():创建kernel_init(PID 1)和kthreadd(PID 2)进程,PID 0变为idle进程

关键机制:initcall初始化层级

do_initcalls()是驱动和子系统初始化的核心,通过8级优先级确保依赖项先初始化,避免子系统启动顺序错乱,RK3576的所有硬件驱动均通过该机制初始化,层级及用途如下:

┌─────────────────────────────────────────────────────────────────┐│           initcall 层级结构              │├─────────────────────────────────────────────────────────────────┤│ 层级     优先级  用途                  ││ ───────────────────────────────────────────────────────────── ││ pure    0    最早期初始化,不依赖任何其他子系统    ││ core    1    内核核心子系统初始化           ││ postcore  2    核心子系统之后,架构特定初始化之前    ││ arch    3    RK3576架构特定初始化           ││ subsys   4    外设子系统初始化(PCI、USBSD等)    ││ fs     5    文件系统子系统初始化(ext4、tmpfs等)  ││ device   6    硬件设备驱动初始化(RK3576外设驱动)  ││ late    7    最后阶段初始化,依赖所有前置子系统    ││                                 ││ 使用示例:                           ││ staticint__initmy_driver_init(void){ ... }         ││ device_initcall(my_driver_init);// 驱动加入6级初始化      │└─────────────────────────────────────────────────────────────────┘

实现原理:通过宏将初始化函数放入内核镜像的特定段(如.initcall6.init),do_initcalls()遍历各段依次执行函数,保证顺序性。

关键代码片段

定义了系统状态机、启动命令行、init进程参数等核心全局变量,是内核启动的基础配置:

// 1. 系统启动状态机,标识内核当前启动阶段enumsystem_statessystem_state __read_mostly;// 状态:SYSTEM_BOOTING(启动中)、SYSTEM_SCHEDULING(调度器就绪)、SYSTEM_FREEING_INITMEM(释放init内存)、SYSTEM_RUNNING(正常运行)// 2. 启动命令行处理,存储U-Boot传递的启动参数char__initdata boot_command_line[COMMAND_LINE_SIZE];char*saved_command_line;   // 保存原始命令行staticchar*static_command_line; // 用于参数解析的命令行// 3. init进程相关参数,定义默认启动程序和环境变量staticchar*execute_command;     // init=参数指定的自定义程序staticchar*ramdisk_execute_command ="/init"; // initrd/initramfs中的默认initstaticconstchar*argv_init[MAX_INIT_ARGS+2] = {"init",NULL, };constchar*envp_init[MAX_INIT_ENVS+2] = {"HOME=/","TERM=linux",NULL, };

4.2 init_task.c - 0号进程的“身份证”

文件定位:kernel-6.1/init/init_task.c

核心概念

定义了init_task结构体,即Linux内核的第一个进程(PID 0),也称为idle进程或swapper进程。它是所有进程的祖先,内核中所有进程均通过fork从它衍生,同时也是CPU空闲时执行的进程,负责进入低功耗状态。

wKgZO2nTHQKARVtjAABDfIs94bA592.jpg

核心数据结构

init_task是struct task_struct类型的全局变量,包含进程调度、内存管理、文件系统、信号处理等所有核心属性,是内核进程的“模板”,关键代码如下:

// 初始信号结构,为PID 0提供基础信号处理能力staticstructsignal_struct init_signals = {  .nr_threads =1,  .thread_head = LIST_HEAD_INIT(init_task.thread_node),  .wait_chldexit = __WAIT_QUEUE_HEAD_INITIALIZER(init_signals.wait_chldexit),  .shared_pending = {    .list = LIST_HEAD_INIT(init_signals.shared_pending.list),    .signal = {{0}}  },  .rlim = INIT_RLIMITS, // 初始资源限制  .pids = {    [PIDTYPE_PID] = &init_struct_pid,    [PIDTYPE_TGID] = &init_struct_pid,    [PIDTYPE_PGID] = &init_struct_pid,    [PIDTYPE_SID] = &init_struct_pid,  },};// 核心:初始任务结构体,定义PID 0的所有属性structtask_struct init_task = {  .__state =0,          // 任务状态:运行中  .stack = init_stack,      // 指向初始栈空间  .usage = REFCOUNT_INIT(2),   // 引用计数  .flags = PF_KTHREAD,      // 标记为内核线程
 // 调度相关:默认普通分时调度,优先级100  .prio = MAX_PRIO -20,  .static_prio = MAX_PRIO -20,  .normal_prio = MAX_PRIO -20,  .policy = SCHED_NORMAL,
 // CPU亲和性:允许在所有CPU上运行(适配RK3576多核)  .cpus_ptr = &init_task.cpus_mask,  .cpus_mask = CPU_MASK_ALL,  .nr_cpus_allowed = NR_CPUS,
 // 内存管理:内核线程无用户态内存,借用init_mm  .mm = NULL,  .active_mm = &init_mm,
 // 进程关系:自身作为父进程(所有进程的祖先)  .real_parent = &init_task,  .parent = &init_task,  .group_leader = &init_task,
 // 文件系统:绑定初始文件系统结构  .fs = &init_fs,  .files = &init_files,
 // 信号处理与命名空间:绑定初始结构  .signal = &init_signals,  .sighand = &init_sighand,  .nsproxy = &init_nsproxy,
 // 凭证与名称:root权限,进程名"swapper"  .real_cred = &init_cred,  .cred = &init_cred,  .comm = INIT_TASK_COMM,  .thread = INIT_THREAD,     // RK3576架构相关线程状态};

init_task的生命周期

PID 0的生命周期贯穿内核启动全程,角色随启动阶段动态变化:

1.汇编启动阶段:汇编代码为init_task设置栈空间,完成基础初始化;

2.C代码初始化阶段:start_kernel()中基于init_task完成各子系统初始化,此时它是内核的“主进程”;

3.rest_init()阶段:创建kernel_init(PID 1)和kthreadd(PID 2)后,init_task变为idle进程

4.运行阶段:CPU无其他可运行进程时,执行init_task的cpu_idle_loop(),进入低功耗状态,RK3576的每个CPU核心都有一个专属的idle进程(swapper/n,n为CPU编号)。

4.3 calibrate.c - BogoMIPS计算的秘密

文件定位:kernel-6.1/init/calibrate.c

核心概念:BogoMIPS

BogoMIPS =Bogo(假的)+MIPS(每秒百万条指令),并非CPU的实际性能指标,而是一个延迟校准值,用于计算udelay()、ndelay()等内核延迟函数的循环次数,保证延迟的准确性。

其核心计算目标是loops_per_jiffy(每个jiffies的循环次数),jiffies是内核基本时间单位,通常1 jiffies = 1/HZ 秒(RK3576默认HZ=1000,即1ms)。

校准原理与优先级

内核提供5种校准方式,按精度从高到低、优先级从高到低执行,确保在不同硬件环境下都能得到准确的loops_per_jiffy:

1.预设值:用户通过lpj=启动参数指定,直接使用无需校准;

2.精确值:从定时器频率计算得到lpj_fine,适配硬件定时器的高精度

3.已知值:多核系统中,直接使用其他CPU已校准的结果;

4.直接校准:架构支持read_current_timer时,通过硬件定时器直接计算;

5.收敛校准:通用方法,通过二分搜索调整lpj,精确到1/256 jiffies。

收敛校准核心算法:等待jiffies变化→执行lpj*band次空循环→检查jiffies是否变化→过快则减少lpj,过慢则增加lpj→反复迭代直至达到精度要求。

关键代码与计算示例

// 全局变量:存储校准结果和预设值unsignedlongloops_per_jiffy = (1<<12);  // 初始猜测值:4096unsigned long lpj_fine;                    // 硬件定时器计算的精确值unsigned long preset_lpj;                  // 用户通过lpj=指定的预设值// 核心校准函数void calibrate_delay(void){    unsigned long lpj;    // 按优先级选择校准方式    if (preset_lpj) lpj = preset_lpj;    else if (lpj_fine) lpj = lpj_fine;    else if ((lpj = calibrate_delay_is_known())) ;    else if ((lpj = calibrate_delay_direct()) != 0) ;    else lpj = calibrate_delay_converge(); // 通用兜底方案
    loops_per_jiffy = lpj;    // 打印BogoMIPS值,内核启动日志中可查看    pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)n",        lpj/(500000/HZ), (lpj/(5000/HZ)) % 100, lpj);}

BogoMIPS计算示例(RK3576默认HZ=1000)

若测得loops_per_jiffy=500000,则:

BogoMIPS= lpj / (500000/ HZ) =500000/ (500000/1000) =1000.00

表示RK3576的CPU每毫秒可执行约50万次简单空循环,延迟函数将基于此值计算循环次数。

4.4 do_mounts.c -根文件系统挂载核心

文件定位:kernel-6.1/init/do_mounts.c

核心职责

作为根文件系统挂载的核心,负责解析root=启动参数等待设备就绪自动探测文件系统类型挂载根文件系统,是内核空间进入用户空间的关键一步,若挂载失败,内核会直接panic。

root=参数解析流程

name_to_dev_t()函数负责解析root=参数,支持多种设备指定方式,适配RK3576的EMMC、SD卡、NFS、USB等多种根文件系统载体,解析顺序如下:

┌─────────────────────────────────────────────────────────────────┐│         name_to_dev_t() 解析流程            │├─────────────────────────────────────────────────────────────────┤│ 输入:root=参数值(如root=/dev/mmcblk0p1、root=PARTUUID=xxx)  ││ 1. 特殊设备名:/dev/nfs→网络根、/dev/cifs→SMB根、/dev/ram→RAM盘 ││ 2. PARTUUID=xxx:通过分区UUID查找设备,支持PARTNROFF偏移    ││ 3. PARTLABEL=xxx:通过GPT分区标签查找设备,无需记住设备名    ││ 4. /dev/xxx:直接指定设备名(如/dev/mmcblk0p1、/dev/sda1)   ││ 5. 设备号/16进制:主设备号:次设备号(如8:1)或16进制编码(如b301)│└─────────────────────────────────────────────────────────────────┘

根文件系统核心挂载流程

prepare_namespace()是挂载根文件系统的总函数,整合了设备等待、initrd/initramfs处理、实际挂载等所有逻辑,流程如下:

wKgZO2nTHQKAM0Q0AAELbjHbnG4564.jpg

1.rootdelay处理:若指定rootdelay=N,等待N秒再执行挂载,解决设备探测延迟问题;

2.wait_for_device_probe():等待异步设备探测完成,确保根设备已被内核识别;

3.md_run_setup():若配置RAID,完成RAID阵列初始化;

4.解析root=参数:将解析结果赋值给ROOT_DEV,标识根设备;

5.initrd_load():若存在initrd,加载并执行其内部的/linuxrc;

6.rootwait处理:若指定rootwait,循环等待根设备出现,直到设备就绪;

7.mount_root():实际挂载根文件系统,自动探测ext4、tmpfs等文件系统类型,支持NFS/CIFS/块设备;

8.devtmpfs_mount():挂载devtmpfs到/dev,创建设备节点;

9.init_chroot("."):将当前目录设置为新的根目录,完成根文件系统切换。

关键代码:自动文件系统探测

内核会自动获取所有支持的块设备文件系统类型,依次尝试挂载,直到成功,确保无需手动指定文件系统类型,关键代码如下:

// 自动探测并挂载块设备根文件系统void__initmount_block_root(char*name,intflags){ char*fs_names; intnum_fs, i;
 // 获取内核支持的所有块设备文件系统类型 if(root_fs_names)    num_fs = split_fs_names(fs_names, PAGE_SIZE, root_fs_names); else    num_fs = list_bdev_fs_names(fs_names, PAGE_SIZE);
 // 依次尝试每种文件系统类型,挂载成功则直接返回 for(i =0, p = fs_names; i < num_fs; i++, p += strlen(p)+1) {        err = do_mount_root(name, p, flags, root_mount_data);        if (err == 0)            return;    }    // 所有类型尝试失败,内核panic    panic("VFS: Unable to mount root fs on %s", b);}// 实际执行挂载操作static int __init do_mount_root(const char *name, const char *fs,                                 const int flags, const void *data){    ret = init_mount(name, "/root", fs, flags, data_page);    if (ret) goto out;
    init_chdir("/root");    s = current->fs->pwd.dentry->d_sb;  ROOT_DEV = s->s_dev; // 打印挂载成功日志,包含文件系统类型和设备号  printk(KERN_INFO"VFS: Mounted root (%s filesystem)%s on device %u:%u.n",     s->s_type->name, sb_rdonly(s) ?" readonly":"",     MAJOR(ROOT_DEV), MINOR(ROOT_DEV));}

4.5 initramfs.c -早期用户空间的载体

文件定位:kernel-6.1/init/initramfs.c

核心概念:initramfs

initramfs(initial RAM filesystem)是临时的内存根文件系统,在真正的根文件系统(如EMMC中的ext4)可用之前加载到内存中,相当于系统启动的“临时指挥部”。

其核心作用是解决驱动依赖问题:若根文件系统所在的设备(如NVMe、SATA)需要特定驱动才能识别,而该驱动未编译进内核,可将驱动放入initramfs,先启动initramfs加载驱动,再识别并挂载真实根文件系统。

wKgZO2nTHQKASvBdAADbLSmcErE148.jpg

initramfs的核心用途

1.加载根设备所需的驱动(SATA、NVMe、USB、网络等);

2.解密LUKS加密的根文件系统分区;

3.组装软件RAID/LVM逻辑卷;

4.支持网络启动,从NFS/iSCSI挂载根文件系统;

5.提供系统救援模式,紧急修复根文件系统。

核心格式:CPIO(Newc)

initramfs采用CPIO Newc格式存储,支持无压缩或gzip/xz/bzip2压缩,单个CPIO文件项由110字节固定头部+文件名+文件数据+4字节对齐填充组成,结构如下:

┌─────────────────────────────────────────────────────────────────┐│         initramfsCPIO格式解析(Newc)        │├─────────────────────────────────────────────────────────────────┤│ 固定头部(110字节,ASCII十六进制):              ││ 偏移 长度 字段     格式    示例值          ││0  6  c_magic    字符串  "070701"(Newc标识)   ││6  8  c_ino     十六进制 "00000001"(inode号)   ││14 8  c_mode    十六进制 "0000755"(可执行权限)  ││22 8  c_uid     十六进制 "00000000"(root用户)   ││30 8  c_gid     十六进制 "00000000"(root组)    ││38 8  c_nlink    十六进制 "00000001"(硬链接数)   ││46 8  c_mtime    十六进制 "65432100"(时间戳)    ││54 8  c_filesize  十六进制 "00001234"(数据长度)   ││62-10240 设备号/校验和 十六进制 "00000000"(默认值)   ││94 8  c_namesize  十六进制 "0000000b"(文件名长度)  ││102 8  c_check    十六进制 "00000000"(校验和)    ││                                ││ 数据区:                            ││110 可变  文件名    字符串  "./init"(以0结尾)    ││ 对齐 可变  文件数据   二进制   驱动/可执行程序/脚本   ││ 对齐 -   填充    0x00   按4字节对齐,补全空位   ││                                ││ 结束标记:文件名为"TRAILER!!!"的空文件项,标识CPIO归档结束  │└─────────────────────────────────────────────────────────────────┘

核心流程:解包与执行

populate_rootfs()是initramfs的核心解包函数,属于fs_initcall层级,在文件系统子系统初始化后执行,流程如下:

1.检查内置initramfs:若编译时将initramfs嵌入内核(initramfs_start非空),直接解包到内存;

2.处理外部initrd:若存在外部initrd(initrd_start非空),解包initrd镜像;

3.创建默认设备:解包完成后,创建/dev、/proc、/sys等核心目录和设备节点;

4.执行/init:内核切换到initramfs的根目录,优先执行/init程序,由该程序负责加载驱动、识别并挂载真实根文件系统;

5.切换真实根:若/init执行完毕,内核自动尝试挂载root=指定的真实根文件系统,完成切换。

核心代码

// 解包initramfs到内存根文件系统staticint__initpopulate_rootfs(void){ char*err; // 解包内置initramfs(编译时嵌入内核) if(initramfs_start && !initrd_start) {    err = unpack_to_rootfs((char*)initramfs_start,               initramfs_end - initramfs_start,0);   if(err) panic("unpacking initramfs failed: %s", err);  } // 解包外部initrd镜像 if(initrd_start) {    unsignedlonglen = initrd_end - initrd_start;    err = unpack_to_rootfs((char*)initrd_start, len,1);   if(err) pr_warn("unpacking initrd failed: %sn", err);  } // 创建核心目录和设备节点,保证基础运行环境  create_default_devices(); return0;}rootfs_initcall(populate_rootfs);// 加入fs_initcall层级

4.6 noinitramfs.c -无initramfs时的兜底方案

文件定位:kernel-6.1/init/noinitramfs.c

核心功能

当内核未配置initramfs(CONFIG_NO_INITRAMFS=y)时,创建最小化的根文件系统(rootfs),基于tmpfs实现,保证即使无外部根文件系统,内核也能进入紧急shell,避免直接panic。

核心代码

// 创建最小化rootfs,作为无initramfs时的兜底staticint__initdefault_rootfs(void){ structvfsmount *mnt; // 挂载基于tmpfs的rootfs  mnt = vfs_kern_mount(&rootfs_fs_type, SB_KERNMOUNT,"rootfs", NULL); if(IS_ERR(mnt)) panic("Can't create rootfs"); // 将rootfs设置为当前根目录  init_task.fs->root = dget(mnt->mnt_root);  init_task.fs->pwd = dget(mnt->mnt_root);  init_task.fs->mnt = mntget(mnt);  mntput(mnt); // 创建核心目录:/dev(设备)、/root(root家目录)  mkdir((constchar__user __force *)"/dev",0755);  mkdir((constchar__user __force *)"/root",0700); return0;}rootfs_initcall(default_rootfs);// 加入fs_initcall层级

4.7 do_mounts_rd.c & do_mounts_initrd.c - RAM磁盘专属处理

这两个文件是RAM磁盘和initrd的专属处理模块,互补配合,支撑传统initrd的加载和执行,是initramfs的早期替代方案。

4.7.1 do_mounts_rd.c:RAM磁盘基础处理

•核心功能:初始化RAM磁盘设备,解析rd=启动参数(如rd=/dev/ram0),加载传统RAM磁盘镜像;

•关键函数:rd_load()-从指定介质(SD/EMMC/USB)加载RAM磁盘镜像到内存;

•RK3576适配:支持从EMMC、SD卡、USB等多种介质加载RAM磁盘,适配嵌入式场景。

4.7.2 do_mounts_initrd.c:initrd专用处理

•核心功能:处理外部initrd镜像,挂载并执行其内部的/linuxrc程序;

•关键函数:handle_initrd()-核心处理函数,流程为:将initrd解压到ramdisk→挂载ramdisk为根文件系统→执行/linuxrc→/linuxrc执行完毕后切换到真实根文件系统;

•与initramfs的区别:initrd是块设备镜像,需要文件系统驱动(如ext2)才能解析;initramfs是CPIO归档,无需额外驱动,是现代内核的推荐方案。

4.8 version.c & version-timestamp.c -内核版本与编译信息

4.8.1 version.c:内核版本核心定义

•核心功能:定义内核版本字符串、编译信息、UTS命名空间,是cat /proc/version、uname -a等命令的信息来源;

•关键代码:

// 内核启动banner,启动日志中第一个版本信息const char linux_banner[] = "Linux version "UTS_RELEASE" ("LINUX_COMPILE_BY"@"  LINUX_COMPILE_HOST") ("LINUX_COMPILER") "UTS_VERSION"n";///proc/version对应的版本字符串const char linux_proc_banner[] = "Linux version%s("LINUX_COMPILE_BY"@"  LINUX_COMPILE_HOST") ("LINUX_COMPILER")%sn";//UTS命名空间,包含主机名、内核版本、架构等信息struct uts_namespace init_uts_ns = {  .name = {    .sysname  = UTS_SYSNAME,  //"Linux"    .nodename  ="(none)",   //主机名,可通过hostname=启动参数设置    .release  = UTS_RELEASE,  //内核版本,如"6.1.0-rockchip"    .version  = UTS_VERSION,  //编译版本号,如"#1 SMP PREEMPT"    .machine  = UTS_MACHINE,  //架构,RK3576为"aarch64"    .domainname = UTS_DOMAINNAME,//NIS域名,默认"(none)"  },};

4.8.2 version-timestamp.c:编译时间戳

•核心功能:记录内核的编译时间和日期,由Makefile自动生成,无需手动修改;

•生成逻辑:编译时由scripts/mkcompile_h脚本生成,定义LINUX_COMPILE_TIME(编译时间)和LINUX_COMPILE_DATE(编译日期)两个宏;

•示例内容:

#defineLINUX_COMPILE_TIME"1536"#defineLINUX_COMPILE_DATE"Apr 06 2026"

4.9 Kconfig & Makefile -编译配置与规则

4.9.1 Kconfig:init目录编译配置项

定义init目录相关的内核编译开关,可通过make menuconfig配置,RK3576嵌入式场景中常用的关键配置项如下:

配置项 核心含义 RK3576默认值
CONFIG_INITRAMFS_SOURCE 指定initramfs源文件/目录路径 filesystem/rootfs.cpio.gz
CONFIG_BLK_DEV_RAM 启用RAM磁盘设备支持 y
CONFIG_INITRD 启用initrd支持 y
CONFIG_NO_INITRAMFS 禁用initramfs,使用兜底rootfs n
CONFIG_INIT_ENV_ARG_LIMIT init进程的参数数量限制 32
CONFIG_INIT_DEBUG 启用启动调试输出,打印详细日志 n(调试时设为y)
CONFIG_CALIBRATE_DELAY 启用BogoMIPS延迟校准 y
CONFIG_INITCALL_ASYNC 异步执行initcall,加快启动速度 y(RK3576优化)

4.9.2 Makefile:init目录编译规则

定义init目录的文件编译依赖、生成逻辑,自动处理版本时间戳等动态生成文件,核心规则如下:

# 基础编译文件:所有场景都编译的核心文件obj-y := main.o init_task.o calibrate.o version.o# 条件编译文件:根据Kconfig配置开关编译obj-$(CONFIG_INITRAMFS)+= initramfs.oobj-$(CONFIG_BLK_DEV_RAM)+= do_mounts_rd.oobj-$(CONFIG_INITRD)+= do_mounts_initrd.oobj-$(CONFIG_SYSFS)+= do_mounts.oobj-$(CONFIG_NO_INITRAMFS)+= noinitramfs.o# 版本时间戳文件自动生成:由scripts/mkcompile_h脚本创建version-timestamp.o: .version.timestamp.version.timestamp:  @$(CONFIG_SHELL)$(srctree)/scripts/mkcompile_h$@   $(KERNELRELEASE)$(CONFIG_SMP)$(PREEMPT)

•编译方式:单独编译init目录可执行make init/;

•生成产物:编译后生成init/built-in.o,链接到内核镜像中。

五、RK3576 Linux 6.1完整启动流程(从上电到用户空间)

结合init目录的核心逻辑,RK3576的Linux内核启动是一个硬件→Bootloader→内核汇编→内核C代码→根文件系统→用户空间的完整流程,各阶段无缝衔接,init目录在内核C代码→根文件系统阶段起核心作用,完整流程如下:

阶段1:硬件启动与Bootloader执行(ROM → U-Boot)

1.RK3576上电,片上ROM加载MiniLoader,初始化DDR、EMMC等基础硬件;

2.MiniLoader加载U-Boot到DDR,U-Boot完成板级初始化(网卡、串口、SD卡);

3.U-Boot加载内核镜像(Image)和设备树(rk3576.dtb)到指定内存地址;

4.U-Boot执行bootcmd,传递启动参数(如root=/dev/mmcblk0p1 init=/sbin/init),跳转到内核汇编入口。

阶段2:内核汇编启动(arch/arm64/kernel/head.S)

1.设置ARM64异常向量表、内核栈、MMU(一级页表),开启地址映射;

2.解析U-Boot传递的启动参数和设备树,传递给setup_arch();

3.调用setup_arch()完成RK3576架构特定初始化,跳转到start_kernel()(init/main.c),进入C代码初始化阶段。

阶段3:内核C代码初始化(init/main.c核心)

1.start_kernel()执行基础环境初始化:栈检测、CPU标识、中断关闭、内存管理、调度器、中断、控制台、VFS等;

2.执行do_initcalls(),按8级优先级初始化RK3576的所有硬件驱动和子系统;

3.调用rest_init(),创建kernel_init线程(PID 1,第一个用户态进程的父进程)和kthreadd线程(PID 2,内核线程管理进程);

4.PID 0(init_task)变为idle进程,CPU空闲时执行低功耗逻辑。

阶段4:根文件系统挂载(init/do_mounts*.c + initramfs.c)

1.kernel_init线程调用prepare_namespace(),解析root=启动参数,等待根设备就绪;

2.若配置initramfs/initrd,解包并执行/init(或/linuxrc),加载根设备所需驱动;

3.自动探测文件系统类型,挂载root=指定的真实根文件系统(EMMC/SD/NFS);

4.挂载devtmpfs到/dev,创建设备节点,完成根文件系统切换。

阶段5:用户空间启动

1.内核执行/sbin/init(或init=指定的程序),进入用户空间;

2.init进程执行系统初始化脚本(如/etc/rc.d),启动网络、外设、服务等;

3.启动登录终端或图形界面,RK3576开发板进入可操作状态,启动完成。

六、RK3576平台专属优化

Linux 6.1内核针对RK3576的硬件特性做了多项专属优化,主要集中在init目录中,大幅提升启动速度和硬件适配性,核心优化点如下:

6.1命令行分段打印优化

针对RK3576启动参数可能过长的问题,在main.c中添加架构专属处理,将超长命令行分段打印,避免日志截断:

#ifdefCONFIG_ARCH_ROCKCHIP{ constchar*s = saved_command_line; constchar*e = &saved_command_line[strlen(saved_command_line)]; intn =pr_notice("Kernel command line: %sn", saved_command_line);  n -=strlen("Kernel command line: ");  s += n; // 命令行过长时分段打印 while(n >0&& s < e) {        n = pr_cont("%sn", s);        s += n;    }}#else    pr_notice("Kernel command line: %sn", saved_command_line);#endif#ifdef CONFIG_ARCH_ROCKCHIP{    const char *s = saved_command_line;    const char *e = &saved_command_line[strlen(saved_command_line)];    int n = pr_notice("Kernel command line: %sn", saved_command_line);    n -= strlen("Kernel command line: ");    s += n;    // 命令行过长时分段打印    while (n >0&& s < e) {        n = pr_cont("%sn", s);        s += n;    }}#else    pr_notice("Kernel command line: %sn", saved_command_line);#endif

6.2 Thunder Boot启动加速

启用CONFIG_ROCKCHIP_THUNDER_BOOT后,将memblock内存块的释放推迟到后台线程执行,减少启动过程中的阻塞,提升启动速度:

#ifdefCONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK kthread_run(defer_free_memblock,NULL,"defer_mem");#endif

6.3硬件解压缩支持

在initramfs.c中适配RK3576的硬件解压缩模块,利用硬件加速initramfs的解包过程,大幅提升解压缩速度:

#ifdefined(CONFIG_ROCKCHIP_THUNDER_BOOT) && defined(CONFIG_ROCKCHIP_HW_DECOMPRESS)  wait_initrd_hw_decom_done();#endif

6.4异步initcall执行

启用CONFIG_INITCALL_ASYNC,并行执行无依赖的initcall初始化函数,减少串行初始化的时间消耗,RK3576默认启用该配置。

七、调试实战:init目录相关问题排查技巧

针对RK3576内核启动过程中init目录相关的问题(如根文件系统挂载失败、initcall执行异常、启动速度慢),提供以下实战调试技巧,快速定位问题。

7.1启动参数调试:添加关键参数

通过U-Boot修改bootargs,添加调试参数,开启详细日志或强制进入紧急shell,核心调试参数如下:

•initcall_debug:打印每个initcall函数的执行时间,定位慢启动的初始化函数;

•init=/bin/sh:跳过正常的/sbin/init,直接进入紧急shell,排查根文件系统挂载后的环境问题;

•rootdelay=10:延迟10秒挂载根文件系统,解决RK3576设备探测延迟导致的挂载失败;

•rootwait:循环等待根设备出现,直到设备就绪,适用于热插拔设备作为根文件系统;

•debug:开启内核全局调试输出,打印更多启动日志;

•earlyprintk:启用早期打印,在控制台初始化前就输出日志,排查早期启动崩溃问题;

•lpj=500000:预设loops_per_jiffy值,跳过BogoMIPS校准,节省启动时间。

7.2关键日志分析:过滤核心信息

通过dmesg过滤init目录相关的核心日志,快速定位问题,常用过滤命令如下:

# 查看所有initcall函数的执行日志,包括执行时间dmesg |grep-i initcall# 查看根文件系统挂载日志,确认是否挂载成功、文件系统类型dmesg |grep-i"VFS: Mounted root"# 查看BogoMIPS校准结果,确认延迟校准是否正常dmesg |grep-i bogo# 查看启动命令行,确认U-Boot传递的参数是否正确dmesg |grep-i"Kernel command line"# 查看initramfs/initrd解包日志,排查解包失败问题dmesg |grep-i initramfs|initrd

7.3内核编译与反汇编调试

单独编译init目录:修改init目录代码后,无需全量编译内核,执行make init/即可快速编译;

开启调试配置:设置CONFIG_INIT_DEBUG=y,重新编译内核,开启init目录的详细调试输出;

反汇编分析:对init/main.o进行反汇编,分析start_kernel()的执行流程,命令如下:

objdump -D init/main.o>main.S

内核镜像分析:查看内核镜像中initcall相关段的分布,确认初始化函数是否正确加入,命令如下:

readelf-S vmlinux | grep initcall

7.4常见问题排查

1.根文件系统挂载失败(panic: VFS: Unable to mount root fs)

○检查root=参数是否正确,设备名是否与RK3576的实际设备匹配;

○添加rootdelay=10或rootwait,解决设备探测延迟;

○确认内核已编译该文件系统的驱动(如ext4、nfs)。

2.initramfs解包失败

○检查initramfs的CPIO格式是否为Newc,是否压缩过度;

○确认内核配置CONFIG_INITRAMFS_SOURCE指向正确的文件;

○查看dmesg日志,确认解包失败的具体原因(如文件损坏、内存不足)。

3.启动速度过慢

○添加initcall_debug,定位执行时间过长的initcall函数;

○启用CONFIG_INITCALL_ASYNC,并行执行无依赖的初始化函数;

○预设lpj=值,跳过BogoMIPS校准;

○精简initramfs,只保留根设备所需的核心驱动。

八、核心要点回顾

1.init目录是内核启动的“总控中心”:从start_kernel()到根文件系统挂载,再到用户进程启动,所有核心逻辑均由init目录统筹;

2.main.c是核心中的核心:定义start_kernel()入口,通过do_initcalls()按8级优先级初始化所有子系统和驱动;

3.init_task(PID 0)是所有进程的祖先:启动初期是主进程,后期变为idle进程,CPU空闲时执行低功耗逻辑;

4.initcall机制是初始化的“骨架”:8级优先级确保依赖项先初始化,RK3576的所有硬件驱动均通过该机制启动;

5.根文件系统挂载是跨空间的关键:do_mounts.c解析root=参数,自动探测文件系统,initramfs解决驱动依赖问题;

6.RK3576专属优化提升启动性能:命令行分段打印、Thunder Boot、硬件解压缩、异步initcall,大幅缩短启动时间;

7.BogoMIPS是校准值非性能指标:用于计算延迟函数的循环次数,而非CPU的实际运算能力。

九、参考资源

1.内核官方文档

○Documentation/admin-guide/init.rst:内核初始化指南

○Documentation/admin-guide/initrd.rst:initrd/initramfs使用指南

○Documentation/admin-guide/bootconfig.rst:启动参数配置指南

2.RK3576官方文档

○RK3576 Technical Reference Manual(TRM):硬件手册

○Rockchip Linux SDK开发指南:平台适配指南

3.相关内核源码

○arch/arm64/kernel/setup.c:RK3576架构特定初始化

○kernel/sched/core.c:进程调度器实现

○mm/page_alloc.c:内存页面分配器实现

○fs/vfs.c:虚拟文件系统核心逻辑

本文基于RK3576平台Linux 6.1内核源码完成全维度解析,涵盖init目录的所有核心文件、机制和流程,若需针对某一具体函数、场景做更深入的拆解,可结合实际需求进一步分析。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    88

    文章

    11807

    浏览量

    219508
  • rk3576
    +关注

    关注

    1

    文章

    292

    浏览量

    1668
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    RK3576 单板机 LinuxSDK 开发与系统定制手册(二)

    本文为创龙科技RK3576 单板机 Linux 系统开发指南,涵盖 SDK 搭建、内核与引导程序开发、根文件系统修改、镜像打包、分区配置及硬件资源调优等全流程。提供命令实操、参数说明与故障处理,支持系统
    的头像 发表于 04-16 15:39 135次阅读
    <b class='flag-5'>RK3576</b> 单板机 LinuxSDK 开发与系统定制手册(二)

    RK3576 单板机 LinuxSDK 开发与系统定制手册(一)

    本文为创龙科技RK3576 单板机 Linux 系统开发指南,涵盖 SDK 搭建、内核与引导程序开发、根文件系统修改、镜像打包、分区配置及硬件资源调优等全流程。提供命令实操、参数说明与故障处理,支持系统
    的头像 发表于 04-15 11:18 188次阅读
    <b class='flag-5'>RK3576</b> 单板机 LinuxSDK 开发与系统定制手册(一)

    迅为电子RK系列开发板SDK内核正式升级至6.1 LTS版本

    迅为RK3568、RK3588、RK3576RK3562系列开发板,SDK内核5.10版本正
    的头像 发表于 03-16 16:18 472次阅读

    初次编译rk3568(rk3576Linux 6.1内核踩坑记录:报错终止到成功解决的完整流程

    很多刚接触瑞芯微 rk 系列芯片开发的小伙伴,在初次编译基于 Linux 6.1 内核的系统时,很容易因为环境依赖问题卡壳。最近我在编译 rk3576
    的头像 发表于 02-06 16:47 3139次阅读
    初次编译<b class='flag-5'>rk</b>3568(<b class='flag-5'>rk3576</b>)<b class='flag-5'>Linux</b> 6.1<b class='flag-5'>内核</b>踩坑记录:<b class='flag-5'>从</b>报错终止到成功解决的完整流程

    瑞芯微RK3576基于Linux平台CUPS架构标准打印机适配实战教程

    RK3576是一款中高端八核Arm国产处理器,原厂配套LinuxKernel6.1内核。触觉智能旗下RK3576核心板/开发板、行业主板,率先适配支持开源鸿蒙、
    的头像 发表于 01-16 17:45 725次阅读
    瑞芯微<b class='flag-5'>RK3576</b>基于<b class='flag-5'>Linux</b>平台CUPS架构标准打印机适配实战教程

    迅为如何在RK3576上部署YOLOv5;基于RK3576构建智能门禁系统

    迅为如何在RK3576开发板上部署YOLOv5;基于RK3576构建智能门禁系统
    的头像 发表于 11-25 14:06 1950次阅读
    迅为如何在<b class='flag-5'>RK3576</b>上部署YOLOv5;基于<b class='flag-5'>RK3576</b>构建智能门禁系统

    如何米尔RK3576开发板上移植EtherCAT Igh

    本文将介绍基于米尔电子MYD-LR3576开发板(米尔基于瑞芯微 RK3576开发板)的板端移植EtherCAT Igh方案的开发测试。摘自优秀创作者-EPTmachine米尔基于瑞芯微RK3576
    发表于 09-26 16:02

    如何移植EtherCAT Igh--基于米尔RK3576开发板

    本文将介绍基于米尔电子MYD-LR3576开发板(米尔基于瑞芯微RK3576开发板)的板端移植EtherCATIgh方案的开发测试。摘自优秀创作者-EPTmachine米尔基于瑞芯微RK3576
    的头像 发表于 09-26 08:04 1w次阅读
    如何移植EtherCAT Igh--基于米尔<b class='flag-5'>RK3576</b>开发板

    【作品合集】米尔RK3576开发板测评

    测试 作者:鲁治驿【米尔RK3576开发板免费体验】测评综合解析 【米尔RK3576开发板免费体验】集成MQ-2烟雾传感器和ADS1263模块实现气体监测 【米尔RK3576开发板免费
    发表于 09-11 10:19

    【作品合集】灵眸科技EASY EAI Orin Nano(RK3576)开发板测评

    【EASY EAI Orin Nano(RK3576)开发板试用体验】01-开箱报告及开发环境准备 【EASY EAI Orin Nano(RK3576)开发板试用体验】02-拓展空间内核
    发表于 09-09 09:59

    瑞芯微RK3576RK3576S有什么区别,性能参数配置与型号差异解析

    瑞芯微第二代8nm高性能AIOT平台RK3576家族再添新成员-RK3576S,先说结论:相较主型号的RK3576/RK3576J,性能略有缩减,而功耗有所降低。主要应用于商显终端、智
    的头像 发表于 08-14 23:57 2671次阅读
    瑞芯微<b class='flag-5'>RK3576</b>与<b class='flag-5'>RK3576</b>S有什么区别,性能参数配置与型号差异<b class='flag-5'>解析</b>

    RK这2款旗舰芯片RK3588 PK RK3576,谁是最优选

    ,了解两者的区别十分重要,以下将从多个方面进行详细对比。一、处理器性能解析(一)CPU 性能1. 核心架构◦ RK3576:采用四核 Cortex - A72 和四核 Cortex - A53 架构
    发表于 07-10 18:24

    Mpp支持RK3576

    想问下,https://github.com/rockchip-linux/mpp这里面支持RK3576么,看介绍没有提到说支持RK3576 目前是买了个rk3576的机顶盒,搭载了安
    发表于 06-13 15:35

    RK3576 vs RK3588:为何越来越多的开发者转向RK3576

    的成本结构以及针对特定场景的深度优化,正在成为中高端市场的热门选择。那么,RK3576 究竟有哪些优势?它是否真的能替代 RK3588?我们来做一个全面对比。 1. 核心性能对比:够用且高效[td
    发表于 05-30 08:46

    RK3576 Android 14.0 SDK开发指南(第一集)

    RK3576 Android 14.0 SDK代码编译 SDK下载到本地后大概70多个G 下载后要做个校验 解压后内核源码 kernel代码路径说明 Android14支持6.1 版本
    发表于 05-20 08:43