Chinaunix首页 | 论坛 | 博客
  • 博客访问: 54076
  • 博文数量: 22
  • 博客积分: 975
  • 博客等级: 准尉
  • 技术积分: 222
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-02 21:41
文章分类
文章存档

2011年(8)

2010年(8)

2009年(6)

我的朋友

分类: LINUX

2010-11-16 16:11:24

linux内核从启动开始到设备模型初始化完毕经历的函数
 
start_kernel() ——>
rest_init()——>
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)——kernel_init——>
do_basic_setup()——>
driver_init()——>
devices_init()——>
kobject_create_and_add()
 
-----
1)
所有标识为__init的函数在链接的时候都放在.init.text这个区段内,
在这个区段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。

2)
所有的__init函数在区段.initcall.init中还保存了一份函数指针,
在初始化时内核会通过这些函数指针调用这些__init函数指针,
并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等),

注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,
和1)中所述的这些函数本身在.init.text区段中的顺序无关。
在2.4内核中,这些函数指针的顺序也是和链接的顺序有关的,是不确定的。

在2.6内核中,initcall.init区段又分成7个子区段,分别是
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init

当需要把函数fn放到.initcall1.init区段时,只要声明
core_initcall(fn);
即可。

其他的各个区段的定义方法分别是:
core_initcall(fn) --->.initcall1.init
postcore_initcall(fn) --->.initcall2.init
arch_initcall(fn) --->.initcall3.init
subsys_initcall(fn) --->.initcall4.init
fs_initcall(fn) --->.initcall5.init
device_initcall(fn) --->.initcall6.init
late_initcall(fn) --->.initcall7.init

而与2.4兼容的initcall(fn)则等价于device_initcall(fn)。

各个子区段之间的顺序是确定的,即先调用.initcall1.init中的函数指针
再调用.initcall2.init中的函数指针,等等。
而在每个子区段中的函数指针的顺序是和链接顺序相关的,是不确定的。

在内核中,不同的init函数被放在不同的子区段中,因此也就决定了它们的调用顺序。
这样也就解决了一些init函数之间必须保证一定的调用顺序的问题。
 
Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;
压缩过的kernel入口在arch/arm/boot/compressed/head.S,它将调用函数decompress_kernel()<./arch/arm/boot/compressed/misc.c>解压,打印“Uncompressing Linux...”,调用gunzip(),打印"done, booting the kernel."
然后call_kernel,执行解压后的kernel,经linux/arch/arm/kernel/head.S调用start_kernel转入体系结构无关的通用C代码,在start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成:
<./init/main.c>-------------------------

asmlinkage void __init start_kernel(void)
{
 char * command_line;
 extern struct kernel_param __start___param[], __stop___param[];

··································
 printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);
                                                          //打印内核命令行
 parse_early_param();
 parse_args("Booting kernel", command_line, __start___param,
     __stop___param - __start___param,
     &unknown_bootoption);
                                  //解析由BOOT传递的启动参数
····································································

 /* Do the rest non-__init'ed, we're now alive */
 rest_init();
}

start_kernel()中的函数rest_init()将创建第一个核心线程kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND),调用init()函数:

static int init(void * unused)-------------------
{
                ·······················
                 do_basic_setup();
                ······················

}

继而调用函数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)-----------------
{
 /* drivers will send hotplug events */
 init_workqueues();
 usermodehelper_init();
 driver_init();     //建立设备模型子系统

#ifdef CONFIG_SYSCTL
 sysctl_init();
#endif

 /* Networking initialization needs a process context */
 sock_init();

 do_initcalls();   //系统初始化(包括设备,文件系统,内核模块等)
}

<./drivers/base/init.c>-------------------------
/**
 * driver_init - initialize driver model.
 *
 * Call the driver model init functions to initialize their
 * subsystems. Called early from init/main.c.
 */

void __init driver_init(void)
{
 /* These are the core pieces */
 devices_init();
                       <./drivers/base/core.c>-------------
                                  int __init devices_init(void)
                                  {
                   return subsystem_register(&devices_subsys);
                                  }
                        -----------------------
 buses_init();
 classes_init();
 firmware_init();

 /* These are also core pieces, but must come after the
  * core core pieces.
  */
 platform_bus_init();
 system_bus_init();
 cpu_dev_init();
 memory_dev_init();
 attribute_container_init();
}
---------------------------
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
 initcall_t *call;
 int count = preempt_count();

 for (call = __initcall_start; call < __initcall_end; call++) {
                ··················
                (*call)();     //调用一系列初始化函数
               ···················
}
---------------------------
      __initcall_start和__initcall_end界定了存放初始化函数指针区域的起始地址,即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。 由 (*call)()完成各个部分的初始化工作,且便于扩充。具体实现如下:
<./arch/arm/kernel/vmlinux.lds.S>-----------------
  __initcall_start = .;
   *(.initcall1.init)
   *(.initcall2.init)
   *(.initcall3.init)
   *(.initcall4.init)
   *(.initcall5.init)
   *(.initcall6.init)
   *(.initcall7.init)
  __initcall_end = .;

 <./include/linux/init.h>---------------------

#ifndef MODULE     /*    如果驱动模块静态编译进内核   */

  ···············································

/* initcalls are now grouped by functionality into separate
 * subsections. Ordering inside the subsections is determined
 * by link order.
 * For backwards compatibility, initcall() puts the call in
 * the device init subsection.
 */

#define __define_initcall(level,fn) \
 static initcall_t __initcall_##fn __attribute_used__ \
 __attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn)  __define_initcall("1",fn)
#define postcore_initcall(fn)  __define_initcall("2",fn)
#define arch_initcall(fn)  __define_initcall("3",fn)
                                            //此处初始化了设备
                                           /*----eg:arch_initcall(at91sam9261_device_init)---
                                               static int __init at91sam9261_device_init(void)
                                               {
                                                 at91_add_device_udc();
                                                 at91_add_device_dm9000();
                                                 armebs3_add_input_buttons();
                                                 return platform_add_devices(at91sam9261_devices, ARRAY_SIZE(at91sam9261_devices));
                                                }
                                        ------------------------*/

#define subsys_initcall(fn)  __define_initcall("4",fn)
#define fs_initcall(fn)  __define_initcall("5",fn)
#define device_initcall(fn)  __define_initcall("6",fn)
                                   //此处初始化了静态编译的驱动模块
#define late_initcall(fn)  __define_initcall("7",fn)

#define __initcall(fn) device_initcall(fn)


  /**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 *
 * module_init() will either be called during do_initcalls (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x) __initcall(x);
                                       //静态编译的驱动模块作为device_initcall在内核启动就被do_initcalls

/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 *
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x) __exitcall(x);

#else /* MODULE    如果驱动模块动态加载入内核   */

  ···············································

/* Each module must use one module_init(), or one no_module_init */
#define module_init(initfn)     \
 static inline initcall_t __inittest(void)  \
 { return initfn; }     \
 int init_module(void) __attribute__((alias(#initfn)));
     //insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)
     //将动态驱动模块载入到内核空间

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)     \
 static inline exitcall_t __exittest(void)  \
 { return exitfn; }     \
 void cleanup_module(void) __attribute__((alias(#exitfn)));

-----------------------------

zz:http://www.ibm.com/developerworks/cn/linux/kernel/startup/index.html

对于I386平台
在i386体系结构中,因为i386本身的问题,在"arch/alpha/kernel/head.S"中需要更多的设置,但最终也是通过call SYMBOL_NAME(start_kernel)转到start_kernel()这个体系结构无关的函数中去执行了。
所不同的是,在i386系统中,当内核以bzImage的形式压缩,即大内核方式(__BIG_KERNEL__)压缩时就需要预先处理bootsect.S和setup.S,按照大核模式使用$(CPP) 处理生成bbootsect.S和bsetup.S,然后再编译生成相应的.o文件,并使用 "arch/i386/boot/compressed/build.c"生成的build工具,将实际的内核(未压缩的,含 kernel中的head.S代码)与"arch/i386/boot/compressed"下的head.S和misc.c合成到一起,其中的head.S代替了"arch/i386/kernel/head.S"的位置,由Bootloader引导执行(startup_32入口),然后它调用misc.c中定义的decompress_kernel()函数,使用 "lib/inflate.c"中定义的gunzip()将内核解压到0x100000,再转到其上执行 "arch/i386/kernel/head.S"中的startup_32代码。
对于2.4.x版内核
没有变化。

start_kernel()中调用了一系列初始化函数,以完成kernel本身的设置。这些动作有的是公共的,有的则是需要配置的才会执行的。

在start_kernel()函数中,
  • 输出Linux版本信息(printk(linux_banner))
  • 设置与体系结构相关的环境(setup_arch())
  • 页表结构初始化(paging_init())
  • 使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口(trap_init())
  • 使用alpha_mv结构和entry.S入口初始化系统IRQ(init_IRQ())
  • 核心进程调度器初始化(包括初始化几个缺省的Bottom-half,sched_init())
  • 时间、定时器初始化(包括读取CMOS时钟、估测主频、初始化定时器中断等,time_init())
  • 提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理,(parse_options())
  • 控制台初始化(为输出信息而先于PCI初始化,console_init())
  • 剖析器数据结构初始化(prof_buffer和prof_len变量)
  • 核心Cache初始化(描述Cache信息的Cache,kmem_cache_init())
  • 延迟校准(获得时钟jiffies与CPU主频ticks的延迟,calibrate_delay())
  • 内存初始化(设置内存上下界和页表项初始值,mem_init())
  • 创建和设置内部及通用cache("slab_cache",kmem_cache_sizes_init())
  • 创建uid taskcount SLAB cache("uid_cache",uidcache_init())
  • 创建文件cache("files_cache",filescache_init())
  • 创建目录cache("dentry_cache",dcache_init())
  • 创建与虚存相关的cache("vm_area_struct","mm_struct",vma_init())
  • 块设备读写缓冲区初始化(同时创建"buffer_head"cache用户加速访问,buffer_init())
  • 创建页cache(内存页hash表初始化,page_cache_init())
  • 创建信号队列cache("signal_queue",signals_init())
  • 初始化内存inode表(inode_init())
  • 创建内存文件描述符表("filp_cache",file_table_init())
  • 检查体系结构漏洞(对于alpha,此函数为空,check_bugs())
  • SMP机器其余CPU(除当前引导CPU)初始化(对于没有配置SMP的内核,此函数为空,smp_init())
  • 启动init过程(创建第一个核心线程,调用init()函数,原执行序列调用cpu_idle() 等待调度,init())

至此start_kernel()结束,基本的核心环境已经建立起来了。

对于I386平台
i386平台上的内核启动过程与此基本相同,所不同的主要是实现方式。
对于2.4.x版内核
2.4.x中变化比较大,但基本过程没变,变动的是各个数据结构的具体实现,比如Cache。

init()函数作为核心线程,首先锁定内核(仅对SMP机器有效),然后调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。过程如下:

  • 总线初始化(比如pci_init())
  • 网络初始化(初始化网络数据结构,包括sk_init()、skb_init()和proto_init()三部分,在proto_init()中,将调用protocols结构中包含的所有协议的初始化过程,sock_init())
  • 创建bdflush核心线程(bdflush()过程常驻核心空间,由核心唤醒来清理被写过的内存缓冲区,当bdflush()由kernel_thread()启动后,它将自己命名为kflushd)
  • 创建kupdate核心线程(kupdate()过程常驻核心空间,由核心按时调度执行,将内存缓冲区中的信息更新到磁盘中,更新的内容包括超级块和inode表)
  • 设置并启动核心调页线程kswapd(为了防止kswapd启动时将版本信息输出到其他信息中间,核心线调用kswapd_setup()设置kswapd运行所要求的环境,然后再创建 kswapd核心线程)
  • 创建事件管理核心线程(start_context_thread()函数启动context_thread()过程,并重命名为keventd)
  • 设备初始化(包括并口parport_init()、字符设备chr_dev_init()、块设备 blk_dev_init()、SCSI设备scsi_dev_init()、网络设备net_dev_init()、磁盘初始化及分区检查等等,device_setup())
  • 执行文件格式设置(binfmt_setup())
  • 启动任何使用__initcall标识的函数(方便核心开发者添加启动函数,do_initcalls())
  • 文件系统初始化(filesystem_setup())
  • 安装root文件系统(mount_root())

至此do_basic_setup()函数返回init(),在释放启动内存段(free_initmem())并给内核解锁以后,init()打开/dev/console设备,重定向stdin、stdout和stderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用 execve()系统调用加载执行init程序。

init()函数到此结束,内核的引导部分也到此结束了,这个由start_kernel()创建的第一个线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体:

  • start_kernel()本身所在的执行体,这其实是一个"手工"创建的线程,它在创建了init()线程以后就进入cpu_idle()循环了,它不会在进程(线程)列表中出现
  • init线程,由start_kernel()创建,当前处于用户态,加载了init程序
  • kflushd核心线程,由init线程创建,在核心态运行bdflush()函数
  • kupdate核心线程,由init线程创建,在核心态运行kupdate()函数
  • kswapd核心线程,由init线程创建,在核心态运行kswapd()函数
  • keventd核心线程,由init线程创建,在核心态运行context_thread()函数
对于I386平台
基本相同。
对于2.4.x版内核
这一部分的启动过程在2.4.x内核中简化了不少,缺省的独立初始化过程只剩下网络(sock_init())和创建事件管理核心线程,而其他所需要的初始化都使用__initcall()宏包含在do_initcalls()函数中启动执行。


阅读(936) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~