Chinaunix首页 | 论坛 | 博客
  • 博客访问: 304259
  • 博文数量: 78
  • 博客积分: 3635
  • 博客等级: 中校
  • 技术积分: 1115
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-28 09:35
文章分类

全部博文(78)

文章存档

2011年(13)

2010年(65)

我的朋友

分类: LINUX

2010-11-16 17:24:42

1.  Linux内核启动第三阶段start_kernel:
内核从现在开始就进入C语言部分,内核启动第三阶段从init/main.c文件中的start_kernel()函数开始,到该函数结束。这一阶段对整个系统内存、cache、信号、设备等进行初始化,最后生成init进程后,调用cpu_idle()完成内核启动的第三阶段。Start_kernel()中调用了一系列的初始化函数,以完成kernel本身的设置。这些动作有些是公共的,有的则是需要配置才会执行的。
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
/*中断是禁止的,作必要的设置之后,使能中断*/
lock_kernel();
/* 如果内核配置成支持抢占,那么在这里禁止抢占,将0号进程的init_thread_info.preempt_count 加1,如果配置成不支持抢占,那么内核全局自旋锁kernel_flag上锁*/
page_address_init();    /*ARM9不支持高端内存(>896MB),一般嵌入式产品也不会用高端内存,所以这里是空函数*/
printk(KERN_NOTICE);
/* 将linux_banner的内容打印到log_buf缓冲区中去,等到串口或者其它终端初始化之后,在一次性打印到终端上去*/
printk(linux_banner);
setup_arch(&command_line);
/*    setup_arch()原型在arch/arm/kernel/setup.c中,根据处理器硬件平台设置系统;
解析linux命令行参数,设置0号进程的内存描述结构init_mm,系统内存管理初始化,
统计并注册系统各种资源,其它项目的初始化  */
setup_per_cpu_areas(); //为系统中的每个cpu的per_cpu变量申请空间
/* Mark the boot cpu "online" so that it can call console drivers in
 * printk() and can access its per-cpu storage.       */
smp_prepare_boot_cpu();
/*开启任何中断之前,初始化调度器. Full topology setup happens at smp_init()
 * time - but meanwhile we still have a functioning scheduler.
 初识化每个处理器的可运行进程队列,设置系统初始化进程,即0号进程; */
sched_init();
preempt_disable();   //禁止抢占
build_all_zonelists(); //建立系统内存页区(zone)链表
page_alloc_init();
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); //解析新格式内核参数
sort_main_extable(); /* 排序内核内建的异常表 ,将__start___ex_table到 __stop___ex_table 之间的*(__ex_table)区中的struct exception_table_entry型全局结构变量按insn成员变量值从小到大排序,即将可能导致缺页异常的指令按期指令二进制代码值从小到大排序。*/
trap_init();/* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
 * into the vector page, mapped at 0xffff0000, and ensure these
 * are visible to the instruction stream.  */
 /*   * Copy signal return handlers into the vector page, and
 * set sigreturn to be a pointer to these. */
rcu_init();
 /* * Initializes rcu mechanism.  Assumed to be called early.
 * That is before local timer(SMP) or jiffie timer (uniproc) is setup.
 * Note that rcu_qsctr and friends are implicitly
 * initialized due to the choice of ``0'' for RCU_CTR_INVALID. */
init_IRQ(); /*中断初始化,初始化系统中支持的最大可能的中断描述结构struct irqdesc变量数组irq_desc[NR_IRQS],把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断 描述结构变量bad_irq_desc,并初始化该中断的链表表头成员结构变量pend.*/
pidhash_init();/*
 * The pid hash table is scaled according to the amount of memory in the
 * machine.  From a minimum of 16 slots up to 4096 slots at one gigabyte or
 * more.设置系统中每种pid hash表中的hash链表数的移位值全局变量pidhash_shift,将pidhash_shift设置为mini(12);分别为每种hash表的连续hash链表表头结构空间申请内存,把申请到的内存虚拟基址分别传给pid_hash[n](n=0~3),并将每种hash表中的每个hash链表表头结构struct hlist_head中的first成员指针设置成NULL.
 */
init_timers();
softirq_init();
time_init();//检查系统定时器描述结构struct sys_timer全局变量system_timer是否为空,如果为空将其指向dummy_gettimeoffset()函数。
/*
 * 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.
 */
console_init(); //初始化系统控制台结构,该函数执行后可调用printk()函数将log_buf
//中符合打印级别要求的系统信息打印到控制台上。
if (panic_later)
        panic(panic_later, panic_param);
profile_init(); //对系统剖析做相关初始化,系统剖析用于系统调用
local_irq_enable(); //使能IRQ
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
               initrd_start < min_low_pfn << PAGE_SHIFT) {
        printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
            "disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
        initrd_start = 0;
}
#endif
vfs_caches_init_early();
mem_init();
//该函数执行完成之后,就不能再用像alloc_bootmem(),alloc_bootmem_low(), alloc_bootmem_pages()等申请低端内存的函数来申请内存,也就不能再申请大块连续的物理内存了。
kmem_cache_init();//slab分配器的相关初始化,执行高速缓存内存管理
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
        late_time_init();
calibrate_delay();/*
 * This is the number of bits of precision for the loops_per_jiffy.  Each
 * bit takes on average 1.5/HZ seconds.  This (like the original) is a little
 * better than 1%
 */

pidmap_init();
pgtable_cache_init();
prio_tree_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
        efi_enter_virtual_mode();
#endif
fork_init(num_physpages);//执行进程创建相关的初始化
proc_caches_init();
buffer_init();
unnamed_dev_init();
key_init();
security_init();  // security_init - initializes the security framework
vfs_caches_init(num_physpages);
radix_tree_init();
signals_init();
/* 调用函数kmem_cache_create("sigqueue", sizeof(struct sigqueue), __alignof__(struct sigqueue), SLAB_PANIC, NULL, NULL); 为信号队列结构struct sigqueque创建高速缓存内存描述结构kmem_cache_t变量,名字叫”sigqueue”,不要求其对象按处理器硬件cache line大小对齐,没有定义其对象的构造和析构函数,将创建的kmem_cache_t结构变量的地址传给全局指针sigqueue_cachep.*/
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
 //在系统支持proc文件系统即配置了CONFIG_PROC_FS选项时被调用
#endif
cpuset_init();
check_bugs();
acpi_early_init(); /* before LAPIC and SMP init */
/* Do the rest non-__init'ed, we're now alive */
rest_init();
/*    创建init()进程,即1号进程,启动调度器,然后系统启动进程即0号进程进入空闲; */
}
下面来分析几个非常重要的函数:
首先主要分析setup_arch()函数,定义在arch/arm/kernel/setup.c文件中,它完成体系结构相关的初始化,内核移植的过程一般也就到此函数为止了,其余的就只是一些相关的外设驱动。
Setup_arch()中的第一个主要函数为setup_processor(),它只是简单的遍历__proc_info_begin开始的proc_info_list找到相应的表项,并从中得到proc_info 结构。注意这些proc_info_list结构定义在arch/arm/mm/proc_arm920.S文件中。
然后就是setup_machine函数,它根据machine_arch_type[机器 ID],在__arch_info_begin中找到对应的machine_desc表项。对于FS2410(实际上这里用的是SMDK2410的相关内容,因为二者基本相同,所以在移植的时候没有修改这一部分,用的还是SMDK2410的机器ID)在arch/arm/mach-s3c2410/mach-smdk2410.c中定义为:
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
                          * to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.phys_ram     = S3C2410_SDRAM_PA,
.phys_io  = S3C2410_PA_UART,
.io_pg_offst   = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io         = smdk2410_map_io,
.init_irq  = smdk2410_init_irq,
 .init_machine   = sdmk2410_init,
.timer            = &s3c24xx_timer,
MACHINE_END
接着需要解释的就是u-boot给内核传参数了,linux-2.6.14没有通过u-boot引导时的thekernel的第三个参数传递u-boot传递给内核参数的位置,即boot_params,而是通过指定机器描述结构的mdesc->boot_params来制订u-boot传给内核参数的位置,这时需要先就参数进行分析,由parse_tags(tags);来完成,其中tags = phys_to_virt(mdesc->boot_params);
paging_init(&meminfo, mdesc);是一个非常重要的函数尤其在移植内核时,主要完成页表的初始化。----〉调用memtable_init(mi);初始化内存页表----〉create_mapping(init_maps)创建页目录项荷页表项。
另外在paging_init()函数中还会调用mdesc->map_io();来完成平台相关的IO映射。
接着就是 设置平台相关的指针
init_arch_irq = mdesc->init_irq; //在start_kernel 接下来的init_IRQ()函数中会掉用该函数完成平台相关的中断初始化。
system_timer = mdesc->timer; //在start_kernel 接下来的time_init()函数中会掉用该结构体中的内容完成平台相关的定时器初始化。
init_machine = mdesc->init_machine;
setup_arch()的分析到此结束。
这里再简单分析一下reset_init()函数:
static void noinline rest_init(void)
__releases(kernel_lock)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);  //启动init线程
numa_default_policy();
unlock_kernel();
preempt_enable_no_resched();
/* The boot idle thread must execute schedule()at least one to get things moving: */
schedule();  //启动调度器
cpu_idle();//cpu空闲,实际上就是启动进程进入空闲状态,系统交给调度器来管理
}
再来看一下启动init线程的过程:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.ARM_r1 = (unsigned long)arg;
regs.ARM_r2 = (unsigned long)fn;
regs.ARM_r3 = (unsigned long)do_exit;
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = SVC_MODE;
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
//调用do_fork函数创建init内核线程。
}
接着我们来看一下这个init线程的内容,它是init/main.c中的一个函数,代码如下:
static int init(void * unused)
{
lock_kernel();
set_cpus_allowed(current, CPU_MASK_ALL);
…… …… …… …….
/* Do this before initcalls, because some drivers want to access firmware files.       */
populate_rootfs();
do_basic_setup(); //很重要的一个函数,做一些基本的设置
…… …… …… …….
/* 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..  */
free_initmem();
unlock_kernel();
system_state = SYSTEM_RUNNING;
numa_default_policy();
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);    //dup文件描述符1
(void) sys_dup(0);    //dup文件描述符2
…… …… …… …….
run_init_process("/sbin/init");  //启动init进程
run_init_process("/etc/init");   //启动init进程
run_init_process("/bin/init");   //启动init进程
run_init_process("/bin/sh");    //启动shell
panic("No init found.  Try passing init= option to kernel.");
}
/*机器已经初始化完成,cpu子系统已经起来正在运行,内存和进程管理已经工作,接下来要做些真正的工作了  */
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();  //初始化工作队列
usermodehelper_init(); //用户模式help程序
driver_init();  //设备初始化
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initialization needs a process context */
sock_init();
do_initcalls();  //非常重要的一个函数,会在这里执行驱动模块加载函数,也就module_init()的函数。
}
do_initcalls()的一部分代码如下:
static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call < __initcall_end; call++)
{
               char *msg;
               (*call)(); //真正执行驱动模块加载函数的地方
}
最后一个要分析的问题是:关于设备驱动什么时候调用,以网卡驱动为例来进行分析,这里采用的是CS8900A。
驱动里面的模块注册函数module_init(xxx_init);
网卡注册过程:
cs8900_init()---〉register_netdve(&cs8900_dev)---〉register_netdevice()
cs8900_dev定义如下:
int cs8900_probe (struct net_device *dev);
static struct net_device cs8900_dev =
{
        init: cs8900_probe
};
其调用过程:
Register_netdev(struct net_device *dev)----〉register_netdevice(struct net_device * dev)—〉dev->init
这样实际在启动过程中会执行cs8900_probe,里面会对网卡进行侦测,注册中断,DMA中断等一些操作。
而cs8900_init()会在内核启动的时候被执行:
Start_kernel()---〉reset_init()---〉起了init内核线程----〉do_basic_setup()---〉do_initcalls()
--------〉for(call=__initcall_start;call<__initcall_end;call++){
(*call)();《--------------此处会执行cs8900_init,以及其他驱动程序的module_init函数。
}
Include/linux/init.h 中关于initcall的定义,这里简单描述,如果感兴趣可以查阅该文件。
#ifndef MODULE
#ifndef __ASSEMBLY__
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
//Initcall 定义了7个级别,我们关系的设备initcall位于级别6。
#define core_initcall(fn)             __define_initcall("1",fn)
#define postcore_initcall(fn)              __define_initcall("2",fn)
#define arch_initcall(fn)             __define_initcall("3",fn)
#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)
 
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
#define security_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn
#define __setup_param(str, unique_id, fn, early)                  \
static char __setup_str_##unique_id[] __initdata = str; \
static struct obs_kernel_param __setup_##unique_id   \
        __attribute_used__                           \
        __attribute__((__section__(".init.setup")))     \
        __attribute__((aligned((sizeof(long)))))    \
        = { __setup_str_##unique_id, fn, early }
#define __setup_null_param(str, unique_id)    __setup_param(str, unique_id, NULL, 0)
#define __setup(str, fn)              __setup_param(str, fn, fn, 0)
#define __obsolete_setup(str)     __setup_null_param(str, __LINE__)
#define early_param(str, fn)              __setup_param(str, fn, fn, 1)
void __init parse_early_param(void);
#endif /* __ASSEMBLY__ */
#define module_init(x)      __initcall(x);
#define module_exit(x)     __exitcall(x);
#else /* MODULE   定义为模块  */
#define core_initcall(fn)             module_init(fn)
#define postcore_initcall(fn)              module_init(fn)
#define arch_initcall(fn)             module_init(fn)
#define subsys_initcall(fn)          module_init(fn)
#define fs_initcall(fn)                 module_init(fn)
#define device_initcall(fn)          module_init(fn)
#define late_initcall(fn)              module_init(fn)
#define security_initcall(fn)        module_init(fn)
 
#define module_init(initfn)                \
static inline initcall_t __inittest(void)             \
{ return initfn; }                              \
int init_module(void) __attribute__((alias(#initfn)));
#define module_exit(exitfn)                             \
static inline exitcall_t __exittest(void)            \
{ return exitfn; }                              \
void cleanup_module(void) __attribute__((alias(#exitfn)));
#define __setup_param(str, unique_id, fn)       /* nothing */
#define __setup_null_param(str, unique_id)    /* nothing */
#define __setup(str, func)                 /* nothing */
#define __obsolete_setup(str)                  /* nothing */
#endif
以上是对FS2410平台基于linux-2.6.14内核的启动过程分析,因个人水平有限,如有分析不当之初,还望不吝指正,在此感谢!
参考文档:PXA255 Linux 内核启动过程分析   易松华 华清远见

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sustzombie/archive/2010/06/12/5667624.aspx
阅读(1319) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~