Linux 介绍_4 系统进程

进程

我的博客
将以如下几个部分进行讨论:

  • 多用户与多任务
  • 进程类型
  • 使用不同的信号控制进程
  • 进程属性
  • 进程的生命周期
  • 系统启动过程以及关闭过程
  • SUID 以及 SGID
  • 系统速度与响应
  • 进程调度
  • Vixie 定时任务系统
  • 如何充分利用系统

注意,本文档中的部分内容对不同的版本,比如 RedHatDebian 的系统可能有所区别。

进程内外

多用户与多任务

并不是每一个命令都会启动一个单一的进程。一些命令比如 mozilla 会启动一系列进程;而 ls,只是作为单一的命令进行执行的。
Linux 基于 UNIX,它的基础策略就是在同一个系统上满足多用户执行多命令。显然需要有一种方法可以用来帮助 CPU 管理这些进程,且满足用户在进程间切换的功能性需求。在有些场景下,即便用户退出登录,依旧要保证进程持续运行。且用户需要有一种方法能够中断一个进程,并重新激活一个进程。

进程类型

交互式进程

交互式进程由终端会话初始化、控制。话句话说,需要有某个用户连接到系统,来启动这些进程;这些进程不是系统自动运行的一部分。这些进程可以运行在前台,占用着启动它的终端,因此只要这个进程保持运行,你就不能在这个终端窗口上启动其他的应用。当然,你也可以让进程在后台运行,这样启动它的终端可以在启动这个进程之后,依旧能够接受新的命令运行。现阶段,我们专注于保持在前台运行的进程,因为这些进程需要的运行时间是分段,比如使用 less 命令查看文件中内容就是一个占用终端窗口的很好的例子。这样的进程,激活的程序一般等待你进行某种操作。运行在前台的程序,与启动它的终端具有某种联系,在这个进程在终端的前台运行过程中,可以接受它能够理解的指令,并按照指令进行行动,其他的指令将会报错或不做响应。

当进程在后台运行时,用户可以在这个进程在后台运行过程中,在启动这个进程的终端窗口继续进行其他的操作。

shell 提供一个工作控制 job control 的机制来允许处理多进程。这个机制可以将进程在前台与后台之间切换。使用这个机制,程序可以在后台马上启动。

只有哪些不需要用户通过 shell 输入数据的程序在后台运行才有意义。将一个运行的程序放到后台运行,一般因为这个程序的运行比较耗费时间。为了在键入命令后释放终端,可以在命令的后面跟一个 & 符号。下面的例子中,使用图形化模式,我们从现有的终端窗口中打开一个新的终端窗口。

$ xterm &
[1] 26558

$ jobs
[1]+ Running xterm &

完整的工作控制特性在 bashinfo 页中有详细的讲解,所以只在下面列出常用的一些:

命令 含义
常规 command 在前台运行这个命令
command & 在后台运行这个命令(释放终端)
jobs 展示运行在后台的命令
Ctrl+Z 暂时挂起(只是停止,没有退出)在前台运行的进程
Ctrl+C 中断(终止并退出)运行在前台的进程
%n 每一个运行在后台的命令都分配一个编号,使用 % 加这个进程的编号,可以访问到这个进程,比如 fg %2
bg 重启一个挂起的进程,放到后台运行
fg 将进程放到前台运行
kill 结束一个进程

大部分 UNIX 系统可以运行 screen 命令,当你想要在另一个 shell 中执行命令时,可以使用这个命令。调用 screen 命令后,会创建一个新的会话。

自动化进程

自动化或批处理进程没有连接到终端上,这些进程作为任务排入一个队列中,在这里它们以 FIFO 的形式执行。这些任务通常在符合如下两个标准中的一个时执行:

  • 在指定的日期时间,通过 at 命令完成,后面会介绍到
  • 当总系统负载已经有足够的能力接受额外的任务时,使用 batch 命令。默认情况下,当系统负载低于 0.8 时,执行这个队列中的任务。在比较大的环境中,当具有大量的数据等待处理或任务需要大量的系统资源时,系统管理员偏向于使用这样的批处理方式。批处理方式也可以用来优化系统性能。

守护进程

守护进程式持续运行的服务进程。大部分时间内,它们在系统启动时初始化,并在后台持续等待,直到需要它们提供的服务。一个典型的例子是网络守护进程 xinetd,在大部分启动过程时开启,在系统启动完成后,网络守护进程等待,直到需要连接客户程序,比如需要连接 FTP 客户端。

进程属性

一个进程具有一系列的特性,可以使用 ps 命令进行查看:

  • 进程 IDPID,是进程唯一的编号
  • 父进程 IDPPID,启动这个进程的进程的唯一编号
  • Nice 编号,这个进程对其他进程的友好程度(不要与进程优先级弄混,进程优先级是基于这个 nice 编号与最近进程的 CPU 使用情况进行计算的)
  • 终端或 TTY,进程连接到的终端
  • 真实用户名 RUID 以及有效用户名 EUID,进程的拥有者,真实用户是发出命令的用户,有效用户是决定访问系统资源的用户。通常情况下 RUID 以及 EUID 是相同的,且进程拥有的权限与启动这个进程的用户拥有的进程相同。一个例子如下,在 /usr/bin 中的浏览器 mozilla(firefox) 属于 root 用户,但是普通用户也可以执行这个命令:
    $ ls -l /usr/bin/firefox
    lrwxrwxrwx 1 root root 25 3月  23  2021 /usr/bin/firefox -> ../lib/firefox/firefox.sh
    
    $ firefox &
    [1] 37210
    
    $ ps -af | grep firefox
    Arvin      37210   11583  4 21:29 pts/0    00:00:02 /usr/lib/firefox/firefox
    Arvin      37293   37210  0 21:29 pts/0    00:00:00 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -prefsLen 1 -prefMapSize 232463 -parentBuildID 20210318103112 -appdir /usr/lib/firefox/browser 37210 true tab
    Arvin      37342   37210  0 21:30 pts/0    00:00:00 /usr/lib/firefox/firefox -contentproc -childID 2 -isForBrowser -prefsLen 6161 -prefMapSize 232463 -parentBuildID 20210318103112 -appdir /usr/lib/firefox/browser 37210 true tab
    Arvin      37383   37210  0 21:30 pts/0    00:00:00 /usr/lib/firefox/firefox -contentproc -childID 3 -isForBrowser -prefsLen 7188 -prefMapSize 232463 -parentBuildID 20210318103112 -appdir /usr/lib/firefox/browser 37210 true tab
    Arvin      37406   11583  0 21:30 pts/0    00:00:00 grep firefox
    
    当普通用户 Arvin 启动这个进程,进程自身以及所有后续被启动的进程,属于 Arvin,而不是系统管理员。当 firefox 访问一个文件时,它对文件具有的权限与 Arvin 这个用户所拥有的权限一致。
  • 真实组 RGID 以及有效组 EGID,真实组是启动这个进程的用户所在的小组;有效组通常一致,除非 SGID 访问模式被应用到文件中

展示进程信息

使用 ps 命令是可视化进程的一种方法。这个命令具有一些可以组合的选项,来展示进程的不同属性。

在不使用选项时,ps 只展示当前 shell 的进程信息:

$ ps
    PID TTY          TIME CMD
  11583 pts/0    00:00:00 bash
  37472 pts/0    00:00:00 ps

因为这样使用通常不会打印我们需要的信息。一般情况下,系统上至少运行着几十个进程,通过管道使用 grep 命令,查看所有进程中我们需要的进程:

ps -ef | grep username

上面的命令,将会打印所有带有 username 的进程,下面的例子打印所有带有 bash 的进程:

$ ps -ef | grep bash
Arvin      11583   11551  0 12月19 pts/0  00:00:00 -bash
Arvin      37542   37540  0 21:50 pts/1    00:00:00 -bash
root       37588   37587  0 21:50 pts/1    00:00:00 bash
Arvin      37597   11583  0 21:50 pts/0    00:00:00 grep bash

下面的例子,展示所有的 bash 进程,大部分 Linux 系统的默认登录 shell:

$ ps -auxw | grep bash
Arvin      11583  0.0  0.0  15012  6592 pts/0    Ss   12月19   0:00 -bash
Arvin      37661  0.0  0.0  13936  4984 pts/1    Ss   21:52   0:00 -bash
root       37695  0.0  0.0  12912  4156 pts/1    S+   21:52   0:00 bash
Arvin      37705  0.0  0.0  11992   724 pts/0    S+   21:52   0:00 grep bash

bash shell 是一个特殊的案例,这个进程列表也展示了哪一个是登录 shell 的用户,这样的登录 shell 带有 -

更多关于 ps 的信息可以使用 ps --helpman ps 查看。GNU ps 支持不同类型的选项。

注意: ps 只会给出活动进程的瞬时状态,只是一次的记录情况。 top 命令展示每 5 秒更新的 ps 信息,定期生成最多负载的进程列表,同时从 proc 文件中继承正使用的交换内存以及 CPU 状态信息。

$ top
top - 22:03:29 up 10 days,  7:46,  2 users,  load average: 0.00, 0.00, 0.00
任务: 176 total,   1 running, 175 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  0.3 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7692.9 total,   5084.7 free,    414.6 used,   2193.6 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   6972.7 avail Mem

 进程号 USER      PR  NI    VIRT    RES    SHR    %CPU  %MEM     TIME+ COMMAND
      1 root      20   0  170696  12660   8260 S   0.0   0.2   0:06.73 systemd
      2 root      20   0       0      0      0 S   0.0   0.0   0:00.04 kthreadd
      3 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_gp

top 展示信息的第一行包含 uptime 命令展示的相同信息:

$ uptime
 22:07:01 up 10 days,  7:49,  2 users,  load average: 0.01, 0.01, 0.00

这些命令访问的数据存储在 /var/run/utmp 中,这个文件中包含当前连接用户的信息;还有 /proc 命令,比如 /proc/loadavg 平均负载信息。

进程间的信息可以通过 pstree 命令进行查看(下面的表格我删除了一下内容,看个大概就好):

$ pstree
systemd─┬─NetworkManager───2*[{NetworkManager}]
        ├─accounts-daemon───2*[{accounts-daemon}]
        ├─agetty
        ├─apache2─┬─apache2
        │         └─2*[apache2───26*[{apache2}]]
        ├─assist_daemon───7*[{assist_daemon}]
        ├─at-spi-bus-laun─┬─dbus-daemon
        │                 └─3*[{at-spi-bus-laun}]
        ├─at-spi2-registr───2*[{at-spi2-registr}]
        ├─atd
        ├─avahi-daemon───avahi-daemon
        ├─chronyd───chronyd
        ├─colord───2*[{colord}]
        ├─cron
        ├─dbus-daemon
        ├─gdm3─┬─gdm-session-wor─┬─gdm-x-session─┬─Xorg───9*[{Xorg}]
        │      │                 │               ├─dbus-run-sessio─┬─dbus-daemon
        │      │                 │               │                 └─gnome-session-b+++
        │      │                 │               └─2*[{gdm-x-session}]
        │      │                 └─2*[{gdm-session-wor}]
        │      └─2*[{gdm3}]
        ├─gjs───4*[{gjs}]
        ├─gsd-printer───2*[{gsd-printer}]
        ├─ibus-portal───2*[{ibus-portal}]
        ├─ibus-x11───2*[{ibus-x11}]
        ├─networkd-dispat
        ├─nmbd
        ├─polkitd───2*[{polkitd}]
        ├─rsyslogd───3*[{rsyslogd}]
        ├─rtkit-daemon───2*[{rtkit-daemon}]
        ├─smbd─┬─cleanupd
        │      ├─lpqd
        │      └─smbd-notifyd
        ├─sshd─┬─sshd───sshd───bash───pstree
        │      ├─sshd───sshd───bash───sudo───su───bash
        │      └─sshd───sshd───sftp-server
        ├─switcheroo-cont───2*[{switcheroo-cont}]
        ├─systemd─┬─(sd-pam)
        │         ├─dbus-daemon
        │         ├─goa-daemon───3*[{goa-daemon}]
        │         ├─goa-identity-se───2*[{goa-identity-se}]
        │         ├─gvfs-afc-volume───3*[{gvfs-afc-volume}]
        │         └─tracker-miner-f───4*[{tracker-miner-f}]
        ├─systemd─┬─(sd-pam)
        │         ├─at-spi-bus-laun───3*[{at-spi-bus-laun}]
        │         ├─dbus-daemon
        │         ├─goa-daemon───3*[{goa-daemon}]
        │         └─tracker-miner-f───4*[{tracker-miner-f}]
        ├─systemd-journal
        ├─systemd-logind
        ├─systemd-network
        ├─systemd-resolve
        ├─systemd-udevd
        └─wpa_supplicant

使用 -u 以及 -a 选项,可以给出额外的信息,更多的选项可以参考 info 页面。

进程生命周期

进程创建

现有的进程做了一个自身的完全复制,新进程就这样被创建出来。这个子进程与它的父进程有相同的环境,只有 PID 编号不同,这个过程称为 forking

forking 进程之后,子进程的地址空间由新进程的数据覆写,这是通过系统调用 exec 实现。

这个 fork-and-exec 机制就这样将旧的命令切换到新的命令,新程序执行的环境保留一致,包括输入、输出设备的配置,环境变量以及优先级。这个机制用来创建所有的 UNIX 进程,因此它也被应用到 Linux 操作系统中。即使是第一个进程 init,它的 PID1,也是在启动过程中以 bootstrapping 过程被 fork 出来的。

下面的关系阐述了 fork-and-exec 机制,在 fork 过程之后 PID 改变:

过程 实例 PID
- init 1
fork init 342
exec getty 342
exec login 342
exec sh 342
fork sh 631
exec grep 631

有很多的进程,不是由 init 启动,却成为了 init 进程的子进程,这在我们之前使用 pstree 已经看到过了。一些程序,守护着他们的子进程,保证即使在它们的父进程结束或被终止之后,依然能够确保这些子进程保持运行。一个窗口管理器就是这样的例子,它启动一个 xterm 进程生成一个 shell 来接收处理命令。窗口管理器之后拒绝任何的响应,之后将子进程传递给 init。使用这个机制,可以在不中断运行应用的情况下,修改窗口管理器。

当然总是会有一些事情会变得糟糕。当一个进程的父进程还没有等到它完成自身进程就结束了,那这样的子进程就称为僵尸进程。

结束进程

当进程正常结束(不是被杀死或其他非期望中断),程序向它的父进程返回它的退出状态 exit status,这个 exit status 是通过程序返回的数字,提供程序的执行结果。系统的返回信息基于它的 C 代码。

父进程、脚本可以通过返回码解析子进程的执行情况。返回码的值是由程序指定的。这个信息通常可以在指定程序的 man 页面中查看到,比如 grep 命令如果返回 -1,表示没有找到匹配项,会打印 "No files found" 字串。

信号

进程结束是因为他们接收到一个信号。存在多种信号,可以发送给进程。使用 kill 命令来发送信号到一个进程。命令 kill -l 展示信号列表。

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

大部分信号在系统内部使用,或在程序员编辑他们自己的程序时使用。作为一个用户,你需要了解如下的信号:

信号名 信号 含义
SIGTERM 15 以顺序地方式结束进程
SIGINT 2 中断进程,进程在实现的时候,可以忽略这个信号
SIGKILL 9 中断进程,进程实现时,必须接受并处理这个信号
SIGHUP 1 守护进程: 重新读取配置文件

SUID 与 SGID

特殊模式 SUID 以及 SGID,这些模式让普通用户能够执行一些他们在一般情况下因为严格的文件权限不能执行的任务。在理想情况下,特殊模式是要尽少使用的,因为这可能存在安全风险。Linux 开发者应该尽量避免使用它们。比如在Linux 中的 ps,使用存储在 /proc 文件中的信息,这个文件是每一个用户都可以访问的,这样就可以避免将系统的数据、资源等暴露给大众。在此之前,以及在一些老版本的 UNIX 系统中,ps 程序需要访问像 /dev/mem/dev/kmem 这样的文件,然而这些文件的权限是具有高度限制的:

$ ls -l /dev/*mem
crw-r----- 1 root kmem 1, 1 12月 17 14:17 /dev/mem

在老版本的 ps 中,以普通用户唤起这个程序是不行的,普通用户只有在特殊模式下才能应用它。

虽然我们一般会尽量避免应用任何的特殊模式,但总有些场景下,我们需要使用 SUID 模式。比如,修改密码时。当然,用户希望自己修改密码,而无需通过管理员之手。我们已经直到,用户名与密码在 /etc/passwd 文件中,这个文件具有如下的权限配置:

$ ls -l /etc/passwd
-rw-r--r-- 1 root root 2680 6月  23  2021 /etc/passwd

在这样的权限配置下,普通用户依然需要有能力自己修改这个文件中的信息,这是通过给定 passwd 程序特殊的权限实现的:

$ which passwd
/usr/bin/passwd

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 5月  28  2020 /usr/bin/passwd

当调用 passwd 命令时,会使用 root 的访问权限运行,因此允许普通用户修改属于系统管理者的 passwd 文件。

一个文件的 SGID 模式不像 SUID 这样的模式出现的这么频繁,因为 SGID 通常会涉及到创建额外的组。有时候,我们可能必须要解决这个额外组的问题。这是 write 以及 wall 程序通常会遇到的情况,这些命令可以用来发送信息到其他用户的终端(ttys)中。write 命令写信息到单个用户;wall命令写信息到所有的连接用户。

发送文本到另一个用户的终端或图形界面通常是不被允许的。为了跳过这个问题,创建了一个小组,这个小组包含了所有的终端设备。当 write 以及 wall 具有 SGID 权限时,这些命令将使用适用于该组的访问权限运行。因为这个组具有目标终端的写权限,因此没有这个权限的用户有能力实现向这个终端发送信息。

下面的例子种,用户 joe 首先使用 who 命令确定他当前连接到的终端。之后他使用 write 命令给她发送了一条信息。

joe:~> which write
write is /usr/bin/write
joe:~> ls -l /usr/bin/write
-rwxr-sr-x    1 root     tty      8744 Dec  5 00:55 /usr/bin/write*
joe:~> who
jenny     tty1     Jan 23 11:41
jenny     pts/1    Jan 23 12:21 (:0)
jenny     pts/2    Jan 23 12:22 (:0)
jenny     pts/3    Jan 23 12:22 (:0)
joe       pts/0    Jan 20 10:13 (lo.callhost.org)
joe:~> ls -l /dev/tty1
crw--w----    1 jenny   tty  4,     1 Jan 23 11:41 /dev/tty1
joe:~> write jenny tty1
hey Jenny, shall we have lunch together?
^C

用户 Jenny 会在她的屏幕上收到如下的信息:

Message from joe@lo.callhost.org on ptys/1 at 12:36 ...
hey Jenny, shall we have lunch together?
EOF

在接收这个信息之后,可以使用 Ctrl+L 组合键清屏。如果不想接收到除管理员外的信息,可以使用 mesg 命令实现。

启动过程,Init 以及 shutdown

介绍

Linux 最强大的一个方面是它启动和停止操作系统的开放方法,它使用指定的配置

启动过程,初始化以及关机

介绍

Linux 一个最强力的方面是它启动以及关闭操作系统的开放方法,它使用特定的配置来载入指定的程序,允许用户修改这些配置以控制启动过程,并能够以优雅地有组织性地方式进行关机。

在控制启动以及关闭进程之上,Linux 的这个开放特性能够帮助用户更简单地确定启动或关闭你的系统出现问题时的源。对这个过程的基础理解可以帮助每一位使用 Linux 系统的用户。

一些 Linux 系统使用 lilo 作为载入 Linux 的载体来启动操作系统。后面我们只会讨论 GRUB,它更加简单、灵活。如果对 lilo 感兴趣,可以自行搜索。

启动过程

当一台 x86 架构的设备启动时,处理器在系统内存末尾处查找 BIOS(Basic Input/Output System) 并运行 BIOSBIOS 存储在非易失只读存储器中,并总是可以访问到。BIOS 提供外设最低等级的访问接口,并控制启动过程的第一步。

BIOS 运行时会测试系统,查找并检测外设,之后查找一台设备来启动系统。通常它检查软驱(或一些新系统支持的 CD-ROM)作为可启动媒介,如果存在,之后它会查看硬盘。实际的启动顺序通常由 BIOS 的设置控制。如果 Linux 安装到系统的硬盘中,BIOS 从第一个硬盘的第一个扇区开始查找主启动记录(MBR: Master Boot Record),载入这些内容到内存中,之后将控制权移交给它。

这个 MBR 包含如何载入 GRUB boot-loaderLILO boot-loader 的指令。之后 MBR 载入 boot-loader,接管后续的过程。在默认的 Red Hat Linux 配置中,GRUB 使用 MBR 中的设置来在菜单中显示启动选项。如果 GRUB 从命令行或配置文件收到正确的指令来启动操作系统,它将查找需要的启动文件,并将设备的控制权移交给操作系统。

GRUB 特性

这个启动方法成为直接载入 direct loading,因为指令直接用来载入操作系统,在 boot-loader 以及操作系统的主文件(比如内核)之间没有中间代码。其他操作系统使用的启动过程可能与我们介绍的方法有所不同。比如微软的 DOSWindows 系统完全覆写 MBR 的内容。这将会摧毁所有其他操作系统存储的信息(比如 Linux)。微软的操作系统以及其他的部分操作系统,采用一种链式载入启动方法。这个方法下,MBR 指向存有操作系统的分区的第一个扇区,并在这里找到真正启动操作系统的特定文件。

GRUB 同时支持这两种启动方式,允许用户在几乎所有的操作系统、主流的文件系统、以及几乎所有的几乎所有的硬盘上使用。

GRUB 包含一些其他的特性,最重要的包括:

  • GRUBx86 提供一个在操作系统之前的基于命令的环境,允许以特定选项或系统的收集信息来载入操作系统

  • GRUB 支持逻辑块寻址LBA: Logical Block Addressing 模式,可以用来访问一些 IDE 以及所有的 SCSI 硬盘。在 LBA 之前,硬盘可能会遇到 1024 扇面限制,BIOS 不能在这之后的区域找到文件

  • GRUB 配置文件在启动时,都会从硬盘上读取一次,防止你修改了启动选项,而没有执行

Init

内核在载入之后,会在 sbin 中查找 init 并执行它。

init 启动后,它会成为所有自动在 Linux 系统中启动的进程的父进程或祖进程(我自己造的名词,看个大概意思就好了)。init 做的第一件事是,读取它的初始化文件 /etc/inittab,它指导 init 为环境读取初始化配置脚本,将会配置 PATH、启动交换内存,检测文件系统等。基本上,这一步骤会照看所有你的系统需要在系统初始化完成的内容: 设置时钟、初始化串口等。

init 持续从 /etc/inittab 文件中读数据,这个文件描述了系统应该怎样设置每一个运行等级,并设置默认的运行等级。一个运行等级是进程的配置。所有的类 UNIX 系统可以运行在不同的配置下,比如单用户模式的运行等级为 1S。在这个模式下,只有系统管理员能够连接到系统。这个模式用来执行维护任务,而避免了毁坏系统或用户数据的风险。自然情况下,在这个配置模式下,我们不必提供用户服务,因此它们都被关闭了。另一个运行等级是 reboot 运行等级,即等级 6,在这个运行等级下,将以合适的过程关闭所有运行服务,之后重启系统。

使用 who 来查看当前的运行等级:

$ who -r
         run-level 5 2021-12-17 20:17

在确定默认的运行等级之后,init 查看这个运行等级指定的 rc 目录,查看系统在这个运行等级下需要在后台运行的所有进程。initstop 参数运行每一个 kill 脚本(他们的文件名以 K 开头)。之后运行所有的该运行等级的 start 脚本(文件名以 S 开头),这样所有的服务与应用都能够正常的启动了。实际上,你可以在系统启动结束后,以 root 身份使用诸如 /etc/init.d/httpd stopservice httpd stop 这样的命令来手动的完整相同的动作。在这个案例中,是停止 web 服务器。

所有的启停脚本并不真正地位于 /etc/rc<x>.d 目录下,所有的 /etc/rc<x>.d 中的文件都是指向 /etc/init.d 中文件的符号链接。符号链接以一个特定的顺序进行编号,这样他们在启动时就会以这样的顺序进行。你可以通过修改符号链接的名称,来修改这个启停顺序。如果你想要确保一个服务在连一个服务之前启动,或一个服务在另一个服务之后启动,可以使用相同的编号多次。在下面的例子中,列出了 /etc/rc5.d 中的文件,在其中 crond 以及 xfs 都是以链接名 S90 启动。这个情况下,脚本以字母排序进行顺序执行。

$ ls /etc/rc5.d
K15httpd@     K45named@    S08ipchains@  S25netfs@      S85gpm@
K16rarpd@     K46radvd@    S08iptables@  S26apmd@       S90crond@
K20nfs@       K61ldap@     S09isdn@      S28autofs@     S90xfs@
K20rstatd@    K65identd@   S10network@   S30nscd@       S95anacron@
K20rusersd@   K74ntpd@     S12syslog@    S55sshd@       S95atd@
K20rwalld@    K74ypserv@   S13portmap@   S56rawdevices@ S97rhnsd@
K20rwhod@     K74ypxfrd@   S14nfslock@   S56xinetd@     S99local@
K25squid@     K89bcm5820@  S17keytable@  S60lpd@
K34yppasswdd@  S05kudzu@    S20random@    S80sendmail@

init 处理了默认运行等级,/etc/inittab 脚本 fork 为每一个虚拟控制台(以文本模式登录的提示符) getty 进程。getty 开启 tty 行,并设置他们的模式,打印登录提示符,获取到用户名,之后为这个用户初始化登录进程。这允许用户认证自身身份。默认情况下,大部分系统提供 6 个虚拟控制台,你可以查看 inittab 文件,这个数值是可以配置的。

/etc/inittab 也可以告知 init 它应该如何处理用户在终端中按下的 Ctrl+Alt+Delete 组合键。比如 init 被告知执行 /sbin/shutdown -t3 -r now,系统应该以合适的方式关机并重启,而不是马上执行重启。另外,如果你的系统连接有 UPS/etc/inittab 也给出了当电源失效时 init 的解决方案。

在大部分基于 RPM 的系统中,图形登录界面是以运行等级 5 启动的,/etc/inittab 会运行 /etc/X11/prefdm 脚本。这个脚本基于 /etc/sysconfig/desktop 目录运行偏好的 X 显示管理器,如果你运行 GNOME 通常是 gdm,如果你运行 KDE 通常是 kdm,当然也有标准的 Xxdm

/etc/default 以及 /etc/sysconfig 目录包含一系列的功能以及服务条目,这些是在启动时读取的。这些内容的位置,在不同的 Linux 版本而不同。

除了图形用户环境,一些其他的服务也会启动。如果一切正常,在启动阶段完成后,你会看到登录提示符,或图形环境的登录界面。

注意: 在这里我们介绍的是如何在 x86 架构上运行 SysV。在其他架构以及其他的版本的启动过程可能会不相同。比如,其他系统可能使用 BSD 风格的 init,这个风格的启动文件不会划分成多个 /etc/rc<Level>.d 的形式。你的系统也有可能使用 /etc/rc.d/init.d 而不是 /etc/init.d

Init 运行等级

以不同的运行等级运行系统的初衷是不同的系统可能用在不同的方式。一些服务不在特定的状态、模式下就不能使用,比如满足多用户使用。

有时候,你可能会想要以一个更低的等级对系统进行操作。比如以运行等级 1 修复硬盘崩溃问题,这样其他用户就不能在系统上进行操作了。再比如,让一个服务运行在没有 X 会话的等级 3 运行。在这些运行等级的情况下,运行一些依赖于较高运行等级的服务是不能实现的,因为它们不能正常的工作。通过将每一个服务预先分配到它应该有的运行等级上,可以保证顺序启动过程,同时也能够使你快速的修改设备运行模式,而不需要担心服务需要手动的启停。

可用的运行等级在 /etc/inittab 中描述,通常展示如下。

#
# inittab   This file describes how the INIT process should set up
#           the system in a certain run-level.
# Default run level. The run levels are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS 
#       (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
# 
id:5:initdefault:
<--cut-->

剩下的那个没有使用的运行等级,就自由的配置吧!一些用户会配置这个运行等级,离开默认等级,执行自己配置的运行等级,运行自己想要的结果。这允许他们快速的进入、退出自己定制的配置,而不需要修改标准运行模式的运行特性。

如果你的设备因为 /etc/inittab 文件而不能启动,或你的 /etc/passwd 文件崩溃而不能登录(或你只是单纯的忘记了密码),可以以单用户模式启动。

工具

如果 chkconfigupdate-rc.d 安装到了你的系统上,可以为你提供一个简单的命令行工具,来维护 /etc/init.d 目录等级。这解放了系统管理员比如直接操作 /etc/rd[x].d 目录下数量众多的符号链接。

有的系统提供 ntsysv 工具,提供一个基于文本的界面,这个工具相较于 chkconfig 命令行接口会更加好用一点。在 SuSE 的系统上,你会看到 yast 以及 insserv 工具。

大部分版本提供一个图形用户界面来配置进程。

所有的这些工具必须以超级用户身份使用。系统管理员可能会手动的在对应的运行等级的目录下创建启动或停止一个应用的符号连接。

关机

UNIX 被设计来是要持续运行的,如果一定要关机,可以使用 shutdown 命令。如果使用这个命令时,使用了 -h 选项,那么会停机;如果使用了 -r 选项,会重启。

reboot 以及 halt 命令现在可以在运行等级 1-5 种用来调用 shutdown,因此可以用来替代 shutdown 命令。但并不是所有的 UNIX/Linux 版本都有这两条命令的。

如果你的设备没有将自己关机,在你看到信息指示系统已经关闭或关机结束时,不要拔电源,以为系统提供时间来卸载分区。如果没有耐心的话,可能会导致数据丢失。

管理进程

消耗多长时间

Bash 提供一个内建的命令,展示命令执行消耗了多长时间。计时是十分精确的,并且可以用在所有的命令上。在下面的例子中,执行 ls 命令总共消耗了 0.001 秒。

$ time ls
bitbake  hello  mylayer  pyshtables.py  test  TT  unix_program

real    0m0.001s
user    0m0.001s
sys     0m0.000s

GNUtime 命令在 /usr/bin 目录下,能够展示更多的信息:

$ /usr/bin/time find .
0.03user 0.08system 0:00.96elapsed 12%CPU (0avgtext+0avgdata 4228maxresident)k
17056inputs+0outputs (0major+418minor)pagefaults 0swaps

性能

对一个用户而言,性能意味着更快速的执行命令。系统性能,会与成千上百的微小的事情有关:

  • 执行的程序优化的不好,或没有何理的使用设备
  • 访问硬盘、控制器、显示器等接口
  • 访问远程系统(网络性能)
  • 过多用户在系统中同时工作
  • ...

CPU 资源

如果不想你的系统耗费不必要的 CPU 资源,可以采用如下的策略:

  • 在负载较低时运行负载较重的程序,比如在晚上你不怎么使用设备的时候
  • 放置系统做一些不必要的工作,停止一些你不使用的程序以及守护进程,使用 locate 而不是 find,等
  • 以低优先级运行大型任务

如果这些操作对提升你的设备性能都没有帮助,那么,换台好的设备吧。

内存资源

如果当前运行的进程需要用到比系统实际物理内存更多的空间,Linux 系统并不会崩溃,它将会开始 pagingswaping,意味着进程将使用硬盘上的空间或交换内存空间,移动物理内存上的内容到硬盘上,依次来进行更多的操作。这会极大放慢系统运行速度,因为访问硬盘相较于访问内存更耗费时间。top 命令可以用来展示内存以及交换内存空间的使用情况。系统使用 glibc 提供 memusage 以及 memusagestat 命令来可视化内存使用情况。

如果你发现内存以及交换空间被用去了很大一部分,那么,你可以:

  • 杀死、停止或重新排列这些使用比较大的内存空间的程序
  • 为你的系统添加更多的内存

I/O 资源

psvmstat 以及 top 指示有多少程序在等待 I/Onetstat 展示网络接口状态,但是没有工具可以测算 I/O 对系统负载的响应,iostat 命令提供了一般 I/O 使用情况的摘要。每一个设备都具有它自己的问题,但是网络接口带宽以及硬盘带宽是两个主要的 I/O 性能瓶颈。

网络 I/O 问题:

  • 网络过载
    需要用网络传输的数据超出了网络负荷,导致所有用户网络相关的任务执行缓慢。可以通过清理网络(关闭不需要的特定的网络协议或网络服务)、重新配置网络(比如使用子网、使用交换机替代集线器、升级网络接口和设备)来解决。
  • 网络集成问题
    当数据没有正确传输时会发生。解决此类问题的方法只能通过隔离故障单元并更换它完成。

硬盘 I/O 问题:

  • 单个进程传输效率过低
    单个进程的读写速度不够
  • 总传输效率过低
    系统能够为所有程序提供的总带宽不够

数据过载,升级到更快的总线、控制器以及硬盘通常是最好的选择。
如果没有数据过载,那么你的硬件可能与系统连接存在问题,检查一下触点、连接器以及插头是否有问题。

中断你的进程

作为一个非特权用户,你只能影响你自己的进程。我们已经展示过如何查看进程以及过滤属于特定用户的进程。如果你看到你的进程中的一个吃了太多的系统资源,你可以做两件事:

  • 在不中断进程的前提下让进程使用更少的资源
  • 停止进程

如果你希望你的进程保持运行,但是不希望它吃更多的资源,你可以 renice 进程。除了使用 nicerenice 命令,top 是一个更加简单的方式来提出有问题的进程并降低进程优先级。详细的使用方式不再阐述(总体来说就是限制其能够调用的系统资源数量)。

如果你希望停止一个进程,使用 kill 命令。可以首先尝试通过发送 SIGTERM 信号来软杀死进程。无论这个进程在做什么,这个指令会终止进程:

joe:~> ps -ef | grep mozilla
joe    25822    1  0 Mar11 ?    00:34:04 /usr/lib/mozilla-1.4.1/mozilla-
joe:~> kill -15 25822

上面的例子中,用户 joe 因为 Mozzila 挂了,停止了他的 Mozzilla 浏览器。

可以使用 SIGINT 信号来中断进程,如果尝试多次没有效果,可以使用 SIGKILL 来杀死进程。

joe:~> ps -ef | grep mozilla
joe    25915    1  0 Mar11 ?    00:15:06 /usr/lib/mozilla-1.4.1/mozilla-
joe:~> kill -9 25915
joe:~> ps -ef | grep 25915
joe     2634 32273 0 18:09 pts/4   00:00:00 grep 25915

这种情况下,你可能希望看一下进程是否已经成功杀死,使用 grep 跟着 PID,如果只返回类似 joe 2634 32273 0 18:09 pts/4 00:00:00 grep 25915 的打印,那么你成功杀死了进程。

在所有的进程中,最难被杀死的就是你的 shell,这很容易理解,如果它很容易被杀死,那么你在终端中按下 Ctrl+C 组合键就杀死你的 shell,就有点离谱,因为它等同于 SIGINT

调度进程

使用空闲时间

可以等系统空闲时候执行命令,有三种类型的方式:

  • 等待一段时间,之后重新唤起执行工作,使用 sleep 命令。程序执行时刻依赖于提交执行的系统时间
  • 在指定时间点执行命令,使用 at 命令。执行工作依赖于系统时间,而不是提交时间
  • 以月、周、天、小时为基准,周期性地执行命令,使用 cron

sleep 命令

sleep 所做的工作就是等待,默认是以秒为计时单位。比如 1800 秒之后,在终端打印一个提示信息:

(sleep 1800;echo "Lunch time..")&

at 命令

at 在给定的时间点执行命令,并在未人为指定的情况下,使用默认的 shellat 命令的选项是十分友好的:

Arvin@XDD:~$ at tomorrow + 2 days
warning: commands will be executed using /bin/sh
at> cat reports | mail myboss@mycompanny
at> <EOT>
job 1 at Thu Jan  6 16:02:00 2022

按下 Ctrl + D 来退出 at 实例,生成 <EOT> 信息。

cron 以及 crontab

cron 系统由 cron 守护进程管理。它从系统以及用户的 crontab 条目中获取到要执行哪些程序,以及什么时候执行程序。只有 root 用户可以访问系统所有的 crontab。普通用户只能访问自己的 crontab,在一些系统中,用户没有访问 cron 的权限。

系统启 cron 守护进程时,会在 /var/spool/cron 中搜索 crontab 条目,它查找 /etc/cron.d/ 以及 /etc/crontab,之后每分钟检查依次,是否需要执行某个任务。

在使用 Vixie cron 的系统中,分别以小时、天、周、月周期执行的任务,被各自保存在 /etc 下的文件中。


Arvin@XDD:/etc$ more crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sa
t
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed

可以执行 crontab -l 看一些信息。

在上面的打印信息中,每一行一个任务,起始为 5 个时间描述字段,第一个字段包括分钟(059),第二个字段为小时(023),第三个字段是一个月份中的那一天(131),第四个字段为那个月(112),最后一个字段是每周的那一天(0~7,0、7 均为周日)。允许指定一系列时间点,比如,周一到周五每天都执行一个任务,在最后一个字段键入 1-5;在周一、周三以及周五执行任务,最后一个字段键入 1,3,5

posted @ 2021-12-30 22:27  ArvinDu  阅读(208)  评论(0编辑  收藏  举报