openwrt mtk7628nn启动流程分析

1 概述

       本文以mtk7628n openwrt为软件环境进行分析

       在linux的发展过程中,linux的启动程序也在发展,从sysv init到现在的upstart、systemd,通常该程序是进程号为1的进程,该程序在linux系统有着举足轻重的地方。在openwrt中,使 用了另外一种启动程序叫做procd,本文的重点并不在于介绍procd,本文主要介绍并解析procd、preinit及各种脚本如何完成整个系统的初始化。

2 启动顺序概述

       2.1 硬件启动       

  • 路由器cpu根据引脚高低电平选择spi flash启动还是nand flash启动

       2.2 软件启动

  • bootloader加载内核
  • 内核开动,同时扫描mtd文件系统分区
  • 内核执行/etc/preinit脚本
  • /etc/preinit执行/sbin/init二进制程序
  • /sbin/init根据/etc/inittab定义执行启动过程
  • /etc/inittab首先执行/etc/init.d/rcS,此脚本将顺序启动/etc/rc.d/目录以S开关的脚本

3 软件启动详解

        3.1  启动init进程

              位于linux-3.3.8/init/main.c

               汇编语言  b star_kernel----->start_kernel()--->rest_init()–→kernel_init()--→init_post()

              重点分析init_post()函数

              在openwrt系统中一般都会通过patch方式对源码进行修改,而对于init_post()函数,

             

              但是在QCA9531平台启动内核启动的代码中会作出如下修改,target/linux/generic/patches-3.4/921-use_preinit_as_init.patch

              

             删除了默认的启动方式,直接启动/etc/preinit。

             我们只分析mtk7628n平台的,从代码中可以看到init_post函数要启动init进程

        3.2 init进程的作用

             ./build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/procd-2014-09-15/initd/init.c

              

                 

            early()

                 ./build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/procd-2014-09-15/initd/early.c

  • mount /proc /sys /tmp /dev /dev/pts目录(early_mount)
  • 创建设备节点和/dev/null文件结点(early_dev)
  • 设置PATH环境变量(early_env)
  • 初始化/dev/console

          cmdline()

  • 根据/proc/cmdline内容init_debug=([0-9]+)判断debug级别

          watchdog_init()

  • 初始化内核watchdog(/dev/watchdog)
  • 如果存在/dev/watchdog设备,设置watchdog timeout等于30秒,如果内核在30秒内没有收到任何数据将重启系统。用户状进程使用uloop定时器设置5秒周期向/dev/wathdog设备写一些数据通知内核,表示此用户进程在正常工作

           加载内核模块

  • 创建子进程/sbin/kmodloader加载/etc/modules-boot.d/目录中的内核模块

           preinit()

                ./build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/procd-2014-09-15/initd/preinit.c

                

                

                      /sbin/init进程是来自procd这个package里面的,不再使用busybox了,而且它是从内核调用过来的,所以它的pid是1,pid 0是内核本身。fork创建父子进程,子进程做一些procd的配置后退出,注意这时procd并不算真正起来,它的pid不是1;父进程继续创建父子进程,

             子进程调用/etc/preinit后退出。在这过程中/sbin/init的pid为1,始终没有让位。

                   创建子进程执行/etc/preinit脚本时,此时PREINIT环境变量被设置为1,主进程(pid=1)同时使用uloop_process_add()把/etc/preinit子进程加入uloop进行监控,当/etc/preinit执行结束时回调plugd_proc_cb()函数把监控/etc/preinit进程对应对象中pid属性设置为0,表示/etc/preinit已执行完成
                   创建子进程执行/sbin/procd -h/etc/hotplug-preinit.json,主进程同时使用uloop_process_add()把/sbin/procd子进程加入uloop进行监控,当/sbin/procd进程结束时回调spawn_procd()函数,spawn_procd()函数繁衍后继真正使用的/sbin/procd进程,这时procd的进程号将是1。

             下面这个函数会用procd将/sbin/init进程替换,从而procd的进程号为1:

              

             最终大家在openwrt环境中看到的进程运行结果是

            

 3.3 procd进程的作用

       build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/procd-2014-09-15/state.c       

       

    

       procd有5个状态,分别为STATE_EARLYSTATE_INITSTATE_RUNNINGSTATE_SHUTDOWNSTATE_HALT,这5个状态将按顺序变化,当前状态保存在全局变量state中,可通过procd_state_next()函数使用状态发生变化

      STATE_EARLY状态 - init前准备工作

  • 初始化watchdog
  • 根据"/etc/hotplug.json"规则监听hotplug
  • procd_coldplug()函数处理,把/dev挂载到tmpfs中,fork udevtrigger进程产生冷插拔事件,以便让hotplug监听进行处理
  • udevstrigger进程处理完成后回调procd_state_next()函数把状态从STATE_EARLY转变为STATE_INIT

      STATE_INIT状态 - 初始化工作

  • 连接ubusd,此时实际上ubusd并不存在,所以procd_connect_ubus函数使用了定时器进行重连,而uloop_run()需在初始化工作完成后才真正运行。当成功连接上ubusd后,将注册service main_object对象,system_object对象、watch_event对象(procd_connect_ubus()函数),
  • 初始化services(服务)和validators(服务验证器)全局AVL tree
  • 把ubusd服务加入services管理对象中(service_start_early)
  • 根据/etc/inittab内容把cmd、handler对应关系加入全局链表actions中
  • 顺序加载respawnaskconsoleaskfirstsysinit命令
  • sysinit命令把/etc/rc.d/目录下所有启动脚本执行完成后将回调rcdone()函数把状态从STATE_INITl转变为STATE_RUNNING

      STATE_RUNNING状态

  • 进入STATE_RUNNING状态后procd运行uloop_run()主循环

 3.4 /etc/rc.d/*目录下启动脚本简介

      

     https://wiki.openwrt.org/doc/techref/process.boot

     S开头的表示要启动的

     K开头表示要杀掉的

     根据数字顺序表示启动顺序

4 参考文献

    https://wiki.openwrt.org/doc/techref/start

你可能感兴趣的:(openwrt)