Embedded ARM Linux kernel startup process: a brief analysis of the start_kernel function
[Copy link]
After understanding the assembly before kernel startup, let's take a look at the official C language startup code, that is, our start_kernel function. start_kernel is quite large, and each function called in it is enough to make us worry. Here we only briefly describe the function's function, so that we can have a more intuitive understanding of the kernel startup process. To truly understand many functions, you need to have a deep understanding of the Linux related system. To be honest, my only feeling after reading the startup code so far is that there are too many global variables in the kernel. It is quite painful to understand a process and track the changes in the value of a variable. However, after reading it patiently, I gained a lot and had a more intuitive understanding of many concepts. I won't say much gossip, let's get straight 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(); //Usearm cpsid i instruction to disable IRQ early_boot_irqs_off(); early_init_irq_lock_class();/* Basically the above functions are to initialize lockdep and cgroup, and then disable IRQ to create conditions for subsequent operation*/ lock_kernel(); early_boot_irqs_off(); early_init_irq_lock_class();/* Before looking at this function, let's first have some knowledge. The linux kernel supports preemption (preemption) by default. In order to lock the kernel in the SMP environment, the kernel uses the concept of BKL (big kernel lock). It first locks the BKL during the initialization process, and then continues with other startup or initialization processes. This prevents the startup process from being interrupted. After res_init is executed, the kernel will release the lock, thus ensuring that the entire start_kernel process will not be preempted or interrupted. From this we can see that this is mainly for multi-processors. In fact, if there is only one processor, it will not be interrupted or preempted. */ /* Let's take a look at the execution process of this function, and we can learn a lot from it: int depth = current->lock_depth+1; This current is actually a macro, defined in arch/arm/include/asm/current.h, it is actually atask_struct structure, this structure describes a series of information of thistask(task should belinuxa basic unit of process scheduling?). Each process under linux has a corresponding task_struct. This task_struct has several information that we often see. One is PID, then the name of the comm process, and then the mm_struct, which defines the management of all requested memory related to this process. For embedded Internet of Things and other system learning, please add Penguin. It is easy to be angry for a long time. This curren_thread_info()is also a very interesting function. It directly reads SP to obtain the structure information of the current thread thread_info. thread_infoand task_structthese two structures should represent all the information of the current thread. The initialized lock_depth is -1, only the init task's lock_depth is 0. if (likely(!depth))__lock_kernel(); Here, it is judged whether it is the init task. If it is, it will execute__lock_kernel(); This__lock_kernel first prohibits preemption, and then tries to obtainBKL. If successful, it returns directly. If unsuccessful, it first determines whether the prohibition of preemption is successful. If successful, it uses the spin lock to lockBKL. If prohibiting preemption is not successful, it will wait forBKL under the condition that preemption is valid until it obtainsBKL. Because the QC film is not SMP, the first try here succeeded directly. current->lock_depth = depth; There is nothing much to say about this *//align/* Basically, lock_kernel is to prohibit preemption, and then obtain BKL, and do this*/tick_init();//Initialization related to the clock seems to be registering notify events, but I haven't studied it carefully.boot_cpu_init(); //This is actually selecting CPU in SMP environment. Here CPUID directly selects 0 cpu. page_address_init(); //Initialize high memory, in arm environment this function is actually empty, that is to say arm does not support high memory printk(KERN_NOTICE); printk(linux_banner); // KER_NOTICE here is the string <5>, I don't quite understand what it means. . . The following linux_banner is defined in kernel/init/version.c, the printk here is a profound knowledge, and I will analyze it carefully when I look at the console in the future. setup_arch(&command_line);/* This is a heavyweight function. I will analyze it more carefully. It mainly completes four aspects of work. One is to obtain the information of MACHINE and PROCESSOR and assign them to the corresponding global variables of the kernel. Then it is to The nt]boot_command_line and tags lines are parsed, and then the memory and cach are initialized, and finally resources are requested for the subsequent operation of the kernel. */ /* Let's take a closer look at the implementation of this function: setup_processor(); This function first gets the cpu IDfrom the armregister, and then calls lookup_processor_type to get the proc_info_liststructure. This process is actually the same as the process in head-common.S. I don't know why it doesn't directly read the data already saved in switch_data but instead queries it again? After getting proc_info_list, assign the contents to the corresponding global variables one by one, and then print out the CPU information. Then it will get the cache information from the arm register and print out the cache information. Finally, it will call the cpu_proc_init() function, which is actually defined in proc-v6.S and does not do anything. mdesc = setup_machine(machine_arch_type); First of all, the machine_arch_type is defined in the generated ./include/asm-arm/mach-types.h. In fact, setup_macine is similar to processor above. Both call the function in head-common.S to get the information of Machine according to machine ID and pass it to the new machine. nameis printed out. if (mdesc->soft_reboot) reboot_setup("s"); Setrebootmode, the default 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 we first judgehead-common.Is the __atags_pointer defined in S empty? If not, it means that the bootloader has set the initialization parameters and converted the physical address of the parameters into a virtual address. There is an overlay here, which means that the physical address of the initialization parameters can be relocated in the 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 it is the correct atag format. If it is the old version of the param_struct format, it will first be converted into the tag format. If it is still incorrect after conversion, the default init_tags will be used. The judgment process here is based on whether the first value of the structure is ATAG_CORE. if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo); if (tags->hdr.tag == ATAG_CORE) { (meminfo.nr_banks != 0)squash_mem_tags(tags);save_atags(tags);parse_tags(tags);Here first determine the fixup function pointer, which is usually empty. If it is not empty, fixup will be used to re-modify memory. map, the meminfo structure is defined in arch/arm/include/asm/setup.h, which describes the information of the memory block, the number of memory blocks, the starting address and size of each memory block. If the memory map is modified, you need to remove the memory passed by the bootloader from the atag parameter. map information, and then saveatag. This save function is actually empty here and does not do any operation. Finally, theatag parameter is parsed. It should be explained here thattags is actually an array or queue oftag, which contains multipletag structures. Each structure is aheader plus a parameter. We can seesetup.h for the specific structure. The parsing of ATAG parameters is all defined in arch/arm/kernel/setup.c. First, a macro similar to __tagtable(ATAG_CORE, parse_tag_core) is defined in setup.c. This macro actually declares a structure placed between __tagtable_begin and __tagtable_end sections. This structure defines the parameter type and the function that parses the parameter type. We can get all the parameter parsing from setup.cto find the corresponding function, for example, for the analysis of boot_commad_line, the default_commad_line obtained from the config file will be obtained from ATAG. alibri]commad_linewill be replaced bycommad_line; for example,ramdiskwill assign theramdiskinformation inATAGtord_image_start, rd_sizeand other system global variables. 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 theinit_mmstructure. I don't know how these things are used, but they are to assign values to thetextanddatasegments. memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '/0'; parse_cmdline(cmdline_p, from); The boot_command_line[/font=宋体] here comes from the CONFIG_CMDLINE[/font=宋体] in the config[/font=宋体] file, and may also be overwritten by the boot[/font=宋体] parameter in ATAG[/font=宋体]. After obtaining the command_line[/font=宋体], the command_line[/font=宋体] is parsed. This parsing process is also carried out in setup.c. It first searches for the structure registered in the early_param.init segment, and registers the early_param structure to these segments through __early_param. ont]early_param is very simple. It is a string similar to "initrd=", which is used to match the characters in command_line. If a match is found, the function in early_param is executed, and the matched characters are passed to the function as parameters. For example, let's say our current commadline contains a sentence initrd=0x11000000, and then we first search in the early_param.init segment to see if early_param->arg matches this initrd=, and if found, we execute func in it and pass the value of this initrd= as a parameter. paging_init(mdesc); This function is a big function. I didn't look at the specific content carefully. It requires a deep understanding of arm MMU. Here I will only paste the comments about this function in the 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. I haven't studied the specific content carefully and don't understand it. cpu_init(); Initialize the CPU, mainly for the arm register cpsr operation init_arch_irq = mdesc->init_irq; system_timer = mdesc->timer; init_machine = mdesc->init_machine; Here, several functions related to the architecture, interrupts, initialization, timers, etc. are assigned to kernel global variables; conswitchp = &vga_con;A variable about console is set here. I don't know how to use it. I will analyze it carefully when I see console later.early_trap_init();I don't know what this function is used for. . . *///align/* Basically we can conclude that setup_arch mainly assigns some architecture-related information to kernel's global variables, including cpu, machine, memory, cache, and then i]kernel then does the corresponding work according to 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*/ mdesc); This function is used to apply for some memory resources. I haven't studied the specific content carefully and don't understand it. cpu_init(); Initialize the CPU, mainly for the arm register cpsr operation init_arch_irq = mdesc->init_irq; system_timer = mdesc->timer; init_machine = mdesc->init_machine; Here, several functions related to the architecture, interrupts, initialization, timers, etc. are assigned to kernel global variables; conswitchp = &vga_con;A variable about console is set here. I don't know how to use it. I will analyze it carefully when I see console later.early_trap_init();I don't know what this function is used for. . . *///align/* Basically we can conclude that setup_arch mainly assigns some architecture-related information to kernel's global variables, including cpu, machine, memory, cache, and then i]kernel then does the corresponding work according to 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*/ mdesc); This function is used to apply for some memory resources. I haven't studied the specific content carefully and don't understand it. cpu_init(); Initialize the CPU, mainly for the arm register cpsr operation init_arch_irq = mdesc->init_irq; system_timer = mdesc->timer; init_machine = mdesc->init_machine; Here, several functions related to the architecture, interrupts, initialization, timers, etc. are assigned to kernel global variables; conswitchp = &vga_con;A variable about console is set here. I don't know how to use it. I will analyze it carefully when I see console later.early_trap_init();I don't know what this function is used for. . . *///align/* Basically we can conclude that setup_arch mainly assigns some architecture-related information to kernel's global variables, including cpu, machine, memory, cache, and then i]kernel then does the corresponding work according to 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*/
|