ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2021-04-12 23:59:42  阅读:243  来源: 互联网

标签:__ 初始化 init CPU early 正点 内核 linux cpu


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

标签:__,初始化,init,CPU,early,正点,内核,linux,cpu
来源: https://blog.csdn.net/weixin_43471255/article/details/115643838

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有