疯狂内核之系统初始化
文章平均质量分 82
yunsongice
这个作者很懒,什么都没留下…
展开
-
初始化本地软时钟
5.10.2 初始化本地软时钟<br />native_init_IRQ结束后,init_IRQ也就结束了,回到start_kernel中,607行,prio_tree_init函数很简单:<br /> <br />void __init prio_tree_init(void)<br />{<br /> unsigned int i;<br /> <br /> for (i = 0; i < ARRAY_SIZE(index_bits_to_maxindex) - 1; i++)<原创 2011-02-01 02:32:00 · 2529 阅读 · 0 评论 -
第二次启动分页管理
5.2.4 第二次启动分页管理<br />回到init_memory_mapping()函数,之后nr_range次调用kernel_physical_mapping_init(),nr_range为map_range数组的总的元素数,前面总共执行了两次save_mr,所以nr_range为2,分别是0~1<<9和2MB~end>>12的map_range结构,代表4k和2MB两种形式的内存页表。不过为了方便起见,我们暂时忽略2MB页面大小的体系,只分析传统4k页面体系。<br /> <br />238un原创 2011-01-11 23:09:00 · 2718 阅读 · 1 评论 -
激活第一个CPU
<br />回到start_kernel,559行,boot_cpu_init函数,跟start_kernel位于同一文件:<br /> <br />494static void __init boot_cpu_init(void)<br /> 495{<br /> 496 int cpu = smp_processor_id();<br /> 497 /* Mark the boot cpu "present", "online" etc for SMP and UP case原创 2011-01-11 22:57:00 · 7769 阅读 · 9 评论 -
打印版本信息
5.1.6 打印版本信息<br />我们来简单的看看在内核中,在屏幕上打印一条信息的原理是怎么实现的。由于我们配置了CONFIG_PRINTK编译选项,所以调用位于kernel/printk.c中的printk函数:<br /> <br />584 asmlinkage int printk(const char *fmt, ...)<br />585 {<br />586 va_list args;<br />587 int r;<br />588 <br />589原创 2011-01-11 23:01:00 · 2064 阅读 · 0 评论 -
建立内存管理架构
5.2.5 建立内存管理架构回到setup_arch函数的中,第995行调用initmem_init来启用初始化期间的内存管理器early。这个函数在两个文件中有定义,arch/x86/mm/init_32.c和arch/x86/mm/numa_32.c,取决于是否启动了编译选项CONFIG_NEED_MULTIPLE_NODES。这个编译选项是什么意思?这得从NUMA说起。NUMA翻译成中文就叫“非对称内存访问体系”,其目的是为多CPU,或大型计算机集群提供一个分布式内存访问环境,而每一个分布式节点就叫做原创 2011-01-11 23:13:00 · 3029 阅读 · 0 评论 -
拷贝可用内存区信息
5.2.1 拷贝可用内存区信息首先setup_arch第一步要做的就是保存arch/x86/kernel/head_32.S初始化的new_cpu_data数据到boot_cpu_data中。new_cpu_data主要是保存CPU的相关信息,在哪儿初始化的?还记得我们在arch/x86/kernel/head_32.S中忽略过的checkCPUtype吗?就在那里,感兴趣的同学可以去探究一下。784行,setup_memory_map()函数,进入start_kernel内核初始化函数中第一个内存管理函数原创 2011-01-11 23:05:00 · 2673 阅读 · 0 评论 -
初始化地址散列表
5.1.5 初始化地址散列表<br />560行,page_address_init()函数,来自mm/highmem.c:<br /> <br />409void __init page_address_init(void)<br /> 410{<br /> 411 int i;<br /> 412<br /> 413 INIT_LIST_HEAD(&page_address_pool);<br /> 414 for (i = 0; i < ARRAY_SIZE(原创 2011-01-11 23:00:00 · 3540 阅读 · 0 评论 -
着手建立内核永久页表
5.2.3 着手建立内核永久页表<br />得到了总的页面数max_pfn和高端页面数highmem_pages之后,来到setup_arch的947行,调用init_memory_mapping()函数来建立系统初始化阶段的临时分页体系,传入的参数意义代表从0~max_low_pfn对应的32位物理地址(低12位全为0,也就是页面对齐),在函数init_memory_mapping函数中先后调用下面的几个函数来设置内存相关数据(因为bootmem此时没有初始化):<br />find_early_tabl原创 2011-01-11 23:07:00 · 4049 阅读 · 2 评论 -
获得总页面数
5.2.2 获得总页面数<br />回到setup_arch()中,接下来,继续走,891行调用e820_end_of_ram_pfn()函数根据e820的数据来获得32位可用物理内存地址的最大值并右移PAGE_SHIFT,也就是12位,最后由函数e820_end_pfn返回这个20位的值,保存在内部变量max_pfn中,作为总的页面数量:<br /> <br />855static unsigned long __init e820_end_pfn(unsigned long limit_pfn, uns原创 2011-01-11 23:06:00 · 2236 阅读 · 0 评论 -
执行setup_arch()函数
5.2 执行setup_arch()函数<br />回到start_kernel当中,562行,调用setup_arch函数,传给他的参数是那个未被初始化的内部变量command_line。这个setup_arch()函数是start_kernel阶段最重要的一个函数,每个体系都有自己的setup_arch()函数,是体系结构相关的,具体编译哪个体系的setup_arch()函数,由顶层Makefile中的ARCH变量决定:<br /> <br />724void __init setup_arch(cha原创 2011-01-11 23:02:00 · 4050 阅读 · 0 评论 -
注册时钟事件监听器
5.1.3 注册时钟事件监听器<br />由于我们在.config文件中设置了CONFIG_GENERIC_CLOCKEVENTS,所以,start_kernel函数的第558行调用kernel/time/tick-common.c中的tick_init函数,来注册一个时钟事件监听器。tick_init只调用一个clockevents_register_notifier函数:<br /> <br />static struct notifier_block tick_notifier = {<br />原创 2011-01-11 22:53:00 · 2905 阅读 · 0 评论 -
启动大内核锁
5.1.2 启动大内核锁<br />回到start_kernel,557行,lock_kernel(),实际的代码来了,大内核锁。我们在.config文件中配置了CONFIG_LOCK_KERNEL的,所以这个函数是start_kernel中继local_irq_disable之后实际执行到的第二个函数。<br /> <br />有关大内核的知识,这里又简单的介绍一下。在早期的Linux内核版本中,大内核锁(big kernel block,也叫全局内核锁或BKL)被广泛使用。在2.0版本中,这个锁是相对粗原创 2011-01-11 22:52:00 · 2277 阅读 · 0 评论 -
初始化同步与互斥环境
5.1 初始化同步与互斥环境<br />要了解本节的内容,需要补充一下“疯狂内核之同步与互斥”的预备知识。<br /><br /> 5.1.1 屏蔽中断<br /> <br />void lockdep_init(void)<br />{<br /> int i;<br /> <br /> /*<br /> * Some architectures have their own start_kernel()<br /> * code which calls原创 2011-01-11 22:49:00 · 5124 阅读 · 0 评论 -
走向现代:start_kernel函数
5 走向现代:start_kernel函数<br />这是内核初始化至此,终于跑出arch/x86目录了,它在linux/init/main.c,以后的代码除了少数特例,其余的都在linux/init/目录中。<br /> <br />528asmlinkage void __init start_kernel(void)<br /> 529{<br /> 530 char * command_line;<br /> 531 extern struct kernel_param原创 2011-01-11 22:40:00 · 2790 阅读 · 0 评论 -
加载全局/中断描述符表
4.2 向start_kernel进发<br />分段、分页单元完成以后,我们的0号进程的运行环境就初步搭建起来了,那么就要“走向现代”,去调用start_kernel了,调用它之前还有几个重要的步骤要走。<br /> 4.2.1 加载全局/中断描述符表<br />很多人就有疑问,刚才我们设置好了中断描述符表,那内核怎么用它呢?不好意思,刚才在略过了的checkCPUtype过程中有一步非常重要的步骤,现在把它补上:<br />418 is386: movl $2,%ecx # se原创 2010-12-31 22:38:00 · 2592 阅读 · 0 评论 -
第一次启动分页管理
4.2.4初始化分页环境从227行开始,著名的分页机制就粉墨登场了:227page_pde_offset = (__PAGE_OFFSET >> 20); 228 229 movl $pa(__brk_base), %edi 230 movl $pa(swapper_pg_dir), %edx 231 movl $PTE_IDENT_ATTR, %eax 23210: 233 leal PDE_IDENT_ATTR(%edi),%ecx原创 2010-12-31 22:33:00 · 3307 阅读 · 3 评论 -
第二次启动保护模式
4.2.3解压缩后的内核代码我们在链接vmlinux一节中看到,定义phys_startup_32为程序入口点,这个入口点才是解压缩后内核的真正开始的地方,而在链接脚本中,phys_startup_32是逻辑地址startup_32的物理地址。从进入保护模式那一刻起,程序就是用逻辑地址了,不过在启动分页机制之前,逻辑地址向物理地址的转换很简单,仅仅是va和pa操作,即物理地址转成逻辑地址和逻辑地址转成物理地址。第二个startup_32在arch/x86/kernel/head_32.S的85行,我们就开始原创 2010-12-31 22:28:00 · 3365 阅读 · 0 评论 -
添砖加瓦
5.2.6 添砖加瓦<br />回到setup_arch,来到1007,调用paging_init()进行页面初始化。<br /> <br />825void __init paging_init(void)<br /> 826{<br /> 827 pagetable_init();<br /> 828<br /> 829 __flush_tlb_all();<br /> 830<br /> 831 kmap_init();<br /> 832<br /> 833原创 2011-01-12 00:23:00 · 3548 阅读 · 0 评论 -
设置每CPU环境
5.3 设置每CPU环境<br />回到start_kernel,563行调用mm_init_owner函数,将init_mm的owner字段指回init_task。这个函数可以说进入start_kernel以来最简单的函数了。继续走,setup_command_line也很简单:<br /> <br />static void __init setup_command_line(char *command_line)<br />{<br /> saved_command_line = allo原创 2011-02-01 02:09:00 · 6260 阅读 · 0 评论 -
启用伙伴算法
5.8 初始化内存管理<br />回到start_kernel,下一个函数执行mm_init()。这个函数很重要了,来自同一个文件。<br /> <br />static void __init mm_init(void)<br />{<br /> /*<br /> * page_cgroup requires countinous pages as memmap<br /> * and it's bigger than MAX_ORDER unless SPARSE原创 2011-02-01 02:22:00 · 3666 阅读 · 1 评论 -
利用early_res分配内存
5.5 利用early_res分配内存<br />回到start_kernel中,570行,page_alloc_init()函数:<br />void __init page_alloc_init(void)<br />{<br /> hotcpu_notifier(page_alloc_cpu_notify, 0);<br />}<br /> <br />这个函数会调用hotcpu_notifier函数。当然,在编译选项CONFIG_HOTPLUG_CPU起作用时,这个函数才有效。这个编译选原创 2011-02-01 02:13:00 · 2504 阅读 · 0 评论 -
初始化内存管理区列表
5.4 初始化内存管理区列表<br />回到start_kernel函数,569行的build_all_zonelists()函数,来自mm/page_alloc.c:<br /> <br />2815void build_all_zonelists(void)<br />2816{<br />2817 set_zonelist_order();<br />2818<br />2819 if (system_state == SYSTEM_BOOTING) {<br />2820原创 2011-02-01 02:11:00 · 3439 阅读 · 0 评论 -
启动shell环境
6.3 启动shell环境<br />init_post函数来自同一个文件的814行:<br /> <br />811/* This is a non __init function. Force it to be noinline otherwise gcc<br /> 812 * makes it inline to init() and it becomes part of init.text section<br /> 813 */<br /> 814static noinline int init原创 2011-02-01 02:52:00 · 2147 阅读 · 0 评论 -
子系统的初始化
6.2 子系统的初始化<br />所以接下来说do_basic_setup函数,任然是来自init/main.c:<br /> <br />778/*<br /> 779 * Ok, the machine is now initialized. None of the devices<br /> 780 * have been touched yet, but the CPU subsystem is up and<br /> 781 * running, and memory and process m原创 2011-02-01 02:49:00 · 3213 阅读 · 0 评论 -
创建1号进程
6 后start_kernel时代<br />至此,start_kernel()函数完成了Linux内核的初始化工作。几乎每天内核部件都是由这个函数进行初始化的,下面让我们再来回顾一下其中最重要的部分:<br /> <br />● 调用setup_arch()函数,根据处理器硬件平台设置系统;解析linux命令行参数;设置0号进程的内存描述结构init_mm;系统内存管理初始化;统计并注册系统各种资源;以及其它项目的初始化等。<br />● 调用sched_init()函数来初始化调度程序。<br />原创 2011-02-01 02:42:00 · 2867 阅读 · 5 评论 -
安装根文件系统
5.12 安装根文件系统<br />start_kernel下步是另一个重要的函数,678行的vfs_caches_init,用于初始化VFS那些数据结构的slab缓存,来自fs/dcache.c:<br /> <br />2355void __init vfs_caches_init(unsigned long mempages)<br />2356{<br />2357 unsigned long reserve;<br />2358<br />2359 /* Base ha原创 2011-02-01 02:40:00 · 3030 阅读 · 0 评论 -
走进start_kernel尾声
5.11 走进start_kernel尾声<br />中断体系建立起来后,虽然后面还有很多行代码,但是都是些比较好理解的初始化函数了,也就是说start_kernel进入尾声了。5.11.1 初始化slab的后续工作<br />继续分析start_kenel的下一个函数,613行,profile_init函数,用于对系统剖析做相关初始化,系统剖析用于系统调用:<br /> <br />int __ref profile_init(void)<br />{<br /> int buffer_byt原创 2011-02-01 02:36:00 · 3369 阅读 · 0 评论 -
初始化定时器中断
5.10.4 初始化定时器中断<br />回到start_kernel,612行time_init函数:<br />void __init time_init(void)<br />{<br /> late_time_init = x86_late_time_init;<br />}<br /> <br />函数x86_late_time_init实际上是初始化tsc时钟源。在time_init中只是把该函数的地址赋给全局变量late_time_init,以后某个时刻肯定会调用它的,这里先提前详原创 2011-02-01 02:34:00 · 3241 阅读 · 0 评论 -
软中断初始化
5.10.3软中断初始化<br />open_softirq结束后,init_timers就结束了,整个内核就可以享受时钟服务了,接下来start_kernel的609行调用hrtimers_init。由于我们没有配置CONFIG_HIGH_RES_TIMERS,所以这个函数仅仅是把全局notifier_block变量hrtimer_cpu_notify加入通知链,供将来的内核各模块使用。<br /> <br />然后,start_kernel的610行调用softirq_init来初始化整个软中断系统:<原创 2011-02-01 02:33:00 · 2467 阅读 · 0 评论 -
设置APIC中断服务
5.10 初始化中断处理系统<br />start_kernel接下来要做的事是初始化中断处理系统。整个内核的中断系统的核心就是我们在“初始化中断描述符表”里面设置的那个中断描述符表。而这个表的前19个表项我们已经在“初始化异常服务”中设置为了一些中断和异常的服务。内核接下来会如何设置其余的表项呢?<br /> <br />前面提到过高级可编程中断控制器APIC,这里简单地提一下它的体系结构,简单地说就是由两部分组成:本地高级中断控制器(Local APIC,LAPIC),位于每个CPU中,主要负责传递中断原创 2011-02-01 02:30:00 · 4705 阅读 · 0 评论 -
初始化调度程序
5.9 初始化调度程序<br />回到start_kernel函数中,mm_init()执行后,所有的绝大多数内存管理的初始化都完毕,后面的代码可以开开心心的使用Linux复杂、庞大而又高效的内存管理器了。来看下一个函数,超级重点的进程调度初始化函数sched_init()。不过自从Linux 2.6.23(2007年5月),内核引入了一种所谓的完全公平调度程序(Completely Fair Scheduler,CFS),试图按照对 CPU 时间的“最大需求(gravest need)”运行任务;这有助于原创 2011-02-01 02:28:00 · 2885 阅读 · 0 评论 -
初始化非连续内存区
5.8.3 初始化非连续内存区<br />回到mm_init()函数,继续走,下一个函数pgtable_cache_init (),不知道咋的,是个空函数,也许是保留着以后开发吧。最后一个函数是vmalloc_init(),来自mm/vmalloc.c:<br /> <br />1088void __init vmalloc_init(void)<br />1089{<br />1090 struct vmap_area *va;<br />1091 struct vm_stru原创 2011-02-01 02:27:00 · 2228 阅读 · 1 评论 -
初始化slab分配器
5.8.2 初始化slab分配器<br />回到mm_init()函数,继续走,下一个函数kmem_cache_init(),也是重点函数,用于初始化内核slab分配体系。这个函数来自文件mm/slab.c<br /> <br />1375void __init kmem_cache_init(void)<br />1376{<br />1377 size_t left_over;<br />1378 struct cache_sizes *sizes;<br />1379原创 2011-02-01 02:25:00 · 2030 阅读 · 0 评论 -
初始化异常服务
5.7 初始化异常服务<br />继续走,start_kernel的583行,sort_main_extable,把编译期间,kbuild设置的异常表,也就是__start___ex_table和__stop___ex_table之中的所有元素进行排序。<br /> <br />584行,调用trap_init函数,重要的函数,初始化中断向量表。该函数来自arch/x86/kernel/traps.c<br /> <br />882void __init trap_init(void)<br /> 883{原创 2011-02-01 02:20:00 · 3149 阅读 · 0 评论 -
触碰虚拟文件系统
5.6 触碰虚拟文件系统<br />回到start_kernel中,下面我们该第一次接触文件系统了,582行执行vfs_caches_init_early:<br /> <br />void __init vfs_caches_init_early(void)<br />{<br /> dcache_init_early();<br /> inode_init_early();<br />}<br /> <br />vfs_caches_init_early调用两个函数dcache_原创 2011-02-01 02:19:00 · 2737 阅读 · 0 评论 -
第一次启动保护模式
3.4.3安装临时全局描述符表回到go_to_protected_mode()函数中,接下来116和119行的两个函数比较简单,我们快速过一下: 35/* 36 * Disable all interrupts at the legacy PIC. 37 */ 38static void mask_all_interrupts(void) 39{ 40 outb(0xff, 0xa1); /* Mask all interrupts on the secondary P原创 2010-12-31 22:11:00 · 4251 阅读 · 6 评论 -
打开A20地址线
3.4.2打开A20地址线<br />回到go_to_protected_mode()的110行,调用一个enable_a20()函数。这里又用到“PC汇编及BIOS编程”的知识了。PC及其兼容机的第21根地址线(A20)较特殊,这就是“Intel 80286工作模式”提到的PC中安排的一个“门”控制该地址线是否有效。到了80286,系统的地址总线有原来的20根发展为24根,这样能够访问的内存可以达到2^24=16M。Intel在设计80286时提出的目标是向下兼容。所以,在实模式下,系统所表现的行为应该和原创 2010-12-31 22:09:00 · 8205 阅读 · 1 评论 -
实模式代码go_to_proteced_mode函数
3.4 实模式代码go_to_proteced_mode函数<br />main函数走到go_to_protected_mode,就是将告别实模式环境,进入保护模式了。保护模式(Protected Mode,或有时简写为 pmode)是一种 80286 系列和之后的 x86 兼容 CPU 操作模式。保护模式有一些新的特色,设计用来增强多功能和系统稳定度,像是内存保护,分页系统,以及硬件支援的虚拟内存。大部分的现今x86操作系统都在保护模式下运行,包含Linux、FreeBSD、以及微软 Windows 2.原创 2010-12-31 22:03:00 · 3154 阅读 · 1 评论 -
内核映像的形成——prepare和scripts目标
2.2.3 prepare和scripts目标<br />上一节我们找到了整个KBuild体系的第一个目标,根据是否进行了编译配置而分别是385行的scripts_basic和949行的include/config/kernel.release。更重要的是,通过第一个目标的寻找,我们把KBuild体系的整个顶层Makefile文件脉络梳理了一遍,这对我们研究vmlinux的形成及其重要。下面我们就从第一个目标开始,对编译配置后make的整个过程来过一遍。<br /> <br />949行,第一个目标:<br原创 2010-12-29 22:37:00 · 5838 阅读 · 1 评论 -
内核映像的形成——制作bzImage
2.2.6制作bzImage<br />回到顶层Makefile的849行,随着vmlinux的链接工作结束,在主目录下生成了vmlinux文件,vmlinux目标也就结束了。那么就会来到arch/x86/Makefile的155行bzImage目标。看到这个目标,就知道后面的工作就算打包压缩vmlinux并制作bzImage了。看到156行,因为CONFIG_X86_DECODER_SELFTEST没有设置,所以会直接执行159行以后的命令:<br />159 $(Q)$(MAKE) $(b原创 2010-12-29 22:54:00 · 7302 阅读 · 1 评论