2020-2021-1 20209309《Linux内核原理与分析》第四周作业

Linux内核源码目录结构

  • arch:与CPU体系结构相关的子目录列表
  • block:存放Linux存储体系中关于块设备管理的代码
  • crypto:存放常见的加密算法的C语言代码
  • Documentation:用于存放文档
  • drivers:驱动目录,里面分门别类地存放了Linux内核支持的所有硬件设备的驱动源代码
  • firmware:固件目录
  • fs:文件系统,列出了Linux支持的各种文件系统的实现。
  • include:头文件目录,存放公共的头文件
  • init:存放Linux内核启动时的初始化代码
  • ipc:ipc目录里面是Linux支持的进程间通信的代码实现
  • kernel:存放内核本身需要的一些核心代码文件,包括进程号pid等
  • lib:公用的库文件
  • mm:与内存管理相关
  • net:存放网络相关的代码

调试Linux内核启动过程

使用gdb跟踪调试内核

断点调试与按步运行


启动过程分析(start_kernel)

asmlinkage __visible void __init start_kernel(void)
{
	char *command_line;
	char *after_dashes;
	lockdep_init();
	set_task_stack_end_magic(&init_task);
	smp_setup_processor_id();
	debug_objects_early_init();
	boot_init_stack_canary();
	cgroup_init_early();
	local_irq_disable();
	early_boot_irqs_disabled = true;
	boot_cpu_init();
	page_address_init();
	pr_notice("%s", linux_banner);
	setup_arch(&command_line);
	mm_init_cpumask(&init_mm);
	setup_command_line(command_line);
	setup_nr_cpu_ids();
	setup_per_cpu_areas();
	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */
	build_all_zonelists(NULL, NULL);
	page_alloc_init();
	pr_notice("Kernel command line: %s
", boot_command_line);
	parse_early_param();
	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();
	setup_log_buf(0);
	pidhash_init();
	vfs_caches_init_early();
	sort_main_extable();
	trap_init();
	mm_init();
	sched_init();
	preempt_disable();
	if (WARN(!irqs_disabled(),
		 "Interrupts were enabled *very* early, fixing it
"))
		local_irq_disable();
	idr_init_cache();
	rcu_init();
	context_tracking_init();
	radix_tree_init();
	early_irq_init();
	init_IRQ();
	tick_init();
	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
");
	early_boot_irqs_disabled = false;
	local_irq_enable();
	kmem_cache_init_late();
	console_init();
	if (panic_later)
		panic("Too many boot %s vars at `%s'", panic_later,
		      panic_param);

	lockdep_info();
	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.
",
		    page_to_pfn(virt_to_page((void *)initrd_start)),
		    min_low_pfn);
		initrd_start = 0;
	}
#endif
	page_cgroup_init();
	debug_objects_mem_init();
	kmemleak_init();
	setup_per_cpu_pageset();
	numa_policy_init();
	if (late_time_init)
		late_time_init();
	sched_clock_init();
	calibrate_delay();
	pidmap_init();
	anon_vma_init();
	acpi_early_init();
#ifdef CONFIG_X86
	if (efi_enabled(EFI_RUNTIME_SERVICES))
		efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
	init_espfix_bsp();
#endif
	thread_info_cache_init();
	cred_init();
	fork_init(totalram_pages);
	proc_caches_init();
	buffer_init();
	key_init();
	security_init();
	dbg_late_init();
	vfs_caches_init(totalram_pages);
	signals_init();
	page_writeback_init();
	proc_root_init();
	cgroup_init();
	cpuset_init();
	taskstats_init_early();
	delayacct_init();
	check_bugs();
	sfi_init_late();
	if (efi_enabled(EFI_RUNTIME_SERVICES)) {
		efi_late_init();
		efi_free_boot_services();
	}
	ftrace_init();
	rest_init();
}

宏定义

asmlinkage宏定义作用主要是让传送给函数的参数全部使用栈式传送,不用寄存器来传送,由于寄存器数量有限,使用栈可以容纳更多参数。
__init用来标志这个函数编译出来的目标代码具体放在那一段里。

lockdep_init

这个函数主要作用是初始化锁的状态跟踪模块,内核中使用锁来进行多线程与多处理器的同步操作,该函数用于调试内核加锁顺序,检测死锁可能。

set_task_stack_end_magic

该函数设置整个系统的第一个进程,其中的参数init_task为系统创建的第一个进程,也就是第0号进程,是唯一没有通过fork或kernel_thread产生的进程,是进程列表的第一个。

smp_setup_processor_id

该函数目的是直接获取对应多处理器的ID,相比于smp_processor_id函数其不需要使用setup_arch函数进行初始化。

debug_objects_early_init

函数主要作用是对调试对象进行早期的初始化,大致是对HASH锁和静态对象池进行初始化。

cgroup_init_early

这个函数主要作用是控制组进行早期的初始化,而控制组就是定义一组进程具有相同资源的占有程度。

local_irq_disable

这个函数主要作用是关闭当前CPU的所有中断响应。

early_boot_irqs_off

这个函数主要作用是标记内核还在早期初始化代码阶段,并且中断在关闭状态,如果有任何中断打开或请求中断的事情出现,都是会提出警告,以便跟踪代码错误情况。等到早期代码初始化结束之后,就会调用函数early_boot_irqs_on来设置这个标志为真。

boot_cpu_init

这个函数主要作用是设置当前引导系统的CPU在物理上存在,在逻辑上可以使用,并且初始化准备好。

page_address_init

初始化高端内存的映射表,在32位机中系统仅能访问4G,内核能访问的空间就更小了,所以为了映射更多的空间在原有内存的基础上分出高端内存,以访问更多的物理内存空间。

setup_arch

对内核架构进行初始化。再次获取CPU类型和系统架构,分析引导程序传入的命令行参数,进行页面内存初始化,处理器初始化,中断早期初始化等等。

mm_init_owner

设置最开始的初始化任务属于init_mm内存,而init_mm为mm_struct内存描述符的结构体实例。

setup_command_line

保存命令行,以便后面可以使用,与前面声明的command_line指针对应,这个指针是指向命令行参数的指针,主要用来指向引导程序传送给内核的命令行参数,函数setup_arch和函数setup_command_line会对它进行处理。

setup_nr_cpu_ids

设置最多有多少个nr_cpu_ids结构,nr_cpu_ids是一个特殊的值,在单CPU情况下是1,而SMP情况下,则为一个全局变量。

setup_per_cpu_areas

设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据。

smp_prepare_boot_cpu

为SMP系统里引导CPU进行准备工作。

build_all_zonelists

初始化所有内存管理节点列表,以便后面进行内存管理初始化。

page_alloc_init

设置内存页分配通知器。

pidhash_init

初始化进程ID的hash表,以便通过PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。

vfs_caches_init_early

虚拟文件系统的缓存初始化。

sort_main_extable

对内核内部的异常表进行堆排序,以便加速访问。

trap_init

对中断向量进行初始化。

mm_init

标记那些内存可以使用,并且告诉系统还有多少内存(除内存使用的)可以使用。

sched_init

对进程调度器进行初始化,比如分配调度器占用的内存,初始化任务队列,为当前任务设置空线程。

preempt_disable

关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度。

rcu_init

初始化直接读拷贝更新的锁机制。

radix_tree_init

初始化radix树,radix树是基于二进制键值的查找树。

init_IRQ

初始化中断相关的工作,主要初始化中断描述数组,然后调用每个CPU架构中断初始化。

prio_tree_init

初始化优先搜索树,主要用在内存反向搜索方面。

init_timers

初始化引导CPU的时钟相关的数据结构,注册时钟的回调函数,当时钟到达时可以回调时钟处理函数,最后初始化时钟软件中断处理。

hrtimers_init

初始化高精度的定时器。

softirq_init

初始化软件中断,软件中断是使用线程来监视中断信号,而硬件中断是使用CPU硬件来监视中断。

timekeeping_init

设置初始化系统时钟计时,初始化内核里与时钟计时相关的变量。

time_init

初始化系统时钟。

profile_init

分配内核性能统计保存的内存,以便统计的性能变量可以保存到这里。

early_boot_irqs_on

设置内核还在早期初始化阶段的标志,以便用来调试时输出信息,与上面的early_boot_irqs_off对应。

local_irq_enable

打开CPU的中断,允许本CPU处理中断事件,与上面的local_irq_disable对应。

console_init

初始化控制台,从这个函数之后就可以输出内容到控制台了,这个函数初化之前,都没有办法输出内容,就是输出,也是写到输出缓冲区里,缓存起来,等到这个函数调用之后,就立即输出内容。

lockdep_info

打印锁的依赖信息,用来调试锁,这个函数可以查看目前锁的状态,以便可以发现那些锁产生死锁,那些锁使用有问题。

locking_selftest

用来测试锁的API是否使用正常,进行自我测试。比如测试自旋锁、读写锁、一般信号量和读写信号量。

idr_init_cache

创建IDR机制的内存缓存对象,IDR就是整数标识管理机制,用于管理整数的ID与对象的指针的关系,由于这个ID可以达到32位,也就是说,如果使用线性数组来管理,那么分配的内存太大了;如果使用线性表来管理,又效率太低了,所以就引用IDR管理机制来实现这个需求。

setup_per_cpu_pageset

创建每个CPU的高速缓存集合数组。因为每个CPU都不定时需要使用一些页面内存和释放页面内存,为了提高效率,就预先创建一些内存页面作为每个CPU的页面集合。

numa_policy_init

初始化NUMA的内存访问策略。NUMA(NonUniform Memory AccessAchitecture)主要用来提高多个CPU访问内存的速度。多个CPU访问同一个节点的内存速度远远比访问多个节点的速度来得快。

calibrate_delay

主要计算CPU需要校准的时间,对应的时间是CPU执行时间。

thread_info_cache_init

初始化线程信息的缓存。

fork_init

根据当前物理内存计算出来可以创建进程(线程)的数量,并进行进程环境初始化。

proc_caches_init

进程缓存初始化。

buffer_init

初始化文件系统的缓冲区,并计算最大可以使用的文件缓存。

key_init

初始化安全键管理列表和结构。

security_init

初始化安全管理框架,以便提供访问文件/登录等权限。

vfs_caches_init

对虚拟文件系统进行缓存初始化,提高虚拟文件系统的访问速度。

signals_init

初始化信号队列缓存。

cgroup_init

初始化进程控制组,主要用来为进程和其子程提供性能控制。比如限定这组进程的CPU使用率.

taskstats_init_early

初始化任务状态相关的缓存、队列和信号量。任务状态主要向用户提供任务的状态信息。

delayacct_init

初始化每个任务延时计数。当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间。

启动过程分析(rest_init)

static noinline void __init_refok rest_init(void)
{
	int pid;
	rcu_scheduler_starting();
	kernel_thread(kernel_init, NULL, CLONE_FS);
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
	complete(&kthreadd_done);
	init_idle_bootup_task(current);
	schedule_preempt_disabled();
	cpu_startup_entry(CPUHP_ONLINE);
}

rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。之后调用schedule函数开启内核的调度系统,使系统开始运转。最终其结束了整个内核的启动,以一个死循环的方式保持系统运行,linux内核最终的状态是:有任务的时候去执行各个进程任务,没有任务执行就运行死循环(空闲进程)维持。

  • 进程0:进程0其实就是空闲进程(死循环)以维持系统运行。
  • 进程1:kernel_init函数就是进程1,这个进程被称为init进程,也是第一个用户进程。
  • 进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。它的作用是管理调度其他内核进程,保证linux内核自身能正常工作的。
原文地址:https://www.cnblogs.com/yanzs/p/13910344.html