Linux驱动之系统移植-----linux内核启动流程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

linux内核的第一行代码

linux链接脚本文件 : arch/arm/kernel/vmlinux.lds
通过这个文件可以知道linux启动的第一行代码,

ENTRY(stext)

ENTRY 指明了了 Linux 内核入口入口为 stext stext 定义在文件arch/arm/kernel/head.S 中

head.S中是一堆用于启动的汇编代码, 笔者才疏学浅无法看懂, 结论便是最终调用了start_kernel函数

start_kernel函数定义在文件 init/main.c

start_kernel函数

start_kernel函数前半段是各种初始化

asmlinkage __visible void __init start_kernel(void)
{
	char *command_line;
	char *after_dashes;

	set_task_stack_end_magic(&init_task);
	smp_setup_processor_id();
	debug_objects_early_init();
	......
		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() 函数

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

	rcu_scheduler_starting();
	/*
	 * 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);
	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);

	/*
	 * 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);
}

kernel_thread函数
创建kernel_init进程, 即init内核进程, init进程进程PID为1,init进程一开始是内核进程(也就是运行在内核态)后面 init进程会在根文件系统中查找名为“init”这个程序这个“init”程序处于用户态通过运行这个“init”程序init进程就会实现从内核态到用户态的转变。
kernel_thread函数
调用函数 kernel_thread创建 kthreadd内核进程此内核进程的 PID为 2。kthreadd
进程负责所有内核进程的调度和管理。
cpu_startup_entry函数
最后调用函数 cpu_startup_entry来进入 idle进程cpu_startup_entry会调用
cpu_idle_loopcpu_idle_loop是个 while循环也就是 idle进程代码。idle进程的 PID为 0idle进程叫做空闲进程如果学过 FreeRTOS或者 UCOS的话应该听说过空闲任务。idle空闲进程就和空闲任务一样当 CPU没有事情做的时候就在 idle空闲进程里面“瞎逛游”反正就是给CPU找点事做。当其他进程要工作的时候就会抢占 idle进程从而夺取 CPU使用权。其实大家应该可以看到 idle进程并没有使用 kernel_thread或者 fork函数来创建因为它是有主进程演变而来的。

kernel_init进程


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

	kernel_init_freeable();
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	free_initmem();
	mark_readonly();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	rcu_end_inkernel_boot();

	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 (!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/init.txt for guidance.");
}

ramdisk_execute_command函数
ramdisk_execute_command是一个全局的 char指针变量此变量值为“/init”也就是根目录下的 init程序。ramdisk_execute_command也可以通过 uboot传递在 bootargs中使用“rdinit=xxx”即可xxx为具体的 init程序名字。
寻找init脚本程序
如果 ramdisk_execute_command和 execute_command都为空那么就依次查**“/sbin/init”、“/etc/init” 、“/bin/init”和“/bin/sh”** 这四个相当于备用 init 程序如果这四个也不存在那么 Linux启动失败

kernel_init_freeable函数

kernel_init_freeable函数用于完成 init进程的一些其他初始化工作。

static noinline void __init kernel_init_freeable(void)
{
	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	/* 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]);
	/*
	 * init can run on any cpu.
	 */
	set_cpus_allowed_ptr(current, cpu_all_mask);

	cad_pid = task_pid(current);

	smp_prepare_cpus(setup_max_cpus);

	do_pre_smp_initcalls();
	lockup_detector_init();

	smp_init();
	sched_init_smp();

	page_alloc_init_late();

	do_basic_setup();

	/* Open the /dev/console on the rootfs, this should never fail */
	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
		pr_err("Warning: unable to open an initial console.\n");

	(void) sys_dup(0);
	(void) sys_dup(0);
	/*
	 * check if there is an early userspace init.  If yes, let it do all
	 * the work
	 */

	if (!ramdisk_execute_command)
		ramdisk_execute_command = "/init";

	if (sys_access((const char __user *) ramdisk_execute_command, 0) != 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();
	load_default_modules();
}

do_basic_setup函数
用于完成 Linux下设备驱动初始化工作非常重要。do_basic_setup会调用 driver_init函数完成 Linux下驱动模型子系统的初始化。

	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
		pr_err("Warning: unable to open an initial console.\n");

	(void) sys_dup(0);
	(void) sys_dup(0);

打开/dev/console文件, 生成第一个文件描述符0, 所谓标准输入, 将控制台作为标准输入.
sys_dup函数将标准输入(0)的文件描述符复制了 2次一个作为标准输出(1)一个作为标准错误(2)。这样标准输入、输出、错误都是/dev/console了。console通过uboot的 bootargs环境变量设置“console=ttymxc0,115200” 表示将/dev/ttymxc0设置为 console也就是 I.MX6U的串口 1。当然也可以设置其他的设备为 console比如虚拟控制台 tty1设
置 tty1为 console就可以在 LCD屏幕上看到系统的提示信息。
prepare_namespace()函数
prepare_namespace来挂载根文件系统。根文件系统也是由命令行参数指定的就是 uboot的 bootargs环境变量。比如“root=/dev/mmcblk1p2 rootwait rw”就表示根文件系统在/dev/mmcblk1p2中也就是 EMMC的分区 2中。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux