Arm linxu启动过程分析(四)

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(); //为系统中的每个cpuper_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(&regs, 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, &regs, 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 内核启动过程分析   易松华 华清远见

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值