linux内核启动
linux内核启动流程
start_kernel
start_kernel是所有 Linux 平台进入系统内核初始化后的入口函数,它主要完成剩余的与 硬件平台相关的初始化工作,在进行一系列与内核相关的初始化后,调用第一个用户进程
1 | asmlinkage __visible void __init __no_sanitize_address start_kernel(void) |
start_kernel
函数主要调用了以下子函数来完成初始化:
setup_arch(&command_line)
: 架构特定的第二次初始化,解析设备树(DTB)或ATAGS(传递自Bootloader的参数),进行内存映射等。trap_init()
: 初始化异常向量表和中断处理函数的入口。mm_init()
: 内存管理子系统初始化,初始化伙伴系统(Buddy System)等 slab allocator 的基础。sched_init()
: 调度器初始化,初始化系统进程调度器。early_irq_init()
和init_IRQ()
: 中断处理机制初始化。time_init()
: 系统时钟初始化,读取实时时钟(RTC),初始化定时器中断。console_init()
: 控制台初始化。在这之前,内核通过printk
输出的信息可能只是保存在缓冲区中。此后,信息才能显示在控制台上。arch_call_rest_init()
: 这是start_kernel()
的最后一步,它会调用rest_init()
,后者将创建第一个用户空间进程(init)并完成启动过程
创建第一个进程
start_kernel
在最后调用rest_init()
,它负责结束内核启动并“孵化”出用户空间。
工作内容:
- 通过
kernel_thread
创建内核线程kernel_init
。这个线程就是后续的用户空间init进程(PID 1) 的雏形。 - 通过
kernel_thread
创建内核线程kthreadd
(PID 2),它负责管理和调度所有其他的内核线程。 - 调用
schedule()
开启进程调度。 - 将当前任务(0号进程,即
idle
进程)标记为可调度,并调用cpu_idle()
进入空闲循环。当没有其他任务可运行时,CPU就执行idle
进程。
initramfs的处理与根文件系统挂载
kernel_init
线程首先会尝试处理initramfs
。
- 为什么需要initramfs?
内核可能不包含访问真实根文件系统所在磁盘所需的驱动程序(例如SCSI、RAID、LVM、加密设备的驱动)。initramfs
是一个临时的、放在内存中的根文件系统,它包含了这些驱动和工具。在内核无法直接访问根设备时,提供一个临时的环境来加载必要驱动,从而挂载真正的根文件系统。 - 工作内容:
- 内核解压并加载
initramfs
到内存的一个临时文件系统中。 kernel_init
线程执行initramfs
中的/init
脚本(这是一个用户空间程序!)。/init
脚本负责加载必要的硬件驱动模块(如磁盘控制器、文件系统驱动)。- 驱动加载后,
/init
脚本挂载真正的根文件系统(例如/dev/sda1
)。 - 最后,
/init
脚本通过pivot_root
或chroot
系统调用,将根目录切换到新挂载的真实根文件系统上。 /init
程序退出,kernel_init
线程继续执行。
- 内核解压并加载
用户空间初始化
切换到真正的根文件系统后,kernel_init
线程会尝试执行用户空间的第一个程序。
kernel_init
会按顺序尝试执行以下程序之一:/sbin/init
(最常见)/etc/init
/bin/init
/bin/sh
- 通常,
/sbin/init
是一个指向现代init系统(如systemd或SysV init)的符号链接。 - init进程(PID 1) 被启动,它成为所有用户进程的父进程。
- init系统根据其配置文件(如systemd的
/etc/systemd/system/
目录或SysV init的/etc/inittab
和/etc/rc.d/
脚本)来:- 初始化主机名、挂载文件系统(
/proc
,/sys
,/dev
)、设置内核参数。 - 启动系统服务(如网络、日志、定时任务)。
- 启动登录管理器(如图形界的GDM、LightDM)或文本界的
getty
进程。 getty
进程在终端上显示login:
提示符,等待用户登录。
- 初始化主机名、挂载文件系统(
linux移植工作
添加开发板默认配置文件
将arch/arm/configs 目录下的imx_v7_mfg_defconfig 重新复制一份, 命名为imx_alientek_emmc_defconfig,打开imx_alientek_emmc_defconfig 文件,找到“CONFIG_ARCH_MULTI_V6=y”这一行,将其屏蔽掉(因为I.MX6ULL 是ARMV7 架构的,因此要屏蔽掉V6 相关选项,否则后面做驱动实验的时候可能会遇到驱动模块无法加载的情况。)
1
2
3
4
5
6#CPU Core family selection
CONFIG_ARCH_MULTI_V6=y#屏蔽
CONFIG_ARCH_MULTI_V7=y
CONFIG_ARCH_MULTI_V6_V7=y
CONFIG_ARCH_MULTI_CPU_AUTO is not set
CONFIG_ARCH_VIRTisnot set以后就可以使用如下命令来配置正点原子EMMC版开发板对应的Linux 内核了
1
make imx_alientek_emmc_defconfig
添加开发板对应的设备树文件
添加适合正点原子EMMC 版开发板的设备树文件,进入目录arch/arm/boot/dts 中,复制一
份imx6ull-14x14-evk.dts,然后将其重命名为imx6ull-alientek-emmc.dts编译测试
1
2
3
4
5!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihfimx_alientek_emmc_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16执行shell 脚本imx6ull_alientek_emmc.sh 编译Linux 内核
编译完成以后就会在目录arch/arm/boot 下生成zImage 镜像文件。在arch/arm/boot/dts 目录下生成imx6ull-alientek-emmc.dtb 文件
linux内核裁剪
裁剪原因: IMX6ULL这款资源有限的ARM处理器制作一个轻量、高效且稳定可靠的定制化系统,移除不需要的模块可以减小内核体积、降低内存占用、提高启动速度,并减少潜在的安全漏洞。”
主要工具:我使用标准的
make menuconfig
基于图形界面进行配置,因为它相比make xconfig
更轻量,相比make config
更直观。基本方法:首先获取芯片原厂(NXP)提供的默认配置文件(通常是
imx_v7_defconfig
),以此为基础进行修改,而不是从零开始配置。这样可以最大程度保证硬件基本功能的正常。1
2
3
4
5
6获取默认配置
make imx_v7_defconfig
进入交互配置菜单
make menuconfig
编译内核
make -j4具体裁剪内容及原因:
- 处理器类型与平台支持:移除所有其他架构的CPU支持(如x86, PowerPC, MIPS等),以及NXP i.MX系列中其他型号的芯片支持(如i.MX7, i.MX8等)。
- 设备驱动: 这是裁剪的大头。驱动代码量非常大,针对性保留可以极大缩小内核尺寸。
- 图形驱动:移除所有其他GPU(如NVIDIA, AMD)和显示器驱动。
- 输入设备:移除了游戏手柄、触摸板等驱动(我们只保留了触摸屏驱动
evdev
)。 - 声卡驱动:整个系统没有音频功能,所以全部移除。
- 文件系统:
经过上述裁剪,最终的内核镜像(
zImage
)大小从原始的7MB减少到了5MB,系统启动时间和内存占用也有了明显的优化。验证:
- 基本功能测试:确保系统能正常启动、挂载根文件系统。
- 硬件功能测试:逐一测试所有需要的外设功能是否正常:I2C读写传感器、SPI通信、PWM控制舵机、GPIO控制风扇/水泵、以太网通信、LCD显示和触摸。