文章目录
为了实验ebpf,需要自己编译内核开启相关选项,正好手头有树莓派的板子,所以正好用上。
编译并更换内核
首先我自己用官方工具在sd卡上烧录了64位无桌面的系统(需要注意,这里烧录的不是ubuntu,而是树莓派官方镜像),然后按照官方的文档,进行了内核的交叉编译,更换后使用uname -a发现确实更换成功了。编译内核没花时间,但wsl2挂载sd卡,去安装新编译的文件折腾了好久,详见WSL2简单探索
内核版本如下:
$ head Makefile
# SPDX-License-Identifier: GPL-2.0
VERSION = 5
PATCHLEVEL = 10
SUBLEVEL = 110
EXTRAVERSION =
NAME = Dare mighty things
挂载sd卡:
#!/bin/bash
sudo mkdir -p /mnt
sudo mkdir -p /mnt/fat32
sudo mkdir -p /mnt/ext4
sudo mount /dev/sdd1 /mnt/fat32
sudo mount /dev/sdd2 /mnt/ext4
卸载sd卡
#!/bin/bash
sudo umount /mnt/fat32
sudo umount /mnt/ext4
编译:
#!/bin/bash
KERNEL=kernel8
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs -j12
安装:
#!/bin/bash
root=/mnt
bootfs=${root}/fat32
rootfs=${root}/ext4
sudo env PATH=$PATH make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=${rootfs} modules_install
#sudo mkdir -p ${bootfs}
#sudo mkdir -p ${bootfs}/overlays/
sudo cp arch/arm64/boot/Image ${bootfs}/kernel-myconfig.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb ${bootfs}/
sudo cp arch/arm64/boot/dts/overlays/*.dtb* ${bootfs}/overlays/
sudo cp arch/arm64/boot/dts/overlays/README ${bootfs}/overlays/
#tar -acvf output.tar.gz output/
#sudo scp output.tar.gz pi@link.local:/home/pi
实际来说,内核模块和设备树文件都无变化,只需要更换内核即可
更换内核成功后开始搞uboot
编译并安装uboot
使用以下脚本编译一次成功
#!/bin/bash
ARCH=arm64
CROSS_COMPILE=aarch64-linux-gnu-
make rpi_3_defconfig
make -j 12
config.h的配置,最主要的可能是这一句。
kernel=u-boot.bin
然而编好改完启动不起来,怀疑是配置有问题,但是看了官方文档,确认64位 3b的板子就是这个defconfig.
重新编译另一个defconfig,成功加载。
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
#make rpi_3_defconfig
make rpi_arm64_defconfig
make -j 12
最终uboot能成功加载,并且从sd卡启动系统成功:
U-Boot> help
? - alias for 'help'
...
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
mmc - MMC sub system
U-Boot> mmc part
Partition Map for MMC device 0 -- Partition Type: DOS
Part Start Sector Num Sectors UUID Type
1 8192 524288 95d33193-01 0c
2 532480 61988864 95d33193-02 83
U-Boot> fatls mmc 0:1
overlays/
29707 bcm2710-rpi-2-b.dtb
18693 COPYING.linux
1594 LICENCE.broadcom
145 issue.txt
...
52476 bootcode.bin
131 cmdline.txt
2217 config.txt
...
8219600 kernel8.img
624640 u-boot.bin
...
21590528 kernel-myconfig.img
41 file(s), 4 dir(s)
U-Boot> fatload mmc 0:1 ${kernel_addr_r} kernel-myconfig.img
21590528 bytes read in 903 ms (22.8 MiB/s)
U-Boot> booti ${kernel_addr_r} - ${fdt_addr}
Moving Image from 0x80000 to 0x200000, end=17e0000
## Flattened Device Tree blob at 2eff7f00
Booting using the fdt blob at 0x2eff7f00
Working FDT set to 2eff7f00
Using Device Tree in place at 000000002eff7f00, end 000000002f002ffe
Working FDT set to 2eff7f00
搞一个环境变量:
mmcboot=fatload mmc 0:1 ${kernel_addr_r} kernel-myconfig.img;booti ${kernel_addr_r} - ${fdt_addr}
之后可以直接run mmcboot来从sd卡启动了。
这里没设置bootargs,看下启动参数
pi@link:~$ cat /proc/cmdline
coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=Composite-1:720x480@60i vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000 console=ttyAMA0,115200 console=tty1 root=PARTUUID=95d33193-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=CN
pi@link:~$ cat /boot/cmdline.txt
console=serial0,115200 console=tty1 root=PARTUUID=95d33193-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=CN
对比启动后的参数和cmdline.txt里的参数,发现两者不同。也不知道这参数哪来的。
把cmdline.txt里的内容设置为bootargs,Ok,起不来了。。。仔细对比发现,上面的参数比下面多,并且最关键的,console的参数不一样,把下面的改成console=ttyAMA0,115200 ,然后启动,有打印了,但是停住了
[ 4.594079] smsc95xx v2.0.0
[ 4.711357] SMSC LAN8700 usb-001:003:01: attached PHY driver [SMSC LAN8700] (mii_bus:phy_addr=usb-001:003:01, irq=POLL)
[ 4.723345] smsc95xx 1-1.1:1.0 eth0: register 'smsc95xx' at usb-3f980000.usb-1.1, smsc95xx USB 2.0 Ethernet, b8:27:eb:43:86:0a
[ 4.941958] random: crng init done
[ 35.806937] cam-dummy-reg: disabling
我决定还是不乱改了。
内核启动流程
uboot到内核流程简述
因为我是用了booti来启动的,因此调用的是do_booti,这里的uboot版本如下:
$ head Makefile
# SPDX-License-Identifier: GPL-2.0+
VERSION = 2024
PATCHLEVEL = 01
SUBLEVEL =
EXTRAVERSION = -rc5
NAME =
我这里最终调的可能是do_bootm_linux,这个函数的实现不多:
/* Main Entry point for arm bootm implementation
*
* Modeled after the powerpc implementation
* DIFFERENCE: Instead of calling prep and go at the end
* they are called if subcommand is equal 0.
*/
int do_bootm_linux(int flag, int argc, char *const argv[],
struct bootm_headers *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
boot_jump_linux(images, flag);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}
看前面do_booi里面的flag,最终应该是直接调了boot_prep_linux,然后返回0.再往后,推断函数结构,应该是调了image_setup_linux,然后再调board_prep_linux。这里不继续分析了,可以参考《U-Boot 学习》。各种处理后,调用boot_selected_os,里面调用了boot_jump_linux,
/* Subcommand: GO */
static void boot_jump_linux(struct bootm_headers *images, int flag)
{
#ifdef CONFIG_ARM64
void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
void *res2);
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
void *res2))images->ep;
debug("## Transferring control to Linux (at address %lx)...\n",
(ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);//这里面会打印Starting kernel ...
if (!fake) {
...
do_nonsec_virt_switch();
update_os_arch_secondary_cores(images->os.arch);
...
if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &&
(images->os.arch == IH_ARCH_ARM))
armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number,
(u64)images->ft_addr, 0,
(u64)images->ep,
ES_TO_AARCH32);
else
armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
images->ep,
ES_TO_AARCH64);
}
...
}
其中,images->ep就是内核的入口点,看前面的booti ${kernel_addr_r} - ${fdt_addr}
,分别指定了kernel 镜像在内存中的首地址、无效参数、设备树文件首地址,这里的images->ep其实就是boot里面指定的${kernel_addr_r} 。
/*
* armv8_switch_to_el2() - switch from EL3 to EL2 for ARMv8
*
* @args: For loading 64-bit OS, fdt address.
* For loading 32-bit OS, zero.
* @mach_nr: For loading 64-bit OS, zero.
* For loading 32-bit OS, machine nr
* @fdt_addr: For loading 64-bit OS, zero.
* For loading 32-bit OS, fdt address.
* @arg4: Input argument.
* @entry_point: kernel entry point
* @es_flag: execution state flag, ES_TO_AARCH64 or ES_TO_AARCH32
*/
void __noreturn armv8_switch_to_el2(u64 args, u64 mach_nr, u64 fdt_addr,
u64 arg4, u64 entry_point, u64 es_flag);
实现在 arch/arm/cpu/armv8/transition.S,看汇编代码:
.pushsection .text.armv8_switch_to_el2, "ax"
ENTRY(armv8_switch_to_el2)
switch_el x6, 1f, 0f, 0f
0:
cmp x5, #ES_TO_AARCH64
b.eq 2f//如果arch type为arm64,则跳转到label 2处
/*
* When loading 32-bit kernel, it will jump
* to secure firmware again, and never return.
*/
bl armv8_el2_to_aarch32
2:
/*
* x4 is kernel entry point or switch_to_el1
* if CONFIG_ARMV8_SWITCH_TO_EL1 is defined.
* When running in EL2 now, jump to the
* address saved in x4.
*/
br x4//跳转到kernel entry处
1: armv8_switch_to_el2_m x4, x5, x6
ENDPROC(armv8_switch_to_el2)
.popsection
在这里,就进入了内核的入口点
一个 系统启动的打印例子:
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
[ 0.000000] Linux version 5.10.0 (-V1.1.18) (aarch64-none-linux-gnu-gcc (GNU Toolchain for the A-profile Architecture 9.2-2019.12 (arm-9.10)) 9.2.1 20191025, GNU ld (GNU Toolchain for the A-profile Architecture 9.2-2019.12 (arm-9.10)) 2.33.1.20191209) #2 SMP PREEMPT Wed Mar 13 08:18:06 UTC 2024
[ 0.000000] Machine model: Netfactory soc928 EVB
[ 0.000000] earlycon: pl11 at MMIO32 0x0000000030225000 (options '')
[ 0.000000] printk: bootconsole [pl11] enabled
[ 0.000000] cma: Reserved 32 MiB at 0x00000000b4800000
[ 0.000000] Zone ranges:
[ 0.000000] DMA [mem 0x0000000080000000-0x00000000b7ffffff]
[ 0.000000] DMA32 empty
[ 0.000000] Normal empty
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000080000000-0x00000000b7ffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000080000000-0x00000000b7ffffff]
[ 0.000000] On node 0 totalpages: 229376
[ 0.000000] DMA zone: 3584 pages used for memmap
[ 0.000000] DMA zone: 0 pages reserved
[ 0.000000] DMA zone: 229376 pages, LIFO batch:63
[ 0.000000] percpu: Embedded 21 pages/cpu s47960 r8192 d29864 u86016
[ 0.000000] pcpu-alloc: s47960 r8192 d29864 u86016 alloc=21*4096
[ 0.000000] pcpu-alloc: [0] 0 [0] 1
[ 0.000000] Detected VIPT I-cache on CPU0
[ 0.000000] CPU features: detected: ARM erratum 845719
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 225792
[ 0.000000] Kernel command line: console=ttyAMA0,115200 earlycon=pl011,mmio32,0x30225000 root=/dev/ram0 loglevel=0 startcode_version=1.1.21 uboot_version=1.1.21
如上面所说,uboot 打印完 Starting kernel . . .后,就跳转到内核入口点,并把控制权交给 kernel了。另外uboot还会读取bootargs参数并传递给内核
内核入口点
内核入口点:
在linux5.10的代码中,流程大概如下:primary_entry->__primary_switch->__primary_switched->start_kernel.
准备工作
进程描述符定义
在内核代码中经常看到使用current来进行操作,而curret是当前cpu上运行的进程描述符task_struct,可以看到下面的currten是从sp_el0寄存器取的,
/*
* We don't use read_sysreg() as we want the compiler to cache the value where
* possible.
*/
static __always_inline struct task_struct *get_current(void)
{
unsigned long sp_el0;
asm ("mrs %0, sp_el0" : "=r" (sp_el0));
return (struct task_struct *)sp_el0;
}
#define current get_current()
而所有进程的进程描述符都是复制自0号进程的进程描述符,0号进程的进程描述符怎么来的呢?进程是如何切换的呢?实际0号进程的描述符在调用start_kernel前就准备好了。
参考kernel启动流程-head.S的执行_9.__primary_switched
/*
* The following fragment of code is executed with the MMU enabled.
*
* x0 = __PHYS_OFFSET
*/
SYM_FUNC_START_LOCAL(__primary_switched)
//将init_thread_union地址保存在x4中,它存放了init进程栈起始地址;
adrp x4, init_thread_union
//设置sp指针为init_thread_union偏移THREAD_SIZE,自此init进程真正诞生,它就是内核的0号进程,将来将化身为idle进程;
add sp, x4, #THREAD_SIZE
//保存当前进程描述符到sp_el0,sp_el0在el1时被用来保存当前的进程描述符指针,即全局current
adr_l x5, init_task
msr sp_el0, x5 // Save thread_info
//...
add sp, sp, #16
mov x29, #0
mov x30, #0
b start_kernel
SYM_FUNC_END(__primary_switched)
init_thread_union和init_stack定义
extern union thread_union init_thread_union;
union thread_union {
//...
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
extern unsigned long init_stack[THREAD_SIZE / sizeof(unsigned long)];
init_thread_union定义在vmlinux.lds的data段中, 它定义了init进程的栈空间:
_data = .;
_sdata = .;
. = ALIGN((1 << 12)); .data : AT(ADDR(.data) - 0) {
. = ALIGN((2 * (((1)) << (14 + 0))));
__start_init_task = .;
init_thread_union = .;
init_stack = .;
KEEP(*(.data..init_task)) KEEP(*(.data..init_thread_info)) . = __start_init_task + (((1)) << (14 + 0));
__end_init_task = .;
再看下init_task的定义:
/*
* Set up the first task table, touch at your own risk!. Base=0,
* limit=0x1fffff (=2MB)
*/
struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
__init_task_data
#endif
__aligned(L1_CACHE_BYTES)
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK
.thread_info = INIT_THREAD_INFO(init_task),
.stack_refcount = REFCOUNT_INIT(1),
#endif
.state = 0,
.stack = init_stack,
进程切换
接着看下arm64进程切换的代码,最终会调用架构代码__switch_to
/*
* Thread switching.
*/
__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
struct task_struct *next)
{
struct task_struct *last;
fpsimd_thread_switch(next);
tls_thread_switch(next);
hw_breakpoint_thread_switch(next);
contextidr_thread_switch(next);
entry_task_switch(next);
uao_thread_switch(next);
ssbs_thread_switch(next);
erratum_1418040_thread_switch(next);
/*
* Complete any pending TLB or cache maintenance on this CPU in case
* the thread migrates to a different CPU.
* This full barrier is also required by the membarrier system
* call.
*/
dsb(ish);
/*
* MTE thread switching must happen after the DSB above to ensure that
* any asynchronous tag check faults have been logged in the TFSR*_EL1
* registers.
*/
mte_thread_switch(next);
/* the actual thread switch */
last = cpu_switch_to(prev, next);
return last;
}
cpu_switch_to是汇编:
/*
* Register switch for AArch64. The callee-saved registers need to be saved
* and restored. On entry:
* x0 = previous task_struct (must be preserved across the switch)
* x1 = next task_struct
* Previous and next are guaranteed not to be the same.
*
*/
SYM_FUNC_START(cpu_switch_to)
mov x10, #THREAD_CPU_CONTEXT
add x8, x0, x10
mov x9, sp
//将previous task_struct 的x19 - x29、 x9、 lr寄存器都存储在内核堆栈中。
stp x19, x20, [x8], #16 // store callee-saved registers
stp x21, x22, [x8], #16
stp x23, x24, [x8], #16
stp x25, x26, [x8], #16
stp x27, x28, [x8], #16
stp x29, x9, [x8], #16
str lr, [x8]
add x8, x1, x10
//将next task_struct的x19 - x29、 x9、 lr寄存器从堆栈中恢复。
ldp x19, x20, [x8], #16 // restore callee-saved registers
ldp x21, x22, [x8], #16
ldp x23, x24, [x8], #16
ldp x25, x26, [x8], #16
ldp x27, x28, [x8], #16
ldp x29, x9, [x8], #16
ldr lr, [x8]
mov sp, x9
//x1就是next进程的struct task_struct结构,将next task_struct存储在sp_el0寄存器中
msr sp_el0, x1
ptrauth_keys_install_kernel x1, x8, x9, x10
scs_save x0, x8
scs_load x1, x8
ret
SYM_FUNC_END(cpu_switch_to)
NOKPROBE(cpu_switch_to)
因此在进程切换时会将下一个进程的进程描述符存到sp_el0,再使用get_current取就能取到了。
内核初始化
关于start_kernel,参考《Linux内核4.14版本:ARM64的内核启动过程(二)——start_kernel》。
这个start_kernel在init/main.c里面。内核居然也有main.c,长知识了。
在start_kernel中就开始打印了,
start_kernel最终调用了reset_init,这里相比之前的流程多了一层调用:
实现如下。
noinline void __ref rest_init(void)
{
struct task_struct *tsk;
int pid;
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
/*
* Pin init on the boot CPU. Task migration is not properly working
* until sched_init_smp() has been run. It will set the allowed
* CPUs for init to the non isolated CPUs.
*/
rcu_read_lock();
tsk = find_task_by_pid_ns(pid, &init_pid_ns);
set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
rcu_read_unlock();
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
/*
* Enable might_sleep() and smp_processor_id() checks.
* They cannot be enabled earlier because with CONFIG_PREEMPTION=y
* kernel_thread() would trigger might_sleep() splats. With
* CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
* already, but it's stuck on the kthreadd_done completion.
*/
system_state = SYSTEM_SCHEDULING;
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
引用下解释:
rcu_scheduler_starting();
通过调用函数rcu_scheduler_starting,来启动 RCU 锁调度器。pid = kernel_thread(kernel_init, NULL, CLONE_FS);pid = kernel_thread(kernel_init, NULL, CLONE_FS);
调用函数 kernel_thread 创建 kernel_init 线程,也就是大名鼎鼎的 init 内核进程。init 进程的 PID 为 1。 init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程序, init 进程就会实现从内核态到用户态的转变。pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
调用函数 kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd进程负责所有内核进程的调度和管理。cpu_startup_entry(CPUHP_ONLINE);
调用函数cpu_startup_entry 来进入 idle 进程, cpu_startup_entry 会调用cpu_idle_loop, cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。 idle 进程的 PID 为 0, idle进程叫做空闲进程,如果学过 FreeRTOS 或者 UCOS 的话应该听说过空闲任务。 idle 空闲进程就和空闲任务一样,当 CPU 没有事情做的时候就在 idle 空闲进程里面“瞎逛游”,反正就是给CPU 找点事做。当其他进程要工作的时候就会抢占 idle 进程,从而夺取 CPU 使用权。其实大家应该可以看到 idle 进程并没有使用 kernel_thread 或者 fork 函数来创建,因为它是有主进程演变而来的。
————————————————
版权声明:本文为优快云博主「风雨兼程8023」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/yangguoyu8023/article/details/121452085
看最后调用的cpu_startup_entry:
void cpu_startup_entry(enum cpuhp_state state)
{
arch_cpu_idle_prepare();
cpuhp_online_idle(state);
while (1)
do_idle();
}
内核也有死循环whiel(1);泪目。
init进程初始化
继续看kernel_init:
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
...
if (ramdisk_execute_command) {//static char *ramdisk_execute_command = "/init";
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* 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.
*/
//execute_command 的值是通过uboot 传递,在 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系统中的 linuxrc 就是要执行的用户空间 init 程序。
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
//想尽办法去运行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/admin-guide/init.rst for guidance.");
}
这里开头调用的kernel_init_freeable会去调用do_initcalls初始化子系统。
do_initcalls()这里就是驱动程序员需要关心的步骤,其中按照各个内核模块初始化函数所自定义的启动级别(1~7),按顺序调用器初始化函数。对于同一级别的初始化函数,安装编译是链接的顺序调用,也就是和内核Makefile的编写有关。在编写内核模块的时候需要知道这方面的知识,比如你编写的模块使用的是I2C的API,那你的模块的初始化函数的级别必须低于I2C子系统初始化函数的级别(也就是级别数(1~7)要大于I2C子系统)。如果编写的模块必须和依赖的模块在同一级,那就必须注意内核Makefile的修改了。
0 1 2号句柄创建
在kernel_init()—>kernel_init_freeable()—>console_on_rootfs()中,会把当前进程的文件描述符0(stdin)、1(stdout)、2(stderr)都对应到 /dev/console。
/* Open /dev/console, for stdin/stdout/stderr, this should never fail */
void __init console_on_rootfs(void)
{
struct file *file = filp_open("/dev/console", O_RDWR, 0);
if (IS_ERR(file)) {
pr_err("Warning: unable to open an initial console.\n");
return;
}
init_dup(file);
init_dup(file);
init_dup(file);
fput(file);
}
int __init init_dup(struct file *file)
{
int fd;
fd = get_unused_fd_flags(0);
if (fd < 0)
return fd;
fd_install(fd, get_file(file));
return 0;
}
这里会申请空闲句柄,也就是0,1,2,然后安装到Init进程的fdt当中去,以后所有其他进程的0,1,2句柄都会先从init继承
init进程启动和身份转换
static int try_to_run_init_process(const char *init_filename)
{
int ret;
ret = run_init_process(init_filename);
if (ret && ret != -ENOENT) {
pr_err("Starting init: %s exists but couldn't execute it (error %d)\n",
init_filename, ret);
}
return ret;
}
...
static int run_init_process(const char *init_filename)
{
const char *const *p;
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
pr_debug(" with arguments:\n");
for (p = argv_init; *p; p++)
pr_debug(" %s\n", *p);
pr_debug(" with environment:\n");
for (p = envp_init; *p; p++)
pr_debug(" %s\n", *p);
return kernel_execve(init_filename, argv_init, envp_init);
}
参考资料
树莓派4B U-boot移植并加载裸机程序
在树莓派3b上运行uboot
树莓派4 嵌入式Linux开发过程详解
使用U-Boot让树莓派从U盘启动
RPI 3 booting from U-boot
uboot的烧写及使用
U-boot启动流程U-boot启动流程
Linux、树莓派启动过程
内核启动参数详解
U-Boot 学习
Linux内核4.14版本:ARM64的内核启动过程(二)——start_kernel
Linux Early printk技术
bootz 启动 kernel
kernel启动流程-head.S的执行_9.__primary_switched
Linux 进程管理之current