Linux-进程的管理与调度6(基于6.1内核)

Linux-进程的管理与调度6(基于6.1内核)---Linux下1号进程

一、1号进程


前面我们了解到了0号进程是系统所有进程的先祖, 它的进程描述符init_task是内核静态创建的, 而它在进行初始化的时候, 通过kernel_thread的方式创建了两个内核线程,分别是kernel_init和kthreadd,其中kernel_init进程号为1

start_kernel在其最后一个函数rest_init的调用中,会通过kernel_thread来生成一个内核进程,后者则会在新进程环境下调 用kernel_init函数,kernel_init一个让人感兴趣的地方在于它会调用run_init_process来执行根文件系统下的 /sbin/init等程序:

1.1、kernel_init


0号进程创建1号进程的方式如下:

pid = user_mode_thread(kernel_init, NULL, CLONE_FS);

1号进程的执行函数就是kernel_init, 这个函数init/main.c,如下所示

kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。

由0号进程创建1号进程(内核态),1号内核线程负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。

1.2、init进程


1号进程调用do_execve运行可执行程序init,并演变成用户态1号进程,即init进程。

init进程是linux内核启动的第一个用户级进程。init有许多很重要的任务,比如像启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。

它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号…的若干终端注册进程getty。

每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数do_execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。

上述过程可描述为:0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程

注意,上述过程描述中提到:1号内核进程调用执行init函数并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:

1、kernel_init函数在内核态运行,是内核代码。

2、init进程是内核启动并运行的第一个用户进程,运行在用户态下。

3、一号内核进程调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。

当内核启动了自己之后(已被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等),通过启动用户级程序init来完成引导进程的内核部分。因此,init总是第一个进程(它的进程号总是1)。

当init开始运行,它通过执行一些管理任务来结束引导进程,例如检查文件系统、清理/tmp、启动各种服务以及为每个终端和虚拟控制台启动getty,在这些地方用户将登录系统。

在系统完全起来之后,init为每个用户已退出的终端重启getty(这样下一个用户就可以登录)。init同样也收集孤立的进程:当一个进程启动了一个子进程并且在子进程之前终止了,这个子进程立刻成为init的子进程。对于各种技术方面的原因来说这是很重要的,知道这些也是有好处的,因为这便于理解进程列表和进程树图。init的变种很少。绝大多数Linux发行版本使用sysinit和systemD。

1. 内核启动与 init 程序

当计算机启动时,Linux 内核会首先完成硬件初始化,并开始加载必要的内核模块。然后,内核会查找启动过程中的第一个用户空间进程,这个进程通常是 init。内核通过 execve 调用启动 init 程序。

在传统的 System V init 系统中,内核会通过 execve() 启动 /sbin/init 程序,成为 PID 1 的进程。而在采用 systemd 的现代 Linux 系统中,内核会启动 /lib/systemd/systemd,这就是 systemd 作为用户空间的第一个进程。

2. systemd 启动的详细步骤

  1. 内核引导阶段: 内核启动时,会根据启动引导程序(如 GRUB)加载内核映像和初始 RAM 磁盘(initramfs)。然后,内核会执行其内核空间初始化,最后通过调用 execve("/lib/systemd/systemd", ...) 来启动 systemd,并将其设置为 PID 1 号进程。

  2. systemd 启动阶段: 一旦 systemd 被加载并执行,它将成为 PID 1,接管系统的控制,并负责管理系统中的所有其他进程。systemd 在启动过程中会执行以下操作:

    • 读取配置systemd 会首先读取其配置文件,通常位于 /etc/systemd/system//lib/systemd/system/ 中。它会读取单元文件(unit files),这些文件定义了系统中各个服务和进程的启动顺序、依赖关系、资源分配等信息。

    • 启动系统服务systemd 根据这些单元文件的定义来启动和管理系统服务。它不仅管理传统的系统服务(如网络、SSH、日志等),还可以启动多种不同类型的服务,支持并行启动和依赖关系解析,优化启动性能。

    • 进程管理:作为 PID 1,systemd 负责管理所有用户空间进程的生命周期,它会监控这些进程的状态、处理子进程的退出、以及执行进程的重启等操作。

  3. 用户空间的其他进程启动: 一旦 systemd 启动了初步的系统服务,它会启动多个其他进程,如登录会话(getty)、图形界面(如 gdmlightdm)、网络服务等。systemd 支持目标(target)的概念,它允许按照不同的目标状态来分阶段启动系统。例如,multi-user.targetgraphical.target 就定义了不同的系统运行级别。

  4. 控制系统服务systemd 提供了一套强大的工具来管理和控制系统服务,如 systemctl。用户和管理员可以通过 systemctl 启动、停止、重启、启用或禁用服务。

1.3、关于init程序


1号进程通过execve执行init程序来进入用户空间,成为init进程,那么这个init在哪里呢

内核在几个位置上来查寻init,这几个位置以前常用来放置init,但是init的最适当的位置(在Linux系统上)是/sbin/init。如果内核没有找到init,它就会试着运行/bin/sh,如果还是失败了,那么系统的启动就宣告失败了。

 

Ubuntu等使用deb包的系统可以通过dpkg -S查看程序所在的包

CentOS等使用rpm包的系统可以通过rpm -qf查看系统程序所在的包

二、附录


2.1、kernel_init_freeable流程分析

static noinline void __init kernel_init_freeable(void)
{
	/* Now the scheduler is fully set up and can do blocking allocations */
	gfp_allowed_mask = __GFP_BITS_MASK;

	/*
	 * init can allocate pages on any node
	 */
	set_mems_allowed(node_states[N_MEMORY]);

	cad_pid = get_pid(task_pid(current));

	smp_prepare_cpus(setup_max_cpus);

	workqueue_init();

	init_mm_internals();

	rcu_init_tasks_generic();
	do_pre_smp_initcalls();
	if (disable_sdei_nmi_watchdog)
		lockup_detector_init();

	smp_init();
	sched_init_smp();

	workqueue_init_topology();
	padata_init();
	page_alloc_init_late();

	do_basic_setup();

	/* sdei_watchdog needs to be initialized after sdei_init */
	if (!disable_sdei_nmi_watchdog)
		lockup_detector_init();

	kunit_run_all_tests();

	wait_for_initramfs();
	console_on_rootfs();

	/*
	 * check if there is an early userspace init.  If yes, let it do all
	 * the work
	 */
	if (init_eaccess(ramdisk_execute_command) != 0) {
		ramdisk_execute_command = NULL;
		prepare_namespace();
	}

	/*
	 * Ok, we have completed the initial bootup, and
	 * we're essentially up and running. Get rid of the
	 * initmem segments and start the user-mode stuff..
	 *
	 * rootfs is available now, try loading the public keys
	 * and default modules
	 */

	integrity_load_keys();
}
执行流程说明
gfp_allowed_mask__GFP_BITS_MASK;设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作
set_mems_allowed(node_states[N_MEMORY]);init进程可以分配物理页面
set_cpus_allowed_ptr通过设置cpu_bit_mask, 可以限定task只能在特定的处理器上运行, 而initcurrent进程此时必然是init进程,设置其cpu_all_mask即使得init进程可以在任意的cpu上运行
smp_prepare_cpus体系结构相关的函数,实例在arch/arm/kernel/smp.c中,调用smp_prepare_cpus时,会以全局变量setup_max_cpus为函式参数max_cpus,以表示在编译核心时,设定支援的最大CPU数量
do_pre_smp_initcalls透过函式do_one_initcall,执行Symbol中 __initcall_start与__early_initcall_end之间的函数
smp_init实例在kernel/smp.c中, 函数主要是由Bootstrap处理器,进行Active多核心架构下其它的处理器. 如果发生Online的处理器个数(from num_online_cpus)超过在核心编译时,所设定的最大处理器个数 setup_max_cpus (from NR_CPUS),就会终止流程.如果该处理器目前属於Present (也就是存在系统中),但尚未是Online的状态,就会呼叫函式cpu_up(in kernel/cpu.c)来啟动该处理器.
sched_init_smp实例在kernel/sched.c中, (1), 呼叫get_online_cpus,如果目前CPU Hotplug Active Write行程是自己,就直接返回.反之就把 cpu_hotplug.refcount加1 (表示多一个Reader)
(2),取得Mutex Lock “sched_domains_mutex”
(3),呼叫arch_init_sched_domains,设定scheduler domains与groups,参考Linux Documentation/scheduler/sched-domains.txt文件,一个Scheduling Domain会包含一个或多个CPU Groups,排程的Load-Balance就会根据Domain中的Groups来做调整.
(4),释放Mutex Lock “sched_domains_mutex”
(5),呼叫put_online_cpus,如果目前CPU Hotplug Active Writer行程是自己,就直接返回.反之就把 cpu_hotplug.refcount减1,如果 cpu_hotplug.refcount减到為0,表示没有其他Reader,此时如果有CPU Hotplug Active Writer行程在等待,就会透过wake_up_process唤醒该行程,以便让等待中的Writer可以被执行下去.(也可以参考_cpu_up中对於函式cpu_hotplug_begin的说明).
(6)注册CPU Notifier cpuset_cpu_active/cpuset_cpu_inactive/update_runtime
(7),呼叫set_cpus_allowed_ptr,透过这函式可以设定CPU bitmask,限定Task只能在特定的处理器上运作.在这会用参数”non_isolated_cpus”,也就是会把init指定给non-isolated CPU. Linux Kernel可以在啟动时,透过Boot Parameters “isolcpus=“指定CPU编号或是范围,让这些处理器不被包含在Linux Kernel SMP balancing/scheduling算法内,可以在啟动后指派给特定的Task运作.而不在 “isolcpus=“ 指定范围内的处理器就算是non-isolated CPU.
(8),呼叫sched_init_granularity,透过函式update_sysctl,让sysctl_sched_min_granularity=normalized_sysctl_sched_min_granularity,sysctl_sched_latency=normalized_sysctl_sched_latency,sysctl_sched_wakeup_granularity=normalized_sysctl_sched_wakeup_granularit
do_basic_setup实例在init/main.c中,
1,usermodehelper_init (in kernel/kmod.c),产生khelper workqueue.
2,调用init_tmpfs (in mm/shmem.c),对VFS注册Temp FileSystem.
3,呼叫driver_init (in drivers/base/init.c),初始化Linux Kernel Driver System Model.
4,呼叫init_irq_proc(in kernel/irq/proc.c),初始化 “/proc/irq”与其下的File Nodes.
5,呼叫do_ctors (in init/main.c),执行位於Symbol __ctors_start 到 __ctors_end间属於Section “.ctors” 的Constructor函式.
6,透过函式do_initcalls,执行介於Symbol __early_initcall_end与__initcall_end之间的函式呼叫,

wait_for_initramfs

与prepare_namespace
1,如果

wait_for_initramfs

為0,就设定ramdisk_execute_command = “/init”
2,如果sys_access确认档案

wait_for_initramfs

失败,就把ramdisk_execute_command 设定為0,然后呼叫prepare_namespace去mount root FileSystem.
integrity_load_keys至此我们初始化工作完成, 文件系统也已经准备好了,那么接下来加载 load integrity keys hook
load_default_modules加载基本的模块

2.2、kernel_init分析

static int __ref kernel_init(void *unused)
{
	int ret;

	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	kernel_init_freeable();
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();

	system_state = SYSTEM_FREEING_INITMEM;
	kprobe_free_init_mem();
	ftrace_free_init_mem();
	kgdb_free_init_mem();
	exit_boot_config();
	free_initmem();
	mark_readonly();

	/*
	 * Kernel mappings are now finalized - update the userspace page-table
	 * to finalize PTI.
	 */
	pti_finalize();

	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	rcu_end_inkernel_boot();

	do_sysctl_args();

	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}

	if (CONFIG_DEFAULT_INIT[0] != '\0') {
		ret = run_init_process(CONFIG_DEFAULT_INIT);
		if (ret)
			pr_err("Default init %s failed (error %d)\n",
			       CONFIG_DEFAULT_INIT, ret);
		else
			return 0;
	}

	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");
}
执行流程说明
kernel_init_freeable调用kernel_init_freeable完成初始化工作,准备文件系统,准备模块信息
async_synchronize_full用以同步所有非同步函式呼叫的执行,在这函数中会等待List async_running与async_pending都清空后,才会返回. Asynchronously called functions主要设计用来加速Linux Kernel开机的效率,避免在开机流程中等待硬体反应延迟,影响到开机完成的时间
free_initmemfree_initmem(in arch/arm/mm/init.c),释放Linux Kernel介於__init_begin到 __init_end属于init Section的函数的所有内存.并会把Page个数加到变量totalram_pages中,作为后续Linux Kernel在配置记忆体时可以使用的Pages. (在这也可把TCM范围(__tcm_start到__tcm_end)释放加入到总Page中,但TCM比外部记忆体有效率,适合多媒体,中断,…etc等对效能要求高的执行需求,放到总Page中,成为可供一般目的配置的存储范围
system_state设置运行状态SYSTEM_RUNNING
加载init进程,进入用户空间a,如果ramdisk_execute_command不為0,就执行该命令成為init User Process.
b,如果execute_command不為0,就执行该命令成為init User Process.
c,如果上述都不成立,就依序執行如下指令
run_init_process(“/sbin/init”);
run_init_process(“/etc/init”);
run_init_process(“/bin/init”);
run_init_process(“/bin/sh”);
也就是说会按照顺序从/sbin/init, /etc/init, /bin/init 與 /bin/sh依序执行第一个 init User Process.
如果都找不到可以執行的 init Process,就會進入Kernel Panic.如下所示panic(“No init found. Try passing init= option to kernel. ”“See Linux Documentation/init.txt for guidance.”);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值