本篇是通用内核启动阶段,一般是c语言实现。
承接上篇,start_kernel函数板级引导阶段进入通用内核启动阶段的第一个函数,从编程语言角度理解,也是汇编进入c语言的第一个函数。
该函数定义在init/main.c文件内,它主要完成linux启动之前的一些初始化工作,该函数内调用的子函数非常多,大多子函数十分复杂,我们先从整体理解初始化流程,后续结合每个模块再回头理解子函数初始化的意义。
1、start_kernel函数添加注释,根据注释来理解
asmlinkage __visible void __init start_kernel(void){ char *command_line; // 存放bootloader的传参 char *after_dashes; set_task_stack_end_magic(&init_task); // 设置任务堆栈结束幻数,可以检测堆栈溢出 smp_setup_processor_id(); // 如果非smp则为空函数,是smp则设置处理器id debug_objects_early_init(); // debug提前初始化 cgroup_init_early(); // control group 提前初始化 local_irq_disable(); // 关闭当前cpu中断 early_boot_irqs_disabled = true; // 系统中断关闭标志,当early init完成后,设置为false /* * interrupts are still disabled. do necessary setups, then * enable them. */ boot_cpu_init(); // cpu相关初始化,cpu位图管理 page_address_init(); // 页地址初始化,主要是初始化高端内存映射表 pr_notice(%s, linux_banner); // 打印linux版本信息和kernel编译时间等信息 early_security_init(); // lsm 早期初始化 setup_arch(&command_line); // 和arm架构相关,解析atags或设备树,解析的参数放入command_line setup_command_line(command_line); // 保存命令行,日后再用 setup_nr_cpu_ids(); // 获取nr_cpu_ids个数,即cpu核数量 setup_per_cpu_areas(); // 设置smp体系每个cpu使用的内存空间,并拷贝初始化段内的数据 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ boot_cpu_hotplug_init(); // 初始化cpu热插拔 build_all_zonelists(null); // 设置内存管理相关的node(节点,每个cpu一个内存节点)和其中的zone(内存域,包含于节点中,如)数据结构,以完成内存管理子系统的初始化,并设置bootmem分配器 page_alloc_init(); // 设置内存页分配通知器 pr_notice(kernel command line: %s\\n, boot_command_line); /* parameters may set static keys */ jump_label_init(); parse_early_param(); // 解析boot_command_line的参数 after_dashes = parse_args(booting kernel, static_command_line, __start___param, __stop___param - __start___param, -1, -1, null, &unknown_bootoption); if (!is_err_or_null(after_dashes)) parse_args(setting init args, after_dashes, null, 0, -1, -1, null, set_init_arg); /* * these use large bootmem allocations and must precede * kmem_cache_init() */ setup_log_buf(0); // 使用boot mem分配一个记录启动信息的缓冲区 vfs_caches_init_early(); // 前期虚拟文件系统(vfs)的缓存初始化 sort_main_extable(); // 对内核异常表(exception_table)按照异常向量号大小进行排序,以便加速访问 trap_init(); // 对内核陷阱异常进行初始化,在arm系统里是空函数,没有任何的初始化 mm_init(); // 内存初始化,标记可使用内存,告知系统还剩多少内存可使用 ftrace_init(); // ftrace用于内核函数的trace功能 /* trace_printk can be enabled here */ early_trace_init(); // 为trace_printk等分配buffer /* * set up the scheduler prior starting any interrupts (such as the * timer interrupt). full topology setup happens at smp_init() * time - but meanwhile we still have a functioning scheduler. */ sched_init(); // 对进程调度器的数据结构进行初始化,创建运行队列,设置当前任务的空线程,当前任务的调度策略为cfs调度器 /* * disable preemption - early bootup scheduling is extremely * fragile until we cpu_idle() for the first time. */ preempt_disable(); // 关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度 if (warn(!irqs_disabled(), interrupts were enabled *very* early, fixing it\\n)) local_irq_disable(); radix_tree_init(); // 内核radis 树算法初始化 /* * set up housekeeping before setting up workqueues to allow the unbound * workqueue to take non-housekeeping into account. */ housekeeping_init(); // 在设置工作队列之前设置内部管理 /* * allow workqueue creation and work item queueing/cancelling * early. work item execution depends on kthreads and starts after * workqueue_init(). */ workqueue_init_early(); // 工作队列早期初始化 rcu_init(); // read copy update 初始化 /* trace events are available after this */ trace_init(); // trace event的初始化 if (initcall_debug) initcall_debug_enable(); context_tracking_init(); /* init some links before init_isa_irqs() */ early_irq_init(); // 前期外部中断描述符初始化,主要初始化数据结构 init_irq(); // 调用machine_desc- >init_irq()对中断初始化 tick_init(); // 初始化内核时钟系统 rcu_init_nohz(); init_timers(); // 初始化引导cpu的时钟相关的数据结构体,和初始化时钟软中断 hrtimers_init(); // 初始化高精度的定时器 softirq_init(); // 初始化软中断(tasklet和hi) timekeeping_init(); // 初始化系统时钟计时 /* * for best initial stack canary entropy, prepare it after: * - setup_arch() for any uefi rng entropy and boot cmdline access * - timekeeping_init() for ktime entropy used in rand_initialize() * - rand_initialize() to get any arch-specific entropy like rdrand * - add_latent_entropy() to get any latent entropy * - adding command line entropy */ rand_initialize(); // 初始化内核随机数产生器 add_latent_entropy(); add_device_randomness(command_line, strlen(command_line)); boot_init_stack_canary(); time_init(); // system timer或arch timer初始化 printk_safe_init(); perf_event_init(); // cpu性能监视机制初始化 profile_init(); // 分配内核性能统计保存的内存 call_function_init(); // 初始化percpu的call_single_queue,注册cpu热插拔通知函数到cpu通知链 warn(!irqs_disabled(), interrupts were enabled early\\n); early_boot_irqs_disabled = false; local_irq_enable(); // 打开本地cpu的中断 kmem_cache_init_late(); // 内核内存缓存(slab分配器)的后期初始化,之后可使用通用内存缓存 /* * 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(too many boot %s vars at `%s', panic_later, panic_param); lockdep_init(); // 打印锁的依赖信息,用来调试锁。 /* * need to run this when irqs are enabled, because it wants * to self-test [hard/soft]-irqs on/off lock inversion bugs * too: */ locking_selftest(); // 自测锁的api是否使用正常 /* * this needs to be called before any devices perform dma * operations that might use the swiotlb bounce buffers. it will * mark the bounce buffers as decrypted so that their usage will * not cause plain-text data to be decrypted when accessed. */ mem_encrypt_init();#ifdef config_blk_dev_initrd // 检查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 schedule()开启内核调度调用cpu_startup_entry->do_idle()结束内核启动。noinline void __ref rest_init(void){ struct task_struct *tsk; 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. */ pid = kernel_thread(kernel_init, null, clone_fs); /* * pin init on the boot cpu. task migration is not properly working * until sched_init_smp() has been run. it will set the allowed * cpus for init to the non isolated cpus. */ rcu_read_lock(); tsk = find_task_by_pid_ns(pid, &init_pid_ns); set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id())); rcu_read_unlock(); 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(); /* * enable might_sleep() and smp_processor_id() checks. * they cannot be enabled earlier because with config_preemption=y * kernel_thread() would trigger might_sleep() splats. with * config_preempt_voluntary=y the init task might have scheduled * already, but it's stuck on the kthreadd_done completion. */ system_state = system_scheduling; complete(&kthreadd_done); /* * the boot idle thread must execute schedule() * at least once to get things moving: */ schedule_preempt_disabled(); /* call into cpu_idle with preempt disabled */ cpu_startup_entry(cpuhp_online);}3、总体流程图如下,下一篇阅读1号进程和2号进程
光的本质是波还是粒子?
索尼Z9G带你走进8K智能电视
为自动驾驶发展铺路ADAS SoC须兼顾性能/安全
GTC中国站注册免费,聚焦人工智能与云计算
什么是软件测试?软件测试的目的?
Linux内核启动流程(下)
CES 2013 英特尔展示高性能自适应型一体机系统
三星正在开发一款新的平板电脑,它将是Galaxy Tab A系列产品
阻尼振荡波模拟器的校准方法
虚拟化和云计算之间的关系
解析智能驾驶关键技术:LPDDR5
单片机C语言的主程序,通常要用一个while(1)语句来让程序进入一个无限循环,目的是为了让程序一直保持在我
英特尔下一代处理器细节参数曝光
数字货币交易平台Paxful与线上娱乐平台Bspin正式达成了合作关系
面部识别是什么_面部识别的应用
导航定位芯片厂商华大北斗发布了新一代北斗三号导航定位芯片
NV400F语音IC,充电桩语音方案,支持UART音频更换
章丘南外环隧道项目电力监控系统系统简介
产品推荐 | 红外光源OPT-IXX-IR系列
波音公司正在考虑研发767宽体飞机用于货运市场