init/main.c start_kernel() 代码分析

本文转自 http://www.cnblogs.com/darren-715/p/3920033.html

head-common.S  

---具体做了哪些动作  

---跳转到init/main.c   
---b start_kernel  
//关于start_kernel的强文深入理解linux内核,第八章
   
main.c  
asmlinkage void __init start_kernel(void)  
{  
char * command_line;  

extern struct kernel_param __start___param[], __stop___param[];  


//来设置smp process id,当然目前看到的代码里面这里是空的   
smp_setup_processor_id();  
/* 
* Need to run as early as possible, to initialize the 
* lockdep hash: 
*/  
//lockdep是linux内核的一个调试模块,用来检查内核互斥机制尤其是自旋锁潜在的死锁问题。   
//自旋锁由于是查询方式等待,不释放处理器,比一般的互斥机制更容易死锁,   
//故引入lockdep检查以下几种情况可能的死锁(lockdep将有专门的文章详细介绍,在此只是简单列举):   
//   
//·同一个进程递归地加锁同一把锁;   
//   
//·一把锁既在中断(或中断下半部)使能的情况下执行过加锁操作,   
// 又在中断(或中断下半部)里执行过加锁操作。这样该锁有可能在锁定时由于中断发生又试图在同一处理器上加锁;   
//   
//·加锁后导致依赖图产生成闭环,这是典型的死锁现象。   
lockdep_init();  
debug_objects_early_init();  
/* 
* Set up the the initial canary ASAP: 
*/  
//初始化stack_canary栈3   
//stack_canary的是带防止栈溢出攻击保护的堆栈。   
//  当user space的程序通过int 0x80进入内核空间的时候,CPU自动完成一次堆栈切换,    
//从user space的stack切换到kernel space的stack。   
//   在这个进程exit之前所发生的所有系统调用所使用的kernel stack都是同一个。   
//kernel stack的大小一般为4096/8192,   
//内核堆栈示意图帮助大家理解:   
//   
//   内存低址                                                              内存高址   
//                      |                 |<-----------------------------esp|      
//   +-----------------------------------4096-------------------------------+   
//   |        72        |     4           |       x < 4016    |     4       |   
//   +------------------+-----------------+---------------------------------+   
//   |thread_info  |    | STACK_END_MAGIC |   var/call chain  |stack_canary |   
//   +------------------+-----------------+---------------------------------+   
//   |     28      | 44 |                 |                                 |   
//                 V    |                                                   |   
//             restart_block                                                V   
//   
//esp+0x0                                                                   +0x40   
//    +---------------------------------------------------------------------------+   
//    |ebx|ecx|edx|esi|edi|ebp|eax|ds|es|fs|gs|orig_eax|eip|cs|eflags|oldesp|oldss|   
//    +---------------------------------------------------------------------------+   
//    |             kernel完成                          |         cpu自动完成       |   
//http://hi.baidu.com/wzt85/blog/item/112a37132f6116c2f6039e44.html   
boot_init_stack_canary();   
//  cgroup: 它的全称为control group.即一组进程的行为控制.   
//  比如,我们限制进程/bin/sh的CPU使用为20%.我们就可以建一个cpu占用为20%的cgroup.   
//  然后将/bin/sh进程添加到这个cgroup中.当然,一个cgroup可以有多个进程.   
//http://blogold.chinaunix.net/u1/51562/showart_1736813.html   
cgroup_init_early();  
//更新kernel中的所有的立即数值,但是包括哪些需要再看?   
core_imv_update();  
//关闭当前CUP中断   
local_irq_disable();  
//修改标记early_boot_irqs_enabled;   
//通过一个静态全局变量 early_boot_irqs_enabled来帮助我们调试代码,   
//通过这个标记可以帮助我们知道是否在”early bootup code”,也可以通过这个标志警告是有无效的终端打开   
early_boot_irqs_off();  
//每一个中断都有一个IRQ描述符(struct irq_desc)来进行描述。   
//这个函数的主要作用是设置所有的 IRQ描述符(struct irq_desc)的锁是统一的锁,   
//还是每一个IRQ描述符(struct irq_desc)都有一个小锁。   
early_init_irq_lock_class();  
/* 
 * Interrupts are still disabled. Do necessary setups, then 
 * enable them 
 */  
// 大内核锁(BKL--Big Kernel Lock)   
//大内核锁本质上也是自旋锁,但是它又不同于自旋锁,自旋锁是不可以递归获得锁的,因为那样会导致死锁。   
//但大内核锁可以递归获得锁。大内核锁用于保护整个内核,而自旋锁用于保护非常特定的某一共享资源。   
//进程保持大内核锁时可以发生调度,具体实现是:   
//在执行schedule时,schedule将检查进程是否拥有大内核锁,如果有,它将被释放,以致于其它的进程能够获得该锁,   
//而当轮到该进程运行时,再让它重新获得大内核锁。注意在保持自旋锁期间是不运行发生调度的。   
//需要特别指出,整个内核只有一个大内核锁,其实不难理解,内核只有一个,而大内核锁是保护整个内核的,当然有且只有一个就足够了。   
//还需要特别指出的是,大内核锁是历史遗留,内核中用的非常少,一般保持该锁的时间较长,因此不提倡使用它。   
//从2.6.11内核起,大内核锁可以通过配置内核使其变得可抢占(自旋锁是不可抢占的),这时它实质上是一个互斥锁,使用信号量实现。   
//大内核锁的API包括:   
//   
//void lock_kernel(void);   
//   
//该函数用于得到大内核锁。它可以递归调用而不会导致死锁。   
//   
//void unlock_kernel(void);   
//   
//该函数用于释放大内核锁。当然必须与lock_kernel配对使用,调用了多少次lock_kernel,就需要调用多少次unlock_kernel。   
//大内核锁的API使用非常简单,按照以下方式使用就可以了:   
//lock_kernel(); //对被保护的共享资源的访问 … unlock_kernel();   
//http://blog.csdn.net/universus/archive/2010/05/25/5623971.aspx   
lock_kernel();  
//初始化time ticket,时钟   
tick_init();  
//函数 tick_init() 很简单,调用 clockevents_register_notifier 函数向 clockevents_chain 通知链注册元素:   
// tick_notifier。这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,   
//应该执行的操作,该回调函数为 tick_notify    
//http://blogold.chinaunix.net/u3/97642/showart_2050200.html   
boot_cpu_init();  
//初始化页地址,当然对于arm这里是个空函数   
//http://book.chinaunix.net/special/ebook/PrenticeHall/PrenticeHallPTRTheLinuxKernelPrimer/0131181637/ch08lev1sec5.html   
page_address_init();  
printk(KERN_NOTICE "%s", linux_banner);  
//系结构相关的内核初始化过程   
//http://www.cublog.cn/u3/94690/showart_2238008.html   
setup_arch(&command_line);  
//初始化内存管理   
mm_init_owner(&init_mm, &init_task);  
//处理启动命令,这里就是设置的cmd_line   
setup_command_line(command_line);  
//这个在定义了SMP的时候有作用,现在这里为空函数;对于smp的使用,后面在看。。。   
setup_nr_cpu_ids();  
//如果没有定义CONFIG_SMP宏,则这个函数为空函数。   
//如果定义了CONFIG_SMP宏,则这个setup_per_cpu_areas()函数给每个CPU分配内存,   
//并拷贝.data.percpu段的数据。为系统中的每个CPU的per_cpu变量申请空间。   
setup_per_cpu_areas();  
//定义在include/asm-x86/smp.h。   
//如果是SMP环境,则设置boot CPU的一些数据。在引导过程中使用的CPU称为boot CPU   
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */  
//设置node 和 zone 数据结构   
//内存管理的讲解:http://blog.chinaunix.net/space.php?uid=361890&do=blog&cuid=2146541   
build_all_zonelists(NULL);  
//初始化page allocation相关结构   
page_alloc_init();  
printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line);  
//解析内核参数   
//对内核参数的解析:http://hi.baidu.com/yuhuntero/blog/item/654a7411e45ce519b8127ba9.html   
parse_early_param();  
parse_args("Booting kernel", static_command_line, __start___param,  
  __stop___param - __start___param,  
  &unknown_bootoption);  
/* 
* These use large bootmem allocations and must precede 
* kmem_cache_init() 
*/  
//初始化hash表,以便于从进程的PID获得对应的进程描述指针,按照实际的物理内存初始化pid hash表   
//这里涉及到进程管理http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx   
pidhash_init();  
//初始化VFS的两个重要数据结构dcache和inode的缓存。   
//http://blog.csdn.net/yunsongice/archive/2011/02/01/6171324.aspx   
vfs_caches_init_early();  
//把编译期间,kbuild设置的异常表,也就是__start___ex_table和__stop___ex_table之中的所有元素进行排序   
sort_main_extable();  
//初始化中断向量表   
//http://blog.csdn.net/yunsongice/archive/2011/02/01/6171325.aspx   
trap_init();  
//memory map初始化   
//http://blog.csdn.net/huyugv_830913/archive/2010/09/15/5886970.aspx   
mm_init();  
/* 
* 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. 
*/  
//核心进程调度器初始化,调度器的初始化的优先级要高于任何中断的建立,   
//并且初始化进程0,即idle进程,但是并没有设置idle进程的NEED_RESCHED标志,   
//所以还会继续完成内核初始化剩下的事情。   
//这里仅仅为进程调度程序的执行做准备。   
//它所做的具体工作是调用init_bh函数(kernel/softirq.c)把timer,tqueue,immediate三个人物队列加入下半部分的数组   
sched_init();  
/* 
* Disable preemption - early bootup scheduling is extremely 
* fragile until we cpu_idle() for the first time. 
*/  
//抢占计数器加1    
preempt_disable();  
//检查中断是否打开   
if (!irqs_disabled()) {  
printk(KERN_WARNING "start_kernel(): bug: interrupts were "  
"enabled *very* early, fixing it/n");  
local_irq_disable();  
}  
//Read-Copy-Update的初始化   
//RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,   
//从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,   
//在修改数据的时候,首先需要读取数据,然后生成一个副本,对副本进行修改,   
//修改完成之后再将老数据update成新的数据,此所谓RCU。   
//http://blog.ednchina.com/tiloog/193361/message.aspx   
//http://blogold.chinaunix.net/u1/51562/showart_1341707.html   
rcu_init();  
//定义在lib/radix-tree.c。   
//Linux使用radix树来管理位于文件系统缓冲区中的磁盘块,   
//radix树是trie树的一种   
//http://blog.csdn.net/walkland/archive/2009/03/19/4006121.aspx   
radix_tree_init();  
/* init some links before init_ISA_irqs() */  
//early_irq_init 则对数组中每个成员结构进行初始化,    
//例如, 初始每个中断源的中断号.其他的函数基本为空.    
early_irq_init();  
//初始化IRQ中断和终端描述符。   
//初始化系统中支持的最大可能的中断描述结构struct irqdesc变量数组irq_desc[NR_IRQS],   
//把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断描述结构变量bad_irq_desc,   
//并初始化该中断的链表表头成员结构变量pend   
init_IRQ();  
//prio-tree是一棵查找树,管理的是什么?   
//http://blog.csdn.net/dog250/archive/2010/06/28/5700317.aspx   
prio_tree_init();  
//初始化定时器Timer相关的数据结构   
//http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html   
init_timers();  
//对高精度时钟进行初始化   
hrtimers_init();  
//软中断初始化   
//http://blogold.chinaunix.net/u1/51562/showart_494363.html   
softirq_init();  
//初始化时钟源   
timekeeping_init();  
//初始化系统时间,   
//检查系统定时器描述结构struct sys_timer全局变量system_timer是否为空,   
//如果为空将其指向dummy_gettimeoffset()函数。   
//http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html   
time_init();  
//profile只是内核的一个调试性能的工具,   
//这个可以通过menuconfig中的Instrumentation Support->profile打开。   
//http://www.linuxdiyf.com/bbs//thread-71446-1-1.html   
profile_init();  
if (!irqs_disabled())  
printk(KERN_CRIT "start_kernel(): bug: interrupts were "  
"enabled early/n");  
//与开始的early_boot_irqs_off相对应   
early_boot_irqs_on();  
//与local_irq_disbale相对应,开中断   
local_irq_enable();  
/* Interrupts are enabled now so all GFP allocations are safe. */  
gfp_allowed_mask = __GFP_BITS_MASK;  
//memory cache的初始化   
//http://my.chinaunix.net/space.php?uid=7588746&do=blog&id=153184   
kmem_cache_init_late();  
/* 
* 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. 
*/  
//初始化控制台以显示printk的内容,在此之前调用的printk,只是把数据存到缓冲区里,   
//只有在这个函数调用后,才会在控制台打印出内容   
//该函数执行后可调用printk()函数将log_buf中符合打印级别要求的系统信息打印到控制台上。   
console_init();  
if (panic_later)  
panic(panic_later, panic_param);  
//如果定义了CONFIG_LOCKDEP宏,那么就打印锁依赖信息,否则什么也不做   
lockdep_info();  
/* 
* Need to run this when irqs are enabled, because it wants 
* to self-test [hard/soft]-irqs on/off lock inversion bugs 
* too: 
*/  
//如果定义CONFIG_DEBUG_LOCKING_API_SELFTESTS宏   
//则locking_selftest()是一个空函数,否则执行锁自测   
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   
//页面初始化,可以参考上面的cgroup机制   
page_cgroup_init();  
//页面分配debug启用   
enable_debug_pagealloc();  
//此处函数为空   
kmemtrace_init();  
//memory lead侦测初始化,如何侦测???   
kmemleak_init();  
//   
//Called after the kmem_caches are functional to setup a dedicated   
//cache pool, which has the SLAB_DEBUG_OBJECTS flag set. This flag   
//prevents that the debug code is called on kmem_cache_free() for the   
//debug tracker objects to avoid recursive calls.   
//在kmem_caches之后表示建立一个高速缓冲池,建立起SLAB_DEBUG_OBJECTS标志。???   
debug_objects_mem_init();  
//idr在linux内核中指的就是整数ID管理机制,   
//从本质上来说,这就是一种将整数ID号和特定指针关联在一起的机制   
//idr机制适用在那些需要把某个整数和特定指针关联在一起的地方。   
//http://blogold.chinaunix.net/u3/93255/showart_2524027.html   
idr_init_cache();  
//是否是对SMP的支持,单核是否需要??这个要分析   
setup_per_cpu_pageset();  
//NUMA (Non Uniform Memory Access) policy    
//具体是什么不懂   
numa_policy_init();  
if (late_time_init)  
late_time_init();  
//初始化调度时钟   
sched_clock_init();  
//calibrate_delay()函数可以计算出cpu在一秒钟内执行了多少次一个极短的循环,   
//计算出来的值经过处理后得到BogoMIPS 值,   
//Bogo是Bogus(伪)的意思,MIPS是millions of instructions per second(百万条指令每秒)的缩写。   
//这样我们就知道了其实这个函数是linux内核中一个cpu性能测试函数。   
//http://blogold.chinaunix.net/u2/86768/showart_2196664.html   
calibrate_delay();  
//PID是process id的缩写   
//http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx   
pidmap_init();  
//来自mm/rmap.c   
//分配一个anon_vma_cachep作为anon_vma的slab缓存。   
//这个技术是PFRA(页框回收算法)技术中的组成部分。   
//这个技术为定位而生——快速的定位指向同一页框的所有页表项。   
anon_vma_init();  
#ifdef CONFIG_X86   
if (efi_enabled)  
efi_enter_virtual_mode();  
#endif   
//创建thread_info缓存   
thread_info_cache_init();  
//申请了一个slab来存放credentials??????如何理解?   
cred_init();  
//根据物理内存大小计算允许创建进程的数量   
//http://www.jollen.org/blog/2006/11/jollen_linux_3_fork_init.html   
fork_init(totalram_pages);  
//给进程的各种资源管理结构分配了相应的对象缓存区   
//http://www.shangshuwu.cn/index.php/Linux内核的进程创建   
proc_caches_init();  
//创建 buffer_head SLAB 缓存   
buffer_init();  
//初始化key的management stuff   
key_init();  
//关于系统安全的初始化,主要是访问控制   
//http://blog.csdn.net/nhczp/archive/2008/04/29/2341194.aspx   
security_init();  
//与debug kernel相关   
dbg_late_init();  
//调用kmem_cache_create()函数来为VFS创建各种SLAB分配器缓存   
//包括:names_cachep、filp_cachep、dquot_cachep和bh_cachep等四个SLAB分配器缓存   
vfs_caches_init(totalram_pages);  
//创建信号队列   
signals_init();  
/* rootfs populating might need page-writeback */  
//回写相关的初始化   
//http://blog.csdn.net/yangp01/archive/2010/04/06/5454822.aspx   
page_writeback_init();  
#ifdef CONFIG_PROC_FS   
proc_root_init();  
#endif   
//它将剩余的subsys初始化.然后将init_css_set添加进哈希数组css_set_table[ ]中.   
//在上面的代码中css_set_hash()是css_set_table的哈希函数.   
//它是css_set->subsys为哈希键值,到css_set_table[ ]中找到对应项.然后调用hlist_add_head()将init_css_set添加到冲突项中.   
//然后,注册了cgroup文件系统.这个文件系统也是我们在用户空间使用cgroup时必须挂载的.   
//最后,在proc的根目录下创建了一个名为cgroups的文件.用来从用户空间观察cgroup的状态.   
//http://blogold.chinaunix.net/u1/51562/showart_1736813.html   
cgroup_init();  
//http://blogold.chinaunix.net/u1/51562/showart_1777937.html   
cpuset_init();  
进程状态初始化,实际上就是分配了一个存储线程状态的高速缓存   
taskstats_init_early();  
delayacct_init();  
//此处为一空函数   
imv_init_complete();  
//测试CPU的各种缺陷,记录检测到的缺陷,以便于内核的其他部分以后可以使用他们工作。   
check_bugs();  
//电源相关的初始化   
//http://blogold.chinaunix.net/u/548/showart.php?id=377952   
acpi_early_init(); /* before LAPIC and SMP init */  
//   
sfi_init_late();  
ftrace_init();  
/* Do the rest non-__init'ed, we're now alive */  
//创建1号进程,详细分析之   
rest_init();  
}  
linux


先来看下一些基础概念


内核线程(thread)或叫守护进程(daemon),在操作系统中占据相当大的比例,当Linux操作系统启动以后,


尤其是Xwindow也启动以后,你可以用”ps”命令查看系统中的进程,这时会发现很多以”d”结尾的进程名,这些进程就是内核线程。


内核线程也可以叫内核任务,它们周期性地执行,例如,磁盘高速缓存的刷新,网络连接的维护,页面的换入换出等等。在Linux中,内核线程与普通进程有一些本质的区别,从以下几个方面可以看出二者之间的差异:


·      内核线程执行的是内核中的函数,而普通进程只有通过系统调用才能执行内核中的函数。


·      内核线程只运行在内核态,而普通进程既可以运行在用户态,也可以运行在内核态。


·     因为内核线程指只运行在内核态,因此,它只能使用大于PAGE_OFFSET(3G)的地址空间。另一方面,不管在用户态还是内核态,普通进程可以使用4GB的地址空间。




在系统start_kernel最后的一个函数,起来的是系统的1号线程kernel_init


static noinline void __init_refok rest_init(void)  
 __releases(kernel_lock)  
{  
 int pid;  
  
//对于RCU机制可以看看上篇文章中对rcu引用的两篇博文   
 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. 
  */  
 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);  
 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();  
 complete(&kthreadd_done);  
 unlock_kernel();  
  
 /* 
  * The boot idle thread must execute schedule() 
  * at least once to get things moving: 
  */  
 init_idle_bootup_task(current);  
 preempt_enable_no_resched();  
 schedule();  
 preempt_disable();  
  
 /* Call into cpu_idle with preempt disabled */  
 cpu_idle();  
}


  


这里我们来分析下kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)其中的kernel_init。这里是我们建立的init进程。
执行kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)来启动内核线程kthreadd,它的工作是用来运行kthread_create_list全局链表中的kthread
然后创建idle线程来占用掉cpu空闲时的时间片。


现在我们先看看kernel_init


static int __init kernel_init(void * unused)  
{  
 /* 
  * Wait until kthreadd is all set-up. 
  */  
 wait_for_completion(&kthreadd_done);  
 lock_kernel();  
  
 /* 
  * init can allocate pages on any node 
  */  
 set_mems_allowed(node_states[N_HIGH_MEMORY]);  
 /* 
  * init can run on any cpu. 
  */  
 set_cpus_allowed_ptr(current, cpu_all_mask);  
 /* 
  * Tell the world that we're going to be the grim 
  * reaper of innocent orphaned children. 
  * 
  * We don't want people to have to make incorrect 
  * assumptions about where in the task array this 
  * can be found. 
  */  
 init_pid_ns.child_reaper = current;  
  
 cad_pid = task_pid(current);  
  
 smp_prepare_cpus(setup_max_cpus);  
  
 do_pre_smp_initcalls();  
 start_boot_trace();  
  
 smp_init();  
 sched_init_smp();  
  
 do_basic_setup();  
  
 /* Open the /dev/console on the rootfs, this should never fail */  
 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)  
  printk(KERN_WARNING "Warning: unable to open an initial console./n");  
  
 (void) sys_dup(0);  
 (void) sys_dup(0);  
 /* 
  * check if there is an early userspace init.  If yes, let it do all 
  * the work 
  */  
  
 if (!ramdisk_execute_command)  
  ramdisk_execute_command = "/init";  
  
 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {  
  ramdisk_execute_command = NULL;  
  prepare_namespace();  
 }  
  
 /* 
  * Ok, we have completed the initial bootup, and 
  * we're essentially up and running. Get rid of the 
  * initmem segments and start the user-mode stuff.. 
  */  
  
 init_post();  
 return 0;  



 
进入kernel_init则在等待kthreadd_done的释放,这里就可以看见在reset_init中的对于thread的描述。
lock_kernel则是系统的大内核锁,紧接着系统进行一系列的初始化。
do_basic_setup是我们重点关注的函数


/* 
 * Ok, the machine is now initialized. None of the devices 
 * have been touched yet, but the CPU subsystem is up and 
 * running, and memory and process management works. 
 * 
 * Now we can finally start doing some real work.. 
 */  


static void __init do_basic_setup(void)  
{  
 init_workqueues();  
 cpuset_init_smp();  
 usermodehelper_init();  
 init_tmpfs();  
 driver_init();  
 init_irq_proc();  
 do_ctors();  
 do_initcalls();  
}


  
init_workqueues,初始化工作队列events,每个CPU一个,这里就是管理中断下半部的workqueue在这里初始化了
 
cpuset_init_smp因为没有配置CONFIG_CPUSETS,是个空函数。
 
usermodehelper_init,初始化工作队列khelper,每个CPU一个。
 
init_tmpfs,注册并安装tmpfs文件系统,它的file_system_type结构如下:


static struct file_system_type tmpfs_fs_type = {  
       .owner           = THIS_MODULE,  
       .name             = "tmpfs",  
       .get_sb           = shmem_get_sb,  
       .kill_sb    = kill_litter_super,  
};  
driver_init,建立设备驱动模型sysfs的kset、kobject和subsystem结构,并向其中注册cpu、内存和总线的驱动。
 
init_irq_proc,向/proc文件系统中增加子目录irq来显示中断描述符表中的所有元素,在
系统运行的时候我们可以通过cat /proc/interrupts来查看注册的中断和当前产生的中断数。
 
do_ctors?????
 
最关心的do_initcalls()下面详细分析


static void __init do_initcalls(void)  
{  
 initcall_t *fn;  
  
 for (fn = __early_initcall_end; fn < __initcall_end; fn++)  
  do_one_initcall(*fn);  
  
 /* Make sure there is no pending stuff from the initcall sequence */  
 flush_scheduled_work();  
}  
这里牵扯到内核中的init段。先看看module_init这个常用的接口函数(include/init.h)


#define __define_initcall(level,fn,id) /   
 static initcall_t __initcall_##fn##id __used /  
 __attribute__((__section__(".initcall" level ".init"))) = fn  
   
#define __initcall(fn) device_initcall(fn)   
  
#define module_init(x) __initcall(x);  
可以看见上述宏定义,最终指向init段
对于__early_initcall_end的定义可以参考(include/asm-generic/vmlinux.lds.h)


#define INITCALLS       /   
 *(.initcallearly.init)      /  
 VMLINUX_SYMBOL(__early_initcall_end) = .;   /  
   *(.initcall0.init)      /  
   *(.initcall0s.init)      /  
   *(.initcall1.init)      /  
   *(.initcall1s.init)      /  
   *(.initcall2.init)      /  
   *(.initcall2s.init)      /  
   *(.initcall3.init)      /  
   *(.initcall3s.init)      /  
   *(.initcall4.init)      /  
   *(.initcall4s.init)      /  
   *(.initcall5.init)      /  
   *(.initcall5s.init)      /  
 *(.initcallrootfs.init)      /  
   *(.initcall6.init)      /  
   *(.initcall6s.init)      /  
   *(.initcall7.init)      /  
   *(.initcall7s.init)  
这里再看


#define pure_initcall(fn)  __define_initcall("0",fn,0)   
  
#define core_initcall(fn)  __define_initcall("1",fn,1)   
#define core_initcall_sync(fn)  __define_initcall("1s",fn,1s)   
#define postcore_initcall(fn)  __define_initcall("2",fn,2)   
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)   
#define arch_initcall(fn)  __define_initcall("3",fn,3)   
#define arch_initcall_sync(fn)  __define_initcall("3s",fn,3s)   
#define subsys_initcall(fn)  __define_initcall("4",fn,4)   
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)   
#define fs_initcall(fn)   __define_initcall("5",fn,5)   
#define fs_initcall_sync(fn)  __define_initcall("5s",fn,5s)   
#define rootfs_initcall(fn)  __define_initcall("rootfs",fn,rootfs)   
#define device_initcall(fn)  __define_initcall("6",fn,6)   
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)   
#define late_initcall(fn)  __define_initcall("7",fn,7)   
#define late_initcall_sync(fn)  __define_initcall("7s",fn,7s)  
就应该明白在内核中的init的初始化的调用顺序了吧。
在这里系统里所有的initcall就会被调用了,这是为了避免把所有代码集合到一块的一个方法。但是同一级别的init顺序则是由编译的链接顺序所决定的。


好了,相关的初始化完成之后,最后在kernel_init执行的是


/* 
  * check if there is an early userspace init.  If yes, let it do all 
  * the work 
  */  
  
 if (!ramdisk_execute_command)  
  ramdisk_execute_command = "/init";  
  
 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {  
  ramdisk_execute_command = NULL;  
  prepare_namespace();  
 }  




这段代码检查是否有必要mount根文件系统,如果vmlinuz中带有initfamfs,而且其中已经有init,那么就不这么做了(我现在工作用的目标系统就是这样的,里面有个init),否则的话内核还要mount init所在的(也是所有用户态进程的最除根文件系统)根文件系统,挂在根文件系统和执行init是linux启动过程最后要做的事情


最后执行init_post


static noinline int init_post(void)  
 __releases(kernel_lock)  
{  
 /* need to finish all async __init code before freeing the memory */  
 async_synchronize_full();  
 imv_unref_core_init();  
 free_initmem();  
 unlock_kernel();  
 mark_rodata_ro();  
 system_state = SYSTEM_RUNNING;  
 numa_default_policy();  
  
 log_boot("Kernel_init_done");  
  
 current->signal->flags |= SIGNAL_UNKILLABLE;  
  
 if (ramdisk_execute_command) {  
  run_init_process(ramdisk_execute_command);  
  printk(KERN_WARNING "Failed to execute %s/n",  
    ramdisk_execute_command);  
 }  
  
 /* 
  * We try each of these until one succeeds. 
  * 
  * The Bourne shell can be used instead of init if we are 
  * trying to recover a really broken machine. 
  */  
 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");  
  
 panic("No init found.  Try passing init= option to kernel. "  
       "See Linux Documentation/init.txt for guidance.");  
}  
在这个函数可以看到释放了__init空间,释放了大内核锁,开始启动下一个init咯。


log_boot("Kernel_init_done");,so,kernel init到了这里也就告一段落了。


从启动过程可以看到涉及到内存管理,文件管理,时钟管理,进程管理,中断,调试,还有相关的机制等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
int main(void) { /* USER CODE BEGIN 1 */ uint8_t ucDevType; volatile uint32_t ii; MPU_Config(); /* USER CODE END 1 */ /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_CRC_Init(); MX_FMC_Init(); MX_I2C1_Init(); MX_LTDC_Init(); MX_QUADSPI_Init(); MX_RNG_Init(); MX_SDMMC1_SD_Init(); MX_SPI3_Init(); MX_USART1_UART_Init(); MX_USART3_UART_Init(); MX_DMA2D_Init(); MX_TouchGFX_Init(); /* USER CODE BEGIN 2 */ bsp_InitUart(); bsp_InitDWT(); bsp_InitDS18B20(); // for(ii = 0;ii < 1000000; ii++) GPIOB->BSRR = GPIO_PIN_1 << 16; // if(!ps2is) // { // bsp_InitPS2(); // PS2_StartWork(); // bsp_DelayMS(200); // ucDevType = PS2_GetDevceType(); // if(ucDevType == PS2_KEYBOARD) // { // ps2is = 1; //// key.setVisible(1); // PS2_InitKeyboard(); // } // PS2_StopWork(); /* 停止PS2中断 */ // } //AppTaskCreate (); tx_kernel_enter(); comClearRxFifo(CounterCom2); comClearRxFifo(CounterCom); comClearRxFifo(COM6); comClearTxFifo(CounterCom2); comClearTxFifo(CounterCom); comClearTxFifo(COM6); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }解释这部分代码
最新发布
06-03

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值