After learning about the Assembly before kernel starts, let's take a look at the official C language startup code, that is, our start_kernel function. Start_kernel is quite large, and every function called in it is enough for us to worry about. Here I just give a brief introduction to the function, so as to have a more intuitive understanding of the process of starting the kernel. A lot of functions really need to have a deep understanding of the Linux-related systems, and there is no time for further exploration.
To tell the truth, the only thing I can see when I start the Code is that there are too many global variables in the kernel. It is quite painful to know how to track the changes in the value of a variable in a process, the gains are rich, and we have a more intuitive understanding of many concepts. I won't talk much about it in the idle time. I will go directly to the code ~~
Smp_setup_processor_id ();
// This function is empty now;
Lockdep_init ();
// Runtime locking correctness validator, see documentation/lockdep_design.txt
Debug_objects_early_init ();
Cgroup_init_early ();
// Control group, read documentation/cgroup.txt
Local_irq_disable ();
// Use the arm cpsid I command to disable IRQ
Early_boot_irqs_off ();
Early_init_irq_lock_class ();
/* Basically, the above functions initialize lockdep and cgroup, disable IRQ, and create conditions for subsequent operations */
Lock_kernel ();
/* Before reading this function, let's first take a look at it. Linux kernel supports preemption by default. In the SMP environment, in order to implement the kernel lock, the kernel uses a BKL (big kernel lock) concept. During the initialization process, the BKL is first locked, then, continue with other startup or initialization processes to prevent interruption during startup. After executing res_init, the kernel will release the lock, this ensures that the entire start_kernel process will not be preemptible or interrupted. From this we can see that this is mainly for multi-processor. In fact, if there is only one processor, it will not be interrupted or preemptible. */
/* Let's take a look at the execution process of this function. Here we can learn a lot:
Int depth = Current-> lock_depth + 1;
This current is actually a macro defined in arch/ARM/include/ASM/current. h, it is actually a structure of task_struct, which describes a series of information about this task (the task should be a basic unit of Linux Process Scheduling ?). In Linux, each process has a corresponding task_struct. This task_struct contains several information that we often see. One is PID, the other is the comm process name, and the other is mm_struct, it defines the management of all applied memory related to this process. This curren_thread_info () is also an interesting function. It directly reads the SP to obtain the thread_info structure information of the current thread. Thread_info and task_struct should represent all information of the current thread.
The initialized lock_depth is-1, and only the lock_depth Of The init task is 0.
If (likely (! Depth ))
_ Lock_kernel ();
Check whether it is an init task. If it is an init task, execute _ lock_kernel (); the _ lock_kernel command First disables preemption and then tries to obtain BKL. If it is successful, the system returns the result directly, if it fails, first determine whether the preemption is successful. If it succeeds, use the spin lock to lock BKL. If the preemption is not successful, wait for BKL when the preemption is valid until the BKL is obtained. Because the QC film is not SMP, it is successful when we try it for the first time.
Current-> lock_depth = depth;
This is nothing to say */
/* Basically, this lock_kernel is to prohibit preemption, and then get BKL, doing this */
Tick_init ();
// Initialization related to the clock, as if to register the notify event, has not been carefully studied
Boot_cpu_init ();
// This actually selects the CPU in the SMP environment. Here, the cpuid selects the CPU No. 0 directly.
Page_address_init ();
// Initialize high memory. In the arm environment, this function is actually empty. That is to say, arm does not support high memory.
Printk (kern_notice );
Printk (linux_banner );
// The ker_notice here is a string <5> and does not quite understand it... The linux_banner is defined in kernel/init/version. C. The printk here is a deep learning. It will be analyzed carefully when you look at the console later.
Setup_arch (& command_line );
/* This is a heavyweight function, which will be analyzed carefully and mainly completed in four aspects, one is to obtain information about the machine and processor, or assign them to the corresponding global variables of the kernel, and then parse the boot_command_line and tags, then it is the initialization of memory and cach, and the final request resource for subsequent running of the kernel. */
/* Let's take a closer look at the implementation of this function:
Setup_processor ();
This function first obtains the cpu id from the arm register, and then calls lookup_processor_type to obtain the struct proc_info_list. This process is actually the same as the process in our head-common.S, I don't know why I don't directly read the stored data in switch_data, But I query it again. Why? After obtaining proc_info_list, assign values to corresponding global variables one by one, and print the CPU information. It then obtains the cache information from the arm register and prints the cache information. Finally it will call the cpu_proc_init () function, which is actually defined in the proc-v6.S without doing anything.
Mdesc = setup_machine (machine_arch_type );
First, this machine_arch_type is defined in the generated. /include/ASM-arm/mach-types.h, The setup_macine is actually similar to the processor above, are calling the function in the head-common.S, according to the machine ID to obtain the machine information, print out the machine name.
If (mdesc-> soft_reboot)
Reboot_setup ("S ");
Set the reboot mode. The default mode is hard boot;
If (_ atags_pointer)
Tags = phys_to_virt (_ atags_pointer );
Else if (mdesc-> boot_params)
Tags = phys_to_virt (mdesc-> boot_params );
Here first judge the definition of the head-common.S _ atags_pointer is not empty, not empty, it means that the bootloader sets the initialization parameter, the physical address of the parameter is converted to virtual address, here there is a coverage, that is to say, you can relocate the physical address of the initialization parameter in machine DESC.
If (tags-> HDR. Tag! = Atag_core)
Convert_to_tag_list (TAGS );
If (tags-> HDR. Tag! = Atag_core)
Tags = (struct tag *) & init_tags;
First, determine whether the ATAG format is correct. If it is the earlier version of param_struct format, it will first be converted to the tag format. If it is still incorrect after conversion, use the default init_tags, the process is determined based on whether the first value of the struct is atag_core.
If (mdesc-> fixup)
Mdesc-> fixup (mdesc, tags, & from, & meminfo );
If (tags-> HDR. Tag = atag_core ){
If (meminfo. nr_banks! = 0)
Squash_mem_tags (TAGS );
Save_atags (TAGS );
Parse_tags (TAGS );
Here, we first judge the fixup function pointer, which is generally null. If it is not null, we will use fixup to re-Modify the memory map, the meminfo struct is defined in arch/ARM/include/ASM/setup. h describes the memory block information, the number of memory blocks, and the starting address and size of each memory block, if memory map is modified, you need to remove the memory map information passed by bootloader from the ATAG parameter, and then save ATAG. This saving function is actually empty here, no operation is performed. The ATAG parameter is parsed. Here we need to explain that the tags here is actually a tag array or queue. There are multiple tag struct, each of which is a header and a parameter. For the specific structure, we can look at setup. h. Parsing ATAG parameters is defined in arch/ARM/kernel/setup. c, first in setup. c defines a macro similar to _ tagtable (atag_core, parse_tag_core). This macro actually declares a struct between _ tagtable_begin and _ tagtable_end, this struct defines this parameter type and the function for parsing this parameter type. All parameters can be parsed from setup. find the corresponding function in C, for example, parsing boot_commad_line. The default_commad_line obtained from the config file will be replaced by commad_line in ATAG, for example, ramdisk, the ramdisk information in ATAG is assigned to global variables such as rd_image_start and rd_size.
Init_mm.start_code = (unsigned long) _ text;
Init_mm.end_code = (unsigned long) _ etext;
Init_mm.end_data = (unsigned long) _ edata;
Init_mm.brk = (unsigned long) _ end;
This is to assign values to the init_mm struct, but assign values to the text and data segments.
Memcpy (boot_command_line, from, command_line_size );
Boot_command_line [COMMAND_LINE_SIZE-1] = '/0 ';
Parse_cmdline (Response line_p, from );
Here the boot_command_line comes from config_cmdline In the config file, which may also be overwritten by the boot parameter in the ATAG. After obtaining the command_line, The command_line will be parsed. This parsing process is also in setup. c, which is first searched. the struct registered in the early_param.init section registers the early_param struct to these segments through _ early_param. The actual early_param is a string similar to "initrd =, it is used to match the characters in command_line. If it matches, execute the function in early_param and pass the matched characters as parameters to the function. For example, in our current commadline, there is an initrd = 0x11000000. Then, we first search for early_param-> ARG in the early_param.init segment to match this initrd =, if found, execute the func in it and pass the value of initrd = as a parameter.
Paging_init (mdesc );
This function is a big function, but the specific content is not carefully read. You need to have a deep understanding of arm MMU. Here we only post the comments about this function in Source:
/*
* Paging_init () sets up the page tables, initialises the zone memory
* Maps, and sets up the zero page, bad page and bad page tables.
*/
Request_standard_resources (& meminfo, mdesc );
This function is used to apply for some memory resources. The specific content is not carefully studied and cannot be understood ..
Cpu_init ();
Initialize the CPU. Here we mainly operate the arm register CPSR.
Init_arch_irq = mdesc-> init_irq;
System_timer = mdesc-> timer;
Init_machine = mdesc-> init_machine;
Here, we will assign values of the system structure-related functions, interrupt, initialization, and timer to the global variables of the kernel;
Conswitchp = & vga_con;
A variable about the console is set here. I don't know how to use it. I will analyze it carefully later when I look at the console.
Early_trap_init ();
I don't know what to do with this function... */
/* Basically, we can conclude that setup_arch mainly assigns some system structure information to the global variables of the kernel, including CPU, machine, memory, and cahce, then the kernel will perform the corresponding work based on these functions or variables, and it is obvious that this setup_arch and the previous head. s, head-common.S, proc-v6.S, board-msm7x27.c are closely linked together */
Here we will talk about the rest of start_kernel in the next article ~~