初步了解Linux内核 (3)
2016-01-20 16:15
477 查看
对于start_kernel函数的具体内容如下:
在start_kernel函数中,会调用setup_arch函数,具体内容如下:
在该函数中通过调用setup_machine(machine_arch_type),以板子的机器ID找到struct machine_desc类型的结构体,里面指出了Uboot传入的参数所保存的地址位置。接着调用tags = phys_to_virt(mdesc->boot_params),内核去该地址上获取Uboot传入的参数,然后解析这些参数。
接着调用以下两条语句:
至于__setup_start与__setup_end则是在链接脚本中定义的,打开链接脚本可以看见有以下几句话:
在__setup_start与__setup_end之间存放.init.setup段,这里有一个疑问,这些数据结构内容是如何放到.init.setup段去的,通过搜索发现,找到以下一个定义:
这里以__setup(“root=”, root_dev_setup)为例,其中参数一是要匹配的名字,参数二则是匹配成功后需要执行的函数。
展开的内容则为:
而函数root_dev_setup内容为:
其中early则是标记该数据结构是否在parse_early_param()中处理,该函数根据early值进行早期的参数处理,在里面会调用do_early_param 函数。若early标记为0的话,例如__setup(“root=”, root_dev_setup),则该数据结构的处理则会在unknown_bootoption函数中处理。而该函数会再去调用obsolete_checksetup函数。在该函数中做具体处理。obsolete_checksetup函数内容如下:
在该函数中,在__setup_start和__setup_end之间找出相对应的结构体。
以bootargs环境变量为例,它的值为:
然后通过该值来查找有无对应的结构体,例如已知有定义__setup(“root=”, root_dev_setup),所以就会找到这么一项root=/dev/nfs,接着在obsolete_checksetup函数中执行p->setup_func语句调用与之对应的结构体中保存的函数,对于root=来说,则是调用root_dev_setup函数,它的作用则是将root=的内容保存到saved_root_name变量中。接着循环其它的__setup(),查看有无与第一个参数匹配的项,有的话则调用与之对应的结构体中保存的函数。
接下来回到start_kernel函数,在该函数最后,调用了rest_init函数。在rest_init函数中又会去调用kernel_init函数,继续跟踪,在这函数中会调用到prepare_namespace函数。对prepare_namespace函数进行分析。函数内容如下:
从以上代码可以发现根文件系统的名字保存在变量saved_root_name中,而该变量的内容则通过之前的obsolete_checksetup函数已经实现了赋值。函数最后则会调用mount_root(),挂载该根文件系统。返回到kernel_init函数后,在该函数中最后还会调用init_post函数,该函数的作用则是执行应用程序。
整个内核的启动流程大致总结为:
到现在初步了解了整个内核的启动过程,但是我知道还远远不够。。很多内容没有去深究,都只是粗略的顺藤摸瓜,说的比较浅,但是让自己对内核有所大概的了解,在以后的路上我也一定会加油,谢谢各位阅读,希望有错或者有疑问的地方可以提出来,一块交流。
asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __stop___param[]; ... printk(linux_banner); setup_arch(&command_line); ... parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); ... /* Do the rest non-__init'ed, we're now alive */ rest_init(); }
在start_kernel函数中,会调用setup_arch函数,具体内容如下:
void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags; struct machine_desc *mdesc; char *from = default_command_line; setup_processor(); mdesc = setup_machine(machine_arch_type); machine_name = mdesc->name; if (mdesc->soft_reboot) reboot_setup("s"); if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params); /* * If we have the old style parameters, convert them to * a tag list. */ if (tags->hdr.tag != ATAG_CORE) convert_to_tag_list(tags); if (tags->hdr.tag != ATAG_CORE) tags = (struct tag *)&init_tags; if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo); if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); parse_tags(tags); } 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; memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '\0'; parse_cmdline(cmdline_p, from); ... }
在该函数中通过调用setup_machine(machine_arch_type),以板子的机器ID找到struct machine_desc类型的结构体,里面指出了Uboot传入的参数所保存的地址位置。接着调用tags = phys_to_virt(mdesc->boot_params),内核去该地址上获取Uboot传入的参数,然后解析这些参数。
接着调用以下两条语句:
parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); 这两条指令都是从__setup_start与__setup_end之间取出相应的数据结构后然后进行处理。
至于__setup_start与__setup_end则是在链接脚本中定义的,打开链接脚本可以看见有以下几句话:
__setup_start = .; *(.init.setup) __setup_end = .
在__setup_start与__setup_end之间存放.init.setup段,这里有一个疑问,这些数据结构内容是如何放到.init.setup段去的,通过搜索发现,找到以下一个定义:
#define __setup(str, fn) \ __setup_param(str, fn, fn, 0) #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 }
这里以__setup(“root=”, root_dev_setup)为例,其中参数一是要匹配的名字,参数二则是匹配成功后需要执行的函数。
展开的内容则为:
static char __setup_str_root_dev_setup[] __initdata = root=; static struct obs_kernel_param __setup_root_dev_setup __attribute_used__ __attribute__((__section__(".init.setup"))) __attribute__((aligned((sizeof(long))))) = { __setup_str_root_dev_setup, root_dev_setup, early }
而函数root_dev_setup内容为:
static int __init root_dev_setup(char *line) { strlcpy(saved_root_name, line, sizeof(saved_root_name)); return 1; }
其中early则是标记该数据结构是否在parse_early_param()中处理,该函数根据early值进行早期的参数处理,在里面会调用do_early_param 函数。若early标记为0的话,例如__setup(“root=”, root_dev_setup),则该数据结构的处理则会在unknown_bootoption函数中处理。而该函数会再去调用obsolete_checksetup函数。在该函数中做具体处理。obsolete_checksetup函数内容如下:
static int __init obsolete_checksetup(char *line) { struct obs_kernel_param *p; int had_early_param = 0; p = __setup_start; do { int n = strlen(p->str); if (!strncmp(line, p->str, n)) { if (p->early) { if (line == '\0' || line == '=') had_early_param = 1; } else if (!p->setup_func) { printk(KERN_WARNING "Parameter %s is obsolete," " ignored\n", p->str); return 1; } else if (p->setup_func(line + n)) return 1; } p++; } while (p < __setup_end); return had_early_param; }
在该函数中,在__setup_start和__setup_end之间找出相对应的结构体。
以bootargs环境变量为例,它的值为:
noinitrd root=/dev/nfs nfsroot=192.168.3.16:/work/nfs_root/first ip=192.168.3.123:192.168.3.16:192.168.3.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,
然后通过该值来查找有无对应的结构体,例如已知有定义__setup(“root=”, root_dev_setup),所以就会找到这么一项root=/dev/nfs,接着在obsolete_checksetup函数中执行p->setup_func语句调用与之对应的结构体中保存的函数,对于root=来说,则是调用root_dev_setup函数,它的作用则是将root=的内容保存到saved_root_name变量中。接着循环其它的__setup(),查看有无与第一个参数匹配的项,有的话则调用与之对应的结构体中保存的函数。
接下来回到start_kernel函数,在该函数最后,调用了rest_init函数。在rest_init函数中又会去调用kernel_init函数,继续跟踪,在这函数中会调用到prepare_namespace函数。对prepare_namespace函数进行分析。函数内容如下:
void __init prepare_namespace(void) { ... if (saved_root_name[0]) { root_device_name = saved_root_name; if (!strncmp(root_device_name, "mtd", 3)) { mount_block_root(root_device_name, root_mountflags); goto out; } ROOT_DEV = name_to_dev_t(root_device_name); if (strncmp(root_device_name, "/dev/", 5) == 0) root_device_name += 5; } is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR; if (initrd_load()) goto out; if (is_floppy && rd_doload && rd_load_disk(0)) ROOT_DEV = Root_RAM0; mount_root(); }
从以上代码可以发现根文件系统的名字保存在变量saved_root_name中,而该变量的内容则通过之前的obsolete_checksetup函数已经实现了赋值。函数最后则会调用mount_root(),挂载该根文件系统。返回到kernel_init函数后,在该函数中最后还会调用init_post函数,该函数的作用则是执行应用程序。
整个内核的启动流程大致总结为:
arch/arm/kernel/head.S __switch_data start_kernel setup_arch(&command_line) //获取及解析u-boot传入的启动参数 parse_early_param //从__setup_start到__setup_end,调用early函数 do_early_param //早期参数的一些初始化 unknown_bootoption //从__setup_start到__setup_end,调用非early函数 obsolete_checksetup rest_init kernel_init prepare_namespace mount_root //挂载根文件系统 init_post //执行应用程序
到现在初步了解了整个内核的启动过程,但是我知道还远远不够。。很多内容没有去深究,都只是粗略的顺藤摸瓜,说的比较浅,但是让自己对内核有所大概的了解,在以后的路上我也一定会加油,谢谢各位阅读,希望有错或者有疑问的地方可以提出来,一块交流。
相关文章推荐
- Linux signal events Learning note
- 安装linux学习二(1):centos 开启防火墙
- Linux曲径通幽:常用命令(权限管理命令)
- linux配置java环境变量的三种方法(备忘)
- centos下安装使用解压工具:rar/unrar
- Linux Gitlab
- java工程师必须会的linux命令
- 搞下来慢慢学习linux 查看CPU命令行应用!
- Centos6.5 设置nfs
- 查看Linux内核源码技巧的记录
- linux 使用msmtp登陆指定账户发送邮件
- RHEL6.4 Linux手动安装图形化管理桌面(Xorg+Gnome)
- linux下的tar.gz文件后加md5码文件如何解压使用
- Linux系统文件I/O编程(一)---open()等基本函数
- windows开发的python移植到linux的问题
- Linux下查看文件和文件夹大小
- linux 卸载多余的jdk
- Linux系统中的信号量(semphore)与互斥体(mutex)
- centos 6.5 升级php到5.6.17版本
- Linux查找和替换目录下所有字符串【转】