设为首页 收藏本站
查看: 1451|回复: 0

[经验分享] linux启动原理之

[复制链接]

尚未签到

发表于 2018-5-15 12:05:18 | 显示全部楼层 |阅读模式
  init/main.c中的start_kernel函数完成了所有的全局特性初始化,这些全局特性包括内核运转所需要的基础设施,比如虚拟内存设施,进程调度设施,中断设施,缓存设施,VFS设施等,接下来启动1号进程的内核部分,在start_kernel的最后rest_init函数中启动之:
  static void noinline rest_init(void)
  {
      kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
      numa_default_policy();
      unlock_kernel();
       cpu_idle();
  }
  因此init/main.c中的init内核线程函数即是1号进程的内核部分,它完成内核的另一部分初始化之后即exec到1号进程的用户态,从此一直到关机或者重启,不再返回内核态,实际上exec本质上替换了进程地址空间,也就无从返回了。
       init函数主要进行另一部分的初始化,涉及驱动,网络协议栈,以及为1号进程用户态即init进程准备环境,其中最为重要的就是populate_rootfs函数,在启动initrd的情况下,最为重要的是它将initrd的内存写到了一个文件当中或者直接将initrd的内容写到整个rootfs:
  1.将内容写到文件:
  fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700);
  if (fd >= 0) {
      sys_write(fd, (char *)initrd_start, initrd_end - initrd_start); //将initrd的内容写入文件
      sys_close(fd);
      free_initrd_mem(initrd_start, initrd_end); //释放initrd原始内容所占用的内存
  }
  2.将内容直接放到rootfs:根据rootfs和initrd的内存地址信息直接写。
  可见sys_open调用创建了一个文件,即/initrd.image,它的内容就是initrd内存盘的内容,可是它在/下被创建,在linux中,所有的文件都要有一个“文件系统”作为载体,这个/目录所在的文件系统是什么呢?其实是一个内存盘,在start_kernel中的vfs_caches_init负责初始化文件系统,也就是VFS,这是一个虚拟文件系统的框架,其实现如下:
  void __init vfs_caches_init(unsigned long mempages)
  {
      unsigned long reserve;
  
      /* Base hash sizes on available memory, with a reserve equal to
             150% of current kernel size */
  
      reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1);
      mempages -= reserve;
  
      names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
              SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
      filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
              SLAB_HWCACHE_ALIGN|SLAB_PANIC, filp_ctor, filp_dtor);
      dcache_init(mempages);
      inode_init(mempages);
      files_init(mempages);
      mnt_init(mempages);
      bdev_cache_init();
      chrdev_init();
  }
  在mnt_init中初始化了一个rootfs:
  int __init init_rootfs(void)
  {
      return register_filesystem(&rootfs_fs_type);
  }
  然后在init_mount_tree中调用do_kern_mount挂载了这个rootfs作为“根”,接下来在init函数中创建/initrd.image就有所依托了,它就是在rootfs中创建了一个文件:/initrd.image,rootfs本质上是一个内存文件系统,因为此时还没有加载任何驱动,更别说磁盘,磁带,网卡驱动了,因此文件系统也只能是内存式的。linux的vfs功能十分强大,以文件作为接口使初始化过程如此简洁!
       接下来就要加载驱动了,do_basic_setup初始化了所有硬编译进内核的驱动,然而驱动并不一定要编译进内核,而这些驱动可能对于加载基于磁盘的根文件系统还至关重要,比如ide驱动,比如scsi驱动等,这些就由initrd来加载了,initrd的本质是一只鸡,也就是在有磁盘根文件系统这个“蛋”之前先要有一只“鸡”。
       接下来prepare_namespace登场,我们可以看到在populate_rootfs结束后分开了两条线索:
  if (sys_access((const char __user *) "/init", 0) == 0)
      execute_command = "/init";
  else
      prepare_namespace();
  如果initrd的内容被直接写到了rootfs,那么在rootfs的/目录中会有一个init程序,它可以是ELF程序,也可以是脚本,这是因为linux_binprm这个基础设施已经初始化过了。如果/下有init的话,那么这肯定就是cpio格式的initrd,反之则是image的,这一切都是在populate_rootfs中判断的,如果是cpio格式的,那么可以看到,全局变量execute_command被赋值为/init,然后直接执行,这样就完事了,cpio格式的initrd的/下有一个init程序,它一旦执行就使用exec进入了用户态,再也无法返回内核,最终自己将“真正的根文件系统”挂载,至于谁是真正的根,它需要命令行参数信息,这个信息是通过/proc文件系统得到的,由于内核并不处理挂载procfs的信息,这个proc文件系统需要initrd的/init程序自己挂载。而如果populate_rootfs判断不是cpio格式的,那么就会仅在/下建立一个/initrd.image文件,当然不会有/init存在了,于是就进入了prepare_namespace:
  void __init prepare_namespace(void)
  {
  ...
      if (saved_root_name[0]) {  //设置root文件系统的命令行参数,比如root=/dev/hda1
          root_device_name = saved_root_name;
          ROOT_DEV = name_to_dev_t(root_device_name); //设置ROOT_DEV
          if (strncmp(root_device_name, "/dev/", 5) == 0)
              root_device_name += 5;
      }
  ...
      if (initrd_load())
          goto out;
  ...
  }
  在initrd_load中会将/initrd.image挂载到/dev/ram0,然后将之挂载为“临时根”,chroot到这个临时根,fork出一个内核线程执行/linuxrc程序,这就是image格式的initrd的处理过程,在等待内核子线程执行完/linuxrc之后,1号进程继续在内核执行,由于/linuxrc可能已经加载了需要的磁盘驱动以及其它妨碍挂载真正根文件系统的驱动,内核此时就可以switch root到ROOT_DEV了,按照linux发行版的习惯,一般init程序在sbin下,也可能在bin下或者其它的什么地方,于是内核在这几个地方搜索:
  run_init_process("/sbin/init");
  run_init_process("/etc/init");
  run_init_process("/bin/init");
  如果实在没有找到,那么执行一个shell也行:
  run_init_process("/bin/sh");
  否则只有panic了。
       可见,image格式和cpio格式的initrd采用了截然不同的两种机制来支持initrd,哪一种更好呢?如果从机制和策略分离的角度当然是cpio的,cpio的initrd直接接手所有的工作-加载驱动和挂载根以及switch到根,这些显然都是用户要做的事,而image格式的initrd则需要内核帮忙把这几个步骤完成:
  1.内核执行/linuxrc,加载驱动,再准备一些别的;
  2.内核挂载根;
  3.内核switch到根。
  然而如果不从机制和策略角度分析,image格式的initrd也是一种很好的方式。另外还有一个问题,那就是/linuxrc一定要加载驱动吗?既然它是一个用户态的程序,内核能限制它里面怎么做吗?Debian 3的initrd就是一个现成的好例子,在Debian 3中,其initrd是image格式的,其linuxrc如下:
  #!/bin/sh
  # $Id: linuxrc,v 1.11 2004/04/26 12:04:46 herbert Exp $
  export PATH=/sbin:/bin
  mount -nt proc proc proc
  root=$(cat proc/sys/kernel/real-root-dev)
  echo 256 > proc/sys/kernel/real-root-dev
  mount -nt tmpfs tmpfs bin || mount -nt ramfs ramfs bin
  echo $root > bin/root
  并没有加载驱动,它首先挂载proc文件系统,然后往proc/sys/kernel/real-root-dev中写了一个256,基本这就完了!proc/sys/kernel/real-root-dev中保留的是一个kdev_t类型的数字:real_root_dev,它是一个设备号,记录着根设备的主次设备号,本来在handle_initrd中已经为该变量赋了值:
  real_root_dev = new_encode_dev(ROOT_DEV);
  而ROOT_DEV则是在prepare_namespace的最开始已经被赋值的,就是grub启动命令行中的root=XXX参数,可能是/dev/hda1之类的,在赋值的时候从/sys/block(需要sysfs的支持并且需要挂载sysfs系统)或者直接从参数本身得到信息,将一个字符串转化成成一个kdev_t的数字。然而/linuxrc将这个数字改成了256,也就是(1,0)这个设备号对。这就意味着,linuxrc调用完毕之后,内核将会加载(1,0)这个设备作为根,这是设备其实就是initrd本身!这发生在handle_initrd:
  pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
  if (pid > 0) {
      while (pid != sys_wait4(-1, &i, 0, NULL))
          yield();
  }
  ...
  ROOT_DEV = new_decode_dev(real_root_dev);
  mount_root();
  可见ROOT_DEV根据real_root_dev被重新赋了值,real_root_dev已经被linuxrc改了,因此mount_root的时候仍然mount的是initrd,也就是/dev/ram0,因此在init函数的最后,执行的仍然是initrd中的/sbin/init函数,当我们挂载这个initrd的时候,发现其中有一个sbin/init,该init是一个脚本,负责加载驱动,然后挂载真正的根文件系统,procfs中的根也就是变量real_root_dev由于已经被linuxrc重写了,因此在initrd的sbin/init中就必须从/proc/cmdline中重新解析出真正的根了。
       我不明白为何Debian 3如此设计initrd,是为了兼容还是别的什么原因,希望有人能告诉我。直接用cpio不更好吗?或者说,即使使用了image格式的initrd,为何不在linuxrc中将该做的都做完呢,而如此拐来拐去的又意义何在呢?这些都是问题!
  附:
  1.内核启动参数
  在grub中,我们可以在kernel后面跟上启动参数,比如console=x,root=x,init=x之类的,这些参数是如何被initrd接收的呢?实际上grub会把这些参数放到一个内存区段中,然后linux内核起来的时候,从该区段中将参数读到一个全局变量中:
  movl $boot_params,%edi
  movl $(PARAM_SIZE/4),%ecx
  cld
  rep
  并且procfs中有这个变量的访问接口/proc/cmdline,initrd只需要挂载procfs就可以得到内核的启动参数了。
  2.内核模块参数
  两种方式:
  之一:内核模块参数的名称和设置值的函数被编译到一个特定的“段”中,然后sys_init_module在调用模块的init函数之前会parse_args,这个过程和内核启动时在start_kernel中解析启动参数时所做的一样;或者在parse参数的时候仅仅将参数放到一个位置,模块的init函数中自己解析参数;
  之二:内核模块将参数名称和设置函数编译到内核参数一样的内存段中,完全使用内核参数解析的方式解析模块参数。
  为了节省内存,一般编译进内核的且在启动过程中至关重要不可缺失的参数采用__setup的形式将参数搞到init.setup段,在parse_early_param的时候解析,内核启动完毕后即释放,而动态加载的模块或者编译到内核但是并不是不可缺失的参数编译到__param段,这可以看链接脚本(所述的是2.6.32版本):/arch/x86/kernel/vmlinux.lds
  3.定制内核启动参数
  如果你在内核启动参数中写一个aaa=bbb也没什么,当系统起来以后aaa=bbb可以从/proc/cmdline被取出,你自己解释就可以了,另外你也可以设置aaa=ccc,然后在initrd的linuxrc或者sbin/init或者/init中解释它为root=/dev/hda1或者别的什么!
  4.initrd的做法
  之一:image格式的initrd做法
  此处略,很多资料都演示了这种做法
  之二:cpio格式的initrd做法
  需要说明的是,很多资料都直接引用内核Documentation目录下的initrd.txt中的做法,然而却不知何故修改了cpio命令的参数,原始的做法是:find . | cpio --quiet -H newc -o | gzip -9 -n > /boot/imagefile.img
  而被修改过的却是:
  bash# find . | cpio -c -o > ../initrd.img
  bash# gzip ../initrd.img
  不知何故,用此法做出的initrd无法使用。我推荐的是使用内核源码下的usr/gen_init_cpio工具来制作,也仅仅需要两步骤:
  一:先生成文件列表,这个需要首先准备好initrd的目录,该目录下存在init程序,lib/modules目录,etc目录等运行时目录,然后使用内核源码的scripts/gen_initramfs_list.sh脚本生成文件列表;
  二:使用gen_init_cpio工具针对生成的文件列表进行制作,一步即可。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-460537-1-1.html 上篇帖子: debian 语言配置 下篇帖子: debian用户登录与退出
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表