# 开机

# 系统启动

Linux 系统的启动(boot)过程:

  1. 计算机接通电源,从 ROM 读取 BIOS(Basic Input Output System) 系统,执行它。
  2. BIOS 通电检查 CPU、内存等计算机硬件是否正常。
  3. BIOS 从硬盘的第一个扇区读取 MBR 即 Bootloader 程序,运行它(即将 CPU 的控制权转交给它)。
    • Linux 系统常见的 Bootloader 程序是 grub ,存储在 /boot/grub/ 目录下,它允许用户在开机时从多个操作系统中选择一个启动。
  4. Bootloader 根据硬盘分区表读取 Linux 内核文件,运行它(即将 CPU 的控制权转交给它)。
  5. 内核创建的第一个进程是 idle ,PID 为 0 。
    • 是唯一一个不通过 fork() 或 kernel_thread() 创建的进程。
    • 会通过 kernel_thread() 创建 init 和 kthreadd 进程。
  6. 内核创建的第二个进程是 init ,PID 为 1 。
    • 负责初始化系统,然后创建、管理所有用户态进程。
  7. 内核创建的第三个进程是 kthreadd ,PID 为 2 。
    • 负责创建、管理所有内核态进程,是它们的父进程。

# init

  • 系统启动时,一般是通过运行 /sbin/init 来启动 init 进程。

    • 如果该文件不存在,则尝试启动 /bin/sh 等终端来取代 init 进程。
    • 如果依然不存在,则系统启动失败。
  • 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 进程,不过后来又被 systemd 进程取代。
    • /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      # 重启