内核启动(二)

内核启动(二)

上一篇笔记,写到了start_kernel。但是start_kernel非常的大,需要找到一个结束点。这里将以init进程的启动为结束点。

又因为我的目的,是想查看驱动是怎么被加载的,那么也会涉及到驱动加载过程的学习。

init进程的启动

start_kernel较长,先列出如下:
start_kernel的解析,参考了:
https://www.cnblogs.com/cslunatic/archive/2013/05/11/3072811.html


asmlinkage void __init start_kernel(void)
{
	char * command_line;
	extern struct kernel_param __start___param[], __stop___param[];

    //只有一个cpu时,什么也不做
    //多个cpu时,返回启动的那个CPU id
	smp_setup_processor_id();

	//为了打印各种锁的状态,需要有个模块来保存相应的信息,因此该函数初始化之
	lockdep_init();
    //初始化一些调试对象
	debug_objects_early_init();

	//不明白的地方
	boot_init_stack_canary();

    //control groups的初始化
	cgroup_init_early();

    //关中断
	local_irq_disable();
	early_boot_irqs_off();
    //初始化中断描述符(irq_desc)
	early_init_irq_lock_class();

    //获取大内核锁
	lock_kernel();
    //初始化时钟事件管理器
	tick_init();
    //为了管理cpu,需要使用相应的数据,初始化之
	boot_cpu_init();
    //主要是初始化高端内存的映射表
	page_address_init();
	printk(KERN_NOTICE "%s", linux_banner);
    //对内核架构进行再次初始化
	setup_arch(&command_line);
    //这个函数主要作用是设置最开始的初始化任务属于init_mm内存。在ARM里,这个函数为空。
	mm_init_owner(&init_mm, &init_task);
    //保存命令行,以便后面可以使用。
	setup_command_line(command_line);
    //主要作用是设置最多有多少个nr_cpu_ids结构。
	setup_nr_cpu_ids();
    //设置SMP体系per-CPU使用的内存空间,同时拷贝初始化段里数据。
	setup_per_cpu_areas();
    //作用是为SMP系统里引导CPU进行准备工作。在ARM系统单核里是空函数。
	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */

    //初始化所有内存管理节点列表,以便后面进行内存管理初始化。
	build_all_zonelists();
    //设置内存页分配通知器
	page_alloc_init();

	printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
    //分析命令行最早使用的参数
	parse_early_param();
    //内核参数进行解释,如果不能识别的命令就调用最后参数的函数
	parse_args("Booting kernel", static_command_line, __start___param,
		   __stop___param - __start___param,
		   &unknown_bootoption);
	//pid的hash初始化
	pidhash_init();
    //虚拟文件系统的缓存初始化
	vfs_caches_init_early();
    //对内核内部的异常表进行堆排序,以便加速访问
	sort_main_extable();
    //对异常进行初始化,在ARM系统里是空函数,没有任何的初始化
	trap_init();
    //内存的初始化,比如多少内存可用
	mm_init();
	
    //初始化进程调度器
	sched_init();
	
    //禁止抢占
	preempt_disable();
    //如果打开中断,就会提示,并把关闭中断
	if (!irqs_disabled()) {
		printk(KERN_WARNING "start_kernel(): bug: interrupts were "
				"enabled *very* early, fixing it\n");
		local_irq_disable();
	}
    //初始化RCU
	rcu_init();
	//初始化中断
	early_irq_init();
	init_IRQ();
    //初始化优先树
	prio_tree_init();
    //初始化时钟相关
	init_timers();
    //初始化高精度时钟
	hrtimers_init();
    //软中断初始化
	softirq_init();
    //初始化系统时钟计时
	timekeeping_init();
    //初始化系统时钟
	time_init();
    //初始化profile工具使用的内存
	profile_init();
    //如果打开了中断,则打印提示
	if (!irqs_disabled())
		printk(KERN_CRIT "start_kernel(): bug: interrupts were "
				 "enabled early\n");
    //设置一个变量,该变量用于标识内核启动的早期阶段
	early_boot_irqs_on();
    //打开中断
	local_irq_enable();

	/* Interrupts are enabled now so all GFP allocations are safe. */
	set_gfp_allowed_mask(__GFP_BITS_MASK);

    //高速缓存最后的初始化
	kmem_cache_init_late();

	/*
	 * 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();
    //这段代码是判断分析输入的参数是否出错,如果有出错,就启动控制台输出之后,立即打印出错的参数,以便用户立即看到出错的地方
	if (panic_later)
		panic(panic_later, panic_param);

    //打印锁的依赖信息,用来调试锁
	lockdep_info();

     //用来测试锁的API是否使用正常,进行自我测试
	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) {
		printk(KERN_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_cgroup_init();
    //内存分配的一些调试信息
	enable_debug_pagealloc();
    //内存调试先关
	kmemtrace_init();
	kmemleak_init();
	debug_objects_mem_init();
    //IDR机制的内存缓存
	idr_init_cache();
    //per-CPU的高速缓存设置
	setup_per_cpu_pageset();
    //初始化NUMA的内存访问策略
	numa_policy_init();
    //初始化时钟相关
	if (late_time_init)
		late_time_init();
    //调度器相关时钟初始化
	sched_clock_init();
    //延迟的校准
	calibrate_delay();
    //pid map初始化
	pidmap_init();
    //反向映射的匿名内存
	anon_vma_init();
    //初始化EFI的接口,并进入虚拟模式
#ifdef CONFIG_X86
	if (efi_enabled)
		efi_enter_virtual_mode();
#endif
    //线程信息的缓存
	thread_info_cache_init();
    //不明白
	cred_init();
    //fork环境初始化
	fork_init(totalram_pages);
    //进程缓存初始化
	proc_caches_init();
    //文件缓冲区初始化
	buffer_init();
    //初始化安全键管理列表和结构
	key_init();
    //初始化安全管理框架,以便提供访问文件/登录等权限
	security_init();
    //虚拟文件系统初始化
	vfs_caches_init(totalram_pages);
    //radix tree 初始化
	radix_tree_init();
    //信号队列初始化
	signals_init();
	/* rootfs populating might need page-writeback */
    //不明白
	page_writeback_init();
    //初始化proc文件系统
#ifdef CONFIG_PROC_FS
	proc_root_init();
#endif
    //初始化进程控制组
	cgroup_init();
    //初始化初始化CPUSET
	cpuset_init();
    //初始化任务状态相关的缓存、队列和信号量
	taskstats_init_early();
    //初始化每个任务延时计数,当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间
	delayacct_init();

    //检查CPU配置、FPU等是否非法使用不具备的功能
	check_bugs();

    //初始化ACPI电源管理。
	acpi_early_init(); /* before LAPIC and SMP init */
	sfi_init_late();

    
    //初始化内核跟踪模块
	ftrace_init();

	/* Do the rest non-__init'ed, we're now alive */
    //剩下的初始化流程,创建内核线程init,并运行
	rest_init();

接下来是rest_init的初始化
该函数,主要参考了:https://blog.csdn.net/xichangbao/article/details/52938240

static noinline void __init_refok rest_init(void)
	__releases(kernel_lock)
{
	int pid;
    //使能rcu机制
	rcu_scheduler_starting();
    //创建内核线程kernel_init
	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    //设定NUMA系统的默认内存访问策略
	numa_default_policy();
    //创建内核线程kthreadd
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    //获取kthreadd的进程描述符
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	unlock_kernel();

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
     //设置当前进程(0号进程)为idle进程类
	init_idle_bootup_task(current);
    
	preempt_enable_no_resched();
    //主动调度,并禁止抢占
	schedule();
	preempt_disable();

	/* Call into cpu_idle with preempt disabled */
    //进入idle状态
	cpu_idle();
}

该函数,主要功能是,创建两个内核线程,然后进入idle状态。kernel_init内核线程的启动入口在,kernel_init处,如下:

static int __init kernel_init(void * unused)
{
    
	lock_kernel();

	/*
	 * init can allocate pages on any node
	 */
	set_mems_allowed(node_possible_map);
	/*
	 * init can run on any cpu.
	 */
	set_cpus_allowed_ptr(current, cpu_all_mask);
	/*
	 * Tell the world that we're going to be the grim
	 * reaper of innocent orphaned children.
	 *
	 * We don't want people to have to make incorrect
	 * assumptions about where in the task array this
	 * can be found.
	 */
     //当前进程设置为接受其他孤儿进程的进程
	init_pid_ns.child_reaper = current;

	cad_pid = task_pid(current);

    //enable coherency
	smp_prepare_cpus(setup_max_cpus);

    //依次调用__initcall_start到__initcall_end之间的函数指针。
	do_pre_smp_initcalls();
	start_boot_trace();

    //激活SMP系统中的其他CPU
	smp_init();
	sched_init_smp();

    //此时与体系结构相关的部分已经初始化完
    //现在初始化设备,完成外设及其驱动程序的加载和初始化
	do_basic_setup();

	/*
	 * 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..
	 */
    //已经完成初始化启动,接来下进入init_post函数中
	init_post();
	return 0;
}

init_post函数的内容如下:
参考了:https://www.cnblogs.com/gary-guo/p/6069149.html

static noinline int init_post(void)
	__releases(kernel_lock)
{
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
    //释放__init和__initdata标记的函数和数据使用的空间
	free_initmem();
	unlock_kernel();
    //标记为只读
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

    //打开终端
	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
		printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    //复制两次文件描述符,作为标准输出和标准出错输出
	(void) sys_dup(0);
	(void) sys_dup(0);

    //当前进程不可kill掉
	current->signal->flags |= SIGNAL_UNKILLABLE;

	if (ramdisk_execute_command) {
		run_init_process(ramdisk_execute_command);
		printk(KERN_WARNING "Failed to execute %s\n",
				ramdisk_execute_command);
	}

	/*
	 * 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.
	 */
     //execute_command在init_setup()函数中被初始化,而后者会将命令行参数中"init="的参数值赋值给execute_command,因此,如果在命令行参数中设置了"init"参数,则执行该参数指定用户空间init进程
	if (execute_command) {
		run_init_process(execute_command);
		printk(KERN_WARNING "Failed to execute %s.  Attempting "
					"defaults...\n", execute_command);
	}
    //如果没有,则分别尝试执行"/sbin/init","/etc/init","/bin/init","/bin/sh"
    //否则就报错,为init进程未找到
	run_init_process("/sbin/init");
	run_init_process("/etc/init");
	run_init_process("/bin/init");
	run_init_process("/bin/sh");

	panic("No init found.  Try passing init= option to kernel.");
}

上面启动的init进程,就是用标准c库写的,第一应用程序,进程编号为1。init进程负责初始化其他必要的进程。

驱动模块的加载

现在查看,被编译进内核中的驱动模块是如何被一步步加载执行的。即从start_kernel到各个模块的init函数的执行。

在上面kernel_init函数中,调用了do_basic_setup.函数如下
参考了:https://www.cnblogs.com/Oude/articles/12039103.html


static void __init do_basic_setup(void)
{
    //初始化workqueue
	init_workqueues();
    //初始化cpuset
	cpuset_init_smp();
    //创建一个单线程工作队列khelper
	usermodehelper_init();
    //初始化tmpfs
	init_tmpfs();
    初始化驱动模型中的各子系统,可见的现象是在/sys中出现的目录和文件
	driver_init();
    //在proc文件系统中创建irq目录,并在其中初始化系统中所有中断对应的目录
	init_irq_proc();
    调用链接到内核中的所有构造函数,也就是链接进.ctors段中的所有函数
	do_ctors();
    //调用所有编译内核的驱动模块中的初始化函数。
	do_initcalls();
}

上面函数的最后一个函数,就是调用所有的驱动模块的初始化函数。看看它是如何实现的。

static void __init do_initcalls(void)
{
	initcall_t *call;

    //__early_initcall_end和__initcall_end可在vmlinux.lds中查看
    //可以看到中间定义的一系列段。只要在编译的时候,将这些初始化函数,
    //编译到上述位置,就会被调用
	for (call = __early_initcall_end; call < __initcall_end; call++)
        //执行真正的调用
		do_one_initcall(*call);

	/* Make sure there is no pending stuff from the initcall sequence */
	flush_scheduled_work();
}

linux中使用include/linux/init.h中的宏定义将相应的段,编译到指定的位置,如下:

#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn

#define pure_initcall(fn)		__define_initcall("0",fn,0)

#define core_initcall(fn)		__define_initcall("1",fn,1)
#define core_initcall_sync(fn)		__define_initcall("1s",fn,1s)
#define postcore_initcall(fn)		__define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)	__define_initcall("2s",fn,2s)
#define arch_initcall(fn)		__define_initcall("3",fn,3)
#define arch_initcall_sync(fn)		__define_initcall("3s",fn,3s)
#define subsys_initcall(fn)		__define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)	__define_initcall("4s",fn,4s)
#define fs_initcall(fn)			__define_initcall("5",fn,5)
#define fs_initcall_sync(fn)		__define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)		__define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)		__define_initcall("6",fn,6)
#define device_initcall_sync(fn)	__define_initcall("6s",fn,6s)
#define late_initcall(fn)		__define_initcall("7",fn,7)
#define late_initcall_sync(fn)		__define_initcall("7s",fn,7s)    

比如module_init宏,

#define module_init(x)	__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)		__define_initcall("6",fn,6)

最终会在.initcall6.init段中。因此在do_initcalls中可以被调用,并成功的初始化。

至此,终于看完了从uboot到内核启动,到驱动加载的整个流程。虽有很多不明白的东西,但是大体流程清楚了。接下来就是进入真正的驱动的学习了。

给自己一个鼓励。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值