Punctual atom - Linux materials learning notes - system transplantation - Linux kernel startup process

Posted by ddragas on Mon, 31 Jan 2022 10:37:01 +0100

catalogue

1. Link script vmlinux lds

2.Linux kernel startup process analysis

1) Linux kernel entry stext

    2)__ mmap_switched function

    3)start_kernel function

    4)rest_init function

5) init process

1. Link script vmlinux lds

To analyze the Linux startup process, you also need to compile the Linux source code first, because many files need to be compiled before they are generated

Yes. First, analyze the connection script file arch / arm / kernel / vmlinux LDS, which can be found through the link script

Where is the first line of the Linux kernel executed. vmlinux.lds has the following codes:

492 OUTPUT_ARCH(arm)
493 ENTRY(stext)
494 jiffies = jiffies_64;
495 SECTIONS
496 {
497 /*
498 * XXX: The linker does not define how output sections are
499 * assigned to input sections when there are multiple statements
500 * matching the same input section name. There is no documented
501 * order of matching.
502 *
503 * unwind exit sections must be discarded before the rest of the
504 * unwind sections get included.
505 */
506 	/DISCARD/ : {
507 	*(.ARM.exidx.exit.text)
508 	*(.ARM.extab.exit.text)
509
......
645 }

ENTRY in line 493 indicates the Linux kernel ENTRY. The ENTRY is stext, which is defined in the file

arch/arm/kernel/head.S, so to analyze the startup process of Linux kernel, you have to start from the file

arch/arm/kernel/head. Start analysis at stext of S.

2.Linux kernel startup process analysis

1) Linux kernel entry stext

stext is the entry address of the Linux kernel, which is in the file arch / arm / kernel / head S contains the following prompts:

/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
.....
*/

According to the comments of the above code, the requirements before Linux kernel startup are as follows:

1. Turn off MMU.

2. Close D-cache.

3. I-Cache doesn't matter.

                 4,r0=0.

5. R1 = machinenr (i.e. machine ID).

6. r2=atags or the first address of the device tree (dtb).

The entry point stext of Linux kernel is actually equivalent to the entry function of the kernel. The contents of stext function are as follows:

80 ENTRY(stext)
......
91 		@ ensure svc mode and all interrupts masked
92 		safe_svcmode_maskall r9
93
94 		mrc p15, 0, r9, c0, c0 @ get processor id
95 		bl __lookup_processor_type @ r5=procinfo r9=cpuid
96 		movs r10, r5 @ invalid processor (r5=0)?
97 		THUMB( it eq ) @ force fixup-able long branch encoding
98 		beq __error_p @ yes, error 'p'
99
......
107
108 #ifndef CONFIG_XIP_KERNEL
......
113 #else
114 	ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
115 #endif
116
117 /*
118 * r1 = machine no, r2 = atags or dtb,
119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120 */
121 	bl __vet_atags
......
128 	bl __create_page_tables
129
130 /*
131 * The following calls CPU specific code in a position independent
132 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
133 * xxx_proc_info structure selected by __lookup_processor_type
134 * above. On return, the CPU will be ready for the MMU to be
135 * turned on, and r0 will hold the CPU control register value.
136 */
137 	ldr r13, =__mmap_switched @ address to jump to after
138 		@ mmu has been enabled
139 	adr lr, BSYM(1f) @ return (PIC) address
140 	mov r8, r4 @ set TTBR1 to swapper_pg_dir
141 	ldr r12, [r10, #PROCINFO_INITFUNC]
142 	add r12, r12, r10
143 	ret r12
144 	1: b __enable_mmu
145 ENDPROC(stext)

Line 92, call the function safe_svcmode_maskall ensures that the CPU is in SVC mode and that all the are turned off

Break. safe_svcmode_maskall is defined in the file arch / arm / include / ASM / assembly H medium.

Line 94 reads the processor ID, and the ID value is stored in the r9 register.

Line 95, call the function__ lookup_processor_type check whether the current system supports this CPU. If it supports

Get procinfo information. Procinfo is proc_info_list structure, proc_info_list in file

               arch/arm/include/asm/procinfo.h is defined as follows:

struct proc_info_list {
	unsigned int cpu_val;
	unsigned int cpu_mask;
	unsigned long __cpu_mm_mmu_flags; /* used by head.S */
	unsigned long __cpu_io_mmu_flags; /* used by head.S */
	unsigned long __cpu_flush; /* used by head.S */
	const char *arch_name;
	const char *elf_name;
	unsigned int elf_hwcap;
	const char *cpu_name;
	struct processor *proc;
	struct cpu_tlb_fns *tlb;
	struct cpu_user_fns *user;
	struct cpu_cache_fns *cache;
};

The Linux kernel abstracts each processor as a proc_info_list structure, one for each processor

                procinfo. Therefore, the corresponding procinfo structure can be found through the processor ID__ lookup_processor_type

The function finds the procinfo of the corresponding processor and saves it in the r5 register.

Continue back to the above code, line 121, calling the function__ vet_atags verifies the validity of atags or device tree (dtb)

Sex. Function__ vet_atags is defined in the file arch / arm / kernel / head common S.

Line 128, call the function__ create_page_tables creates a page table.

Line 137, change the function__ mmap_ The switched address is saved in the r13 register__ mmap_switched setting

The meaning is in the file arch / arm / kernel / head common S,__ mmap_switched will eventually call start_kernel

Function.

Line 144, call__ enable_mmu function enables MMU__ enable_mmu is defined in the file

                 arch/arm/kernel/head.S__ enable_mmu will eventually call__ turn_mmu_on to open

                 MMU,__ turn_mmu_on will finally execute the saved in r13__ mmap_switched function.

    2)__ mmap_switched function

               __ mmap_ The switched function is defined in the file arch / arm / kernel / head common S, the function code is as follows:

81 __mmap_switched:
82 		adr r3, __mmap_switched_data
83
84 		ldmia r3!, {r4, r5, r6, r7}
85 		cmp r4, r5 @ Copy data segment if needed
86 		1: cmpne r5, r6
87 		ldrne fp, [r4], #4
88 		strne fp, [r5], #4
89 		bne 1b
90
91 		mov fp, #0 @ Clear BSS (and zero fp)
92 		1: cmp r6, r7
93 		strcc fp, [r6],#4
94 		bcc 1b
95
96 		ARM( ldmia r3, {r4, r5, r6, r7, sp})
97 		THUMB( ldmia r3, {r4, r5, r6, r7} )
98 		THUMB( ldr sp, [r3, #16] )
99 		str r9, [r4] @ Save processor ID
100 	str r1, [r5] @ Save machine type
101 	str r2, [r6] @ Save atags pointer
102 	cmp r7, #0
103 	strne r0, [r7] @ Save control register values
104 	b start_kernel
105 	ENDPROC(__mmap_switched)

Line 104 finally calls start_kernel to start the Linux kernel, start_ The kernel function is defined in the file init / main c

Yes.

    3)start_kernel function

               start_ The kernel completes some initialization before Linux startup by calling many sub functions

               start_ There are too many sub functions called in the kernel function, and these sub functions are very complex, so let's take a simple look

Let's talk about some important subfunctions.

               start_ A large number of functions are called in the kernel. Each function is a huge knowledge point. If you want to learn

Linux kernel, then these functions need to be studied in detail. This tutorial focuses on getting started with embedded Linux, so it doesn't

I will talk too much about the Linux kernel. start_ The kernel function finally calls rest_. Init, let's look at it briefly

Take a rest_init function.

    4)rest_init function

               rest_ The init function is defined in the file init / main In C, the function is as follows:

383 static noinline void __init_refok rest_init(void)
384 {
385 	int pid;
386
387 	rcu_scheduler_starting();
388 	smpboot_thread_init();
389 /*
390 * We need to spawn init first so that it obtains pid 1, however
391 * the init task will end up wanting to create kthreads, which,
392 * if we schedule it before we create kthreadd, will OOPS.
393 */
394 	kernel_thread(kernel_init, NULL, CLONE_FS);
395 	numa_default_policy();
396 	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
397 	rcu_read_lock();
398 	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
399 	rcu_read_unlock();
400 	complete(&kthreadd_done);
401
402 /*
403 * The boot idle thread must execute schedule()
404 * at least once to get things moving:
405 */
406 	init_idle_bootup_task(current);
407 	schedule_preempt_disabled();
408 /* Call into cpu_idle with preempt disabled */
409 	cpu_startup_entry(CPUHP_ONLINE);
410 }

Line 387, call the function rcu_scheduler_starting: start the RCU lock scheduler.

Line 394, call the kernel function_ Thread create kernel_init process, the famous init kernel

Cheng. The PID of init process is 1. The init process starts with the kernel process (that is, running in the kernel state), followed by the init process

The program named "init" will be found in the root file system. The "init" program is in user state. Run this program

"Init" program, the init process will realize the transformation from kernel state to user state.

Line 396, call the kernel function_ Thread creates a kthreadd kernel process with a PID of 2.

kthreadd process is responsible for the scheduling and management of all kernel processes.

The 409th row, and finally call the function cpu_. startup_ Enter to enter the idle process, cpu_startup_entry will call

                 cpu_idle_loop,cpu_ idle_ Loop is a while loop, that is, idle process code. PID of idle process is

0. Idle processes are called idle processes. If you have studied FreeRTOS or UCOS, you should have heard of idle tasks.

Idle processes are the same as idle tasks. When the CPU has nothing to do, it "wanders around" in idle processes

"Swim", anyway, is to find something to do for the CPU. When other processes want to work, they will preempt the idle process and seize it

CPU usage rights. In fact, you should see that the idle process does not use the kernel_thread or fork function

Created because it evolved from the main process.

Enter "ps -A" in the Linux terminal to print out all processes in the current system, where you can see the init entry

Process and kthreadd process, as shown in Figure 1:

Figure 1 current process of Linux system

As can be seen from Figure 1, the PID of init process is 1 and that of kthreadd process is 2. The reason why PID is not shown in Figure 1

The idle process is 0 because the idle process is a kernel process. Let's focus on the init process,

                kernel_init is the process function of init process.

5) init process

               kernel_ The init function is the specific work done by the init process, which is defined in the file init / main In C, the function is as follows:

928 static int __ref kernel_init(void *unused)
929 {
930 	int ret;
931
932 	kernel_init_freeable(); /* init Some other initialization of the process */
933 /* need to finish all async __init code before freeing the memory */
934 	async_synchronize_full(); /* Wait for all asynchronous calls to complete */
935 	free_initmem(); /* Release init segment memory */
936 	mark_rodata_ro();
937 	system_state = SYSTEM_RUNNING; /* Marking system is running */
938 	numa_default_policy();
939
940 	flush_delayed_fput();
941
942 	if (ramdisk_execute_command) {
943 		ret = run_init_process(ramdisk_execute_command);
944 		if (!ret)
945 			return 0;
946 		pr_err("Failed to execute %s (error %d)\n",
947 		ramdisk_execute_command, ret);
948 	}
949
950 /*
951 * We try each of these until one succeeds.
952 *
953 * The Bourne shell can be used instead of init if we are
954 * trying to recover a really broken machine.
955 */
956 	if (execute_command) {
957 		ret = run_init_process(execute_command);
958 	if (!ret)
959 		return 0;
960 	panic("Requested init %s failed (error %d).",
961 	execute_command, ret);
962 	}

963 	if (!try_to_run_init_process("/sbin/init") ||
964 		!try_to_run_init_process("/etc/init") ||
965 		!try_to_run_init_process("/bin/init") ||
966 		!try_to_run_init_process("/bin/sh"))
967 		return 0;
968
969 	panic("No working init found. Try passing init= option to kernel. "
970 			"See Linux Documentation/init.txt for guidance.");
971 }

Line 932, kernel_ init_ The freeable function is used to complete some other initialization of the init process, which will be implemented later

Take a look at this function.

Line 940, ramdisk_execute_command is a global char pointer variable whose value is "/ init",

That is, the init program in the root directory. ramdisk_ execute_ The command can also be passed through uboot. In

"rdinit=xxx" can be used in bootargs, and xxx is the specific init program name.

Line 943, if there is a "/ init" program, use the function run_init_process to run this program.

Line 956, if ramdisk_ execute_ If the command is empty, it depends on execute_ Whether command is empty,

Anyway, you must find a runnable init program in the root file system. execute_command

The value is passed through uboot. Just use "init=xxxx" in bootargs. For example, "init=/linuxrc" indicates the root text

linuxrc in the system is the user space init program to be executed.

Lines 963-966, if ramdisk_execute_command and execute_ If all commands are empty, then

Look for "/ sbin/init", "etc/init", "bin/init" and "/ bin/sh" successively, which are equivalent to standby init programs. If

If these four don't exist, then Linux startup fails! Line 969, if the user space is not found in the above steps

Init program, an error will be prompted! Finally, let's take a brief look at the kernel_init_freeable function, as mentioned earlier

Okay, kernel_init will call this function to initialize the init process. kernel_init_freeable is defined in the text

Init / main In C, the reduced function is as follows:

973 static noinline void __init kernel_init_freeable(void)
974 {
975 /*
976 * Wait until kthreadd is all set-up.
977 */
978 	wait_for_completion(&kthreadd_done);/* Wait for the kthreadd process to be ready */
......
998
999 	smp_init(); /* SMP initialization */
1000 	sched_init_smp(); /* Multi core (SMP) scheduling initialization */
1001
1002 	do_basic_setup(); /* Device initialization is completed in this function */
1003
1004 /* Open the /dev/console on the rootfs, this should never fail */
1005 	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) <
0)
1006 	pr_err("Warning: unable to open an initial console.\n");
1007
1008 	(void) sys_dup(0);
1009 	(void) sys_dup(0);
1010 /*
1011 * check if there is an early userspace init. If yes, let it do
1012 * all the work
1013 */
1014
1015 	if (!ramdisk_execute_command)
1016 		ramdisk_execute_command = "/init";
1017
1018 	if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
1019 		ramdisk_execute_command = NULL;
1020 		prepare_namespace();
1021 	}
1022
1023 /*
1024 * Ok, we have completed the initial bootup, and
1025 * we're essentially up and running. Get rid of the
1026 * initmem segments and start the user-mode stuff..
1027 *
1028 * rootfs is available now, try loading the public keys
1029 * and default modules
1030 */
1031
1032 	integrity_load_keys();
1033 	load_default_modules();
1034 }

Line 1002, do_ basic_ The setup function is used to initialize the device driver under Linux! Very important.

                do_basic_setup calls the driver_ The init function completes the initialization of the driver model subsystem under Linux.

In line 1005, open the device "/ dev/console". In Linux, everything is a file! So is "/ dev/console"

A file that is a console device. Each file has a file descriptor, which is opened here

"/ dev/console" file descriptor is 0 as standard input (0).

Lines 1008 and 1009, Sys_ The DUP function copies the file descriptor of the standard input (0) twice, one as the standard

Output (1), one as standard error (2). In this way, the standard input, output and error are all / dev/console.

Console is set through the bootargs environment variable of uboot, "console = ttymxc0115200" indicates

Set / dev/ttymxc0 to console, that is, serial port 1 of I.MX6U. Of course, other devices can also be set up

Set tty1 to console, such as virtual console tty1. Set tty1 to console, you can see the system status on the LCD screen

Display information.

Line 1020, call the function prepare_namespace to mount the root file system. The file system is also controlled by the command line

Parameter, that is, the bootargs environment variable of uboot. For example, "root=/dev/mmcblk1p2rootwaitrw"

It means that the root file system is in / dev/mmcblk1p2, that is, partition 2 of EMMC.

The Linux kernel startup process is analyzed here. Finally, the Linux kernel needs to deal with the root file system

Mount the root file system and execute the init program in the root file system to enter the user state. Here's the official citation

Out of the root file system, the root file system is also the last piece of puzzle of our system transplantation. Linux porting big three:

uboot, Linux kernel, rootfs (root file system).

The root file system will be explained in detail in the following chapters. Here, we only need to know that we need to wait until the Linux kernel is transplanted

You need to build a root file system.