嵌入式ARM Linux kernel启动过程之浅尝辄止分析start_kernel函数

发布时间:2017年11月20日 14:11    作者:ludi
关键词: 嵌入式

了解了kernel启动以前的汇编之后我们来看看正式的c语言启动代码,也就是我们的start_kernel函数了。start_kernel相当大,里面每一个调用到的函数都足够我们伤脑筋了,这里只是浅尝辄止的描述一下函数的功能,从而对kernel启动的过程有一个比较直观的了解。很多函数真正理解需要对linux相关体系有很深的了解。

说实话启动的代码看到现在唯一的感觉就是kernel的全局变量实在太多了,要了解一个过程跟踪一个变量的值的变化相当痛苦啊,不过耐心看下来,收获还是比较丰富的,对很多概念都有了一个比较直观的理解。闲话就不多说了,直接来上代码~~

smp_setup_processor_id();

//这个函数现在是空的;

lockdep_init();

//Runtime locking correctness validator, see Documentation/lockdep_design.txt

debug_objects_early_init();

cgroup_init_early();

//Control group, read Documentation/cgroup.txt

local_irq_disable();

//使用arm cpsid i指令来禁止IRQ

early_boot_irqs_off();

early_init_irq_lock_class();

/* 基本上面几个函数就是初始化lockdepcgroup,然后禁止IRQ,为后续的运行创造条件 */

lock_kernel();

/* 看这个函数的之前我们首先来了解一段知识,linux kernel默认是支持preemption(抢占)的。在SMP环境下为了实现kernel的锁定,kernel使用了一个BKL(big kernel lock)的概念,在初始化的过程中先锁定这个BKL,然后再继续进行其他启动或者初始化过程,这样就可以防止启动过程中被中断,执行到res_init以后,kernel会释放这个锁,这样就确保了整个start_kernel过程都不会被抢占或者中断。由此我们可以看到这主要是针对多处理器而言的,实际上如果只有一个处理器的话它也不会被中断或者被抢占。 */

/* 下面我们来看看这个函数的执行过程,在这里面能学到很多东西的:

int depth = current->lock_depth+1;

这个current实际上是一个宏,定义在arch/arm/include/asm/current.h里面,它实际是一个task_struct的结构体,这个结构体描述了这个task的一系列信息(task应该是linux进程调度的一个基本单位?)linux下每个进程都有一个相对应的task_struct,这个task_struct有几个我们经常能看到的信息,一个就是PID,然后就是comm进程的名字,然后就是mm_struct,它定义了跟这个进程相关的所有申请的memory的管理。嵌入式物联网等系统学习请加企鹅意义气呜呜吧久零就易,这个curren_thread_info()也是个很有意思的函数,直接读取SP来获得当前线程的结构体信息thread_infothread_infotask_struct这两个结构体应该就代表了当前线程的所有信息。

初始化的lock_depth-1,只有init tasklock_depth0

if (likely(!depth))

__lock_kernel();

这里判断是不是init task,如果是就会执行__lock_kernel();这个__lock_kernel首先禁止抢占,然后试着获得BKL,如果成功则直接返回,如果不成功首先判断是否禁止抢占成功了,如果成功了就用自旋锁去锁BKL。如果禁止抢占没有成功则在抢占有效的情况下去等待BKL,直到获得BKL。因为QC的片子不是SMP,所有这里第一次try的时候就直接成功了。

current->lock_depth = depth;

这个就没什么好说的了 */

/* 基本上来说这个lock_kernel就是禁止抢占,然后获得BKL,干了这么件事 */

tick_init();

//和时钟相关的初始化,好像是注册notify事件,没有仔细研究过

boot_cpu_init();

//这个实际上是在SMP环境下选择CPU,这里直接CPUID选择的是0cpu

page_address_init();

//初始化high memory,在arm环境下实际上这个函数是空的,也就是说arm不支持high memory

printk(KERN_NOTICE);

printk(linux_banner);

//这里的KER_NOTICE是字符串<5>,不太明白它的意思。。。后面的linux_banner定义在kernel/init/version.c里面,这里的printk是门高深的学问,以后看console的时候会仔细分析

setup_arch(&command_line);

/* 这是一个重量级的函数了,会比较仔细地分析一下,主要完成了4个方面的工作,一个就是取得MACHINEPROCESSOR的信息然或将他们赋值给kernel相应的全局变量,然后呢是对boot_command_linetags接行解析,再然后呢就是memorycach的初始化,最后是为kernel的后续运行请求资源。 */

/* 我们来仔细看看这个函数的实现:

setup_processor();

这个函数首先从arm寄存器里面取得cpu ID,然后调用lookup_processor_type来取得proc_info_list这个结构体。这个过程实际上和我们在head-common.S里面的过程是一样的,不知道这里为什么不直接去读switch_data里面已经保存好的数据反而又查询一遍是为什么?取得proc_info_list以后,将里面的内容一个个赋值给相应的全局变量,然后将CPU的信息打印出来。然后它会从arm寄存器里面获得cache的信息,并将cache的信息打印出来。最后它会调用cpu_proc_init()的函数,这个函数实际上定义在proc-v6.S里面,没有做任何事情。

mdesc = setup_machine(machine_arch_type);

首先这个machine_arch_type定义在生成的./include/asm-arm/mach-types.h里面,这个setup_macine实际上也是和上面的processor类似,都是调用head-common.S里面的函数,根据machine ID来获得Machine的信息,并将Machine name打印出来。

if (mdesc->soft_reboot)

reboot_setup("s");

设置reboot方式,默认是硬启动;

if (__atags_pointer)

tags = phys_to_virt(__atags_pointer);

else if (mdesc->boot_params)

tags = phys_to_virt(mdesc->boot_params);

这里首先判断head-common.S里面定义的__atags_pointer是不是为空,不为空的话说明bootloader设置了初始化参数,将参数的物理地址转化为虚拟地址,这里有一个覆盖,就是说可以在Machine desc里面对初始化参数的物理地址重新定位。

if (tags->hdr.tag != ATAG_CORE)

convert_to_tag_list(tags);

if (tags->hdr.tag != ATAG_CORE)

tags = (struct tag *)&init_tags;

首先判断是不是正确的atag格式,如果是以前老版本的param_struct格式会首先将其转换成tag格式,如果转换以后还是不对,则使用默认的init_tags,这里判断的过程都是根据结构体第一个值是不是ATAG_CORE.

if (mdesc->fixup)

mdesc->fixup(mdesc, tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) {

if (meminfo.nr_banks != 0)

squash_mem_tags(tags);

save_atags(tags);

parse_tags(tags);

这里首先判断fixup函数指针,这里一般为空,如果不为空就会地用fixup来重新修改memory mapmeminfo这个结构体定义在arch/arm/include/asm/setup.h里面,描述了内存块的信息,内存块的个数,每个内存块的起始地址和大小,如果修改了memory map则需要从atag参数里面去掉bootloader传过来的的memory map信息,然后是保存一下atag,这个保存函数在这里实际上是空的,没有做任何操作,最后是对atag参数进行解析。这里要说明一下这里的tags实际上是一个tag的数组或者说队列,里面有多个tag结构体,每一个结构体都是一个header加一个参数,具体的结构我们可以看看setup.h。对ATAG参数的解析全部定义在arch/arm/kernel/setup.c里面,首先在setup.c里面定义了一个类似于这样__tagtable(ATAG_CORE, parse_tag_core)的宏,这个宏实际上是声明了一个放在__tagtable_begin__tagtable_end段之间结构体,这个结构体定义了这个一个参数类型,和对这个参数类型进行解析的函数。所有的参数解析我们都可以从setup.c里面找到相对应的函数,比如说对boot_commad_line的解析,从config文件得到的default_commad_line就会被ATAG里面获得commad_line给替换掉;再比如ramdisk,就会将ATAG里面的ramdisk的信息赋值给rd_image_start, rd_size等系统的全局变量。

init_mm.start_code = (unsigned long) _text;

init_mm.end_code = (unsigned long) _etext;

init_mm.end_data = (unsigned long) _edata;

init_mm.brk = (unsigned long) _end;

这就就是对init_mm结构体进行赋值,具体不了解这些东西咋用的,但是就是将textdata段给赋值了。

memcpy(boot_command_line, from, COMMAND_LINE_SIZE);

boot_command_line[COMMAND_LINE_SIZE-1] = '/0';

parse_cmdline(cmdline_p, from);

这里的boot_command_line来自于与config文件里面的CONFIG_CMDLINE,也有可能被ATAG里面的boot参数给覆盖,获得command_line以后对command_line进行解析。这个解析的过程也是在setup.c里面进行的,它首先查找.early_param.init段里面注册的结构体,通过__early_paramearly_param结构体注册到这些段里面,实际这个early_param很简单一个就是类似于"initrd="的字符串,用来与command_line里面的字符进行匹配,如果匹配到了就执行early_param里面的那个函数,并将匹配到的字符作为参数传给函数。举个例子比如说我们现在的commadline里面有一句initrd=0x11000000,然后我们首先在early_param.init段里面搜索时候有没有early_param->arg和这个initrd=匹配,找到了就执行它里面的func然后将这个initrd=的值作为参数传进去。

paging_init(mdesc);

这个函数是个大函数,具体内容没有仔细看,需要对arm MMU了解比较深,这里只贴一下source里面关于这个函数的注释:

/*

* paging_init() sets up the page tables, initialises the zone memory

* maps, and sets up the zero page, bad page and bad page tables.

*/

request_standard_resources(&meminfo, mdesc);

这个函数用来申请一些应该是内存资源,具体的内容没有仔细研究,看不大懂。。

cpu_init();

初始化CPU,这里主要是对arm寄存器cpsr的操作

init_arch_irq = mdesc->init_irq;

system_timer = mdesc->timer;

init_machine = mdesc->init_machine;

这里将体系结构相关的几个函数,中断,初始化,定时器之类的赋值给kernel全局变量;

conswitchp = &vga_con;

这里设置了关于console的一个变量,具体不知道怎么用的,以后看console的时候再仔细分析

early_trap_init();

不知道这个函数具体做什么用的。。。 */

/* 基本上我们可以总结出setup_arch主要将一些体系结构的相关信息来赋值给kernel的全局变量,包括cpu啊,machine啊,memorycahce啊,然后kernel再根据这些函数或者变量来做相应的工作,而且很明显地可以看出来这个setup_arch和前面的head.Shead-common.Sproc-v6.Sboard-msm7x27.c是紧密联系在一起的 */


欢迎分享本文,转载请保留出处:http://www.eechina.com/thread-519622-1-1.html     【打印本页】
您需要登录后才可以发表评论 登录 | 立即注册

相关文章

相关视频演示

关于我们  -  服务条款  -  使用指南  -  站点地图  -  友情链接  -  联系我们
电子工程网 © 版权所有   京ICP备16069177号 | 京公网安备11010502021702
回顶部