正点原子linux内核启动流程学习笔记

2021/4/13 7:25:30

本文主要是介绍正点原子linux内核启动流程学习笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1、Linux 内核入口 stext

在linux内核启动之前要求如下:
①、关闭 MMU。
②、关闭 D-cache。(数据缓存)
③、 I-Cache 无所谓。(指令缓存)
④、 r0=0。
⑤、 r1=machine nr(也就是机器 ID)。
⑥、 r2=atags 或者设备树(dtb)首地址

1.1 为什么需要关闭MMU和D-cache

1.1.1 cache的作用

cache 是高速缓冲存储器
cache是位于主存(即是内存)与CPU内部的寄存器之间的一个存储设施,用来加快cpu与内存之间
数据与指令的传输速率,从而加快处理的速度。

1.1.2 为什么要关闭D-Cache 而I-Cache无所谓

在设备上电之初,内存的初始化速度比cpu初始化慢,在内存没有准备好的情况下,就对内存进行数据读取,那么会造成数据读取异常.
至于为什么I-Cache无所谓.不知道

Volatile:

本质:是告诉编译器不要对我的代码进行优化,作用是让编写者感觉变量的变化情况。因为在优化时,会将常用的代码取出来放到Caches中,它没有从实际的物理地址去取,它直接从CPU的缓存中去取,但常用的代码就是为了检测一些常用变量的变化,如果正在取数据的时候发生跳变,那么就检测不到变量的变化了,所以在这种情况下要用Volatile关键字告诉编译器不要做优化,让cpu每次都从实际的物理地址中去取指令。其实这也是为什么要关闭数据缓存的原因,如果汇编指令读取的时候缓存中的数据,而实际物理地址的数据发生了变化,将导致cpu读取不到真实的最新的值。然而在C语言中是不会关闭Caches的,如果编写者要检测外界物理数据的变化,或变化太快,从Caches中取数据会有误差,就加一个关键字Volatile。

1.2.1 MMU

mmu可以实现虚拟内存和内存保护等功能,完成对内存的操作和管理。
1、没有对MMU进行初始化,且用不到mmu为了避免影响启动时的初始化,先关闭MMU

2 linux内核启动步骤

2.1

  1. safe_svcmode_maskall 确保CPU处于SVC模式,并且关闭所有中断
  2. 读取处理器ID
  3. __lookup_processor_type 检查当前系统是否支持此CPU,支持则获取procinfo信息

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;
};
Linux 内核将每种处理器都抽象为一个 proc_info_list 结构体,每种处理器都对应一个
procinfo。

  1. __vet_atag 验证atags或则设备树(dtb是否有效
  2. __create_page_tables 创建页表
  3. __mmap_switched 的地址保存到 r13 寄存器中
  4. start_kernel
/*
	 * Need to run as early as possible, to initialize the
	 * lockdep hash:
	 */
	lockdep_init();						/* lockdep 是死锁检测模块,此函数会初始化 ,两个hash表。此函数要求尽可能的早执行*/
	set_task_stack_end_magic(&init_task); /* 设置任务栈结束,用于栈溢出检测*/
	
	smp_setup_processor_id();		/* 跟 SMP 有关(多核处理器),设置处理器 ID */
	
	debug_objects_early_init();		/* 做一些和 debug 有关的初始化 */

	/*
	 * Set up the the initial canary ASAP:
	 */
	boot_init_stack_canary();		/* 栈溢出检测初始化 */ 

	cgroup_init_early();			/* cgroup 初始化, cgroup 用于控制 Linux 系统资源*/

	local_irq_disable();			/* 关闭当前 CPU 中断 */
	early_boot_irqs_disabled = true;

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
	boot_cpu_init();			/* 跟 CPU 有关的初始化 */
	page_address_init();		/* 页地址相关的初始化 */
	pr_notice("%s", linux_banner); /* 打印 Linux 版本号、编译时间等信息 */
	setup_arch(&command_line);/* 架构相关的初始化,此函数会解析传递进来的
								* ATAGS 或者设备树(DTB)文件。会根据设备树里面
								* 的 model 和 compatible 这两个属性值来查找
								* Linux 是否支持这个单板。此函数也会获取设备树
								* 中 chosen 节点下的 bootargs 属性值来得到命令
								* 行参数,也就是 uboot 中的 bootargs 环境变量的
								* 值,获取到的命令行参数会保存到
								*command_line 中。
								*/
	mm_init_cpumask(&init_mm);
	setup_command_line(command_line);
	setup_nr_cpu_ids();			/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
								* CPU 核心数量, CPU 数量保存在变量
								* nr_cpu_ids 中。
								*/
	setup_per_cpu_areas();		 /* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */

	build_all_zonelists(NULL, NULL); /* 建立系统内存页区(zone)链表 */
	page_alloc_init();				/* 处理用于热插拔 CPU 的页 */
	
	/* 打印命令行信息 */
	pr_notice("Kernel command line: %s\n", boot_command_line);
	
	parse_early_param();	/* 解析命令行中的 console 参数 */
	after_dashes = parse_args("Booting kernel",
				  static_command_line, __start___param,
				  __stop___param - __start___param,
				  -1, -1, &unknown_bootoption);
	if (!IS_ERR_OR_NULL(after_dashes))
		parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
			   set_init_arg);

	jump_label_init();

	/*
	 * These use large bootmem allocations and must precede
	 * kmem_cache_init()
	 */
	setup_log_buf(0);		/* 设置 log 使用的缓冲区*/
	pidhash_init();			/* 构建 PID 哈希表, Linux 中每个进程都有一个 ID,
							* 这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程
							* 信息结构体。
							*/
	vfs_caches_init_early();/* 预先初始化 vfs(虚拟文件系统)的目录项和
							* 索引节点缓存
							*/
	sort_main_extable();	/* 定义内核异常列表 */
	trap_init();			/* 完成对系统保留中断向量的初始化 */
	mm_init();				/* 内存管理初始化 */
	sched_init();			/* 初始化调度器,主要是初始化一些结构体 */
	preempt_disable();		/* 关闭优先级抢占 */
	if (WARN(!irqs_disabled(),	/* 检查中断是否关闭,如果没有的话就关闭中断 */
		 "Interrupts were enabled *very* early, fixing it\n"))
		local_irq_disable();	
	idr_init_cache();		/* IDR 初始化, IDR 是 Linux 内核的整数管理机
							* 制,也就是将一个整数 ID 与一个指针关联起来。
							*/
	rcu_init();				/* 初始化 RCU, RCU 全称为 Read Copy Update(读-拷贝修改) */
	/* trace_printk() and trace points may be used after this */
	trace_init();			/* 跟踪调试相关初始化 */
	context_tracking_init();
	radix_tree_init();		/* 基数树相关数据结构初始化 */
	/* init some links before init_ISA_irqs() */
	early_irq_init();		/* 初始中断相关初始化,主要是注册 irq_desc 结构体变
							* 量,因为 Linux 内核使用 irq_desc 来描述一个中断。
							*/
	init_IRQ();				/* 中断初始化 */
	tick_init();			/* tick 初始化 */
	rcu_init_nohz();
	init_timers();			/* 初始化定时器 */
	hrtimers_init();		/* 初始化高精度定时器 */
	softirq_init();			/* 软中断初始化 */
	timekeeping_init();
	time_init();			/* 初始化系统时间 */
	sched_clock_postinit();
	perf_event_init();
	profile_init();
	call_function_init();
	WARN(!irqs_disabled(), "Interrupts were enabled early\n");
	early_boot_irqs_disabled = false;
	local_irq_enable();		/* 使能中断 */
	kmem_cache_init_late();	/* slab 初始化, slab 是 Linux 内存分配器 */

	/*
	 * HACK ALERT! This is early. We're enabling the console before
	 * we've done PCI setups etc, and console_init() must be aware of
	 * this. But we do want output early, in case something goes wrong.
	 */
	console_init();		/* 初始化控制台,之前 printk 打印的信息都存放
						* 缓冲区中,并没有打印出来。只有调用此函数
						* 初始化控制台以后才能在控制台上打印信息。
						*/
	if (panic_later)
		panic("Too many boot %s vars at `%s'", panic_later,
		      panic_param);

	lockdep_info();	  /* 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息。 */

	/*
	 * Need to run this when irqs are enabled, because it wants
	 * to self-test [hard/soft]-irqs on/off lock inversion bugs
	 * too:
	 */
	locking_selftest();   /* 锁自测 */

#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start && !initrd_below_start_ok &&
	    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
		pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
		    page_to_pfn(virt_to_page((void *)initrd_start)),
		    min_low_pfn);
		initrd_start = 0;
	}
#endif
	page_ext_init();
	debug_objects_mem_init();
	kmemleak_init();			/* kmemleak 初始化, kmemleak 用于检查内存泄漏 */
	setup_per_cpu_pageset();
	numa_policy_init();
	if (late_time_init)
		late_time_init();
	sched_clock_init();
	calibrate_delay();		 /* 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能
								* BogoMIPS 设置越大,说明 CPU 性能越好。
								*/
	pidmap_init();			/* PID 位图初始化 */
	anon_vma_init();		/* 生成 anon_vma slab 缓存 */
	acpi_early_init();
#ifdef CONFIG_X86
	if (efi_enabled(EFI_RUNTIME_SERVICES))
		efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
	/* Should be run before the first non-init thread is created */
	init_espfix_bsp();
#endif
	thread_info_cache_init();
	cred_init();				/* 为对象的每个用于赋予资格(凭证) */
	fork_init();				/* 初始化一些结构体以使用 fork 函数 */
	proc_caches_init();			/* 给各种资源管理结构分配缓存 */
	buffer_init();				/* 初始化缓冲缓存 */
	key_init();					/* 初始化密钥 */
	security_init();			/* 安全相关初始化 */
	dbg_late_init();
	vfs_caches_init(totalram_pages);		/* 为 VFS 创建缓存 */
	signals_init();							/* 初始化信号 */
	/* rootfs populating might need page-writeback */
	page_writeback_init();					/* 页回写初始化 */
	proc_root_init();					/* 注册并挂载 proc 文件系统 */
	nsfs_init();
	cpuset_init();						/* 初始化 cpuset, cpuset 是将 CPU 和内存资源以逻辑性
										* 和层次性集成的一种机制,是 cgroup 使用的子系统之一
										*/
	cgroup_init();						/* 初始化 cgroup */
	taskstats_init_early();				/* 进程状态初始化 */
	delayacct_init();					/* 检查写缓冲一致性 */

	check_bugs();

	acpi_subsystem_init();
	sfi_init_late();

	if (efi_enabled(EFI_RUNTIME_SERVICES)) {
		efi_late_init();
		efi_free_boot_services();
	}

	ftrace_init();

	/* Do the rest non-__init'ed, we're now alive */
	rest_init();						/* rest_init 函数 */

rest_init();

static noinline void __init_refok rest_init(void)
{
	int pid;

	rcu_scheduler_starting();		/* 启动 RCU 锁调度器 */
	smpboot_thread_init();
	/*
	 * 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.
	 */
	kernel_thread(kernel_init, NULL, CLONE_FS);		 /* 创建init 进程 PID =1 */
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);	/* 创建 kthreadd 内核进程,此内核进程的 PID 为 2*/
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
	complete(&kthreadd_done);

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	init_idle_bootup_task(current);
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE); /* 来进入 idle 进程 PID = 0*/
}

  1. _enable_mmu 函 数 使 能 MMU


这篇关于正点原子linux内核启动流程学习笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程