分类: LINUX
2010-10-13 10:02:25
Linux2.6启动4--start_kernel篇 当内核与体系架构相关的汇编代码执行完毕,即跳入start_kernel。这个函数在kernel/init/main.c中。由于这部分涉及linux众多数据结构的初始化,包括内核命令行解析,内存缓冲区建立初始化,页面分配和初始化,虚拟文件系统建立,根文件系统挂载,驱动文件挂载,二进制程序文件的执行等,限于篇幅和理解水平,只能流程上的大致梳理,以上提及方面后期再做详细分析。为保证准确性,参考了一部分书籍和网上技术文档,如有疑问请及时提出,共同学习探讨。 asmlinkage void __init start_kernel(void) { char
* command_line; extern
struct kernel_param __start___param[], __stop___param[]; //这里引用两个符号,是内核编译脚本定位的内核参数起始地址 smp_setup_processor_id();//多CPU架构的初始化,目前我们的高通linux侧是单核的,此多核不做分析 unwind_init();//本架构中没有用 lockdep_init();//本架构为空 debug_objects_early_init(); cgroup_init_early(); local_irq_disable(); early_boot_irqs_off(); early_init_irq_lock_class(); lock_kernel();//本架构为空函数 tick_init(); //时钟中断初始化函数,调用 clockevents_register_notifier 函数向 clockevents_chain 时钟事件链注册时钟控制函数 tick_notifier。这是个回调函数,指明了当时钟事件发生变化时应该执行的哪些操作,比如时钟的挂起操作等 boot_cpu_init();//用于多核CPU的初始化 page_address_init();//用于高地址内存,我们都用32位CPU,此函数为空 printk(KERN_NOTICE); printk(linux_banner); setup_arch(&command_line); //具体看一下这个架构初始化函数完成哪些功能 void __init setup_arch(char **cmdline_p) { struct
tag *tags = (struct tag *)&init_tags;//定义了一个默认的内核参数列表 struct
machine_desc *mdesc; char
*from = default_command_line; setup_processor();//汇编的CPU初始化部分已讲过,不再讨论 mdesc
= setup_machine(machine_arch_type); machine_name
= mdesc->name; if
(mdesc->soft_reboot) reboot_setup("s"); if
(__atags_pointer) tags
= phys_to_virt(__atags_pointer); else
if (mdesc->boot_params) tags
= phys_to_virt(mdesc->boot_params); //由于MMU单元已打开,此处需要而boot_params是物理地址,需要转换成虚拟地址才能访问,因为此时CPU访问的都是虚拟地址 /* *
If we have the old style parameters, convert them to *
a tag list. */ //内核参数列表第一项必须是ATAG_CORE类型 if
(tags->hdr.tag != ATAG_CORE)//如果不是,则需要转换成新的内核参数类型,新的内核参数类型用下面struct tag结构表示 convert_to_tag_list(tags);//此函数完成新旧参数结构转换 struct tag { struct
tag_header hdr; union
{ struct
tag_core core; struct
tag_mem32 mem; struct
tag_videotext videotext; struct
tag_ramdisk ramdisk; struct
tag_initrd initrd; struct
tag_serialnr serialnr; struct
tag_revision revision; struct
tag_videolfb videolfb; struct
tag_cmdline cmdline; }
u; }; //旧的内核参数列表用下面结构表示 struct param_struct { union { struct
{ unsigned
long
page_size; /* 0
*/ unsigned
long
nr_pages; /* 4
*/ unsigned
long
ramdisk_size; /* 8
*/ unsigned
long
flags; /*
12 */ 。。。。。。。。。。。。//较长,省略 } if
(tags->hdr.tag != ATAG_CORE)//如果没有内核参数 tags
= (struct tag *)&init_tags;//则选用默认的内核参数 if
(mdesc->fixup) mdesc->fixup(mdesc,
tags, &from, &meminfo);//用内核参数列表填充meminfo if
(tags->hdr.tag == ATAG_CORE) { if
(meminfo.nr_banks != 0) squash_mem_tags(tags); save_atags(tags); parse_tags(tags);//解析内核参数列表,然后调用内核参数列表的处理函数对这些参数进行处理。比如,如果列表为命令行,则最终会用parse_tag_cmdlin函数进行解析,这个函数用_tagtable编译连接到了内核里 __tagtable(ATAG_CMDLINE, parse_tag_cmdline); } //下面是记录内核代码的起始,结束虚拟地址 init_mm.start_code
= (unsigned long) &_text; init_mm.end_code =
(unsigned long) &_etext; init_mm.end_data =
(unsigned long) &_edata; init_mm.brk =
(unsigned long) &_end; //下面是对命令行的处理,刚才在参数列表处理parse_tag_cmdline函数已把命令行拷贝到了from空间 memcpy(boot_command_line,
from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1]
= '\0'; parse_cmdline(cmdline_p,
from);//解析出命令行,命令行解析出以后,同样会调用相关处理函数进行处理。系统用__early_param宏在编译阶段把处理函数编译进内核。 paging_init(&meminfo,
mdesc); //这个函数完成页表初始化,具体的方法为建立线性地址划分后每个地址空间的标志;清除在boot阶段建立的内核映射空间,也即把页表项全部清零;调用bootmem_init,禁止无效的内存节点,由于我们的物理内存都是连续的空间,因此,内存节点为1个。接下来判断INITRD映像是否存在,若存在则检查其所在的地址是否在一个有效的地址内,然后返回此内存节点号。 先看两个数据结构。 struct meminfo表示内存的划分情况。Linux的内存划分为bank。每个bank用 struct membank表示,start表示起始地址,这里是物理地址,size表示大小,node表示此bank所在的节点号,对于只有一个节点的内存,所有bank节点都相等 struct membank { unsigned
long start; unsigned
long size; int node; }; struct meminfo { int
nr_banks; struct
membank bank[NR_BANKS]; }; //在page_init函数中比较重要的是bootmem_init函数,此函数在完成原来映射页表的清除后,最终调用bootmem_init_node如下: bootmem_init_node(int node, int initrd_node,
struct meminfo *mi) { unsigned
long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES]; unsigned
long start_pfn, end_pfn, boot_pfn; unsigned
int boot_pages; pg_data_t
*pgdat;// 每个节点用pg_data_t描述,这个结构用在非一致性内存中,我们的内存只有一个,地址是连续的 int
i; start_pfn
= -1UL; end_pfn
= 0; for_each_nodebank(i,
mi, node) { struct
membank *bank = &mi->bank[i]; unsigned
long start, end; start
= bank->start >> PAGE_SHIFT;//计算出页表号,实际也表示第几个物理页号 end
= (bank->start + bank->size) >> PAGE_SHIFT; if
(start_pfn > start) start_pfn
= start; if
(end_pfn < end) end_pfn
= end; map_memory_bank(bank);//将每个节点的每个bank重新映射,比如重新映射内核空间 } if
(end_pfn == 0) return
end_pfn; //一个字节代表8个页,因此找到一个 //可放置这些所有自己的页面即可。用一个bit位表示一个页是否已占用,那么一个字节为8个页,比如4096个页需要4096/8=512字节,容纳这个位图需要一个页 boot_pages
= bootmem_bootmap_pages(end_pfn - start_pfn); boot_pfn
= find_bootmap_pfn(node, mi, boot_pages);//在node节点内存的bank中找到一个可以放置位图的页面的页面序列,然后返回这个页面序列的首个页面号 node_set_online(node);//设置本节点有效 pgdat
= NODE_DATA(node);//获取节点描述符pgdat init_bootmem_node(pgdat,
boot_pfn, start_pfn, end_pfn);//设置本节点内所有映射页的位图,即每个字节全部置为0xff,表示已经映射使用。然后填充pgdat结构 for_each_nodebank(i,
mi, node) free_bootmem_node(pgdat,
mi->bank[i].start, mi->bank[i].size);//设置每个映射的页面空闲,实际是对位图的操作,对每个bit清零 reserve_bootmem_node(pgdat,
boot_pfn << PAGE_SHIFT, boot_pages
<< PAGE_SHIFT, BOOTMEM_DEFAULT); //标示位图所占的页面被占用 if
(node == 0) reserve_node_zero(pgdat); #ifdef CONFIG_BLK_DEV_INITRD /* *
If the initrd is in this node, reserve its memory. */ if
(node == initrd_node) { int
res = reserve_bootmem_node(pgdat, phys_initrd_start, phys_initrd_size,
BOOTMEM_EXCLUSIVE); //INITRD映像占用的空间需要标示占用,INITRD是虚拟根文件系统,此时还未加载,因此挂载之前这个物理空间不能再被分配使用 if
(res == 0) { initrd_start
= __phys_to_virt(phys_initrd_start); initrd_end
= initrd_start + phys_initrd_size; }
else { printk(KERN_ERR "INITRD:
0x%08lx+0x%08lx overlaps in-use " "memory
region - disabling initrd\n", phys_initrd_start,
phys_initrd_size); } } #endif /* *
initialise the zones within this node. */ memset(zone_size,
0, sizeof(zone_size)); memset(zhole_size,
0, sizeof(zhole_size)); /* *
The size of this node has already been determined. If we need *
to do anything fancy with the allocation of this memory to the *
zones, now is the time to do it. */ zone_size[0]
= end_pfn - start_pfn; zhole_size[0]
= zone_size[0]; for_each_nodebank(i,
mi, node) zhole_size[0]
-= mi->bank[i].size >> PAGE_SHIFT; //计算共有多少页空洞,注意,有些bank的起始结束地址并不是刚好4K对齐的,因此,可能存在某些空白页框。用节点总的物理页框减去每个bank页框,就得到页空洞 //这个函数里面主要完成zone区的初始化,linux内存管理将内存节点又分为ZONE区管理,比如ZONE_DMA和ZONE_NORMAL等,因此需要初始化。由于平台只针对一致性内存管理,即物理内存空间只包含DDR部分,此处很多函数是空的,再次略过 arch_adjust_zones(node,
zone_size, zhole_size); free_area_init_node(node,
zone_size, start_pfn, zhole_size); return
end_pfn; } //在page_init的最后完成devicemaps_init初始化,比如中断向量的映射。映射的大致过程是,申请一个物理框,然后调用creat_map将此物理页框映射到0xffff0000.最后再调用struct
machine_desc的map_io完成IO设备的映射 //在完成内存页映射后即进入request_standard_resources,这个函数比较简单,主要完成从iomem_resource空间申请所需的内存资源,比如内核代码和视频所需的资源等 request_standard_resources(&meminfo,
mdesc); #ifdef CONFIG_SMP smp_init_cpus(); #endif cpu_init();//此函数为空 init_arch_irq
= mdesc->init_irq;//初始化与硬件体系相关的指针 system_timer
= mdesc->timer; init_machine
= mdesc->init_machine; #ifdef CONFIG_VT #if defined(CONFIG_VGA_CONSOLE) conswitchp
= &vga_con; #elif defined(CONFIG_DUMMY_CONSOLE) conswitchp
= &dummy_con; #endif #endif early_trap_init();//重定位中断向量,将中断向量代码拷贝到中断向量页,并把信号处理代码指令拷贝到向量页中 } mm_init_owner(&init_mm,
&init_task);//空函数 setup_command_line(command_line);//保存命令行,以备后用,此保存空间需申请 //这个函数调用完了,就开始执行下面初始化函数 unwind_setup();//空函数 setup_per_cpu_areas();//设置每个CPU信息,单核CPU为空函数 setup_nr_cpu_ids();//空函数 smp_prepare_boot_cpu(); //设置启动的CPU为在线状态.在多CPU架构下 //第一个启动的cpu启动到一定阶段后,开始启动其它的cpu,它会为每个后来启动的cpu创建一个0号进程,而这些0号进程的堆栈的thread_info结构中的cpu成员变量则依次被分配出来(利用alloc_cpu_id()函数)并设置好,这样当这些cpu开始运行的时候就有了自己的逻辑cpu号。 sched_init();//初始化调度器,对调度机制进行初始化,对每个CPU的运行队列 preempt_disable();//启动阶段系统比较脆弱,禁止进程调度 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); //执行命令行解析,若参数不存在,则调用unknown_bootoption if
(!irqs_disabled()) { printk(KERN_WARNING
"start_kernel(): bug: interrupts were " "enabled
*very* early, fixing it\n"); local_irq_disable(); } sort_main_extable();//对异常处理函数进行排序 trap_init();//空函数 rcu_init();//linux2.6的一种互斥访问机制 init_IRQ();//中断向量初始化 pidhash_init();//进程嘻哈表初始化 init_timers();//定时器初始化 hrtimers_init();//高精度时钟初始化 softirq_init();//软中断初始化 timekeeping_init();//系统时间初始化 time_init(); sched_clock_init(); profile_init();//空函数 if
(!irqs_disabled()) printk("start_kernel():
bug: interrupts were enabled early\n"); early_boot_irqs_on(); local_irq_enable(); console_init();//打印终端初始化 if
(panic_later) panic(panic_later,
panic_param); lockdep_info(); 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 vfs_caches_init_early();//建立节点嘻哈表和数据缓冲嘻哈表 cpuset_init_early();//空函数 mem_init();//对全局的物理页变量初始化,对没有分配的页面初始化 enable_debug_pagealloc(); cpu_hotplug_init();//没有热插拔CPU,此函数为空 kmem_cache_init();//内核内存缓冲区初始化 debug_objects_mem_init(); idr_init_cache();//创建idr缓冲区 setup_per_cpu_pageset();//采用的是一致性内存,此函数为空 numa_policy_init();//采用的是一致性内存,此函数为空 if
(late_time_init) late_time_init(); calibrate_delay();//校准延时函数的精确度,实际上是校准loops_per_jiffy全局变量,即每个时钟滴答内CPU执行的指令数 pidmap_init();//进程号位图初始化,一般用一个page来指示所有的进程PID占用情况 pgtable_cache_init();//空函数 prio_tree_init();//初始化优先级数组 anon_vma_init();//空函数
chinaunix网友2010-10-13 20:16:38
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com