# 开机

# Linux boot

Linux 系统启动(boot)的一般流程:

  1. 计算机接通电源,运行 ROM 中存储的 BIOS(Basic Input Output System) 系统。

  2. BIOS 检查 CPU、内存、硬盘等计算机硬件是否正常,该过程称为开机自检。

    • 如果检测到设备故障,则会在屏幕上显示报错信息。有的报错可以忽略,有的报错可能导致系统不能启动。
  3. BIOS 从硬盘的 MBR 扇区读取 Bootloader 程序,载入内存,运行它,即将 CPU 的控制权转交给它。

  4. Bootloader 从硬盘读取 Linux kernel 文件,载入内存,运行它其中的 start_kernel() 函数,即将 CPU 的控制权转交给它。

  5. start_kernel() 函数负责启动内核。

    • 首先执行 set_task_stack_end_magic(&init_task); ,配置当前进程的栈边界,防止栈溢出。当前进程称为 idle ,PID 为 0 。
    • 然后执行 setup_boot_config()、fork_init() 等函数进行初始化。
    • 最后执行 rest_init() 函数。
  6. rest_init() 调用 kernel_thread() 函数,创建第二个进程,名为 init ,PID 为 1 。

    • 它会初始化系统,然后通过 fork() 创建所有用户态进程。
  7. rest_init() 调用 kernel_thread() 函数, 创建第三个进程,名为 kthreadd ,PID 为 2 。

    • 它会调用 create_kthread() -> kernel_thread() 创建所有内核线程。
    • init、kthreadd 进程的 PPID 为 0 。
  8. rest_init() 循环让 CPU 执行休眠指令。

    • 当 CPU 空闲时,Linux 会让 CPU 执行 idle 进程,减少耗电量。而 idle 进程的优先级很低,当其它进程需要执行时,就会抢占 CPU 。
    • idle 进程是唯一一个不通过 fork() 或 kernel_thread() 创建的进程,运行在内核态,不能用 ps 命令查看到。

# Bootloader

常见的几种 Bootloader 程序:

  • grub(GRand Unified Bootloader)
    • :于 1995 年发布。是 GNU 项目编写的一个程序。
    • 它存储在 /boot/grub/ 目录下,允许用户在开机时从多个操作系统中选择一个启动。
    • grub 引导执行 Linux kernel 时,会先挂载一个临时的根文件系统 initrd(initial ram disk),以便 kernel 从硬盘找到并挂载 rootfs 根文件系统。
  • grub2
    • :于 2002 年发布,目前被很多 Linux 发行版采用。
    • 它完全重写了 grub ,提高了性能。
  • lilo
  • syslinux

# init

  • Linux 开机启动 init 进程时,一般执行 /sbin/init 程序。

    • 如果该文件不存在,则尝试启动 /bin/sh 等终端来代替init 进程。
    • 如果依然不存在,则 Linux 启动失败。
  • init 进程启动之后,首先负责初始化系统,包括:

    • 读取配置文件 /etc/inittab ,确定当前的运行级别。
    • 根据当前的运行级别,执行 /etc/rc*.d/ 目录下的 shell 脚本。
      • rc 是 Run Command 的缩写。
      • 这些脚本是指向 /etc/init.d/ 目录下的脚本的符号链接,主要用于启动一些系统服务,比如 crond、sshd 等。
      • 用户可以在 /etc/rc.local 中添加一些自定义的启动脚本。
  • init 进程启动之后,会以 daemon 方式保持运行,负责管理所有用户态进程,担任它们的父进程或祖先进程。

    • 如果其它进程产生了孤儿进程,则孤儿进程会自动从 init 进程的孙进程变成子进程,受 init 进程管理。
    • 用 service、chkconfig 命令可以与 init 进程交互,从而管理系统服务。
  • init 有七种运行级别(run level):

    • 0 :关机。
    • 1 :单用户模式(simple mode),又称为紧急模式(emergency mode),可以在忘了 root 密码时修改密码。
    • 2 :多用户模式。
    • 3 :终端模式。
    • 4 :保留。
    • 5 :图形界面模式。
    • 6 :重启。
  • 相关命令:

    init n      # 切换运行级别
    
    runlevel    # 显示当前的运行级别
    

# service

service
        <name>
        start         # 启动服务
        stop          # 停止服务
        restart       # 重启服务(先执行 stop 命令,再执行 start 命令)
        status        # 查看服务的状态
        --status-all  # 显示所有服务的状态

# chkconfig

chkconfig
        <name>        # 查看某服务是否开机自启动
        on            # 设置某服务开机自启动
        off           # 不开机自启动
        --list        # 列出所有已启动的服务

# systemd

  • 传统的类 Unix 系统中,用 init 进程来初始化系统。
    • 该 init 进程分为两种风格:
      • System V :本文描述的是这种。
      • BSD
  • Ubuntu 6 开始,用 Upstart init 进程取代了 System V init 进程。
    • /etc/init/*.conf 文件中定义一些被 Upstart init 管理的进程,称为 job 。
  • RHEL 7 、Ubuntu 15 开始,用 systemd 进程取代了 init 进程。
    • 用 systemctl 命令可以与 systemd 进程进行交互,从而管理系统服务。

# systemctl

systemctl
        start <unit>...         # 启动 unit(会将它变成 active 状态)
        stop <unit>...          # 停止 unit
        restart <unit>...       # 重启 unit(先执行 stop 命令,再执行 start 命令)
        reload <unit>...        # 重新加载 unit
        status <unit>...        # 显示 unit 的状态
        is-active <unit>...     # 显示 unit 是否处于 active 状态
        show <unit>...          # 显示 unit 的配置信息

        enable <unit>...        # 启用 unit
        disable <unit>...       # 不启用 unit
        is-enabled <unit>...    # 查看 unit 是否被启用(从而会开机自启)

        list-units              # 显示已启动的 unit
                --all           # 显示所有 unit
                --type=service  # 显示指定类型的 unit
                --failed        # 显示启动失败的 unit

        rescue                  # 使系统进入救援模式
        emergency               # 使系统进入紧急模式

# systemd unit

systemd 的管理对象称为 unit ,每个 unit 会保存一个特定扩展名的配置文件。主要类型如下:

xx.service  # 系统服务
xx.socket   # 进程间通信的 socket
xx.device   # 硬件设备
xx.mount    # 挂载点
xx.target   # 一组 unit

例:添加配置 /usr/lib/systemd/system/ping.service

[unit]                                          # 对该 unit 的通用描述
Description=A Demo of Ping                      # 名称
# Documentation=man:hello(8)                    # 帮助文档
# After=network.target sshd-keygen.service      # 需要在哪些 unit 之后启动
# Before=network-pre.target                     # 需要在哪些 unit 之前启动
# Conflicts=iptables.service ip6tables.service  # 与哪些 unit 冲突,即不能同时运行
# Wants=sshd-keygen.service                     # 依赖哪些 unit ,即需要同时运行

[Service]                                       # Service 类型的 unit 的专用配置
# User=root                                     # 用哪个用户来启动该 Service
# Group=root                                    # 用户组
# WorkingDirectory=/root                        # 工作目录
# Environment="A=1"                             # 添加环境变量,可以重复使用该参数
# Environment="B=2"
# EnvironmentFile=/etc/sysconfig/sshd           # 加载文件中的环境变量
Type=simple
ExecStart=/usr/bin/ping baidu.com               # 启动命令,会被 systemctl start 调用
ExecStop=/bin/kill -HUP $MAINPID                # 终止命令,会被 systemctl stop 调用。如果不指定 ExecStop ,则默认是发送 SIGTERM 信号给进程
ExecReload=/bin/kill -HUP $MAINPID              # 重载命令,会被 systemctl reload 调用(这里调用了 $MAINPID 变量以获取主进程的 PID )
# ExecStartPre=...                              # 启动之前需要预先执行的命令
# ExecStopPost=...                              # 停止之后需要执行的命令
KillMode=process                                # 终止该 Service 时的方式
# TimeoutStopSec=10s
Restart=on-failure                              # 当该 Service 的主进程退出时的重启策略
RestartSec=1s                                   # 等待多少秒才自动重启(默认是 100 ms)

[Install]                                       # 如何安装该 unit
WantedBy=multi-user.target                      # 被该 unit 依赖,就能实现开机自启
  • 每次修改 unit 的配置文件之后,要执行 systemctl daemon-reload 重新加载才能生效。
  • Service 的 Type 的常见取值:
    simple      # 默认值,表示 ExecStart 启动的进程是该 Service 的主进程(适合在前台运行的进程)
    forking     # ExecStart 启动的进程会 fork 出该 Service 的主进程(适合以 daemon 方式运行的进程)
    oneshot     # 与 simple 类似,但是 systemd 会等待该进程退出之后才启动下一个 unit
    notify      # 与 simple 类似,但是 systemd 会等收到该进程的通知之后才启动下一个 unit
    
  • Restart 策略的常见取值:
    no          # 默认值,不会重启
    always      # 总是重启
    on-failure  # 只有异常退出时才重启
    
  • KillMode 的常见取值:
    none            # 仅执行 ExecStop ,不检查进程是否退出
    process         # 如果主进程超过 TimeoutStopSec 时间之后还没退出,则发送 SIGTERM 信号,但是不管子进程
    mixed           # 向主进程发送 SIGTERM 信号,向子进程发送 SIGKILL 信号
    control-group   # 默认值,向 Cgroup 组中的所有进程发送 SIGKILL 信号
    

# 关机

  • 执行关机命令需要 root 权限。
  • 执行 init 0 关机时的一般流程:
    • 循环调用 close() ,关闭所有文件。
    • 调用 kill(-1, SIGTERM); ,终止所有进程。然后等待一定时长,再调用 kill(-1, SIGKILL); ,强制终止所有进程。
    • 执行 swapoff -a ,停用所有 swap 分区。
    • 执行 umount -a ,卸载所有文件系统。

# shutdown

shutdown [time] [msg]   # 在 1 分钟之后关机,并可以广播消息给所有用户
        now             # 立即关机
        10              # 在 10 分钟之后关机
        12:30           # 在指定时刻关机
        -c              # 取消即将进行的关机任务
        -r              # 重启,而不是关机
  • shutdown 命令会调用 init 0 来关机,然后断开电源,相当于 poweroff 命令。
  • halt 命令会停止运行操作系统,但计算机硬件没有断电。

# reboot

reboot      # 重启