- 浏览: 255008 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
soho00147:
我的想法是在这个插件的基础上编写更加强大的插件,扫描目录时发现 ...
maven增量编译的思考 -
soho00147:
如果没有变动则 maven install
使用时,直接 ...
maven增量编译的思考 -
soho00147:
可以参考这个插件,这个插件是对maven项目以module级别 ...
maven增量编译的思考
转载自:http://blog.chinaunix.net/uid-27052262-id-3404074.html
下面是学习linux内核启动过程的记录
平台是:powerpc mpc8548 + linux2.6.23 内核
通用寄存器的作用
r0 :在函数开始时使用
r1 :存放堆栈指针,相当于ia32架构中的esp寄存器
r2 :存放当前进程的描述符的地址
r3 :存放第一个参数和返回地址
r4-r10 :存放函数的参数
r11 :用在指针的调用和当前一些语言的环境指针
r12 :用于存放异常处理
r13 :保留做为系统线程ID
r14-r31 :作为本地变量,具有非易失性
Linux启动过程描述
第一步:使用Boot Loader(一般是U-boot)加载Linux内核映像到内存,并负责目标系统的基本初始化过程,并搜集这个系统的基本信息,比如内存大小、处理器主频、外设的使用情况等一系列信息。然后把这些信息传递给linux内核。然后Boot loader把linux内核复制到从0x0000 0000 开始的物理内存处(虚拟地址一般为0xc000 0000处)开始执行。
备注:这一部分内容,本文不做重点介绍。请参考《uboot启动过程学习总结.doc》
*
*******************************************************************************
记录2:linux kernel 链接文件、入口函数和相关宏定义等
Bootstraploader过程:从文件\arch\powerpc\boot\zImage.lds中可以看出,bootstraploader的入口为_zimage_start。在代码arch\powerpc\boot\crt0.S中
D:\virtual_machine\share_folder\linux-2.6.23\arch\powerpc\boot\zImage.lds中定义的入口地址为4MB,见下面
SECTIONS
{
. = (4*1024*1024);
_start = .;
.text :
进入linux内核:从vmlinux.lds看到,内核入口为_stext,通过段.text.head 将代码定位到0xc0000000处。
在代码arch/powerpc/kernel/head_32.S中_stext之后紧接着是_start,他们之间没有代码,他们表示相同的地址。
在vmlinux.lds中将.text.head规划为.text的第一个字段(保证了地址定位到0xc0000000)。
*******************************************************************************
第二步:Linux系统的初始化
1、 bootstraploader 过程
注意:需要知道从uboot跳到此处时,r3寄存器的内容,以及其他register的内容
如果运行地址和链接地址不同,则修正got表中各个函数的指针
清零BSS段
调用platform_init(),保存bd到__res,初始化ppc_md(ppc module)中的各个函数。
调用arch\powerpc\boot\main.c中的start()
在start()中:
1.1将命令行拷贝到cmdline中
1.2调用open函数打开串口
1.3解压缩kernel代码
1.4解压缩ramdisk image
1.5最终初始化设备树
1.6跳到内核代码中执行
有两个调用语句,应该是运行了语句:kentry(ft_addr, 0, NULL); need confirm
2、 进入linux内核
入口:arch/powerpc/kernel/head_32.S中的_start。
2.1 early_init() ,arch/powerpc/kernel/setup_32.c中
计算运行地址和链接地址的差值。根据cpu型号调用do_feature_fixups函数来对__ftr_fixup段进行修复处理。
例如若HIDO寄存器的HIGH_BAT_EN位置位,另外的4组寄存器 IBATs (4–7) 和 4组 DBATs (4-7) 将会被激活,__ftr_fixup段中对这8组寄存器进行初始化的代码就会生效;否则__ftr_fixup中的这段代码就会被nop指令所代替!
early_init()函数调用identify_cpu()函数通过cpu中的pvr寄存器存放的CPU核的版本号在全局数组cpu_specs中寻找到当前cpu的详细信息,identify_cpu()函数在找到之后,会调用setup_cpu_spec()函数把上述cpu的信息所在的链接地址赋值给cur_cpu_spec变量
2.2 mmu_off() 关闭mmu
2.3 flush_tlbs() 从TLB中移除页表
2.4 call_setup_cpu():call_setup_cpu()位于misc_32.S文件中
2.5 relocate_kernel():把内核代码拷贝到链接地址指向的位置
2.6 turn_on_mmu():映射了256MB内存,就可以避免调用reloc_offset()函数来显式得把虚拟地址映射到物理地址!
2.7跳到start_here()函数中运行,可以认为是真正内核开始运行。。。
2.7.1加载0号线程上下文,全局变量init_task
注:0号线程优先级为120,从#define INIT_TASK(tsk)中可以看出。
init_task是进程0使用的进程描述符,也是Linux系统中第一个进程描述符,该进程的描述符在arch/powerpc/kernel/init_task.c中定义,代码片段如下:
struct task_struct init_task = INIT_TASK(init_task);
init_task描述符使用宏INIT_TASK对init_task的进程描述符进行初始化,宏INIT_TASK在include/linux/init_task.h文件中
init_task是Linux内核中的第一个线程,它贯穿于整个Linux系统的初始化过程中,该进程也是Linux系统中唯一一个没有用kernel_thread() 函数创建的进程!在init_task进程执行后期,它会调用kernel_thread()函数创建第一个核心进程kernel_init,同时init_task进程继续对Linux系统初始化。在完成初始化后,init_task会退化为cpu_idle进程,当Core 0的就绪队列中没有其它进程时,该进程将会获得CPU运行。新创建的1号进程kernel_init将会逐个启动次CPU,并最终创建用户进程!
备注:core0上的idle进程由init_task进程退化而来,而AP的idle进程则是BSP在后面调用fork()函数逐个创建的,我们会在后面详细讨论。
init_task进程使用init_thread_union数据结构描述的内存区域作为该进程的堆栈空间,并且和自身的thread_info参数公用这一内存空间空间,其数据结构的定义如下(linux- 2.6.38/include/linux/sched.h)
2.7.2 调用machine_init()分析OF树的结构,获得当前处理器的内存使用情况, 创建LMB结构,同时获得当前CPU在OF树中的硬件信息;保存命令行等,从cmd_line拷贝uboot引导kernel时使用的命令行参数到boot_command_line,并并使用parse_early_param()函数分析这些命令行参数。
2.7.3 调用MMU_init(),为LinuxPowerPC建立MMU地址映射,区分memory 的normal 区域和高端区域。(768M或896M为分界线)
注:会把也映射信息更新到init_mm. pgd,即swapper_pg_dir指向的页表中。最终应该会把init_mm填到init1 线程任务结构的mm_struct中。需要后续验证***********************
*****************************************************************************************************************************************
2.7.4 调用load_up_mmu()重新装载MMU相关的寄存器,开启MMU并跳到start_kernel
再次让CPU进入是实地址模式,去运行load_up_mmu()函数。这样做的目的是让core 0在实模式下调用load_up_mmu()函数来重新装载MMU相关的寄存器,比如SDR1,BAT寄存器等。之所以要重新转载,是因为我们在<11>:bl initial_bats,创建的临时BAT块地址映射,只是启动的第一阶段用到的临时映射,现在这个临时地址映射需要舍弃了,我们需要重新初始化MMU,来建立正式的MMU地址映射。
注:从__start()到start_here()再到调用start_kernel(),主要的工作与当前目标板的硬件结构密切相关,包含对一些底层硬件进行最基本初始化操作等等,从start_kernel()开始的初始化操作与处理器的类型基本无关了。
3 start_kernel
本阶段也是有0号线程init_task中调用的,将完成Linux内核核心数据结构的初始化,最终创建1号线程kernel_init,最后由1号内核线程启动1号用户进程。需要后续确认***
3.1 关中断
3.2 调用tick_init(),初始化系统时钟滴答链
3.3 调用page_address_init(),将高端内存组织在一起。应该是为buddy初始化做准备,需要进一步确认
3.4 setup_arch(),setup_arch()函数是start_kernel()中非常重要函数,主要作用是对内存进行初始化后,调用ppc_md结构的setup_arch()函数对当前目标板系统进行基本的初始化。
3.4.1 unflatten_device_tree(),check_for_initrd();对OF Tree和initrd进行检查
3.4.2 find_legacy_serial_ports(),对串口进行检查和设置
3.4.3 register_early_udbg_console()对调试口进行检查和设置
3.4.4设置指令和数据cache的长度
3.4.5 Linux系统进入Panic后,180秒后重启系统
3.4.6设置0号进程mm_struct结构的代码段,数据段和堆栈段
3.4.7 irqstack_early_init();//初始化中断栈,需要进一步研究
3.4.8 do_init_bootmem();//初始化Boot Memory
3.4.9 ppc_md.setup_arch()对具体的处理器系统进行初始化,不同的处理器系统使用不同的ppc_md.setup_arch()函数,该函数在probe_machine()函数中设置
3.4.10 paging_init(),初始化ZONE_DMA,ZONE_NORMAL和ZONE_HIGHMEM区域的空闲页表相关,需要后续深入理解*************************************
3.5 setup_command_line(command_line)该函数对命令行参数进行处理
注:命令行参数保存到全局数组中,见定义:char cmd_buf[256]; char *cmd_line = cmd_buf;
3.6 setup_per_cpu_areas()该函数初始化每个CPU的专用数据区域
3.7 smp_prepare_boot_cpu():它设置当前BSP有效,设置current_set数组中下标为boot_cpuid的指针指向0号进程init_task的thread_info描述符。
struct thread_info *current_set[NR_CPUS];
注:struct thread_info和struct task_struct间的关系,参见下面的数据结构
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
union thread_union init_thread_union; 即init_task->stack 就是init_thread_union的起始地址。
而thread_union中的前12个字是thread_info和stack共用的,其实是thread_info利用了stack(8192B)的低地址的一部分空间而已。Stack 会从高地址向低地址方向使用。
问题:只有init_task这样用?还是所有的线程都这样用法???need confirmation
3.8 build_all_zonelists()该函数调用__build_all_nozelists函数初始化每个CPU的存储节点,并根据Linux PowerPC提供的数据区域,如ZONE_DMA和ZONE_HIMEM来划分内存区间。这部分的初始化也被称之为存储节点的初始化。
3.9 page_alloc_init()该函数为系统设置一个page_alloc_cpu_notify回调函数,该函数用来实现CPU的关闭与使能。在一个MPP结构的处理器系统或者大型服务器中有大量的CPU,该函数可以临时打开或者关闭某些Core或者CPU,此时Linux系统会调用page_alloc_cpu_notify函数,但是在我们的针对MPC8641HPCN平台,linux-smp没有该功能,page_alloc_init()为空!
3.10 parse_early_param()和parse_args():这两个函数解析UBoot传到给Linux内核的一些参数。
3.11 vfs_caches_init_early();初始化VFS系统使用的dentry和inode结构专用SLAB描述符,Linux使用dentry结构保存有关文件目录的信息,使用inode结构保存有关文件的信息。
3.12 sort_main_extable()该函数对异常调用表进行重新排序,Linux系统将异常调用表放在__ex_table调用表中。
3.13 mm_init():该函数是一个静态的函数,它调用mem_init()函数初始化所有的页表描述符,并初始化Buddy System;然后调用kmem_cache_init()函数初始化Linux系统的SLAB分配器。
3.14 sched_init():初始化主调度器的运行队列rq,在Linux SMP系统中每一个CPU都有自己的运行队列!
3.16 preempt_disable():正如代码注释所说的那样,现在是禁用进程抢占功能的,因为目前的调度器还比较脆弱,只有执行了cpu_idle()函数之后才能进行进程抢占功能!
3.17 idr_init_cache():该函数的初始化idr_layer_cache结构的SLAB分配器,idr ?????
Linux基数树(radix tree),ID radix。IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。
可以认为IDR机制是一种可伸展的数组的机制,并且带bitmask。
3.18 rcu_init():初始化Linux PowerPC的RCU部件,RCU是一种锁机制
3.19 radix_tree_init():使用idr_init_cache()分配的Slab cache初始化radix树。
3.20 init_IRQ (),调用ppc_md.init_IRQ()初始化IRQ,需要跟进理解
3.21 init_timers():初始化linux系统使用的定时器
3.22 hrtimers_init():初始化高精度定时器,与Linux 系统提供的标准定时器相比,该定时器更为准确。
3.23 softirq_init():对Linux系统软件中断进行初始化
3.24 time_init(): 初始化Linux系统的定时器,Linux PowerPC可以使用外部RTC作为系统的定时器,也可以使用TB寄存器!
3.25 local_irq_enable():开中断
3.26 console_init():初始化控制器console,console是Linux系统用于输出一些监控信息的设备,Linux系统一般使用串口或者监视器作为console设备!
3.27 setup_per_cpu_pageset():初始化当前CPU(即BSP)每一个数据区,如ZONE_DMA,ZONE_HIMEM使用per_cpu_pageset缓冲
3.28 pidmap_init():建立pid结构使用的专用Cache描述符表pid_cachep
3.29 fork_init(totalram_pages):创建task_struct结构专用的Slab Cache描述符task_struct_cachep,并确定当前Linux系统所能容纳的最大进程数目max_threads
3.30 proc_caches_init():创建专用的Slab Cache描述符sighand_cachep、signal_cachep、files_cachep、fs_cachep、mm_cachep、vm_area_cachep。
3.31 buffer_init():创建buffer_head结构的专用Slab Cache描述符bh_cachep,linux的文件系统使用buffer_head结构保存来自外部设备的数据。
3.32 vfs_caches_init(totalram_pages):创建专用的Slab cache描述符names_cachep和filp_cachep,然后调用 dcache_init()、inode_init()、files_init(mempages)、mnt_init()、bdev_cache_init()和chrdev_init()函数对Linux文件系统的各个子系统进行初始化。
3.33 signals_init():创建信号专用的Slab cache描述符sigqueue_cachep,并初始化Linux系统的信号机制
3.34 page_writeback_init():初始化文件系统的回调函数,Linux文件系统中会有很多“脏”页,这些页不会立即和存储系统中的数据进行同步,只有在需要进行同步时才调用这些回调函数进行同步。
3.35 proc_root_init():创建根文件系统,其实是根据数据结构proc_fs_type,mount 文件系统,并创建相应的文件夹。没看到根文件系统相关,从注释到函数名都显示是proc文件系统。不知何故?????
3.36 调用rest_init()函数。
4. rest_init()函数。
4.1 调用kernel_thread()创建1号内核线程。
4.2 调用kernel_thread()创建kthreadd内核线程。尚不明作用。
4.3 init_idle_bootup_task():当前0号进程init_task最终会退化成idle进程,所以这里调用init_idle_bootup_task()函数,让init_task进程隶属到idle调度类中。即选择idle的调度相关函数。
4.4 调用schedule()函数切换当前进程,在调用该函数之前,Linux系统中只有两个进程,即0号进程init_task和1号进程kernel_init,其中kernel_init进程也是刚刚被创建的。调用该函数后,1号进程kernel_init将会运行!
4.5 调用cpu_idle(),0号线程进入idle函数的循环,在该循环中会周期性地检查。
5 kernel_init 1号线程初始化
主要包括三方面
第一:引导SMP系统中的其它CPU(即AP(Aplication Processor))
第二:调用do_basic_setup()函数,完成Linux系统其它模块的初始化;
第三:更换核心进程kernel_init为普通进程之后,完成对Linux系统的二次引导,即对Linux系统应用程序的引导!
5.1设置当前1号线程所允许的BSP(即core 0)在cpu_all_mask中的对应bit位
5.2 init_pid_ns.child_reaper = current; 设置1号线程回收orphan 线程。1号进程在Linux系统中相当于一个收容所,专门用于处理那些孤儿进程。
5.3 smp_prepare_cpus(setup_max_cpus)该函数的作用是首先探测我们的目标系统中有多少个CPU,该函数为每个CPU创建一个idle进程。
5.4 smp_init()是我们的linux-smp映像中启动另外一个核的最重要的代码,该函数主要是引导SMP系统中的AP,该函数会依次调用:smp_init()-> cpu_up()-> _cpu_up()-> __cpu_up()-> smp_ops-> kick_cpu(kick_cpu是一个函数指针,指向smp_86xx_kick_cpu,故会执行smp_86xx_kick_cpu()函数)smp_86xx_kick_cpu()-> smp_86xx_release_core()-> __secondary_start_mpc86xx()-> __secondary_start()来启动core 1,并调用set_cpu_online()函数将次CPU加入到cpu_online_map变量中,用以向主CPU通知该次CPU已经被激活。
5.5 sched_init_smp()该函数的作用,有待遇进一步的分析!
5.6 do_basic_setup():到目前为止,内核已经初始化了,memory管理和process scheduler 已经开始运行。但尚未注册设备。在本函数中将初始化workqueue,初始化device drivers,初始化中断处理,最后调用do_initcalls()进行静态安装所有模块,其中包括驱动人员最关心的用device_initcall声明的设备模块的安装。
5.7 调用init_post()创建用户模式1号进程。
第三步:Linux的应用程序的初始化
1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,将核心进程kernel_init转换成用户进程init),此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。户进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。然后init进程会执行/bin/sh产生shell界面提供给用户来与Linux系统进行交互。
调用init_post()创建用户模式1号进程。
在init_post()中最终调用下面的任何一个入口(按顺序,第一个执行成功后将不返回)
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
下面是学习linux内核启动过程的记录
平台是:powerpc mpc8548 + linux2.6.23 内核
通用寄存器的作用
r0 :在函数开始时使用
r1 :存放堆栈指针,相当于ia32架构中的esp寄存器
r2 :存放当前进程的描述符的地址
r3 :存放第一个参数和返回地址
r4-r10 :存放函数的参数
r11 :用在指针的调用和当前一些语言的环境指针
r12 :用于存放异常处理
r13 :保留做为系统线程ID
r14-r31 :作为本地变量,具有非易失性
Linux启动过程描述
第一步:使用Boot Loader(一般是U-boot)加载Linux内核映像到内存,并负责目标系统的基本初始化过程,并搜集这个系统的基本信息,比如内存大小、处理器主频、外设的使用情况等一系列信息。然后把这些信息传递给linux内核。然后Boot loader把linux内核复制到从0x0000 0000 开始的物理内存处(虚拟地址一般为0xc000 0000处)开始执行。
备注:这一部分内容,本文不做重点介绍。请参考《uboot启动过程学习总结.doc》
*
*******************************************************************************
记录2:linux kernel 链接文件、入口函数和相关宏定义等
Bootstraploader过程:从文件\arch\powerpc\boot\zImage.lds中可以看出,bootstraploader的入口为_zimage_start。在代码arch\powerpc\boot\crt0.S中
D:\virtual_machine\share_folder\linux-2.6.23\arch\powerpc\boot\zImage.lds中定义的入口地址为4MB,见下面
SECTIONS
{
. = (4*1024*1024);
_start = .;
.text :
进入linux内核:从vmlinux.lds看到,内核入口为_stext,通过段.text.head 将代码定位到0xc0000000处。
在代码arch/powerpc/kernel/head_32.S中_stext之后紧接着是_start,他们之间没有代码,他们表示相同的地址。
在vmlinux.lds中将.text.head规划为.text的第一个字段(保证了地址定位到0xc0000000)。
*******************************************************************************
第二步:Linux系统的初始化
1、 bootstraploader 过程
注意:需要知道从uboot跳到此处时,r3寄存器的内容,以及其他register的内容
如果运行地址和链接地址不同,则修正got表中各个函数的指针
清零BSS段
调用platform_init(),保存bd到__res,初始化ppc_md(ppc module)中的各个函数。
调用arch\powerpc\boot\main.c中的start()
在start()中:
1.1将命令行拷贝到cmdline中
1.2调用open函数打开串口
1.3解压缩kernel代码
1.4解压缩ramdisk image
1.5最终初始化设备树
1.6跳到内核代码中执行
有两个调用语句,应该是运行了语句:kentry(ft_addr, 0, NULL); need confirm
2、 进入linux内核
入口:arch/powerpc/kernel/head_32.S中的_start。
2.1 early_init() ,arch/powerpc/kernel/setup_32.c中
计算运行地址和链接地址的差值。根据cpu型号调用do_feature_fixups函数来对__ftr_fixup段进行修复处理。
例如若HIDO寄存器的HIGH_BAT_EN位置位,另外的4组寄存器 IBATs (4–7) 和 4组 DBATs (4-7) 将会被激活,__ftr_fixup段中对这8组寄存器进行初始化的代码就会生效;否则__ftr_fixup中的这段代码就会被nop指令所代替!
early_init()函数调用identify_cpu()函数通过cpu中的pvr寄存器存放的CPU核的版本号在全局数组cpu_specs中寻找到当前cpu的详细信息,identify_cpu()函数在找到之后,会调用setup_cpu_spec()函数把上述cpu的信息所在的链接地址赋值给cur_cpu_spec变量
2.2 mmu_off() 关闭mmu
2.3 flush_tlbs() 从TLB中移除页表
2.4 call_setup_cpu():call_setup_cpu()位于misc_32.S文件中
2.5 relocate_kernel():把内核代码拷贝到链接地址指向的位置
2.6 turn_on_mmu():映射了256MB内存,就可以避免调用reloc_offset()函数来显式得把虚拟地址映射到物理地址!
2.7跳到start_here()函数中运行,可以认为是真正内核开始运行。。。
2.7.1加载0号线程上下文,全局变量init_task
注:0号线程优先级为120,从#define INIT_TASK(tsk)中可以看出。
init_task是进程0使用的进程描述符,也是Linux系统中第一个进程描述符,该进程的描述符在arch/powerpc/kernel/init_task.c中定义,代码片段如下:
struct task_struct init_task = INIT_TASK(init_task);
init_task描述符使用宏INIT_TASK对init_task的进程描述符进行初始化,宏INIT_TASK在include/linux/init_task.h文件中
init_task是Linux内核中的第一个线程,它贯穿于整个Linux系统的初始化过程中,该进程也是Linux系统中唯一一个没有用kernel_thread() 函数创建的进程!在init_task进程执行后期,它会调用kernel_thread()函数创建第一个核心进程kernel_init,同时init_task进程继续对Linux系统初始化。在完成初始化后,init_task会退化为cpu_idle进程,当Core 0的就绪队列中没有其它进程时,该进程将会获得CPU运行。新创建的1号进程kernel_init将会逐个启动次CPU,并最终创建用户进程!
备注:core0上的idle进程由init_task进程退化而来,而AP的idle进程则是BSP在后面调用fork()函数逐个创建的,我们会在后面详细讨论。
init_task进程使用init_thread_union数据结构描述的内存区域作为该进程的堆栈空间,并且和自身的thread_info参数公用这一内存空间空间,其数据结构的定义如下(linux- 2.6.38/include/linux/sched.h)
2.7.2 调用machine_init()分析OF树的结构,获得当前处理器的内存使用情况, 创建LMB结构,同时获得当前CPU在OF树中的硬件信息;保存命令行等,从cmd_line拷贝uboot引导kernel时使用的命令行参数到boot_command_line,并并使用parse_early_param()函数分析这些命令行参数。
2.7.3 调用MMU_init(),为LinuxPowerPC建立MMU地址映射,区分memory 的normal 区域和高端区域。(768M或896M为分界线)
注:会把也映射信息更新到init_mm. pgd,即swapper_pg_dir指向的页表中。最终应该会把init_mm填到init1 线程任务结构的mm_struct中。需要后续验证***********************
*****************************************************************************************************************************************
2.7.4 调用load_up_mmu()重新装载MMU相关的寄存器,开启MMU并跳到start_kernel
再次让CPU进入是实地址模式,去运行load_up_mmu()函数。这样做的目的是让core 0在实模式下调用load_up_mmu()函数来重新装载MMU相关的寄存器,比如SDR1,BAT寄存器等。之所以要重新转载,是因为我们在<11>:bl initial_bats,创建的临时BAT块地址映射,只是启动的第一阶段用到的临时映射,现在这个临时地址映射需要舍弃了,我们需要重新初始化MMU,来建立正式的MMU地址映射。
注:从__start()到start_here()再到调用start_kernel(),主要的工作与当前目标板的硬件结构密切相关,包含对一些底层硬件进行最基本初始化操作等等,从start_kernel()开始的初始化操作与处理器的类型基本无关了。
3 start_kernel
本阶段也是有0号线程init_task中调用的,将完成Linux内核核心数据结构的初始化,最终创建1号线程kernel_init,最后由1号内核线程启动1号用户进程。需要后续确认***
3.1 关中断
3.2 调用tick_init(),初始化系统时钟滴答链
3.3 调用page_address_init(),将高端内存组织在一起。应该是为buddy初始化做准备,需要进一步确认
3.4 setup_arch(),setup_arch()函数是start_kernel()中非常重要函数,主要作用是对内存进行初始化后,调用ppc_md结构的setup_arch()函数对当前目标板系统进行基本的初始化。
3.4.1 unflatten_device_tree(),check_for_initrd();对OF Tree和initrd进行检查
3.4.2 find_legacy_serial_ports(),对串口进行检查和设置
3.4.3 register_early_udbg_console()对调试口进行检查和设置
3.4.4设置指令和数据cache的长度
3.4.5 Linux系统进入Panic后,180秒后重启系统
3.4.6设置0号进程mm_struct结构的代码段,数据段和堆栈段
3.4.7 irqstack_early_init();//初始化中断栈,需要进一步研究
3.4.8 do_init_bootmem();//初始化Boot Memory
3.4.9 ppc_md.setup_arch()对具体的处理器系统进行初始化,不同的处理器系统使用不同的ppc_md.setup_arch()函数,该函数在probe_machine()函数中设置
3.4.10 paging_init(),初始化ZONE_DMA,ZONE_NORMAL和ZONE_HIGHMEM区域的空闲页表相关,需要后续深入理解*************************************
3.5 setup_command_line(command_line)该函数对命令行参数进行处理
注:命令行参数保存到全局数组中,见定义:char cmd_buf[256]; char *cmd_line = cmd_buf;
3.6 setup_per_cpu_areas()该函数初始化每个CPU的专用数据区域
3.7 smp_prepare_boot_cpu():它设置当前BSP有效,设置current_set数组中下标为boot_cpuid的指针指向0号进程init_task的thread_info描述符。
struct thread_info *current_set[NR_CPUS];
注:struct thread_info和struct task_struct间的关系,参见下面的数据结构
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
union thread_union init_thread_union; 即init_task->stack 就是init_thread_union的起始地址。
而thread_union中的前12个字是thread_info和stack共用的,其实是thread_info利用了stack(8192B)的低地址的一部分空间而已。Stack 会从高地址向低地址方向使用。
问题:只有init_task这样用?还是所有的线程都这样用法???need confirmation
3.8 build_all_zonelists()该函数调用__build_all_nozelists函数初始化每个CPU的存储节点,并根据Linux PowerPC提供的数据区域,如ZONE_DMA和ZONE_HIMEM来划分内存区间。这部分的初始化也被称之为存储节点的初始化。
3.9 page_alloc_init()该函数为系统设置一个page_alloc_cpu_notify回调函数,该函数用来实现CPU的关闭与使能。在一个MPP结构的处理器系统或者大型服务器中有大量的CPU,该函数可以临时打开或者关闭某些Core或者CPU,此时Linux系统会调用page_alloc_cpu_notify函数,但是在我们的针对MPC8641HPCN平台,linux-smp没有该功能,page_alloc_init()为空!
3.10 parse_early_param()和parse_args():这两个函数解析UBoot传到给Linux内核的一些参数。
3.11 vfs_caches_init_early();初始化VFS系统使用的dentry和inode结构专用SLAB描述符,Linux使用dentry结构保存有关文件目录的信息,使用inode结构保存有关文件的信息。
3.12 sort_main_extable()该函数对异常调用表进行重新排序,Linux系统将异常调用表放在__ex_table调用表中。
3.13 mm_init():该函数是一个静态的函数,它调用mem_init()函数初始化所有的页表描述符,并初始化Buddy System;然后调用kmem_cache_init()函数初始化Linux系统的SLAB分配器。
3.14 sched_init():初始化主调度器的运行队列rq,在Linux SMP系统中每一个CPU都有自己的运行队列!
3.16 preempt_disable():正如代码注释所说的那样,现在是禁用进程抢占功能的,因为目前的调度器还比较脆弱,只有执行了cpu_idle()函数之后才能进行进程抢占功能!
3.17 idr_init_cache():该函数的初始化idr_layer_cache结构的SLAB分配器,idr ?????
Linux基数树(radix tree),ID radix。IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。
可以认为IDR机制是一种可伸展的数组的机制,并且带bitmask。
3.18 rcu_init():初始化Linux PowerPC的RCU部件,RCU是一种锁机制
3.19 radix_tree_init():使用idr_init_cache()分配的Slab cache初始化radix树。
3.20 init_IRQ (),调用ppc_md.init_IRQ()初始化IRQ,需要跟进理解
3.21 init_timers():初始化linux系统使用的定时器
3.22 hrtimers_init():初始化高精度定时器,与Linux 系统提供的标准定时器相比,该定时器更为准确。
3.23 softirq_init():对Linux系统软件中断进行初始化
3.24 time_init(): 初始化Linux系统的定时器,Linux PowerPC可以使用外部RTC作为系统的定时器,也可以使用TB寄存器!
3.25 local_irq_enable():开中断
3.26 console_init():初始化控制器console,console是Linux系统用于输出一些监控信息的设备,Linux系统一般使用串口或者监视器作为console设备!
3.27 setup_per_cpu_pageset():初始化当前CPU(即BSP)每一个数据区,如ZONE_DMA,ZONE_HIMEM使用per_cpu_pageset缓冲
3.28 pidmap_init():建立pid结构使用的专用Cache描述符表pid_cachep
3.29 fork_init(totalram_pages):创建task_struct结构专用的Slab Cache描述符task_struct_cachep,并确定当前Linux系统所能容纳的最大进程数目max_threads
3.30 proc_caches_init():创建专用的Slab Cache描述符sighand_cachep、signal_cachep、files_cachep、fs_cachep、mm_cachep、vm_area_cachep。
3.31 buffer_init():创建buffer_head结构的专用Slab Cache描述符bh_cachep,linux的文件系统使用buffer_head结构保存来自外部设备的数据。
3.32 vfs_caches_init(totalram_pages):创建专用的Slab cache描述符names_cachep和filp_cachep,然后调用 dcache_init()、inode_init()、files_init(mempages)、mnt_init()、bdev_cache_init()和chrdev_init()函数对Linux文件系统的各个子系统进行初始化。
3.33 signals_init():创建信号专用的Slab cache描述符sigqueue_cachep,并初始化Linux系统的信号机制
3.34 page_writeback_init():初始化文件系统的回调函数,Linux文件系统中会有很多“脏”页,这些页不会立即和存储系统中的数据进行同步,只有在需要进行同步时才调用这些回调函数进行同步。
3.35 proc_root_init():创建根文件系统,其实是根据数据结构proc_fs_type,mount 文件系统,并创建相应的文件夹。没看到根文件系统相关,从注释到函数名都显示是proc文件系统。不知何故?????
3.36 调用rest_init()函数。
4. rest_init()函数。
4.1 调用kernel_thread()创建1号内核线程。
4.2 调用kernel_thread()创建kthreadd内核线程。尚不明作用。
4.3 init_idle_bootup_task():当前0号进程init_task最终会退化成idle进程,所以这里调用init_idle_bootup_task()函数,让init_task进程隶属到idle调度类中。即选择idle的调度相关函数。
4.4 调用schedule()函数切换当前进程,在调用该函数之前,Linux系统中只有两个进程,即0号进程init_task和1号进程kernel_init,其中kernel_init进程也是刚刚被创建的。调用该函数后,1号进程kernel_init将会运行!
4.5 调用cpu_idle(),0号线程进入idle函数的循环,在该循环中会周期性地检查。
5 kernel_init 1号线程初始化
主要包括三方面
第一:引导SMP系统中的其它CPU(即AP(Aplication Processor))
第二:调用do_basic_setup()函数,完成Linux系统其它模块的初始化;
第三:更换核心进程kernel_init为普通进程之后,完成对Linux系统的二次引导,即对Linux系统应用程序的引导!
5.1设置当前1号线程所允许的BSP(即core 0)在cpu_all_mask中的对应bit位
5.2 init_pid_ns.child_reaper = current; 设置1号线程回收orphan 线程。1号进程在Linux系统中相当于一个收容所,专门用于处理那些孤儿进程。
5.3 smp_prepare_cpus(setup_max_cpus)该函数的作用是首先探测我们的目标系统中有多少个CPU,该函数为每个CPU创建一个idle进程。
5.4 smp_init()是我们的linux-smp映像中启动另外一个核的最重要的代码,该函数主要是引导SMP系统中的AP,该函数会依次调用:smp_init()-> cpu_up()-> _cpu_up()-> __cpu_up()-> smp_ops-> kick_cpu(kick_cpu是一个函数指针,指向smp_86xx_kick_cpu,故会执行smp_86xx_kick_cpu()函数)smp_86xx_kick_cpu()-> smp_86xx_release_core()-> __secondary_start_mpc86xx()-> __secondary_start()来启动core 1,并调用set_cpu_online()函数将次CPU加入到cpu_online_map变量中,用以向主CPU通知该次CPU已经被激活。
5.5 sched_init_smp()该函数的作用,有待遇进一步的分析!
5.6 do_basic_setup():到目前为止,内核已经初始化了,memory管理和process scheduler 已经开始运行。但尚未注册设备。在本函数中将初始化workqueue,初始化device drivers,初始化中断处理,最后调用do_initcalls()进行静态安装所有模块,其中包括驱动人员最关心的用device_initcall声明的设备模块的安装。
5.7 调用init_post()创建用户模式1号进程。
第三步:Linux的应用程序的初始化
1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,将核心进程kernel_init转换成用户进程init),此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。户进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。然后init进程会执行/bin/sh产生shell界面提供给用户来与Linux系统进行交互。
调用init_post()创建用户模式1号进程。
在init_post()中最终调用下面的任何一个入口(按顺序,第一个执行成功后将不返回)
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
发表评论
-
奇怪的linker问题
2014-09-23 14:56 1838下面这个linker的问题找了3个小时: gcc -o ex ... -
coredump with calloc()
2014-04-29 14:23 1749Program terminated with signal ... -
pthread_create: Resource temporarily unavailable (errno = 11)
2014-04-29 14:21 2067原来是pthread_create后没有调pthread_jo ... -
读锁和写锁的区别
2014-03-03 10:20 1125共享锁(S锁)又称读锁,若事务T对数据对象A加上S锁,则事务T ... -
nux下共享库(SO)有关的几个环境变量 zz
2014-01-06 10:32 803Linux支持共享库已经有 ... -
gprof(转载)
2013-10-23 11:07 889CPU profiling tool: O'profile, ... -
转载: PAM验证
2013-10-10 14:49 1134转载 一.PAM简介 Linux-PAM(linux可 ... -
scp的prompt为啥隐藏不掉
2013-08-07 15:25 954把scp的标准输出和标准错误都重定向到一个文件,为什么shel ... -
Linux 之 shell 比较运算符 (转载)
2013-07-09 11:13 765转自:http://blog.csdn.net/s ... -
linux内核版本为何有时会自动添加“+”号
2013-04-16 16:16 1220转载自:http://blog.csdn.ne ... -
在linux pc上挂载jffs2文件系统(zz)
2012-12-24 17:54 1601http://www.sourceware.org/jffs2 ... -
全面解析PowerPC架构下的扁平设备树FDT(ZT)
2012-12-19 14:58 3710Sailor_forever sailing_9806#1 ... -
ldconfig的作用
2012-11-15 18:17 1033转载:ldconfig是一个动态链接库管理命令 为了让动态链 ...
相关推荐
Linux 内核下载和编译指南是 Linux 初学者的一份必备指南,通过这个指南,我们可以学习如何下载和编译 Linux 内核,并了解 Linux 内核的编译过程。以后的学习之路还很长,我们需要继续学习和实践,掌握更多的 Linux...
编译Linux内核是一项基础且重要的任务,尤其对于开发者和系统管理员来说,它允许定制内核以满足特定需求,优化性能,或者支持新的硬件设备。 在本文中,我们将探讨如何作为菜鸟编译Linux内核,主要步骤如下: 1. *...
内核初始化系统(init)是内核启动后第一个运行的用户空间程序(PID=1),在用户态进程管理中起到核心作用。 文档还提到了Gentoo Linux的传统初始化系统OpenRC以及其他脚本基础的系统和服务管理器,如runit。通过...
1. **深入了解Linux内核:** 构建LFS的过程中,用户可以深入学习Linux内核及各个组件的工作原理,这对于提高Linux系统管理技能非常有益。 2. **高度个性化:** 由于是从源代码构建起,用户可以自由选择所需的组件和...
Linux字符设备驱动是操作系统内核与硬件交互的重要组成部分,它允许应用程序通过标准的文件操作接口与特定的硬件设备进行通信。在Linux系统中,字符设备是按照字符流进行数据传输的设备,比如键盘和打印机。虽然通常...
2. **安装与启动**:学习如何下载和安装Linux发行版,理解MBR和UEFI引导方式,以及GRUB多引导器的配置。 3. **桌面环境与终端**:熟悉常见的桌面环境(如Gnome、KDE、XFCE),并熟练使用命令行终端,掌握基础的...
《Linux系统移植项目报告》是一份详尽的教程,涵盖了从基础到高级的...在学习过程中,务必遵循报告的步骤,并理解每个环节背后的原理,以确保移植的成功。同时,尊重原创,不得擅自转载,以免引起不必要的法律纠纷。
摘“为了配合学习《Linux内核完全注释》一书,经过一段时间的摸索,我终于把linux 0.1x内核运行的文件系统制作出来了。为了方便大家实验,我制作了一个在bochs系统下运行的软件包。其中包括启动映像文件bootimage- ...
重启系统,选择新安装的Xen内核启动。一旦进入Xen系统,检查所有服务是否正常启动,特别是网络接口,确保它们都已启动并运行正常。 4. **使用Yum安装Xen** 对于不想从源代码编译的用户,可以通过`yum install`...
对于初学者来说,通过完成LFS项目可以获得对Linux内核及其组件更深层次的理解。 #### 四、核心内容概述 - **构建前的准备工作**:这部分内容涵盖了为构建LFS系统所做的各项准备,包括准备一个新的硬盘分区、获取所...
上层功能模块实现monitor功能和启动Linux内核的基本操作;底层驱动模块处理存储介质读写和主机通信;架构相关代码则专注于初始化硬件平台特有的组件。 在MIPS架构下,u-boot的移植工作需特别关注架构相关代码的适配...
- 配置Linux内核参数:对于Oracle数据库来说,调整内核参数是至关重要的,比如文件描述符限制、内存锁、调度策略等,以确保数据库性能和稳定性。 - 配置网络:对于RAC环境,网络配置是核心之一。需要配置静态IP地址...
总结来说,解决CentOS 7安装腾达U12无线网卡驱动问题的方法包括尝试升级内核和安装第三方驱动,但这可能面临失败的风险。相比之下,切换到支持性更好的操作系统如Ubuntu 18.04.3可能是更可靠的选择。虽然这个过程中...
在GPGPU编程中,计算过程可以被看作是一种特殊的渲染操作。 - **准备运算的内核**:加载和编译着色器程序。 - **设定用于输入的数组/纹理**:指定输入数据的位置。 - **设定用于输出的数组/纹理**:指定输出数据的...