首页 > 其他分享 >chapter-1 start_kernel() part-1

chapter-1 start_kernel() part-1

时间:2024-05-21 14:57:11浏览次数:35  
标签:chapter __ boot start init part command 内核 line

linux kernel v6.6.31(LTS)
start_kernel()的实现在 /init/main.c

asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)

先解释一手上面一大串宏的作用:

  • asmlinkage: 这是一个汇编语言链接约定,用于告诉编译器这个函数的参数是从栈上获取的,而不是通过寄存器。在x86架构中,所有内核的入口点(包括中断和系统调用处理程序)都使用asmlinkage,因为当这些函数被调用时,寄存器可能正在被其他用途。
  • __visible: 这个宏确保函数对于链接器是可见的,这样它就可以被正确地放置在内核的二进制映像中,并可以被其他模块引用。
  • __init: 这个标记告诉编译器这个函数只在初始化阶段需要,一旦初始化完成,这个函数就不再需要了。编译器会因此将这个函数的代码放置在内核映像的一个特殊段中,在初始化之后这个段可以被释放,从而节省内存资源。
  • __no_sanitize_address: 这个修饰符用于告诉AddressSanitizer(一种内存错误检测工具)不要对函数进行检测。这通常用于那些不遵循普通内存访问规则的底层代码。
  • __noreturn: 这个修饰符表明函数不会返回给调用者。对于start_kernel来说,这个函数是内核执行的起点,它不会返回到之前的执行环境。
  • __no_stack_protector: 这个修饰符用于关闭堆栈保护,这通常用于那些对性能要求极高或者在特定情况下堆栈保护会导致问题的代码。

由于start_kernel()实在是太长,下面分段解释各个变量和函数的作用,必要时会给出子一级函数的定义和实现。下面看以下几个变量和初始化函数或宏:

char* command_line;
char* after_dashes;

以上两个变量存储引导过程中传递给内核的参数,可以用来配置内核相关功能

set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
init_vmlinux_build_id();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
  • set_task_stack_end_magic(&init_task) impl in /kernel/fork.c
void set_task_stack_end_magic(struct task_struct *tsk)
{
	unsigned long *stackend;

	stackend = end_of_stack(tsk);
	*stackend = STACK_END_MAGIC;	/* for overflow detection */
}

这个函数用于设置init进程(PID 0)的栈结束魔数(magic number)。这个魔数用于检测栈溢出。init_task是内核中的第一个任务,是所有其他任务的祖先。这个函数确保init_task的栈有一个已知的结束标记,以便在栈溢出时可以检测到。

魔数(magic number):特别的没有什么意义数值,此处的魔数是定义在`/include/uapi/linux/magic.h`中的`STACK_END_MAGIC`(0x57AC6E9D)
  • smp_setup_processor_id()
    这个函数用于在多处理器(SMP)系统中设置当前处理器的ID。在SMP系统中,每个CPU都需要知道自己的身份,以便内核可以正确地管理并发执行和处理器间的通信。
  • debug_objects_early_init() impl in /lib/debugobjects.c
void __init debug_objects_early_init(void)
{
	int i;

	for (i = 0; i < ODEBUG_HASH_SIZE; i++)
		raw_spin_lock_init(&obj_hash[i].lock);

	for (i = 0; i < ODEBUG_POOL_SIZE; i++)
		hlist_add_head(&obj_static_pool[i].node, &obj_pool);
}

这个函数用于初始化内核的调试对象子系统。调试对象是一种内核基础设施,用于跟踪内核中的对象(如锁、引用计数等)的生命周期,以帮助开发者发现和修复内存泄漏和死锁等问题。

  • init_vmlinux_build_id() impl in /lib/buildid.c
void __init init_vmlinux_build_id(void)
{
	extern const void __start_notes __weak;
	extern const void __stop_notes __weak;
	unsigned int size = &__stop_notes - &__start_notes;

	build_id_parse_buf(&__start_notes, vmlinux_build_id, size);
}

这个函数用于初始化内核映像的构建ID。构建ID是一个唯一标识内核构建的字符串,通常包含构建的时间戳和Git哈希值。这个ID可以帮助开发者追踪内核的版本和构建信息。

  • cgroup_init_early() impl in /kernel/cgroup/cgroup.c
int __init cgroup_init_early(void)
{
	static struct cgroup_fs_context __initdata ctx;
	struct cgroup_subsys *ss;
	int i;

	ctx.root = &cgrp_dfl_root;
	init_cgroup_root(&ctx);
	cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

	RCU_INIT_POINTER(init_task.cgroups, &init_css_set);

	for_each_subsys(ss, i) {
		WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,
		     "invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p id:name=%d:%s\n",
		     i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,
		     ss->id, ss->name);
		WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,
		     "cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);

		ss->id = i;
		ss->name = cgroup_subsys_name[i];
		if (!ss->legacy_name)
			ss->legacy_name = cgroup_subsys_name[i];

		if (ss->early_init)
			cgroup_init_subsys(ss, true);
	}
	return 0;
}

这个函数用于初始化控制组(cgroups)的早期阶段。cgroups是Linux内核的一个功能,用于限制、计算和隔离进程组使用的资源(如CPU、内存、网络等)。

  • local_irq_disable() impl in /include/linux/irqflags.h
    定义了CONFIG_TRACE_IRQFLAGS的话 local_irq_disable()的实现是
#define local_irq_disable()				        \
	do {						                \
		bool was_disabled = raw_irqs_disabled();\
		raw_local_irq_disable();		        \
		if (!was_disabled)			            \
			trace_hardirqs_off();		        \
	} while (0)

否则为

#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)

这个宏用于在当前处理器上禁用本地中断。在内核初始化的早期阶段,中断可能会干扰内核的初始化过程,因此有时需要禁用它们。

  • early_boot_irqs_disabled = true
    这行代码将early_boot_irqs_disabled变量设置为true,表示在内核初始化的早期阶段,中断被禁用了。这个变量用于跟踪中断的状态,以确保在适当的时候重新启用中断。
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line);
setup_boot_config();
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */
boot_cpu_hotplug_init();
  • boot_cpu_init() impl in /kernel/cpu.c
void __init boot_cpu_init(void)
{
	int cpu = smp_processor_id();

	/* Mark the boot cpu "present", "online" etc for SMP and UP case */
	set_cpu_online(cpu, true);
	set_cpu_active(cpu, true);
	set_cpu_present(cpu, true);
	set_cpu_possible(cpu, true);

#ifdef CONFIG_SMP
	__boot_cpu_id = cpu;
#endif
}

初始化引导CPU(bootstrapping CPU)的数据结构。这通常包括设置CPU的标识信息,如CPU ID,以及可能的特定于CPU的初始化操作。

  • page_address_init()
    初始化页到物理地址的映射。这允许内核通过页结构访问实际的物理内存地址。
  • pr_notice("%s", linux_banner)
    打印Linux内核的版本信息,这通常是在内核启动日志中看到的第一个信息,用于显示内核版本、编译器版本等信息。
  • early_security_init() impl in /security/security.c
int __init early_security_init(void)
{
	struct lsm_info *lsm;

#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
	INIT_HLIST_HEAD(&security_hook_heads.NAME);
#include "linux/lsm_hook_defs.h"
#undef LSM_HOOK

	for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
		if (!lsm->enabled)
			lsm->enabled = &lsm_enabled_true;
		prepare_lsm(lsm);
		initialize_lsm(lsm);
	}

	return 0;
}

初始化内核的早期安全特性,如安全增强Linux(SELinux)或其他安全模块的早期设置。

  • setup_arch(&command_line) impl in /arch/<arch>/kernel/setup.c
    执行与体系结构相关的初始化。这个函数非常依赖于具体的硬件架构,它负责设置内核的中断处理、内存管理、CPU调度等。
  • setup_boot_config() impl in /init/main.c
static void __init setup_boot_config(void)
{
	static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
	const char *msg, *data;
	int pos, ret;
	size_t size;
	char *err;

	/* Cut out the bootconfig data even if we have no bootconfig option */
	data = get_boot_config_from_initrd(&size);
	/* If there is no bootconfig in initrd, try embedded one. */
	if (!data)
		data = xbc_get_embedded_bootconfig(&size);

	strscpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
	err = parse_args("bootconfig", tmp_cmdline, NULL, 0, 0, 0, NULL,
			 bootconfig_params);

	if (IS_ERR(err) || !(bootconfig_found || IS_ENABLED(CONFIG_BOOT_CONFIG_FORCE)))
		return;

	/* parse_args() stops at the next param of '--' and returns an address */
	if (err)
		initargs_offs = err - tmp_cmdline;

	if (!data) {
		/* If user intended to use bootconfig, show an error level message */
		if (bootconfig_found)
			pr_err("'bootconfig' found on command line, but no bootconfig found\n");
		else
			pr_info("No bootconfig data provided, so skipping bootconfig");
		return;
	}

	if (size >= XBC_DATA_MAX) {
		pr_err("bootconfig size %ld greater than max size %d\n",
			(long)size, XBC_DATA_MAX);
		return;
	}

	ret = xbc_init(data, size, &msg, &pos);
	if (ret < 0) {
		if (pos < 0)
			pr_err("Failed to init bootconfig: %s.\n", msg);
		else
			pr_err("Failed to parse bootconfig: %s at %d.\n",
				msg, pos);
	} else {
		xbc_get_info(&ret, NULL);
		pr_info("Load bootconfig: %ld bytes %d nodes\n", (long)size, ret);
		/* keys starting with "kernel." are passed via cmdline */
		extra_command_line = xbc_make_cmdline("kernel");
		/* Also, "init." keys are init arguments */
		extra_init_args = xbc_make_cmdline("init");
	}
	return;
}

static void __init setup_boot_config(void)
{
	/* Remove bootconfig data from initrd */
	get_boot_config_from_initrd(NULL);
}

设置引导配置, __init标识下说明setup_boot_config()函数是内核函数,链接器有特殊的处理保证不会出现链接错误(具体是什么机制,我也没有查到准确说法)


补充

在Linux操作系统中,initrd(Initial RAM Disk)是一个临时的根文件系统,它在内核启动的早期阶段被加载到内存中,用于提供必要的文件和驱动程序,以便内核能够访问真正的根文件系统。initrd通常被用于当内核还没有加载所有必要的驱动程序来访问硬盘上的文件系统时,或者当根文件系统需要通过网络启动时。

initrd的主要作用如下:

  • 提供驱动: initrd可以包含必要的驱动程序,特别是那些用于磁盘和文件系统的驱动程序,这些驱动程序可能不在内核映像中。这样,即使内核本身不支持某种存储设备,也可以通过initrd中的驱动程序来访问。
  • 准备根文件系统: initrd可以包含工具和脚本,用于挂载真正的根文件系统。例如,它可以包含逻辑卷管理(LVM)或软件RAID的工具,这些工具可以帮助挂载复杂的文件系统配置。
    转换根文件系统: 在某些情况下,initrd可以用于转换根文件系统的状态,例如,从只读状态切换到读写状态,或者从网络文件系统切换到本地文件系统。
  • 引导加载: initrd可以用于网络引导(PXE)的情况,其中整个操作系统是通过网络下载的,包括内核和根文件系统。
    initrd通常是一个压缩的文件系统映像,它在引导过程中由引导加载程序(如GRUB uBoot LILO)加载到内存中。一旦内核启动并运行,它会解压initrd并将其作为临时根文件系统使用。然后,内核会执行initrd中的/init脚本,该脚本负责初始化硬件、加载必要的驱动程序,并最终挂载真正的根文件系统。

在现代Linux系统中,initrd已经被initramfs(Initial RAM File System)所取代。initramfs是一个基于cpio的文件系统映像,它可以在引导时动态地解压到根文件系统中。与initrd相比,initramfs更加灵活,因为它可以动态地调整大小以适应不同的引导情况。


  • setup_command_line(command_line) impl in /init/main.c
/*
 * We need to store the untouched command line for future reference.
 * We also need to store the touched command line since the parameter
 * parsing is performed in place, and we should allow a component to
 * store reference of name/value for future reference.
 */
static void __init setup_command_line(char *command_line)
{
	size_t len, xlen = 0, ilen = 0;

	if (extra_command_line)
		xlen = strlen(extra_command_line);
	if (extra_init_args)
		ilen = strlen(extra_init_args) + 4; /* for " -- " */

	len = xlen + strlen(boot_command_line) + 1;

	saved_command_line = memblock_alloc(len + ilen, SMP_CACHE_BYTES);
	if (!saved_command_line)
		panic("%s: Failed to allocate %zu bytes\n", __func__, len + ilen);

	len = xlen + strlen(command_line) + 1;

	static_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
	if (!static_command_line)
		panic("%s: Failed to allocate %zu bytes\n", __func__, len);

	if (xlen) {
		/*
		 * We have to put extra_command_line before boot command
		 * lines because there could be dashes (separator of init
		 * command line) in the command lines.
		 */
		strcpy(saved_command_line, extra_command_line);
		strcpy(static_command_line, extra_command_line);
	}
	strcpy(saved_command_line + xlen, boot_command_line);
	strcpy(static_command_line + xlen, command_line);

	if (ilen) {
		/*
		 * Append supplemental init boot args to saved_command_line
		 * so that user can check what command line options passed
		 * to init.
		 * The order should always be
		 * " -- "[bootconfig init-param][cmdline init-param]
		 */
		if (initargs_offs) {
			len = xlen + initargs_offs;
			strcpy(saved_command_line + len, extra_init_args);
			len += ilen - 4;	/* strlen(extra_init_args) */
			strcpy(saved_command_line + len,
				boot_command_line + initargs_offs - 1);
		} else {
			len = strlen(saved_command_line);
			strcpy(saved_command_line + len, " -- ");
			len += 4;
			strcpy(saved_command_line + len, extra_init_args);
		}
	}

	saved_command_line_len = strlen(saved_command_line);
}

处理内核命令行参数,这些参数通常由引导加载程序(如GRUB, LILO)传递给内核。

  • setup_nr_cpu_ids()
    设置系统中CPU的总数。这告诉内核有多少个CPU需要初始化和调度。
  • setup_per_cpu_areas()
    为每个CPU分配和初始化 per-cpu 数据区域,用于存储每个CPU的私有数据,如寄存器状态等。
  • smp_prepare_boot_cpu()
    为引导CPU准备 SMP(对称多处理)环境设置CPU间通信的必要数据结构和同步机制。
  • boot_cpu_hotplug_init()
    初始化CPU热插拔支持。允许在系统运行时动态地添加或移除CPU。

TO BE CONTINUE...

标签:chapter,__,boot,start,init,part,command,内核,line
From: https://www.cnblogs.com/uperfic/p/18204059

相关文章

  • AoPS - Chapter 24.5 The Pell Equation
    \[\Large{施工未完成,日后会补上。}\]本来这篇应该包含在Chapter24中,但是篇幅太长故单独分离出来。ThePellEquation重量级人物登场。关于\(x,y\)的形如\(x^2-Dy^2=\pm1\)的方程称为佩尔方程(Pellequation)。其中\(D\)是正整数,不是完全平方数。求解凑出一组特......
  • mit6.828笔记 - lab4 Part C:抢占式多任务和进程间通信(IPC)
    PartC:抢占式多任务和进程间通信(IPClab4到目前为止,我们能够启动多个CPU,让多个CPU同时处理多个进程。实现了中断处理,并且实现了用户级页面故障机制以及写时复制fork。但是,我们的进程调度不是抢占式的,现在每个进程只有在发生中断的时候,才会被调度(调用shed_yeild),这样就有可能会有......
  • mit6.828笔记 - lab4 Part B:写时复制Fork
    PartBCopy-on-WriteForkUnix提供 fork() 系统调用作为主要的进程创建基元。fork()系统调用复制调用进程(父进程)的地址空间,创建一个新进程(子进程)。不过,在调用 fork() 之后,子进程往往会立即调用 exec(),用新程序替换子进程的内存。例如,shell通常就是这么做的。在这种情况......
  • Testing Egineer note:2024_5_20-day12-part01
    管理工具禅道一、禅道的介绍(1)定义禅道是一个项目管理工具,也是一个bug管理工具,还是一个用例管理工具。(2)作用:为了解决众多企业在管理中出现混乱,无序的现象,开发出来(3)来源:禅道属易软天川公司(4)禅道是集于产品管理,项目管理,测试管理于一身,同时包含事务管理,组织管理8众多功能,是中小企......
  • LG学术交流会——规则怪谈 Chapter II
    人群中突然爆发出一阵尖锐的爆鸣声,定睛一看,发现kkksc03惨死在屋中,头上有一个三角形的洞,满屋字都是血迹。地上有一张字条:“各位愚人节快乐!请到主会场去吧。”大家此时都被吓得不轻,看到纸条后都来到了主会场。此时,大屏幕上浮现出几行字,广播在用一个沉稳的男声播报着大屏上的内容:......
  • LG学术交流会——规则怪谈 Chapter I
    (2025/3/3115:00)“各位到访的来宾,大家好,我是kkksc03,当然你们也可以叫我汪楚奇,是LG的负责人。今天,我代表我们LG管理组全体欢迎大家的到来!交流会会持续一周,期间任何人员不得离场,我们有完善的安保系统。对了,在学术交流会期间会举行数次比赛,你们的房间会有电脑供你们刷题,但比赛要去比......
  • JVM-part-运行时数据区
    运行时数据区组成部分:程序计数器(PC寄存器ProgramCounterRegister)Java虚拟机栈(JavaVirtualMachineStacks)本地方法栈(NativeMethodStack)堆(Heap)方法区(MethodArea)其中存在线程共享和线程不共享的区域,如下:线程共享:堆、栈线程不共享:每一个线程都有的,程序计数器、本地......
  • AoPS - Chapter 24 Diophantine Equations
    这一节主要讲解了二元一次丢番图方程、本原勾股数、佩尔方程(ThePellEquation)。丢番图方程(Diophantineequation)是指未知数为整数的整数系数多项式等式。(丢番图方程-维基百科)二元一次丢番图方程关于\(x,y\)的形如\(ax+by=c\)的丢番图方程称为二元一次丢番图方程。求解......
  • B. Mahmoud and Ehab and the bipartiteness
    原题链接题解观察一个二分图会发现同一组的节点不直接相连二分图能够建立的最多的边等于\(n*m\)code#include<bits/stdc++.h>usingnamespacestd;#definelllonglongvector<ll>G[100005];lldepth[100005]={0};llodd=0,even=0;voiddfs(llnow,llfa){i......
  • 部署freeipa中报错:Command '/bin/systemctl start certmonger.service' returned non-
    cat/etc/dbus-1/system.d/certmonger.conf<allowsend_destination="org.fedorahosted.certmonger"send_interface="org.fedorahosted.certmonger"/><allowsend_destination="org.fedorahosted.certmonger"......