Linux启动流程-init守护进程

一.Preface

最进在项目上碰到了很多与“守护(init)进程”相关的问题,例如有:

  • 制作根文件系统的时候需要去选择某个具体的“Init system”。
  • 在开机加速的问题上,需要对Init程序的脚本进行“裁剪”。
  • kernel的启动流程,Init进程作为系统的第一个进程也非常重要。

所以这里做一个笔记,方便日后复习。

二.What is "Init"

  • 内核启动的第一个用户空间进程,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行。
    • 创建Init进程,内核初始化(__init start_kernel函数)的最后一步调用reset_init函数,在内核态创建Init守护进程。
    • 内核态的Init进程的callback
      1. 设备驱动初始化。
      2. 通过uboot的bootargs(环境变量)设置console作为标准的input、output、error。
      3. 挂载filesystem。
      4. 在filesystem特定目录下查找具体的init程序并运行,init进程也会切换至用户态。
  • Init守护进程的实现方式分类
    • SysV init(System V init):最早的Linux发行版采用这种方式(包括imx6ull), 它采用脚本的形式来管理系统服务。在启动过程中,SysV init会按照特定的顺序启动系统服务,并运行对应的启动脚本。缺点是启动速度比较慢,因为它需要按照顺序逐个启动服务。
    • Upstart:Upstart是一种事件驱动的init程序,可以并行启动多个服务,提高系统启动速度。
    • Systemd:,目前最流行的init程序;让所有无论有无依赖关系的程序全部并行启动,并且仅按照系统启动的需要启动相应的服务,最大化提高开机启动速度。



三.What is the "Init" used for

这里根文件系统里的Init程序进行分析,因为笔者用的开发板采用"SysV init"守护进程,所以这里只分析"SysV init"守护进程。

1. 在“/sbin”目录下找到init程序并执行

2. init程序会读取/etc/initab文件,进行初始化

点击查看代码
# /etc/inittab: init(8) configuration.
# The default runlevel.
id:5:initdefault:

# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS

# What to do in single-user mode.
~~:S:wait:/sbin/sulogin

# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.
l0:0:wait:/etc/init.d/rc 0 # 当系统进入运行级别 0 时,init 进程会等待 /etc/init.d/rc 脚本执行完成,并将系统切换到运行级别 0,通常用于系统关闭。
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5 # 当系统进入运行级别 5 时,init 进程会等待 /etc/init.d/rc 脚本执行完成,并将系统切换到运行级别 5,通常用于启动图形界面环境。因为开机默认运行级别为5,所以开机时会默认执行rc5.d这个目录下的脚本。
l6:6:wait:/etc/init.d/rc 6
# Normally not reached, but fallthrough in case of emergency.
z6:6:respawn:/sbin/sulogin
#mxc0:12345:respawn:/bin/start_getty 115200 ttymxc0
mxc0:12345:respawn:/sbin/getty -l /bin/autologin -n -L 115200 ttymxc0

3.首先执行rcS脚本(si::sysinit:/etc/init.d/rcS)

点击查看代码
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel

#       Make sure proc is mounted
#
[ -d "/proc/1" ] || mount /proc

#
#       Source defaults.
#
. /etc/default/rcS


trap ":" INT QUIT TSTP

exec /etc/init.d/rc S  #在当前的 shell 进程中执行 /etc/init.d/rc 脚本。并向它传递一个参数 S。这里的 S 表示特殊启动(special boot),通常用于系统维护或单用户模式。

4.执行rc脚本(si::sysinit:/etc/init.d/rc)

点击查看代码
root@ATK-IMX6U:/etc/init.d# cat rc
#!/bin/sh

. /etc/default/rcS
export VERBOSE

# 启动进度条的更新
startup_progress() {
    step=$(($step + $step_change))
    if [ "$num_steps" != "0" ]; then
        progress=$((($step * $progress_size / $num_steps) + $first_step))
    else
        progress=$progress_size
    fi
    #echo "PROGRESS is $progress $runlevel $first_step + ($step of $num_steps) $step_change $progress_size"
    if type psplash-write >/dev/null 2>&1; then
        TMPDIR=/mnt/.psplash psplash-write "PROGRESS $progress" || true
    fi
    #if [ -e /mnt/.psplash/psplash_fifo ]; then
    #    echo "PROGRESS $progress" > /mnt/.psplash/psplash_fifo
    #fi
}

# Start script or program.
startup() {
  # Handle verbosity
  [ "$VERBOSE" = very ] && echo "INIT: Running $@..."

  case "$1" in
        *.sh)
                # Source shell script for speed.
                (
                        trap - INT QUIT TSTP
                        scriptname=$1
                        shift
                        . $scriptname
                )
                ;;
        *)
                "$@"
                ;;
  esac
  startup_progress
}

  # Ignore CTRL-C only in this shell, so we can interrupt subprocesses.
  trap ":" INT QUIT TSTP

  stty onlcr 0>&1

  [ "$STACK_SIZE" == "" ] || ulimit -S -s $STACK_SIZE      # 限制启动脚本的栈大小

  runlevel=$RUNLEVEL
  # Get first argument. Set new runlevel to this argument.
  [ "$1" != "" ] && runlevel=$1
  if [ "$runlevel" = "" ]
  then
        echo "Usage: $0 <runlevel>" >&2    #检查运行此运行级别的rc目录是否存在,如"/etc/rc2.d"
        exit 1
  fi
  previous=$PREVLEVEL
  [ "$previous" = "" ] && previous=N

  export runlevel previous

  # Is there an rc directory for this new runlevel?
  if [ -d /etc/rc$runlevel.d ]
  then
        # Split the remaining portion of the progress bar into thirds
        progress_size=$(((100 - $PROGRESS_STATE) / 3))    # 根据运行级别,分割进度条并设置进度条的起始点和步长。

        case "$runlevel" in
                0|6)
                        # Count down from -100 to 0 and use the entire bar
                        first_step=-100
                        progress_size=100
                        step_change=1
                        ;;
                S)
                        # Begin where the initramfs left off and use 2/3
                        # of the remaining space
                        first_step=$PROGRESS_STATE
                        progress_size=$(($progress_size * 2))
                        step_change=1
                        ;;
                *)
                        # Begin where rcS left off and use the final 1/3 of
                        # the space (by leaving progress_size unchanged)
                        first_step=$(($progress_size * 2 + $PROGRESS_STATE))
                        step_change=1
                        ;;
        esac

        num_steps=0
        for s in /etc/rc$runlevel.d/[SK]*; do      #根据运行级别,分割进度条并设置进度条的起始点和步长。
            case "${s##/etc/rc$runlevel.d/S??}" in
                gdm|xdm|kdm|reboot|halt)
                    break
                    ;;
            esac
            num_steps=$(($num_steps + 1))
        done
        step=0

        # First, run the KILL scripts.  先运行 K 脚本停止服务。
        if [ $previous != N ]
        then
                for i in /etc/rc$runlevel.d/K[0-9][0-9]*
                do
                        # Check if the script is there.
                        [ ! -f $i ] && continue

                        # Stop the service.
                        startup $i stop
                done
        fi

        # Now run the START scripts for this runlevel.  
        for i in /etc/rc$runlevel.d/S*    #运行 S 脚本启动服务,但会跳过那些在上一个运行级别已经运行且没有对应的 K 脚本的服务。
        do
                [ ! -f $i ] && continue

                if [ $previous != N ] && [ $previous != S ]
                then
                        
                        suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}
                        stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix
                        previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
                        
                        [ -f $previous_start ] && [ ! -f $stop ] && continue
                fi
                case "$runlevel" in
                        0|6)
                                startup $i stop
                                ;;
                        *)
                                startup $i start
                                ;;
                esac
        done
  fi

#Uncomment to cause psplash to exit manually, otherwise it exits when it sees a VC switch
if [ "x$runlevel" != "xS" ] && [ ! -x /etc/rc${runlevel}.d/S??xserver-nodm ]; then      # 如果不是 S 级别,并且没有 xserver-nodm 服务(即没有显示管理器),则发送信号让 Plymouth 退出。
    if type psplash-write >/dev/null 2>&1; then
        TMPDIR=/mnt/.psplash psplash-write "QUIT" || true
        umount -l /mnt/.psplash
    fi
fi

5.最后执行"rc5.d"目录下的脚本

可以在此处对一些不需要的应用进行裁剪。(注释:S表示start,K表示kill)



四.what's the problem with "init"?

若内核没有在根文件系统下没有找到"init"程序,则会启动失败报错。

posted @   Charles_hui  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示