FPs

htop 解釋

"Explanation of everything you can see in htop/top on Linux"

“解釋你在Linux 上htop/top 中看到的所有內容”

很長一段時間我都不清楚htop 中所有內容的意思。
我曾經以爲我的雙核機器上1.0的平均負載意味着CPU 利用率是50%。這並不完全正確。而且,爲什麼是1.0呢?

我決定查清楚,並記錄成這份文檔。

大家也都說,學習事物的最好方式是通過教別人。

Ubuntu Server 16.04 x64 上的htop

這是一張我要解釋的htop 截圖。
canyoukillit-before

Uptime

Uptime 顯示系統已經正常運行了多久。
你可以通過運行uptime 命令看到同樣的內容:

$ uptime
 12:17:58 up 111 days, 31 min,  1 user,  load average: 0.00, 0.01, 0.05

uptime 程序是怎麼獲取這些信息的?
它是從/proc/uptime文件中讀取這些信息。

9592411.58 9566042.33

第一個值是系統總共已經運行的秒數。第二個值是機器空閒的總秒數,在多核機器上第二個值可能某些時刻會大於系統總體的運行時間,因爲它是多個核心的總空閒時間。

我怎麼知道的?我查看了uptime 程序運行的時候打開了哪些文件。我們可以用strace 工具來跟蹤。

strace uptime

執行上述命令會得到一大堆輸出。我們可以使用grep 過濾出open 的系統調用。但是這樣不會起作用,因爲strace 會把所有內容輸出到標准錯誤(stderr)流。我們用2>&1可以把標准錯誤輸出重定向到標准輸出(stdout)流。

我們得到的輸出如下:

$ strace uptime 2>&1 | grep open
...
open("/proc/uptime", O_RDONLY)          = 3
open("/var/run/utmp", O_RDONLY|O_CLOEXEC) = 4
open("/proc/loadavg", O_RDONLY)         = 4

其中包含我提到的/proc/uptime文件。

其實你也可以用strace -e open uptime,不必用grep 過濾。

所以,如果我們可以從這些文件中讀取內容,爲什麼還需要uptime 程序呢?這是因爲uptime的輸出格式對人來說更友好,不過在你的程序或者腳本裏還是使用秒數方便一些。

平均負載

除了正常運行時間,另外還有三個數值表示平均負載。

$ uptime
 12:59:09 up 32 min,  1 user,  load average: 0.00, 0.01, 0.03

它們是從/proc/loadavg文件中讀取的。如果你再看一眼strace的輸出,你也可以看到這個文件被打開了。

$ cat /proc/loadavg
0.00 0.01 0.03 1/120 1500

前三列數分別表示系統最近1分鐘,5分鐘和15分鐘的平均負載。第四列數表示目前正在運行的進程數和總進程數。最後一列顯示了最近一次運行的進程ID。

讓我們從最後一個數開始。

你每次啓動一個新進程時,都會被分配一個ID 號。進程ID 通常是逐漸增大,除非它們已經耗盡,並被重複使用。進程ID 1 屬於/sbin/init,它會在系統啓動的時運行。

在看一遍/proc/loadavg的內容,並在後臺執行sleep命令。當它在後臺啓動時,它的進程ID 會顯示出來。

$ cat /proc/loadavg
0.00 0.01 0.03 1/123 1566
$ sleep 10 &
[1] 1567

所以1/123 意味着這一刻只有一個進程在運行,並且總共有123個進程。

當你運行htop 時,只看到只有一個正在運行的進程的話,這個進程就是htop 自身。

如果你執行sleep 30,然後再次運行htop,你會看到還是只有一個正在運行的進程。這是因爲sleep沒在運行,它正處於休眠或空閒狀態,或者說是在等待某事發生。一個正在運行的進程的定義是當前正在某個物理CPU 上運行,或者等待調度到CPU 上運行的進程。

如果你執行cat /dev/urandom > /dev/null,這個命令會不斷生成隨機的字節,並寫入到一個特殊的無法被讀取的文件,你將會看到有2個正在執行的進程。

$ cat /dev/urandom > /dev/null &
[1] 1639
$ cat /proc/loadavg
1.00 0.69 0.35 2/124 1679

這樣現在有2個運行中的進程(隨機數生成和讀取/proc/loadavg內容的cat),同時你也會注意到平均負載升高了。

平均負載表示一段時間內系統的平均負載。

負載是通過統計運行中的進程(正在運行或者等待運行)和不可中斷(uninterruptible)進程(等待磁盤或網絡的相應)數量計算得到的。簡單來說是一些進程的總數。

所以平均負載是最近1分鐘,5分鐘和15分鐘內這些進程的平均數,對嗎?

實際上沒這麼簡單。

平均負載是負載的指數移動平均。摘自維基百科:

從數學上來講,這三個值都是系統起來以來的平均值。它們都是指數級衰減的,不過衰減速度不同。因此,1分鐘的平均負載是63% 的最近一分鐘的負載,再加上37%的系統啓動以來除去最近一分鐘的負載得到的。所以,1分鐘的平均負載只包含最近60秒的情況從技術上看是不準確的(因爲它還包含了過去37% 的情況),只是大部分是最近一分鐘的情況。

(譯註:Load (computing)移動平均

這是你意料之中的嗎?

讓我們再看看我們的隨機數生成:

$ cat /proc/loadavg
1.00 0.69 0.35 2/124 1679

儘管從技術上看是不準確的,但是爲了便於理解,還是把平均負載簡單化。

在這個例子中,生成隨機數的進程是CPU 密集型(CPU Bound),所以最近一分鐘的平均負載是1,或者說最近一分鐘平均有1個正在運行的進程。

因爲我的系統上只有一顆CPU,一顆CPU上同一時刻只能運行一個進程,所以CPU 利用率是100%。

如果有2個核心,CPU 利用率將會是50%,因爲同一時刻可以運行2個進程。2個核心的計算機的CPU 利用率達到100% 的話,它的平均負載會是2.0

你可以在htop 左上角或者執行nproc 命令,看到你的CPU 核心數。

因爲負載數還包括出於不可中斷狀態的進程,但是這些進程不會太影響CPU 利用率,所以從平均負載推斷CPU 利用率不太準確。這也解釋了你可能遇到過的平均負載很高但是CPU 不忙的情況。

不過也有一些例如mpstat 這樣的工具可以顯示即時的CPU 利用率。

$ sudo apt install sysstat -y
$ mpstat 1
Linux 4.4.0-47-generic (hostname)   12/03/2016      _x86_64_        (1 CPU)

10:16:20 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
10:16:21 PM  all    0.00    0.00  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
10:16:22 PM  all    0.00    0.00  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
10:16:23 PM  all    0.00    0.00  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
# ...
# kill cat /dev/urandom
# ...
10:17:00 PM  all    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
10:17:01 PM  all    1.00    0.00    0.00    2.00    0.00    0.00    0.00    0.00    0.00   97.00
10:17:02 PM  all    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00

那爲什麼我們還要使用平均負載呢?

$ curl -s https://raw.githubusercontent.com/torvalds/linux/v4.8/kernel/sched/loadavg.c | head -n 7
/*
* kernel/sched/loadavg.c
*
* This file contains the magic bits required to compute the global loadavg
* figure. Its a silly number but people think its important. We go through
* great pains to make it work on big machines and tickless kernels.
*/

(譯註:總的來說,平均負載十分愚蠢, tickles 內核參考:无嘀嗒内核),Linux Tick 和 Tickless

進程

htop 右上角顯示了進程總數和運行中的數量,不過它顯示的是任務(Task)而不是進程,這是爲什麼呢?

進程的另一個名字是任務。Linux 內核內部把進程稱爲任務。htop 使用任務(Task)來代替進程(Process)可能是因爲它(Task)更短,更節省屏幕空間。

你可以在htop中看到線程。按下ShiftH 鍵可以切換到顯示線程的模式。如果你看到Tasks: 23, 10 thr,即顯示了線程。

你也可以看到內核的線程,按下ShiftK鍵。它們會顯示的是Tasks: 23, 40 kthr

進程 ID / PID

每次新建一個進程的時候,都會分配給它一個標識的數字(ID),也稱爲進程ID 或簡稱爲PID。

如果你在bash 裏運行一個後臺(&)的程序,你會看到方擴號裏的任務號和PID。

$ sleep 1000 &
[1] 12503

如果你錯過這些內容,在bash 裏面可以用$!這個變量,它會顯示最近的後臺進程的ID。

$ echo $!
12503

進程ID 非常有用。可以用它來查看進程的細節和控制進程。

procfs 是一個虛擬文件系統,用來讓用戶程序通過讀取文件獲取內核的信息。procfs通常掛載在/proc/,對你來說,它看起來想一個普通的文件目錄,你可以使用lscd進行瀏覽。

所有關於一個進程的信息都在/proc/<pid>/

$ ls /proc/12503
attr        coredump_filter  fdinfo     maps        ns             personality  smaps    task
auxv        cpuset           gid_map    mem         numa_maps      projid_map   stack    uid_map
cgroup      cwd              io         mountinfo   oom_adj        root         stat     wchan
clear_refs  environ          limits     mounts      oom_score      schedstat    statm
cmdline     exe              loginuid   mountstats  oom_score_adj  sessionid    status
comm        fd               map_files  net         pagemap        setgroups    syscall

舉個例子,/proc/<pid>/cmdline 包含運行這個進程所用的命令。

$ cat /proc/12503/cmdline
sleep1000$

額,不對。實際上這個命令是以\0字節分隔的。

$ od -c /proc/12503/cmdline
0000000   s   l   e   e   p  \0   1   0   0   0  \0
0000013

我們可以用空格或者換行符替換\0

$ tr '\0' '\n' < /proc/12503/cmdline
sleep
1000
$ strings /proc/12503/cmdline
sleep
1000

一個進程的目錄可以包含鏈接!例如,cwd指向當前的工作目錄,exe指向可執行的二進制文件。

$ ls -l /proc/12503/{cwd,exe}
lrwxrwxrwx 1 ubuntu ubuntu 0 Jul  6 10:10 /proc/12503/cwd -> /home/ubuntu
lrwxrwxrwx 1 ubuntu ubuntu 0 Jul  6 10:10 /proc/12503/exe -> /bin/sleep

這就是htop,top,ps和其他診斷工具獲取進程詳細信息的方式:
都是通過讀取/proc/<pid>/<file>的內容。

進程樹

當啓動一個新進程的時候,啓動這個新進程的那個進程被稱爲父進程。新的進程是父進程的一個子進程。它們是樹狀結構的關係。

如果你在htop裏按F5鍵,就可以看到分層的進程。

你也可以使用psf選項:

$ ps f
PID TTY      STAT   TIME COMMAND
12472 pts/0    Ss     0:00 -bash
12684 pts/0    R+     0:00  \_ ps f

或者pstree

$ pstree -a
init
├─atd
├─cron
├─sshd -D
│   └─sshd
│       └─sshd
│           └─bash
│               └─pstree -a
...

這也就是爲什麼你可能經常看到bash或者sshd 是一些進程的父進程的原因。

當你在bash裏運行date,會發生以下這些事:

  • bash 新建一個進程,這個進程是它自身的拷貝(使用fork系統調用)
  • 接着從可執行文件/bin/date加載程序到內存中(使用exec系統調用)
  • bash作爲父進程將等待直到它的子進程退出

ID 爲1 的/sbin/init是在啓動時運行的,它生成了SSH 守護進程sshd。當你連接到計算機時,sshd將產生一個會話進程,這個會話進程再啓動bash

我喜歡在htop中使用樹狀圖,當我想看到所有線程的時候。

進程用戶

每個進程都屬於一個用戶,通過一個數字標識用戶。

$ sleep 1000 &
[1] 2045
$  grep Uid /proc/2045/status
Uid:    1000    1000    1000    1000

你可以用id命令找出這個UID 的用戶名。

$ id 1000
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm)

實際上id 是從/etc/passwd/etc/group文件中獲取信息。

$ strace -e open id 1000
...
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libnss_compat.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3
...

這是因爲Name Service Switch (NSS)的配置文件/etc/nsswitch.conf表示通過這些文件解析名稱。

$ head -n 9 /etc/nsswitch.conf
# ...
passwd:         compat
group:          compat
shadow:         compat

compat(兼容模式:Compatibility mode)這個配置項和files作用一樣,除了支持一些特殊的條目。files表示數據存在一個文件中(通過libnss_files.so加載)。不過你也可以把你的用戶信息存在其他數據庫或者服務中,例如使用輕型目錄訪問協議(LDAP)等。

/etc/passwd/etc/group是純文本文件,將數字化的用戶ID 映射到對人類可讀的名稱。

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
$ cat /etc/group
root:x:0:
adm:x:4:syslog,ubuntu
ubuntu:x:1000:

passwd?哪裏有密碼?

它們實際上是在/etc/shadow中。

$ sudo cat /etc/shadow
root:$6$mS9o0QBw$P1ojPSTexV2PQ.Z./rqzYex.k7TJE2nVeIVL0dql/:17126:0:99999:7:::
daemon:*:17109:0:99999:7:::
ubuntu:$6$GIfdqlb/$ms9ZoxfrUq455K6UbmHyOfz7DVf7TWaveyHcp.:17126:0:99999:7:::

這些亂七八糟的內容是什麼?

  • $6$是使用的密碼hash 算法,現在這個表示的是sha512
  • 緊跟着的是隨機產生的鹽,防止彩虹表攻擊
  • 最後是密碼+鹽的hash 值

當你運行一個程序的時候,它會以你的用戶運行。即時這個可執行文件不屬於你。

如果你想以root或者其他用戶運行程序,可以使用sudo

$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm)
$ sudo id
uid=0(root) gid=0(root) groups=0(root)
$ sudo -u ubuntu id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm)
$ sudo -u daemon id
uid=1(daemon) gid=1(daemon) groups=1(daemon)

如果你想要登錄其他用戶的帳號來運行更多的命令該怎麼做?可以使用sudo bashsudo -u user bash。你將能以其他用戶的身份使用shell。

如果你不喜歡每次都被要求輸入root 密碼,把你的用戶名添加到/etcsudoers文件中就能關閉它。

讓我們試一試:

$ echo "$USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
-bash: /etc/sudoers: Permission denied

好吧,只有root 才有權限。

$ sudo echo "$USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
-bash: /etc/sudoers: Permission denied

我擦,什麼情況?

現在你是以root 用戶執行了echo命令,不過追加內容到/etc/sudoers還是以你的用戶身份。

通常有兩種方法解決這個問題:

  • echo "$USER ALL=(ALL) NOPASSWD: ALL" | sudo tee -a /etc/sudoers
  • sudo bash -c "echo '$USER ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers"

第一個例子中,tee -a將標準輸出的內容追加到文件中,我們是以root 用戶執行這個命令的。

第二個例子中,我們以root 用戶運行bash,並讓它執行一條命令,這條命令會以root 用戶執行。 注意這裏面複雜的單引號和雙引號,它們決定了$USER標量什麼時候被展開。

如果你看一眼/etc/sudoers文件,在開頭你會看到:

$ sudo head -n 3 /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#

哎呦。

這是一個有用的警告,說你應該使用sudo visudo來編輯這個文件。它會在你保存文件之前校驗內容,防止發生錯誤。 如果你沒有使用visudo而導致錯誤的話,將把你鎖定無法使用sudo。這意味着你沒法修正錯誤。

假設你想要修改你的密碼,你可以使用password命令。如前面提到的,它會把密碼保存在/etc/shaow文件中。

這個文件非常敏感,只有root 用戶才能寫:

$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1122 Nov 27 18:52 /etc/shadow

所以普通用戶執行的password程序是怎麼可能寫入內容到一個守保護的文件呢?

我前面說過當你執行一個程序的時候,它是以你的身份執行的,即使這個可執行文件的所有者是另外一個用戶。

實際上你可以通過修改文件權限改變這個行爲。讓我們看看。

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 54256 Mar 29  2016 /usr/bin/passwd

注意s的字母。這是通過sudo chmod u+s /usr/bin/passwd加上的。它表示一個可執行文件會以文件的所有者的身份被執行,這裏是以root 用戶執行。

你可以用find /bin -user root -perm -u+s 找到具有setuid權限的可執行文件。

注意你也可以對用戶組執行相同的操作(g+s)。

進程狀態

接下來我們看一下htop中用字母s表示的進程狀態這一列。

幾種可能的值:

R    running or runnable (on run queue) 運行中或者即將運行
S    interruptible sleep (waiting for an event to complete) 中斷睡眠
D    uninterruptible sleep (usually IO) 不可中斷睡眠
Z    defunct ("zombie") process, terminated but not reaped by its parent 殭屍進程
T    stopped by job control signal 被制信號停止
t    stopped by debugger during the tracing 被debugger 停止
X    dead (should never be seen) 死亡

我把它們按照出現的頻率如上排序。

注意當你執行ps的時候,也會出現例如SsR+Ss+等狀態。

$ ps x
PID TTY      STAT   TIME COMMAND
1688 ?        Ss     0:00 /lib/systemd/systemd --user
1689 ?        S      0:00 (sd-pam)
1724 ?        S      0:01 sshd: vagrant@pts/0
1725 pts/0    Ss     0:00 -bash
2628 pts/0    R+     0:00 ps x

R - running or runnable (on run queue)

這種狀態下的進程,要麼是正在運行中,要麼就是在等待運行的隊列中。

什麼是運行?

當你從源代碼編譯你的程序後,得到的機器代碼其實是CPU 指令。它們被保存到文件中等待執行。當你加載程序的時候,它們被加載到內存中,接着CPU 會執行這些指令。

基本上這個狀態意味着CPU 在執行指令,或者說在處理數學運算。

S - interruptible sleep (waiting for an event to complete)

這個狀態表示該進程的代碼指令此刻沒在CPU 上運行。相反,進程正在等待一個事件或者一條觸發條件產生。當事件發生時,內核會將狀態設置爲運行中。

一個例子是核心工具包裏的sleep(譯註:GNU核心工具组(英语:GNU Core Utilities,亦常缩写为Coreutils),參考 GNU核心工具组)。它能睡眠指定的秒數(大致上)。

$ sleep 1000 &
[1] 10089
$ ps f
PID TTY      STAT   TIME COMMAND
3514 pts/1    Ss     0:00 -bash
10089 pts/1    S      0:00  \_ sleep 1000
10094 pts/1    R+     0:00  \_ ps f

這是可中斷的睡眠。那我們怎麼中斷它?

通過發送信號。

htop裏你可以按下F9然後在菜單左則選擇一個信號來發送。

發送信號也被稱爲kill。這是因爲kill是一個系統調用,它可以給一個進程發送信號。/bin/kill 程序可以從用戶空間發起系統調用,默認的信號是TERM,這個信號會讓進程退出,或者說會殺死進程。

信號只是一個數字。不過數字很難記,所以我們給它們取了名字。信號名稱常常大寫,並以SIG爲開頭。

一些常用的信號是INTKILLSTOPCONTHUP

讓我們給sleep 進程發送INT(也可稱爲SIGINT2Terminal interrupt)信號中斷睡眠。

$ kill -INT 10089
[1]+  Interrupt               sleep 1000

當你按下CTRL鍵和C鍵的時候,也會發生以上現象。bash會像我們剛剛那樣,給所有後臺程序發送SIGINT信號。

順便說一下,killbash內置的命令,雖然在大多數系統上有/bin/kill。爲什麼呢?這是爲了當你創建的進程數量達到限制時,還可以用它來殺死進程。

下列命令是做同樣一件事:

  • kill -INT 10089
  • kill -2 10089
  • /bin/kill -2 10089

另外一個有用的信號是SIGKILL,又稱爲9。當你狂按CTRLC鍵不起的作用時,你可能曾用過它來殺死過進程。

當你寫一個程序時,你可以寫一些信號處理函數,當你的程序收到信號的時就會調用這些函數。換句話說,你可以捕獲信號來做一些事情。舉個例子,做一些清理工作和優雅的關閉程序。 所以發送SIGINT(用戶想要中斷一個進程)和SIGTERM(用戶想要終止一個進程)並不意味着進程能夠被終止。

你可以見過這個異常,當運行Python 腳本的時候:

$ python -c 'import sys; sys.stdin.read()'
^C
Traceback (most recent call last):
File "<string>", line 1, in <module>
KeyboardInterrupt

你可以通過發送KILL型號讓內核強制終止一個進程,不讓它有機會響應(譯註:原文是 not give it a change to respond ,懷疑是不是作者打錯了chance)。

$ sleep 1000 &
[1] 2658
$ kill -9 2658
[1]+  Killed                  sleep 1000

D - uninterruptible sleep (usually IO)

不同於可中斷睡眠,你無法用信號喚醒這個狀態下的進程。這也就是爲什麼很多人怕看到這個狀態的原因。你不能殺死這樣的進程,以爲殺死意味着給進程發送SIGKILL 信號。

如果進程必須等待並不能被中斷,或者有事件會馬上發生,就會用這個狀態。比如從磁盤讀取內容。但是這只能發生幾秒鐘。

StackOverflow 上一個不錯的解答

Uninterruptable processes are USUALLY waiting for I/O following a page fault. The process/task cannot be interrupted in this state, because it can't handle any signals; if it did, another page fault would happen and it would be back where it was.

換句話說,如果你使用網絡文件系統(NFS),這種清空就會發生,從NSF 讀寫文件需要花一段時間。

根據我的經驗,這也意味着你的一些進程多次讀寫交換分區,空閒的內存空間不夠用了。

我們試一試讓一個進程進入不可中斷狀態。

8.8.8.8是一個Google 提供的公用DNS 服務器。它們沒有提供一個開發的NFS。不過這不能阻止我們。

$ sudo mount 8.8.8.8:/tmp /tmp &
[1] 12646
$ sudo ps x | grep mount.nfs
12648 pts/1    D      0:00 /sbin/mount.nfs 8.8.8.8:/tmp /tmp -o rw

如何找出進入這個狀態原因?strace

讓我們用strace跟蹤ps 上面的命令:

$ sudo strace /sbin/mount.nfs 8.8.8.8:/tmp /tmp -o rw
...
mount("8.8.8.8:/tmp", "/tmp", "nfs", 0, ...

mount這個系統調用阻塞了進程。

如果你想知道的話,你可以運行mount 時加上intr選項,讓它運行在中斷模式下:sudo mount 8.8.8.8:/tmp /tmp -o intr

Z - defunct ("zombie") process, terminated but not reaped by its parent

當進程通過exit 退出後,子進程還存在的話,它的子進程會變成殭屍進程。

  • 如果殭屍進程短時間存在,這很正常。
  • 殭屍進程長時間存在的話,辨明程序有一個bug
  • 殭屍進程不會消耗進程,它只是一個進程ID
  • 你不能kill 一個殭屍進程
  • 你可以請求父進程回收殭屍進程(SIGHLD信號)
  • 你可以kill 殭屍進程的父進程,以此去除父進程和它的殭屍進程

我將寫一段C 代碼來演示這個。

這是我的程序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    printf("Running\n");

    int pid = fork();

    if (pid == 0) {
        printf("I am the child process\n");
        printf("The child process is exiting now\n");
        exit(0);
    } else {
        printf("I am the parent process\n");
        printf("The parent process is sleeping now\n");
        sleep(20);
        printf("The parent process is finished\n");
    }

    return 0;
}

我們來安裝GNU C 編譯器(GCC)。

sudo apt install -y gcc

編譯並運行。

gcc zombie.c -o zombie
./zombie

看下進程樹。

$ ps f
PID TTY      STAT   TIME COMMAND
3514 pts/1    Ss     0:00 -bash
7911 pts/1    S+     0:00  \_ ./zombie
7912 pts/1    Z+     0:00      \_ [zombie] <defunct>
1317 pts/0    Ss     0:00 -bash
 7913 pts/0    R+     0:00  \_ ps f

我們得到了殭屍進程。

當父進程退出之後,殭屍進程也退出了。

$ ps f
PID TTY      STAT   TIME COMMAND
3514 pts/1    Ss+    0:00 -bash
1317 pts/0    Ss     0:00 -bash
 7914 pts/0    R+     0:00  \_ ps f

如果你用sleep 20代替while (true) ;,殭屍進程會馬上退出。

通過exit退出,所有申請的內存和資源會馬上釋放,以供其他進程使用。

父進程可以使用wait系統調用找到子進程的退出代碼(在信號處理函數中)。如果一個進程正在睡眠狀態,需要等待它醒來。

爲什麼不簡單粗暴的喚醒進程,然後殺死它?同樣的原因,你也不會在厭煩你孩子的時候把它丟進垃圾桶。後果很嚴重。

T - stopped by job control signal

我打開了兩個終端窗口,用ps u可以看到我的用戶進程。

$ ps u
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu    1317  0.0  0.9  21420  4992 pts/0    Ss+  Jun07   0:00 -bash
ubuntu    3514  1.5  1.0  21420  5196 pts/1    Ss   07:28   0:00 -bash
ubuntu    3528  0.0  0.6  36084  3316 pts/1    R+   07:28   0:00 ps u

下文的輸出中我會忽略-bashps u進程。

現在在一個終端中運行cat /dev/urandom > /dev/null。它的狀態是R+,意味着它在運行中。

$ ps u
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu    3540  103  0.1   6168   688 pts/1    R+   07:29   0:04 cat /dev/urandom

按下CTRLZ鍵,終止進程。

$ # CTRL+Z
[1]+  Stopped                 cat /dev/urandom > /dev/null
$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu    3540 86.8  0.1   6168   688 pts/1    T    07:29   0:15 cat /dev/urandom

現在它的狀態是T

在第一個終端運行fg,恢復它。

另外一個終止進程的方法是通過kill發送STOP信號。你可以用CONT型號,讓進程恢復執行。

t - stopped by debugger during the tracing

首選,安裝GNU Debugger(gdb)

sudo apt install -y gdb

運行一個進程,它會在1234 端口上監聽進入的網絡連接。

$ nc -l 1234 &
[1] 3905

它在睡眠中意味着它正在等待網絡數據。

$ ps u
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu    3905  0.0  0.1   9184   896 pts/0    S    07:41   0:00 nc -l 1234

運行debugger,連接上ID是3905 的進程。

sudo gdb -p 3905

你會看到進程狀態變爲t,意味着這個進程正在被debugger 跟蹤。

進程時間片

Linux 是一個多任務的操作系統,這意味着即使你只有一顆CPU,也可以同時跑多個進程。當你的Web server 通過互聯網把你的博客內容分發給讀者的時候,你可以通過SSH 連接到你的服務器,看一下htop的輸出內容。

一顆CPU同一時刻只能運行一條指令,那這是怎麼做到的?

答案是分時。

一個進程運行一點時間,接着它進入睡眠,因爲其他進程在等待輪流運行。進程運行的一小段時間被稱謂時間片。

一個時間片通常是幾毫秒,所以在系統負載不高的時候,你不會注意到它。(找出Linux 的時間片是多長很有意思。)

這應該可以解釋爲什麼平均負載是運行中的平均進程數。如果你只有一個核心,並且平均負載是1.0,則CPU 利用率是100%。如果平均負載大於1.0,這意味着等待運行的進程超過CPU 可以運行的數量,所以這時你可能會感覺到卡頓。如果平均負載低於1.0,意味着CPU 有時空閒着不做事。

這應該讓你有了思路,爲什麼有時運行一個執行時間爲10秒的進程,運行時間可能長於或者短於10秒。

進程友好度和優先級

當你有超過CPU H核心數的進程需要運行的時候,你得想個方式決定下一步運行哪些進程,以及讓哪些進程排隊等待。這就是任務調度器的工作。

Linux 內核的調度器負責從運行隊列中選出下一個運行的進程,具體的選取方法取決於內核使用的調度算法。

通常你沒法影響調度器,不過你可以讓調度器知道哪些程序對你來說更重要,調度器會特別關注一下。

友好度(NI)是進程的用戶空間優先級,範圍從-20(優先級最高)到19(優先級最低)。這可能讓人有點費解,不過你可以這樣想,一個友好的進程會謙讓一個不友好的進程。所以一個進程越友好,它謙讓得越多。

通過閱讀StackOverflow 和其他站點,我總結整理了下,一個進程的Nice 值每提高1,會讓出超過10% 的CPU 時間。

優先級(PRI)是Linux 內核使用的內核空間的優先級。優先級的範圍是0到139,其中0到99 用於實時進程,100到139 用於用戶進程。

你可以改變進程的友好度,內核會考慮到,不過你沒法改變優先級。

nice 值和優先級的關係是:

PR = 20 + NI

所以PR=20 + (-20 to +19) 的值是0 到39,對應的優先級是100到139 。
你可以在啓動一個進程前設置它的友好度。

nice -n niceness program

可以用renice改變運行中的進程的友好度。

renice -n niceness -p PID

以下是不同顏色的CPU 利用率的含義:

  • 藍色:低優先級的線程(nice > 0)
  • 綠色:普通優先級的線程
  • 紅色:內核線程

http://askubuntu.com/questions/656771/process-niceness-vs-priority

內存使用情況 - VIRT/RES/SHR/MEM

一個進程會有它是內存裏面唯一一個的錯覺。這是通過虛擬內存實現的。

進程沒有直接訪問物理內存的權限。取而代之的是,它有自己的虛擬地址空間,內核會把虛擬地址空間轉換到物理內存地址,或者映射到磁盤上。這就是爲什麼看起來進程使用的內存超過你計算機上實際的內存。

在這裏我想提出的是弄清楚一個進程到底使用了多少內存不太容易。你也想統計貢獻庫和磁盤映射的內存?內核提供的和htop 展示的一些信息可以幫你估算內存使用情況。

不同顏色的內存使用量的含義:

  • 綠色:已使用的內存
  • 藍色:Buffer
  • 橘黃色:Cache

VIRT/VSZ - 虛擬鏡像

The total amount of virtual memory used by the task. It includes all code, data and shared libraries plus pages that have been swapped out and pages that have been mapped but not used.

VIRT是虛擬內存使用量。它包括所有東西,包括映射的文件。

如果一個應用程序申請了1GB 內存,但是實際只用了1MB,VIRT也會顯示 1GB。如果它mmap一個1GB 的文件,但不使用,VIRT也會顯示爲 1GB。

大多數時候,這個值沒啥用。

RES/RSS - 常駐大小

The non-swapped physical memory a task has used.

RES是常駐內存的使用量,即當前物理內存使用量。

雖然RES相比VIRT可以更好的表示進程的內存使用量,不過請記住

  • 它不包括交換出的內存(譯註:即在swap 上的內容)
  • 可能包括和其他進程的共享內存

如果一個進程使用了1GB 內存,它調用fork(),fork 的結果是有2 個進程,它們的RES都是1GB,不過實際上只用了1GB,因爲Linux 的寫入時複製(譯註:寫入時複製)。

SHR - 共享內存大小

The amount of shared memory used by a task. It simply reflects memory that could be potentially shared with other processes.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    printf("Started\n");
    sleep(10);

    size_t memory = 10 * 1024 * 1024; // 10 MB
    char* buffer = malloc(memory);
    printf("Allocated 10M\n");
    sleep(10);

    for (size_t i = 0; i < memory/2; i++)
        buffer[i] = 42;
    printf("Used 5M\n");
    sleep(10);

    int pid = fork();
    printf("Forked\n");
    sleep(10);

    if (pid != 0) {
        for (size_t i = memory/2; i < memory/2 + memory/5; i++)
            buffer[i] = 42;
            printf("Child used extra 2M\n");
    }
    sleep(10);

    return 0;
}
fallocate -l 10G
gcc -std=c99 mem.c -o mem
./mem
Process  Message               VIRT  RES SHR
main     Started               4200  680 604
main     Allocated 10M        14444  680 604
main     Used 5M              14444 6168 1116
main     Forked               14444 6168 1116
child    Forked               14444 5216 0
main     Child used extra 2M        8252 1116
child    Child used extra 2M        5216 0

TODO: I should finish this.

MEM% - 內存使用率

A task's currently used share of available physical memory.

RES 除以 總物理內存大小。

如果RES是500M,你有8G內存,MEM% 將是400/8192*100 = 4.88%

進程

我在Digital Ocean 上啓動了一臺Ubuntu Server。

系統啓動的時候會運行哪些進程呢?
你真的需要它們嗎?

我在Digital Ocean 新啓動了一臺Ubuntu Server 16.04.1 LTS x64 ,這是關於它的開機啓動程序的調研筆記。

開始之前

canyoukillit-before

/sbin/init

The /sbin/init program (also called init) coordinates the rest of the boot process and configures the environment for the user.

When the init command starts, it becomes the parent or grandparent of all of the processes that start up automatically on the system.

是Systemd 嗎?

$ dpkg -S /sbin/init
systemd-sysv: /sbin/init

是的,就是它。

如果你kill 掉它會發生什麼?

什麼都不會發生(譯註:How does systemd survive a kill -9?)。

/lib/systemd/systemd-journald

systemd-journald is a system service that collects and stores logging data. It creates and maintains structured, indexed journals based on logging information that is received from a variety of sources.

換種說法:

One of the main changes in journald was to replace simple plain text log files with a special file format optimized for log messages. This file format allows system administrators to access relevant messages more efficiently. It also brings some of the power of database-driven centralized logging implementations to individual systems.

你應該用journalctl命令來查詢日誌。

  • journalctl _COMM=sshd sshd 日誌
  • journalctl _COMM=sshd -o json-pretty JSON 格式的sshd 日誌
  • journalctl --since "2015-01-10" --until "2015-01-11 03:00"
  • journalctl --since 09:00 --until "1 hour ago"
  • journalctl --since yesterday
  • journalctl -b 啓動以來的日誌
  • journalctl -f 滾動更新日誌
  • journalctl --disk-usage
  • journalctl --vacuum-size=1G

太酷了。

看起來沒法禁用或者移除這個服務,你只能關閉日誌記錄。

/sbin/lvmetad -f

The lvmetad daemon caches LVM metadata, so that LVM commands can read metadata without scanning disks.

Metadata caching can be an advantage because scanning disks is time consuming and may interfere with the normal work of the system and disks.

什麼是LVM(逻辑分卷管理器)?

You can think of LVM as "dynamic partitions", meaning that you can create/resize/delete LVM "partitions" (they're called "Logical Volumes" in LVM-speak) from the command line while your Linux system is running: no need to reboot the system to make the kernel aware of the newly-created or resized partitions.

聽起來如果你在用LVM 的話,應該保留這個服務。

$ lvscan
$ sudo apt remove lvm2 -y --purge

/lib/systemd/udevd

systemd-udevd listens to kernel uevents. For every event, systemd-udevd executes matching instructions specified in udev rules.

udev is a device manager for the Linux kernel. As the successor of devfsd and hotplug, udev primarily manages device nodes in the /dev directory.

所以這個服務是管理dev的。

我不確定在虛擬機上是否需要它。

/lib/systemd/timesyncd

systemd-timesyncd is a system service that may be used to synchronize the local system clock with a remote Network Time Protocol server.

所以這是用來代替ntpd的。

$ timedatectl status
Local time: Fri 2016-08-26 11:38:21 UTC
Universal time: Fri 2016-08-26 11:38:21 UTC
RTC time: Fri 2016-08-26 11:38:20
Time zone: Etc/UTC (UTC, +0000)
Network time on: yes
NTP synchronized: yes
 RTC in local TZ: no

如果我們看一下服務器上打開的端口:

$ sudo netstat -nlput
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      2178/sshd
tcp6       0      0 :::22                   :::*                    LISTEN      2178/sshd

贊!

之前的Ubuntu 14.04 是這樣的:

$ sudo apt-get install ntp -y
$ sudo netstat -nlput
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1380/sshd
tcp6       0      0 :::22                   :::*                    LISTEN      1380/sshd
udp        0      0 10.19.0.6:123           0.0.0.0:*                           2377/ntpd
udp        0      0 139.59.256.256:123      0.0.0.0:*                           2377/ntpd
udp        0      0 127.0.0.1:123           0.0.0.0:*                           2377/ntpd
udp        0      0 0.0.0.0:123             0.0.0.0:*                           2377/ntpd
udp6       0      0 fe80::601:6aff:fxxx:123 :::*                                2377/ntpd
udp6       0      0 ::1:123                 :::*                                2377/ntpd
udp6       0      0 :::123                  :::*                                2377/ntpd

額。

/usr/sbin/atd -f

atd - run jobs queued for later execution. atd runs jobs queued by at.

at and batch read commands from standard input or a specified file which are to be executed at a later time

不同於週期性重複執行任務的cron,at 只會在指定時刻執行一次任務。

$ echo "touch /tmp/yolo.txt" | at now + 1 minute
job 1 at Fri Aug 26 10:44:00 2016
$ atq
1       Fri Aug 26 10:44:00 2016 a root
$ sleep 60 && ls /tmp/yolo.txt
/tmp/yolo.txt

實際上迄今爲止我都沒用過它。

sudo apt remove at -y --purge

/usr/lib/snapd/snapd

Snappy Ubuntu Core is a new rendition of Ubuntu with transactional updates - a minimal server image with the same libraries as today’s Ubuntu, but applications are provided through a simpler mechanism.

什麼?

Developers from multiple Linux distributions and companies today announced collaboration on the “snap” universal Linux package format, enabling a single binary package to work perfectly and securely on any Linux desktop, server, cloud or device.

顯而易見這是一個簡化的deb 包,你可以將所有依賴打進一個Snappy 來分發。

我從來沒在服務器上用Snappy 部署或分發過應用程序。

sudo apt remove snapd -y --purge

/usr/bin/dbus-daemon

In computing, D-Bus or DBus is an inter-process communication (IPC) and remote procedure call (RPC) mechanism that allows communication between multiple computer programs (that is, processes) concurrently running on the same machine

我的理解是桌面環境或者在服務器上運行web 應用才需要這個?

sudo apt remove dbus -y --purge

我想知道現在是什麼時候,是否和NTP 保持同步。

$ timedatectl status
Failed to create bus connection: No such file or directory

糟糕。應該保留這個的。

/lib/systemd/systemd-logind

systemd-logind is a system service that manages user logins.

/usr/sbin/cron -f

cron - daemon to execute scheduled commands (Vixie Cron)

-f Stay in foreground mode, don't daemonize.

你可以安排cron 週期性的重複執行任務。

crontab -e來編輯你的配置,不過我更傾向於使用/etc/cron.hourly/etc/cron.daily等目錄。

你可以用以下方法查看日誌文件

  • grep cron /var/log/syslog 或者
  • journalctl _COMM=cron 甚至
  • journalctl _COMM=cron --since="date" --until="date"

你可能想保留cron。

如果不想的話,應該停止和禁用這個服務。

sudo systemctl stop cron
sudo systemctl disable cron

因爲如果我們嘗試通過apt remove cron卸載它的話,系統會安裝postfix!

$ sudo apt remove cron
The following packages will be REMOVED:
cron
The following NEW packages will be installed:
  anacron bcron bcron-run fgetty libbg1 libbg1-doc postfix runit ssl-cert ucspi-unix

看起來cron 需要通過一個郵件傳輸代理(MTA)來發送郵件。

$ apt show cron
Package: cron
Version: 3.0pl1-128ubuntu2
...
Suggests: anacron (>= 2.0-1), logrotate, checksecurity, exim4 | postfix | mail-transport-agent

$ apt depends cron
cron
...
Suggests: anacron (>= 2.0-1)
Suggests: logrotate
Suggests: checksecurity
|Suggests: exim4
|Suggests: postfix
Suggests: <mail-transport-agent>
...
exim4-daemon-heavy
    postfix

/usr/sbin/rsyslogd -n

Rsyslogd is a system utility providing support for message logging.

換句話說,它產生/var/log下的日誌文件,例如記錄了SSH登錄的認證消息的/var/log/auth.log

配置文件在/etc/rsyslog.d目錄下。

你也可以配置rsyslogd 發送日誌到遠程服務器,實現集中記錄日誌。

你可以在後臺腳本裏(例如開機啓動的腳本)使用logger命令,把消息記錄到/var/log/syslog

1
2
3
4
5
#!/bin/bash

logger Starting doing something
# NFS, get IPs, etc.
logger Done doing something

對了,我們已經運行着systemd-journald,還需要rsyslogd嗎?

Rsyslog and Journal, the two logging applications present on your system, have several distinctive features that make them suitable for specific use cases. In many situations it is useful to combine their capabilities, for example to create structured messages and store them in a file database. A communication interface needed for this cooperation is provided by input and output modules on the side of Rsyslog and by the Journal's communication socke

所以,可能需要吧?以防萬一我決定留着它。

/usr/sbin/acpid

acpid - Advanced Configuration and Power Interface event daemon

acpid is designed to notify user-space programs of ACPI events. acpid should be started during the system boot, and will run as a background process, by default.

In computing, the Advanced Configuration and Power Interface (ACPI) specification provides an open standard that operating systems can use to perform discovery and configuration of computer hardware components, to perform power management by, for example, putting unused components to sleep, and to do status monitoring.

但是我是在虛擬機裏,我不打算休眠/恢復。

我要移除它看看會發生什麼。

sudo apt remove acpid -y --purge

我能夠成功reboot,但是halt 之後Digital Ocean任務它還在運行,所以不得不在web 界面上關閉電源。

所以我應該留着它。

/usr/bin/lxcfs /var/lib/lxcfs/

Lxcfs is a fuse filesystem mainly designed for use by lxc containers. On a Ubuntu 15.04 system, it will be used by default to provide two things: first, a virtualized view of some /proc files; and secondly, filtered access to the host’s cgroup filesystems.

In summary, on a 15.04 host, you can now create a container the usual way, lxc-create ... The resulting container will have “correct” results for uptime, top, etc.

It’s basically a userspace workaround to changes which were deemed unreasonable to do in the kernel. It makes containers feel much more like separate systems than they would without it.

不用LXC 容器?你可以移除它。

sudo apt remove lxcfs -y --purge

/usr/lib/accountservice/accounts-daemon

The AccountsService package provides a set of D-Bus interfaces for querying and manipulating user account information and an implementation of these interfaces based on the usermod(8), useradd(8) and userdel(8) commands.

我移除DBus 的時候損壞了timedatectl,不知道我移除了這個服務之後會損壞什麼。

sudo apt remove accountsservice -y --purge

時間會說明一切。

/sbin/mdadm

mdadm is a Linux utility used to manage and monitor software RAID devices.

The name is derived from the md (multiple device) device nodes it administers or manages, and it replaced a previous utility mdctl. The original name was "Mirror Disk", but was changed as the functionality increased.

RAID is a method of using multiple hard drives to act as one. There are two purposes of RAID: 1) Expand drive capacity: RAID 0. If you have 2 x 500 GB HDD then total space become 1 TB. 2) Prevent data loss in case of drive failure: For example RAID 1, RAID 5, RAID 6, and RAID 10.

你可以移除它。

sudo apt remove mdadm -y --purge

/usr/lib/policykit-1/polkitd --no-debug

polkitd — PolicyKit daemon

polkit - Authorization Framework

我的理解是這像是一個精細的sudo 。你可以允許非特權用戶以root 的身份做某些操作。例如重啓你的桌面版Linux。

不過我運行的是一台服務器。你可以移除它。

sudo apt remove policykit-1 -y --purge

還在考慮這樣是不是破壞了某些東西。

/usr/sbin/sshd -D

sshd (OpenSSH Daemon) is the daemon program for ssh.

-D When this option is specified, sshd will not detach and does not become a daemon. This allows easy monitoring of sshd.

/sbin/iscsid

iscsid 是一個運行在後臺的daemon(系統服務)進程,用於配置iSCSI 和管理連接。摘自它的主頁:

The iscsid implements the control path of iSCSI protocol, plus some management facilities. For example, the daemon could be configured to automatically re-start discovery at startup, based on the contents of persistent iSCSI database.

http://unix.stackexchange.com/questions/216239/iscsi-vs-iscsid-services

我從來沒聽說過iSCSI:

In computing, iSCSI (Listeni/aɪˈskʌzi/ eye-skuz-ee) is an acronym for Internet Small Computer Systems Interface, an Internet Protocol (IP)-based storage networking standard for linking data storage facilities.

By carrying SCSI commands over IP networks, iSCSI is used to facilitate data transfers over intranets and to manage storage over long distances. iSCSI can be used to transmit data over local area networks (LANs), wide area networks (WANs), or the Internet and can enable location-independent data storage and retrieval.

The protocol allows clients (called initiators) to send SCSI commands (CDBs) to SCSI storage devices (targets) on remote servers. It is a storage area network (SAN) protocol, allowing organizations to consolidate storage into data center storage arrays while providing hosts (such as database and web servers) with the illusion of locally attached disks.

你可以移除它。

sudo apt remove open-iscsi -y --purge

/sbin/agetty --noclear tty1 linux

agetty - alternative Linux getty

getty, short for "get tty", is a Unix program running on a host computer that manages physical or virtual terminals (TTYs). When it detects a connection, it prompts for a username and runs the 'login' program to authenticate the user.

Originally, on traditional Unix systems, getty handled connections to serial terminals (often Teletype machines) connected to a host computer. The tty part of the name stands for Teletype, but has come to mean any type of text terminal.

它可以讓你在物理機上登錄進你的服務器。在Digital Ocean 上,你可以點擊droplet 詳情的Console,記着你應該可以在瀏覽器中和一個終端交互(認爲它其實是一個VNC 連接)。

以前,你會看到一堆tty 在系統啓動時啓動(在/etc/inittab中配置),但是現在它們由Systemd 按需啓動。

爲了好玩,我移除了啓動和生成agetty的配置文件:

sudo rm /etc/systemd/system/getty.target.wants/getty@tty1.service
sudo rm /lib/systemd/system/getty@.service

當我重啓服務器之後,我還能夠通過SSH 連接上,不過沒法通過Digital Ocean web 終端登錄。
htop-login

sshd: root@pts/0 & -bash & htop

sshd: root@pts/0表示用戶root在#0號虛擬終端(pts)上建立了一個SSH 會話。一個虛擬終端模仿一個真正的文本終端。

bash 是我在用的shell。

爲什麼開頭有一個破折號?Reddit 用戶hirnbrot 很好的解釋了:

There's a dash at the beginning because launching it as "-bash" will make it a login shell. A login shell is one whose first character of argument zero is a -, or one started with the --login option. This will then cause it to read a different set of configuration files.

htop是一個運行在截圖中的交互式進程查看工具。

結束之後

sudo apt remove lvm2 -y --purge
sudo apt remove at -y --purge
sudo apt remove snapd -y --purge
sudo apt remove lxcfs -y --purge
sudo apt remove mdadm -y --purge
sudo apt remove open-iscsi -y --purge
sudo apt remove accountsservice -y --purge
sudo apt remove policykit-1 -y --purge

canyoukillit-after

極限版:

sudo apt remove dbus -y --purge
sudo apt remove rsyslog -y --purge
sudo apt remove acpid -y --purge
sudo systemctl stop cron && sudo systemctl disable cron
sudo rm /etc/systemd/system/getty.target.wants/getty@tty1.service
sudo rm /lib/systemd/system/getty@.service

canyoukillit-after-extreme

我按照我的博客文章about unattended installation of WordPress on Ubuntu Server 的步驟安裝,成功了。

這是nginx,PHP7 和MySQL。

canyoukillit-after-extreme-wp

附錄

源代碼

有時候只看strace 是不夠的。
另外一個找出一個程序到底做了什麼是讀它的源代碼。
首先,我需要找到我們從哪裏開始看。

$ which uptime
/usr/bin/uptime
$ dpkg -S /usr/bin/uptime
procps: /usr/bin/uptime

我們發現uptime實際上是位於/usr/bin/uptime,並且在Ubuntu 上它是proccps 軟件包的一部分。

你可以到http://packages.ubuntu.com/ 上搜索這個軟件包。

這是procps的頁面:http://packages.ubuntu.com/source/xenial/procps

如果你滾動到頁面底部,你會看到源代碼庫的鏈接:

文件描述符和重定向

當你想把標準錯誤輸出(stderr)重定向到標準輸出(stdout),是用2&>1 還是2>&1

你可以通過認識到echo something > file將會寫something到文件file中來記住&符號的位置。這和echo something 1> file一樣。現在,echo something 2> file會把stderr 輸出到file

如果你寫的是echo something 2>1,意味着你把stderr 重定向到一個叫1``的文件。加個空格看起來更清晰一些:echo something 2> 1`。

如果你在1前面加一個&,表示1不是一個文件名而是一個流ID。所以應該是echo something 2>&1

PuTTY 的顏色

如果你使用PuTTY 的時候,在htop 發現缺少一些彩色元素,下面是解決方法。

  • 右擊標題欄
  • 點擊Change settings...
  • 跳到Window -> Colours
  • 選擇Both 單選按鈕
  • 點擊應用

putty-settings

C 寫的Shell

讓我們用C 寫一個非常簡單的shell,顯示下使用fork/exec/wait系統調用。這是程序shell.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    printf("Welcome to my shell\n");
    char line[1024];

    while (1) {
        printf("> ");

        fgets(line, sizeof(line), stdin);
        line[strlen(line)-1] = '\0'; // strip \n
        if (strcmp(line, "exit") == 0) // shell built-in
            break;

        int pid = fork();
        if (pid == 0) {
            printf("Executing: %s\n", line);
        if (execlp(line, "", NULL) == -1) {
            printf("ERROR!\n");
            exit(1);
        }
        } else if (pid > 0) {
            int status;
            waitpid(pid, &status, 0);
            printf("Child exited with %d\n", WEXITSTATUS(status));
        } else {
            printf("ERROR!\n");
            break;
        }
    }

    return 0;
}

編譯程序:

gcc shell.c -o shell

接着運行:

$ ./shell
Welcome to my shell
> date
Executing: date
Thu Dec  1 14:10:59 UTC 2016
Child exited with 0
> true
Executing: true
Child exited with 0
> false
Executing: false
Child exited with 1
> exit

你有沒有想過爲什麼運行一個後臺程序的時候,只能在你按下Enter之後一會才能看到它退出了?

$ sleep 1 &
[1] 11686
$ # press Enter
[1]+  Done                    sleep 1

這是因爲shell 一直唉等待你的輸入。只有當你輸入一個命令之後,它才會檢查後臺程序的狀態,如果它們終止了就會顯示出來。

TODO

這是我還想要瞭解的更加詳細的。

  • process state substatuses (Ss, Ss+, R+, etc.)
  • kernel threads
  • /dev/pts
  • more about memory (CODE, DATA, SWAP)
  • figure out time slices length
  • Linux scheduler algorithm
  • pinning proceses to cores
  • write about manual pages
  • cpu/memory colors in bars
  • process ID limit & fork bomb
  • lsof, ionice, schedtool

更新

這是文章自發佈以來較大的更正和更新列表。

  • Idle time in /proc/uptime is the sum of all cores (Dec 2, 2016)
  • My parent/child printf in zombie.c was reversed (Dec 2, 2016)
  • apt remove cron installs postfix because of a dependency to an MTA (Dec 3, 2016)
  • id can load information from other sources (via /etc/nsswitch.conf), not just /etc/passwd (Dec 3, 2016)
  • Describe /etc/shadow password hash format (Dec 3, 2016)
  • Use visudo to edit the /etc/sudoers file to be safe (Dec 3, 2016)
  • Explain MEM% (Dec 3, 2016)
  • Rewrite the section about load averages (Dec 4, 2016)
  • Fix: kill 1234 by default sends TERM not INT (Dec 7, 2016)
  • Explain CPU and memory color bars (Dec 7, 2016)

後記

如果這篇文章有錯誤請讓我知道!我很樂意更正它。