(来自 "linux/init/main.c")
"linux/init/main.c" 通过 start_kernel() 函数开始执行,该函数从 "linux/arch/i386/kernel/head.S" 中调用。start_kernel() 函数从不返回其调用者。它最终会调用 cpu_idle() 函数。
中断仍然被禁用。执行必要的设置,然后启用它们。
锁定内核 (BKL: 大内核锁)。
使用 printk() 打印 linux_banner 字符串 (该字符串位于 "linux/init/version.c" 中)。注意:printk() 实际上并没有将此字符串打印到控制台;它只是缓冲该字符串,直到控制台设备自身向内核注册,然后内核将缓冲的控制台日志内容传递给已注册的控制台设备。可以有多个已注册的控制台设备。
********** printk() 可以很早被调用,因为它实际上并没有打印到任何地方。它只是将消息记录到 "log_buf" 中,该缓冲区在 "linux/kernel/printk.c" 中静态分配。保存在 "log_buf" 中的消息会在控制台设备注册时传递给它们。 **********
调用 setup_arch(&command_line)
这将执行架构特定的初始化 (详情如下)。然后返回到架构无关的初始化....
start_kernel() 的其余部分对于所有处理器架构都是如下完成的,尽管其中几个函数调用是架构特定的设置/初始化函数。
打印内核命令行。
parse_options(command_line): 解析命令行上的内核选项。这是一个简单的内核命令行解析函数。它解析命令行并填充 init (线程) 的参数和环境,以适应需要。如果任何命令行选项包含字符 '=',则将其视为环境变量。它还通过调用 checksetup() 检查用于内核的选项,checksetup() 检查命令行中的内核参数,这些参数通过使用 "__setup" 声明,例如
__setup("debug", debug_kernel);
此声明导致在扫描到字符串 "debug" 时调用 debug_kernel() 函数。请参阅 "linux/Documentation/kernel-parameters.txt" 以获取内核参数列表。
这些选项不传递给 init -- 它们仅供内核内部使用。init 线程的默认参数列表是 {"init", NULL},最多有 8 个命令行参数。init 线程的默认环境列表是 {"HOME=/", "TERM=linux", NULL},最多有 8 个命令行环境变量设置。如果 LILO 将要使用默认命令行引导我们,它会在整个 cmdline 前面加上 "auto",这会使 shell 认为它应该执行一个具有该名称的脚本。因此,我们忽略在 init=... _之前_ 输入的所有参数 [MJ]
(在 linux/arch/i386/kernel/traps.c 中)
为基本的处理器异常安装异常处理程序,即,不是硬件设备中断处理程序。
为系统调用软件中断安装处理程序。
为 lcall7 (用于 iBCS) 和 lcall27 (用于 Solaris/x86 二进制文件) 安装处理程序。
调用 cpu_init() 来执行
(在 linux/arch/i386/kernel/i8259.c 中)
调用 init_ISA_irqs() 来初始化两个 8259A 中断控制器,并为 ISA IRQ 安装默认中断处理程序。
为所有未使用的中断向量设置中断门。
对于 CONFIG_SMP 配置,尽早设置 IRQ 0,因为它在 IO APIC 设置之前使用。
对于 CONFIG_SMP,为用于 "重新调度助手" 的 CPU 到 CPU IPI 安装中断处理程序。
对于 CONFIG_SMP,为用于使 TLB 无效的 IPI 安装中断处理程序。
对于 CONFIG_SMP,为用于通用函数调用的 IPI 安装中断处理程序。
对于 CONFIG_X86_LOCAL_APIC 配置,为自生成的本地 APIC 定时器 IPI 安装中断处理程序。
对于 CONFIG_X86_LOCAL_APIC 配置,为伪中断和错误中断安装中断处理程序。
设置系统时钟芯片以每 HZ Hz 生成一次定时器滴答中断。
如果系统具有外部 FPU,则设置 IRQ 13 以处理浮点异常。
(在 linux/kernel/sched.c 中)
(在 linux/arch/i386/kernel/time.c 中)
从 CMOS 初始化系统的当前时间 (xtime)。
安装 irq0 定时器滴答中断处理程序。
(在 linux/kernel/softirq.c 中)
(在 linux/drivers/char/tty_io.c 中)
黑客警告!这很早。我们在完成 PCI 设置等之前就启用了控制台,并且 console_init() 必须意识到这一点。但是我们确实希望尽早输出,以防出现问题。
(在 linux/kernel/module.c 中)
对于 CONFIG_MODULES 配置,调用 init_modules()。这将初始化内核符号表的大小 (或符号数量)。
如果 profiling ("profile=#" 在内核命令行上):计算内核文本 (代码) profile "段" 大小;计算 profile 缓冲区大小(向上舍入到页);分配 profile 缓冲区:prof_buffer = alloc_bootmem(size);
(在 linux/mm/slab.c 中)
********** 中断现在已启用。 **********
这允许 "calibrate_delay()" (如下) 工作。
计算 "loops_per_jiffy" 延迟循环值,并在 BogoMIPS 中打印它。
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
initrd_start < (min_low_pfn << PAGE_SHIFT)) {
printk("initrd overwritten (initrd_start < (min_low_pfn << PAGE_SHIFT)) - disabling it.\n");
initrd_start = 0; // mark initrd as disabled
}
#endif /* CONFIG_BLK_DEV_INITRD */
(在 linux/arch/i386/mm/init.c 中)
********** 在 mem_init() 之后可以使用 get_free_pages()。 **********
(在 linux/mm/slab.c 中)
设置剩余的内部和通用缓存。在启用 "get_free_page()" 函数之后和 smp_init() 之前调用。
********** 在 kmem_cache_sizes_init() 之后可以使用 kmalloc()。 **********
(在 linux/fs/proc/root.c 中)
对于 CONFIG_PROC_FS 配置
(在 linux/kernel/fork.c 中)
默认的最大线程数设置为安全值:线程结构最多可以占用一半的内存。
(在 linux/kernel/fork.c 中)
调用 kmem_cache_create() 来为 signal_act (信号动作)、files_cache (files_struct)、fs_cache (fs_struct)、vm_area_struct 和 mm_struct 创建 slab 缓存。
(在 linux/fs/dcache.c 中)
调用 kmem_cache_create() 来为 buffer_head、names_cache、filp 以及对于 CONFIG_QUOTA 的 dquot 创建 slab 缓存。
调用 dcache_init() 来创建 dentry_cache 和 dentry_hashtable。
(在 linux/fs/buffer.c 中)
分配缓冲区缓存哈希表并初始化空闲列表。
为哈希表使用 get_free_pages() 以减少 TLB 缺失;为缓冲区头使用 SLAB 缓存。
设置哈希链、空闲列表和 LRU 列表。
(在 linux/mm/filemap.c 中)
分配并清除页缓存哈希表。
(在 linux/fs/iobuf.c 中)
调用 kmem_cache_create() 来创建内核 iobuf 缓存。
(在 linux/kernel/signal.c 中)
调用 kmem_cache_create() 来创建 "sigqueue" SLAB 缓存。
(在 linux/fs/block_dev.c 中)
初始化 bdev_hashtable 列表头。
调用 kmem_cache_create() 来创建 "bdev_cache" SLAB 缓存。
(在 linux/fs/inode.c 中)
(在 linux/ipc/util.c 中)
对于 CONFIG_SYSVIPC 配置,调用 ipc_init()。
各种 System V IPC 资源 (信号量、消息和共享内存) 被初始化。
(在 linux/fs/dquot.c 中)
对于 CONFIG_QUOTA 配置,调用 dquot_init_hash()。
(在 linux/include/asm-i386/bugs.h 中)
smp_init() 以三种方式之一工作,具体取决于内核配置。
对于没有 IO APIC 的单处理器 (UP) 系统 (未定义 CONFIG_X86_IO_APIC),smp_init() 是空的 -- 它没有任何作用。
对于具有 (一个) IO APIC 用于中断路由的 UP 系统,它调用 IO_APIC_init_uniprocessor()。
对于 SMP 系统,其主要工作是调用架构特定的函数 "smp_boot_cpus()",该函数执行以下操作。
我们指望初始线程运行良好。
与 idler 一样,init 是一个未锁定的内核线程,它将进行系统调用 (因此将被锁定)。
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
{详情如下}
释放 BKL。
此函数仍然是进程号 0。其目的是用尽空闲的 CPU 周期。如果内核配置为支持 APM 或 ACPI,则 cpu_idle() 调用这些规范支持的节能功能。否则,它名义上执行 "hlt" 指令。
{start_kernel() 结束}
(在 "linux/arch/i386/kernel/setup.c" 中)
复制和转换从 16 位实模式传递到 32 位启动代码的参数数据。
从实模式参数数据初始化 rd_image_start、rd_prompt 和 rd_doload。
使用 BIOS 提供的内存映射来设置内存区域。
为内核代码的起始地址、内核代码的结束地址、内核数据的结束地址和 "_end" (内核代码的结束地址 = "brk" 地址) 设置值。
为 code_resource 起始地址和结束地址以及 data_resource 起始地址和结束地址设置值。
解析内核命令行上的任何 "mem=" 参数并记住它们。
使用 BIOS 提供的内存映射来设置页帧。
向 bootmem 分配器注册可用的低 RAM 页。
保留物理页 0: "它是许多机器上的特殊 BIOS 页,用于实现干净的重启、SMP 操作、笔记本电脑功能。"
对于 CONFIG_SMP,为堆栈和 trampoline 用途保留紧邻页 0 上方的页,然后调用 smp_alloc_memory() 以分配低内存用于 AP 处理器 (们) 实模式 trampoline 代码。
对于 CONFIG_X86_IO_APIC 配置,调用 find_smp_config() 以查找和保留任何启动时 SMP 配置信息内存,例如来自 BIOS 的 MP (多处理器) 表数据。
paging_init() 设置页表 - 请注意,前 8 MB 已由 head.S 映射。
此例程还会取消映射虚拟内核地址 0 处的页,以便我们可以捕获内核中那些讨厌的 NULL 引用错误。
对于 CONFIG_X86_IO_APIC 配置,调用 get_smp_config() 以读取和保存 MP 表 IO APIC 中断路由配置数据。
对于 CONFIG_X86_LOCAL_APIC 配置,调用 init_apic_mappings()。
对于 CONFIG_BLK_DEV_INITRD 配置,如果初始 RamDisk 有足够的内存,则调用 reserve_bootmem() 以保留 RAM 用于初始 RamDisk。
调用 probe_roms() 并保留其内存空间资源 (如果找到且有效)。这对于标准视频 BIOS ROM 映像、找到的任何选项 ROM 以及系统板扩展 ROM (空间) 完成。
调用 request_resource() 以保留视频 RAM 内存。
调用 request_resource() 以保留所有标准 PC I/O 系统板资源。
{setup_arch() 结束}
init 线程从 "linux/init/main.c" 中的 init() 函数开始。这始终预期为进程号 1。
init() 首先锁定内核,然后调用 do_basic_setup() 来执行大量的总线和/或设备初始化 {更多细节如下}。在 do_basic_setup() 之后,大多数内核初始化已完成。 init() 然后释放任何指定为仅用于初始化的内存 [标记为 "__init"、"__initdata"、"__init_call" 或 "__initsetup"] 并解锁内核 (BKL)。
init() 接下来打开 /dev/console 并复制该文件描述符两次以创建 init 及其所有子进程的 stdin、stdout 和 stderr 文件。
最后,init() 尝试执行内核参数命令行上指定的命令 (如果有),或者如果可以在 {/sbin/init, /etc/init, /bin/init} 中找到 init 程序或脚本,最后尝试 /bin/sh。如果 init() 无法执行任何这些操作,它会 panic ("No init found. Try passing init= option to kernel.")
机器现在已初始化。尚未接触任何设备,但 CPU 子系统已启动并运行,并且内存和进程管理工作正常。
init 进程处理所有孤立的任务。
// SMP 初始化在此之前完成。
对于 CONFIG_MTRR,调用 mtrr_init() [在 linux/arch/i386/kernel/mtrr.c 中]。
对于 CONFIG_SYSCTL 配置,调用 sysctl_init() [在 linux/kernel/sysctl.c 中]。
/*
* Ok, at this point all CPU's should be initialized, so
* we can start looking into devices..
*/
对于 CONFIG_PCI 配置,调用 pci_init() [在 linux/drivers/pci/pci.c 中]。
对于 CONFIG_MCA 配置,调用 mca_init() [在 linux/arch/i386/kernel/mca.c 中]。
对于 CONFIG_ISAPNP 配置,调用 isapnp_init() [在 linux/drivers/pnp/isapnp.c 中]。
/* Networking initialization needs a process context */
sock_init();
[在 linux/net/socket.c 中]
#ifdef CONFIG_BLK_DEV_INITRD
real_root_dev = ROOT_DEV;
real_root_mountflags = root_mountflags;
if (initrd_start && mount_initrd)
root_mountflags &= ~MS_RDONLY; // change to read/write
else
mount_initrd =0;
#endif /* CONFIG_BLK_DEV_INITRD */
[在 linux/kernel/context.c 中]
调用所有标记为 "__initcall" 的函数
do_initcalls();
[在 linux/init/main.c 中]这初始化了许多函数和一些子系统 --- 没有特定的或保证的顺序,除非在它们的 Makefile 中固定 --- 如果它们被构建到内核中,例如
调用 filesystem_setup()
对于 CONFIG_IRDA 配置,调用 irda_device_init()。
/* 必须在协议初始化之后完成 */
[在 linux/net/irda/irda_device.c 中]
/* 最后执行此操作 */
对于 CONFIG_PCMCIA 配置,调用 init_pcmcia_ds()。
[在 linux/drivers/pcmcia/ds.c 中]
mount_root();
[在 linux/fs/super.c 中]
mount_devfs_fs ();
[在 linux/fs/devfs/base.c 中]
#ifdef CONFIG_BLK_DEV_INITRD
if (mount_initrd && MAJOR(ROOT_DEV) == RAMDISK_MAJOR && MINOR(ROOT_DEV) == 0) {
// Start the linuxrc thread.
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
if (pid > 0)
while (pid != wait(&i));
if (MAJOR(real_root_dev) != RAMDISK_MAJOR
|| MINOR(real_root_dev) != 0) {
error = change_root(real_root_dev,"/initrd");
if (error)
printk(KERN_ERR "Change root to /initrd: "
"error %d\n",error);
}
}
#endif /* CONFIG_BLK_DEV_INITRD */
有关初始 RAM 磁盘的更多信息,请参阅 "linux/Documentation/initrd.txt"。
{do_basic_setup() 结束}