环境搭建

环境的搭建参考课件,主要就是编译内核源码和生成镜像

start_kernel

从start_kernel开始,才真正进入了Linux内核的启动过程。我们可以把start_kernel看做平时用C编程时的main函数。

在平时应用程序编程中,main函数并不是一开始就启动的,而是先使用汇编和C准备一些变量,例如我们使用的argc和argv参数,以及一些全局变量的初始化。所以我们使用gdb调试程序时,使用bt打印栈痕迹,发现最下面的函数并不是main,而是__libc_start_main。

在内核启动过程中,前面的汇编代码部分,就是在为运行start_kernel这个main函数做准备。

我们看下start_kernel的代码:

  1. asmlinkage __visible void __init start_kernel(void)
  2. {
  3. char *command_line;
  4. char *after_dashes;
  5.  
  6. /*
  7. * Need to run as early as possible, to initialize the
  8. * lockdep hash:
  9. */
  10. lockdep_init();
  11. set_task_stack_end_magic(&init_task);
  12. smp_setup_processor_id();
  13. debug_objects_early_init();
  14.  
  15. // .......
  16.  
  17. /* Do the rest non-__init'ed, we're now alive */
  18. rest_init();
  19. }

在这里我删除了大部分初始化代码,这里做以下的说明:

其实刚进入start_kernel时,Linux还没有进程的概念,整个进程只有一个控制流,在这个函数中,开始的代码主要是运行了一些初始化工作,初始化了init_task这个全局变量。此时的Linux才算是拥有了第一个进程,也就是0号进程。这些代码将start_kernel之前的代码纳入0号进程的上下文中,我们可以将这个进程看做所谓的操作系统进程。

然后我们要重点分析rest_init函数,在这个函数中,我们创建了第一个真正意义上的用户进程,也就是1号进程init。

rest_init

rest_init函数的代码如下:

  1. static noinline void __init_refok rest_init(void)
  2. {
  3. int pid;
  4.  
  5. rcu_scheduler_starting();
  6. /*
  7. * We need to spawn init first so that it obtains pid 1, however
  8. * the init task will end up wanting to create kthreads, which, if
  9. * we schedule it before we create kthreadd, will OOPS.
  10. */
  11. kernel_thread(kernel_init, NULL, CLONE_FS);
  12. numa_default_policy();
  13. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  14. rcu_read_lock();
  15. kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  16. rcu_read_unlock();
  17. complete(&kthreadd_done);
  18.  
  19. /*
  20. * The boot idle thread must execute schedule()
  21. * at least once to get things moving:
  22. */
  23. init_idle_bootup_task(current);
  24. schedule_preempt_disabled();
  25. /* Call into cpu_idle with preempt disabled */
  26. cpu_startup_entry(CPUHP_ONLINE);
  27. }

在这段代码中,我们注意到kernel_thread(kernel_init, NULL, CLONE_FS);

kernel_thread函数的代码只有一行:

  1. pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
  2. {
  3. return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
  4. (unsigned long)arg, NULL, NULL);
  5. }

所以我们可以确定,这样代码创建一个内核线程,该线程的运行逻辑为kernel_init函数。

注意到,Linux系统实际上是没有真正的线程的,所谓的线程是通过进程模拟实现的,所以这里就是创建了一个用户进程,所谓的init。

然后我们注意到最后一行的cpu_startup_entry(CPUHP_ONLINE);

查看下cpu_startup_entry函数代码:

  1. void cpu_startup_entry(enum cpuhp_state state)
  2. {
  1. // ……… 省略代码 arch_cpu_idle_prepare();
  2. cpu_idle_loop();
  3. }

然后查看cpu_idle_loop的代码:

  1. static void cpu_idle_loop(void)
  2. {
  3. while () {
  4. // ..... 省略代码
  5. schedule_preempt_disabled();
  6. }
  7. }

所以我们看到,rest_init函数在最后一行进入了一个死循环。

所以我们得出结论,rest_init先fork出一个真正意义的进程-1号进程init之后,进入了一个死循环,这个死循环就是0号进程idle。我们可以认为idle进程是真正意义上的操作系统进程。

kernel_init进程

下面我们查看下init进程的逻辑:

  1. static int __ref kernel_init(void *unused)
  2. {
  3. int ret;
  4.  
  5. kernel_init_freeable();
  6. /* need to finish all async __init code before freeing the memory */
  7. async_synchronize_full();
  8. free_initmem();
  9. mark_rodata_ro();
  10. system_state = SYSTEM_RUNNING;
  11. numa_default_policy();
  12.  
  13. flush_delayed_fput();
  14.  
  15. if (ramdisk_execute_command) {
  16. ret = run_init_process(ramdisk_execute_command);
  17. if (!ret)
  18. return ;
  19. pr_err("Failed to execute %s (error %d)\n",
  20. ramdisk_execute_command, ret);
  21. }
  22.  
  23. /*
  24. * We try each of these until one succeeds.
  25. *
  26. * The Bourne shell can be used instead of init if we are
  27. * trying to recover a really broken machine.
  28. */
  29. if (execute_command) {
  30. ret = run_init_process(execute_command);
  31. if (!ret)
  32. return ;
  33. pr_err("Failed to execute %s (error %d). Attempting defaults...\n",
  34. execute_command, ret);
  35. }
  36. if (!try_to_run_init_process("/sbin/init") ||
  37. !try_to_run_init_process("/etc/init") ||
  38. !try_to_run_init_process("/bin/init") ||
  39. !try_to_run_init_process("/bin/sh"))
  40. return ;
  41.  
  42. panic("No working init found. Try passing init= option to kernel. "
  43. "See Linux Documentation/init.txt for guidance.");
  44. }

我们注意末尾几行的:

  1. if (!try_to_run_init_process("/sbin/init") ||
  2. !try_to_run_init_process("/etc/init") ||
  3. !try_to_run_init_process("/bin/init") ||
  4. !try_to_run_init_process("/bin/sh"))
  5. return ;

然后我们继续查看try_to_run_init_process函数:

  1. static int try_to_run_init_process(const char *init_filename)
  2. {
  3. int ret;
  4.  
  5. ret = run_init_process(init_filename);
  6.  
  7. if (ret && ret != -ENOENT) {
  8. pr_err("Starting init: %s exists but couldn't execute it (error %d)\n",
  9. init_filename, ret);
  10. }
  11.  
  12. return ret;
  13. }

继续跟进run_init_process:

  1. static int run_init_process(const char *init_filename)
  2. {
  3. argv_init[] = init_filename;
  4. return do_execve(getname_kernel(init_filename),
  5. (const char __user *const __user *)argv_init,
  6. (const char __user *const __user *)envp_init);
  7. }

我们可以清楚地看到,run_init_process中,调用一个execve来替换当前进程的代码端,熟悉Unix编程的同学都知道,执行execve替换后,之前的代码不会继续执行,但是如果执行了,说明代码替换失败!!

所以,kernel_init中那几行代码的逻辑就很清楚了。

1.先尝试运行/sbin/init来替换本进程

2.如果上面的调用失败(/sbin/init不存在),那么尝试使用/etc/init/

………

代码中提到的这几个文件,就是init的执行逻辑。

实验截图

总结

总结上面的流程,其实就是内核fork出一个init进程,然后之前的进程进入死循环,也就是所谓的idle进程。

通过本周的作业,清晰的认识到了操作系统的启动流程。

作业署名

郭春阳 原创作品转载请注明出处 :《Linux内核分析》MOOC课程

Linux内核启动分析过程-《Linux内核分析》week3作业的更多相关文章

  1. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  2. Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...

  3. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  4. Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)(Linux-3.0 ARMv7) 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4938389.html 在完成了zImage自解压之后,就跳转到了解压后的内核(也就是vmlinux的bin ...

  5. Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

    前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就 ...

  6. 50.Linux-分析ifconfig到内核的调用过程,实现内核启机自动设MAC地址(原)

    内核版本: Linux version 3.10.14 1.由于每次开发板开机的网卡eth0的物理地址都是随机的. 然后在网上找到可以通过命令行实现设置mac物理地址: ifconfig eth0 d ...

  7. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  8. Linux内核启动流程分析(一)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380535.html 很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接 ...

  9. linux内核启动以及文件系统的加载过程

    Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...

  10. ARM linux内核启动时几个关键地址【转】

    转自:http://www.cnblogs.com/armlinux/archive/2011/11/06/2396787.html 1.       内核启动地址1.1.   名词解释ZTEXTAD ...

随机推荐

  1. [原创][LaTex]LaTex学习笔记之框架及宏包

    0. 简介 LaTex在书写文档时的最基本单元就是首部的写作,变相的也可以说是头文件.本文章就来总结一下文档的基本格式和常用宏包. 1. 基本单元 基本单元需要对LaTex语法有一定的了解,这个很简单 ...

  2. pause 和 title

    -------siwuxie095 pause 暂停批处理程序,并显示:请按任意键继续- 暂停高级技巧: pause>nul 只暂停,不显示任何信息,且光标移到下一行 如果不想用默认提示语:请按 ...

  3. Pig Latin

    function translate(str) { //return str; var list = ['a','e','i','o','u']; if(list.indexOf(str[0]) &g ...

  4. Fiddler学习之——对Android应用进行抓包

    Fiddler做为实用的http抓包工具,它的原理是在本机开启了一个http的代理服务器,然后它会转发所有的http请求和响应,因此,它比一般的firebug或者是chrome自带的抓包工具要好用的多 ...

  5. (转)SqlBulkCopy批量复制数据

    在.Net1.1中无论是对于批量插入整个DataTable中的所有数据到数据库中,还是进行不同数据源之间的迁移,都不是很方便.而 在.Net2.0中,SQLClient命名空间下增加了几个新类帮助我们 ...

  6. iscsi: 环境搭建

    组网环境 +----------+---------------+---------------+ | hostname | ip address | iscsi role | +---------- ...

  7. hadoop2.0单机安装

    hadoop发行的版本:apache hadoop;HDP;CDH -----这里只使用apache hadoop---可以在网站hadoop.apache.org网站上找到 hadoop安装方式:自 ...

  8. django 创建项目

    django-admin startproject project-name 启动服务器 python manage.py runserver 0.0.0.0:8000 配置ALLOW_HOST

  9. NameError: name 'pip' is not defined

    NameError: name 'pip' is not defined 直接去cmd下执行...pip pip install virtualenv

  10. 在C代码中调用C++接口

    一 在C源文件中调用C++封装的接口 例如: 要想在A.c文件中,调用生命在B.h,实现在B.cpp中的接口bool getMAC(char *mac_addr); 其实现方法 B.cpp 如下: / ...