進(jìn)程和進(jìn)程的基本操作

2018-02-24 15:41 更新

進(jìn)程和進(jìn)程的基本操作

前言

進(jìn)程作為程序真正發(fā)揮作用時(shí)的“形態(tài)”,我們有必要對(duì)它的一些相關(guān)操作非常熟悉,這一節(jié)主要描述進(jìn)程相關(guān)的概念和操作,將介紹包括程序、進(jìn)程、作業(yè)等基本概念以及進(jìn)程狀態(tài)查詢(xún)、進(jìn)程通信等相關(guān)的操作。

什么是程序,什么又是進(jìn)程

程序是指令的集合,而進(jìn)程則是程序執(zhí)行的基本單元。為了讓程序完成它的工作,必須讓程序運(yùn)行起來(lái)成為進(jìn)程,進(jìn)而利用處理器資源、內(nèi)存資源,進(jìn)行各種 I/O 操作,從而完成某項(xiàng)特定工作。

從這個(gè)意思上說(shuō),程序是靜態(tài)的,而進(jìn)程則是動(dòng)態(tài)的。

進(jìn)程有區(qū)別于程序的地方還有:進(jìn)程除了包含程序文件中的指令數(shù)據(jù)以外,還需要在內(nèi)核中有一個(gè)數(shù)據(jù)結(jié)構(gòu)用以存放特定進(jìn)程的相關(guān)屬性,以便內(nèi)核更好地管理和調(diào)度進(jìn)程,從而完成多進(jìn)程協(xié)作的任務(wù)。因此,從這個(gè)意義上可以說(shuō)“高于”程序,超出了程序指令本身。

如果進(jìn)行過(guò)多進(jìn)程程序的開(kāi)發(fā),又會(huì)發(fā)現(xiàn),一個(gè)程序可能創(chuàng)建多個(gè)進(jìn)程,通過(guò)多個(gè)進(jìn)程的交互完成任務(wù)。在 Linux 下,多進(jìn)程的創(chuàng)建通常是通過(guò) fork 系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)。從這個(gè)意義上來(lái)說(shuō)程序則”包含”了進(jìn)程。

另外一個(gè)需要明確的是,程序可以由多種不同程序語(yǔ)言描述,包括 C 語(yǔ)言程序、匯編語(yǔ)言程序和最后編譯產(chǎn)生的機(jī)器指令等。

下面簡(jiǎn)單討論 Linux 下面如何通過(guò) Shell 進(jìn)行進(jìn)程的相關(guān)操作。

進(jìn)程的創(chuàng)建

通常在命令行鍵入某個(gè)程序文件名以后,一個(gè)進(jìn)程就被創(chuàng)建了。例如,

讓程序在后臺(tái)運(yùn)行

$ sleep 100 &
[1] 9298

查看進(jìn)程 ID

pidof可以查看指定程序名的進(jìn)程ID:

$ pidof sleep
9298

查看進(jìn)程的內(nèi)存映像

$ cat /proc/9298/maps
08048000-0804b000 r-xp 00000000 08:01 977399     /bin/sleep
0804b000-0804c000 rw-p 00003000 08:01 977399     /bin/sleep
0804c000-0806d000 rw-p 0804c000 00:00 0          [heap]
b7c8b000-b7cca000 r--p 00000000 08:01 443354     
...
bfbd8000-bfbed000 rw-p bfbd8000 00:00 0          [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso]

程序被執(zhí)行后,就被加載到內(nèi)存中,成為了一個(gè)進(jìn)程。上面顯示了該進(jìn)程的內(nèi)存映像(虛擬內(nèi)存),包括程序指令、數(shù)據(jù),以及一些用于存放程序命令行參數(shù)、環(huán)境變量的??臻g,用于動(dòng)態(tài)內(nèi)存申請(qǐng)的堆空間都被分配好。

關(guān)于程序在命令行執(zhí)行過(guò)程的細(xì)節(jié),請(qǐng)參考《Linux 命令行下程序執(zhí)行的一剎那》。

實(shí)際上,創(chuàng)建一個(gè)進(jìn)程,也就是說(shuō)讓程序運(yùn)行,還有其他的辦法,比如,通過(guò)一些配置讓系統(tǒng)啟動(dòng)時(shí)自動(dòng)啟動(dòng)程序(具體參考 man init),或者是通過(guò)配置 crond (或者 at)讓它定時(shí)啟動(dòng)程序。除此之外,還有一個(gè)方式,那就是編寫(xiě) Shell 腳本,把程序?qū)懭胍粋€(gè)腳本文件,當(dāng)執(zhí)行腳本文件時(shí),文件中的程序?qū)⒈粓?zhí)行而成為進(jìn)程。這些方式的細(xì)節(jié)就不介紹,下面了解如何查看進(jìn)程的屬性。

需要補(bǔ)充一點(diǎn)的是:在命令行下執(zhí)行程序,可以通過(guò) ulimit 內(nèi)置命令來(lái)設(shè)置進(jìn)程可以利用的資源,比如進(jìn)程可以打開(kāi)的最大文件描述符個(gè)數(shù),最大的??臻g,虛擬內(nèi)存空間等。具體用法見(jiàn) help ulimit 。

查看進(jìn)程的屬性和狀態(tài)

可以通過(guò) ps 命令查看進(jìn)程相關(guān)屬性和狀態(tài),這些信息包括進(jìn)程所屬用戶(hù),進(jìn)程對(duì)應(yīng)的程序,進(jìn)程對(duì) cpu 和內(nèi)存的使用情況等信息。熟悉如何查看它們有助于進(jìn)行相關(guān)的統(tǒng)計(jì)分析等操作。

通過(guò) ps 命令查看進(jìn)程屬性

查看系統(tǒng)當(dāng)前所有進(jìn)程的屬性:

$ ps -ef

查看命令中包含某字符的程序?qū)?yīng)的進(jìn)程,進(jìn)程 ID 是 1 。 TTY 為?表示和終端沒(méi)有關(guān)聯(lián):

$ ps -C init
  PID TTY          TIME CMD
    1 ?        00:00:01 init

選擇某個(gè)特定用戶(hù)啟動(dòng)的進(jìn)程:

$ ps -U falcon

按照指定格式輸出指定內(nèi)容,下面輸出命令名和 cpu 使用率:

$ ps -e -o "%C %c"

打印 cpu 使用率最高的前 4 個(gè)程序:

$ ps -e -o "%C %c" | sort -u -k1 -r | head -5
 7.5 firefox-bin
 1.1 Xorg
 0.8 scim-panel-gtk
 0.2 scim-bridge

獲取使用虛擬內(nèi)存最大的 5 個(gè)進(jìn)程:

$ ps -e -o "%z %c" | sort -n -k1 -r | head -5
349588 firefox-bin
 96612 xfce4-terminal
 88840 xfdesktop
 76332 gedit
 58920 scim-panel-gtk

通過(guò) pstree 查看進(jìn)程親緣關(guān)系

系統(tǒng)所有進(jìn)程之間都有“親緣”關(guān)系,可以通過(guò) pstree 查看這種關(guān)系:

$ pstree

上面會(huì)打印系統(tǒng)進(jìn)程調(diào)用樹(shù),可以非常清楚地看到當(dāng)前系統(tǒng)中所有活動(dòng)進(jìn)程之間的調(diào)用關(guān)系。

用top動(dòng)態(tài)查看進(jìn)程信息

$ top

該命令最大特點(diǎn)是可以動(dòng)態(tài)地查看進(jìn)程信息,當(dāng)然,它還提供了一些其他的參數(shù),比如 -S 可以按照累計(jì)執(zhí)行時(shí)間的大小排序查看,也可以通過(guò) -u 查看指定用戶(hù)啟動(dòng)的進(jìn)程等。

補(bǔ)充: top 命令支持交互式,比如它支持 u 命令顯示用戶(hù)的所有進(jìn)程,支持通過(guò) k 命令殺掉某個(gè)進(jìn)程;如果使用 -n 1 選項(xiàng)可以啟用批處理模式,具體用法為:

$ top -n 1 -b

確保特定程序只有一個(gè)副本在運(yùn)行

下面來(lái)討論一個(gè)有趣的問(wèn)題:如何讓一個(gè)程序在同一時(shí)間只有一個(gè)在運(yùn)行。

這意味著當(dāng)一個(gè)程序正在被執(zhí)行時(shí),它將不能再被啟動(dòng)。那該怎么做呢?

假如一份相同的程序被復(fù)制成了很多份,并且具有不同的文件名被放在不同的位置,這個(gè)將比較糟糕,所以考慮最簡(jiǎn)單的情況,那就是這份程序在整個(gè)系統(tǒng)上是唯一的,而且名字也是唯一的。這樣的話(huà),有哪些辦法來(lái)回答上面的問(wèn)題呢?

總的機(jī)理是:在程序開(kāi)頭檢查自己有沒(méi)有執(zhí)行,如果執(zhí)行了則停止否則繼續(xù)執(zhí)行后續(xù)代碼。

策略則是多樣的,由于前面的假設(shè)已經(jīng)保證程序文件名和代碼的唯一性,所以通過(guò) ps 命令找出當(dāng)前所有進(jìn)程對(duì)應(yīng)的程序名,逐個(gè)與自己的程序名比較,如果已經(jīng)有,那么說(shuō)明自己已經(jīng)運(yùn)行了。

ps -e -o "%c" | tr -d " " | grep -q ^init$   #查看當(dāng)前程序是否執(zhí)行
[ $? -eq 0 ] && exit   #如果在,那么退出, $?表示上一條指令是否執(zhí)行成功

每次運(yùn)行時(shí)先在指定位置檢查是否存在一個(gè)保存自己進(jìn)程 ID 的文件,如果不存在,那么繼續(xù)執(zhí)行,如果存在,那么查看該進(jìn)程 ID 是否正在運(yùn)行,如果在,那么退出,否則往該文件重新寫(xiě)入新的進(jìn)程 ID,并繼續(xù)。

pidfile=/tmp/$0".pid" 
if [ -f $pidfile ]; then
       OLDPID=$(cat $pidfile)
    ps -e -o "%p" | tr -d " " | grep -q "^$OLDPID$"
    [ $? -eq 0 ] && exit
fi

echo $$ > $pidfile

#... 代碼主體

#設(shè)置信號(hào)0的動(dòng)作,當(dāng)程序退出時(shí)觸發(fā)該信號(hào)從而刪除掉臨時(shí)文件
trap "rm $pidfile"      0

更多實(shí)現(xiàn)策略自己盡情發(fā)揮吧!

調(diào)整進(jìn)程的優(yōu)先級(jí)

在保證每個(gè)進(jìn)程都能夠順利執(zhí)行外,為了讓某些任務(wù)優(yōu)先完成,那么系統(tǒng)在進(jìn)行進(jìn)程調(diào)度時(shí)就會(huì)采用一定的調(diào)度辦法,比如常見(jiàn)的有按照優(yōu)先級(jí)的時(shí)間片輪轉(zhuǎn)的調(diào)度算法。這種情況下,可以通過(guò) renice 調(diào)整正在運(yùn)行的程序的優(yōu)先級(jí),例如:`

獲取進(jìn)程優(yōu)先級(jí)

$ ps -e -o "%p %c %n" | grep xfs
 5089 xfs               0

調(diào)整進(jìn)程的優(yōu)先級(jí)

$ renice 1 -p 5089
renice: 5089: setpriority: Operation not permitted
$ sudo renice 1 -p 5089   #需要權(quán)限才行
[sudo] password for falcon:
5089: old priority 0, new priority 1
$ ps -e -o "%p %c %n" | grep xfs  #再看看,優(yōu)先級(jí)已經(jīng)被調(diào)整過(guò)來(lái)了
 5089 xfs               1

結(jié)束進(jìn)程

既然可以通過(guò)命令行執(zhí)行程序,創(chuàng)建進(jìn)程,那么也有辦法結(jié)束它??梢酝ㄟ^(guò) kill 命令給用戶(hù)自己?jiǎn)?dòng)的進(jìn)程發(fā)送某個(gè)信號(hào)讓進(jìn)程終止,當(dāng)然“萬(wàn)能”的 root 幾乎可以 kill 所有進(jìn)程(除了 init 之外)。例如,

結(jié)束進(jìn)程

$ sleep 50 &   #啟動(dòng)一個(gè)進(jìn)程
[1] 11347
$ kill 11347

kill 命令默認(rèn)會(huì)發(fā)送終止信號(hào)( SIGTERM )給程序,讓程序退出,但是 kill 還可以發(fā)送其他信號(hào),這些信號(hào)的定義可以通過(guò) man 7 signal 查看到,也可以通過(guò) kill -l 列出來(lái)。

$ man 7 signal
$ 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

暫停某個(gè)進(jìn)程

例如,用 kill 命令發(fā)送 SIGSTOP 信號(hào)給某個(gè)程序,讓它暫停,然后發(fā)送 SIGCONT 信號(hào)讓它繼續(xù)運(yùn)行。

$ sleep 50 &
[1] 11441
$ jobs
[1]+  Running                 sleep 50 &
$ kill -s SIGSTOP 11441   #這個(gè)等同于我們對(duì)一個(gè)前臺(tái)進(jìn)程執(zhí)行CTRL+Z操作
$ jobs
[1]+  Stopped                 sleep 50
$ kill -s SIGCONT 11441   #這個(gè)等同于之前我們使用bg %1操作讓一個(gè)后臺(tái)進(jìn)程運(yùn)行起來(lái)
$ jobs
[1]+  Running                 sleep 50 &
$ kill %1                  #在當(dāng)前會(huì)話(huà)(session)下,也可以通過(guò)作業(yè)號(hào)控制進(jìn)程
$ jobs
[1]+  Terminated              sleep 50

可見(jiàn) kill 命令提供了非常好的功能,不過(guò)它只能根據(jù)進(jìn)程的 ID 或者作業(yè)來(lái)控制進(jìn)程,而 pkillkillall 提供了更多選擇,它們擴(kuò)展了通過(guò)程序名甚至是進(jìn)程的用戶(hù)名來(lái)控制進(jìn)程的方法。更多用法請(qǐng)參考它們的手冊(cè)。

查看進(jìn)程退出狀態(tài)

當(dāng)程序退出后,如何判斷這個(gè)程序是正常退出還是異常退出呢?還記得 Linux 下,那個(gè)經(jīng)典 hello world 程序嗎?在代碼的最后總是有條 return 0 語(yǔ)句。這個(gè) return 0 實(shí)際上是讓程序員來(lái)檢查進(jìn)程是否正常退出的。如果進(jìn)程返回了一個(gè)其他的數(shù)值,那么可以肯定地說(shuō)這個(gè)進(jìn)程異常退出了,因?yàn)樗紱](méi)有執(zhí)行到 return 0 這條語(yǔ)句就退出了。

那怎么檢查進(jìn)程退出的狀態(tài),即那個(gè)返回的數(shù)值呢?

Shell 中,可以檢查這個(gè)特殊的變量 $?,它存放了上一條命令執(zhí)行后的退出狀態(tài)。

$ test1
bash: test1: command not found
$ echo $?
127
$ cat ./test.c | grep hello
$ echo $?
1
$ cat ./test.c | grep hi
    printf("hi, myself!\n");
$ echo $?
0

貌似返回 0 成為了一個(gè)潛規(guī)則,雖然沒(méi)有標(biāo)準(zhǔn)明確規(guī)定,不過(guò)當(dāng)程序正常返回時(shí),總是可以從 $? 中檢測(cè)到 0,但是異常時(shí),總是檢測(cè)到一個(gè)非 0 值。這就告訴我們?cè)诔绦虻淖詈笞詈檬歉弦粋€(gè) exit 0 以便任何人都可以通過(guò)檢測(cè) $? 確定程序是否正常結(jié)束。如果有一天,有人偶爾用到你的程序,試圖檢查它的退出狀態(tài),而你卻在程序的末尾莫名地返回了一個(gè) -1 或者 1,那么他將會(huì)很苦惱,會(huì)懷疑他自己編寫(xiě)的程序到底哪個(gè)地方出了問(wèn)題,檢查半天卻不知所措,因?yàn)樗湃文懔耍谷粡念^至尾都沒(méi)有懷疑你的編程習(xí)慣可能會(huì)與眾不同!

進(jìn)程通信

為便于設(shè)計(jì)和實(shí)現(xiàn),通常一個(gè)大型的任務(wù)都被劃分成較小的模塊。不同模塊之間啟動(dòng)后成為進(jìn)程,它們之間如何通信以便交互數(shù)據(jù),協(xié)同工作呢?在《UNIX 環(huán)境高級(jí)編程》一書(shū)中提到很多方法,諸如管道(無(wú)名管道和有名管道)、信號(hào)(signal)、報(bào)文(Message)隊(duì)列(消息隊(duì)列)、共享內(nèi)存(mmap/munmap)、信號(hào)量(semaphore,主要是同步用,進(jìn)程之間,進(jìn)程的不同線(xiàn)程之間)、套接口(Socket,支持不同機(jī)器之間的進(jìn)程通信)等,而在 Shell 中,通常直接用到的就有管道和信號(hào)等。下面主要介紹管道和信號(hào)機(jī)制在 Shell 編程時(shí)的一些用法。

無(wú)名管道(pipe)

在 Linux 下,可以通過(guò) | 連接兩個(gè)程序,這樣就可以用它來(lái)連接后一個(gè)程序的輸入和前一個(gè)程序的輸出,因此被形象地叫做個(gè)管道。在 C 語(yǔ)言中,創(chuàng)建無(wú)名管道非常簡(jiǎn)單方便,用 pipe 函數(shù),傳入一個(gè)具有兩個(gè)元素的 int 型的數(shù)組就可以。這個(gè)數(shù)組實(shí)際上保存的是兩個(gè)文件描述符,父進(jìn)程往第一個(gè)文件描述符里頭寫(xiě)入東西后,子進(jìn)程可以從第一個(gè)文件描述符中讀出來(lái)。

如果用多了命令行,這個(gè)管子 | 應(yīng)該會(huì)經(jīng)常用。比如上面有個(gè)演示把 ps 命令的輸出作為 grep 命令的輸入:

$ ps -ef | grep init

也許會(huì)覺(jué)得這個(gè)“管子”好有魔法,竟然真地能夠鏈接兩個(gè)程序的輸入和輸出,它們到底是怎么實(shí)現(xiàn)的呢?實(shí)際上當(dāng)輸入這樣一組命令時(shí),當(dāng)前 Shell 會(huì)進(jìn)行適當(dāng)?shù)慕馕?,把前面一個(gè)進(jìn)程的輸出關(guān)聯(lián)到管道的輸出文件描述符,把后面一個(gè)進(jìn)程的輸入關(guān)聯(lián)到管道的輸入文件描述符,這個(gè)關(guān)聯(lián)過(guò)程通過(guò)輸入輸出重定向函數(shù) dup (或者 fcntl )來(lái)實(shí)現(xiàn)。

有名管道(named pipe)

有名管道實(shí)際上是一個(gè)文件(無(wú)名管道也像一個(gè)文件,雖然關(guān)系到兩個(gè)文件描述符,不過(guò)只能一邊讀另外一邊寫(xiě)),不過(guò)這個(gè)文件比較特別,操作時(shí)要滿(mǎn)足先進(jìn)先出,而且,如果試圖讀一個(gè)沒(méi)有內(nèi)容的有名管道,那么就會(huì)被阻塞,同樣地,如果試圖往一個(gè)有名管道里寫(xiě)東西,而當(dāng)前沒(méi)有程序試圖讀它,也會(huì)被阻塞。下面看看效果。

$ mkfifo fifo_test    #通過(guò)mkfifo命令創(chuàng)建一個(gè)有名管道
$ echo "fewfefe" > fifo_test
#試圖往fifo_test文件中寫(xiě)入內(nèi)容,但是被阻塞,要另開(kāi)一個(gè)終端繼續(xù)下面的操作
$ cat fifo_test        #另開(kāi)一個(gè)終端,記得,另開(kāi)一個(gè)。試圖讀出fifo_test的內(nèi)容
fewfefe

這里的 echocat 是兩個(gè)不同的程序,在這種情況下,通過(guò) echocat 啟動(dòng)的兩個(gè)進(jìn)程之間并沒(méi)有父子關(guān)系。不過(guò)它們依然可以通過(guò)有名管道通信。

這樣一種通信方式非常適合某些特定情況:例如有這樣一個(gè)架構(gòu),這個(gè)架構(gòu)由兩個(gè)應(yīng)用程序構(gòu)成,其中一個(gè)通過(guò)循環(huán)不斷讀取 fifo_test 中的內(nèi)容,以便判斷,它下一步要做什么。如果這個(gè)管道沒(méi)有內(nèi)容,那么它就會(huì)被阻塞在那里,而不會(huì)因死循環(huán)而耗費(fèi)資源,另外一個(gè)則作為一個(gè)控制程序不斷地往 fifo_test 中寫(xiě)入一些控制信息,以便告訴之前的那個(gè)程序該做什么。下面寫(xiě)一個(gè)非常簡(jiǎn)單的例子??梢栽O(shè)計(jì)一些控制碼,然后控制程序不斷地往 fifo_test 里頭寫(xiě)入,然后應(yīng)用程序根據(jù)這些控制碼完成不同的動(dòng)作。當(dāng)然,也可以往 fifo_test 傳入除控制碼外的其他數(shù)據(jù)。

  • 應(yīng)用程序的代碼

  $ cat app.sh
  #!/bin/bash

  FIFO=fifo_test
  while :;
  do
      CI=`cat $FIFO`  #CI --> Control Info
      case $CI in
          0) echo "The CONTROL number is ZERO, do something ..."
              ;;
          1) echo "The CONTROL number is ONE, do something ..."
              ;;
          *) echo "The CONTROL number not recognized, do something else..."
              ;;
      esac
  done
  • 控制程序的代碼

  $ cat control.sh
  #!/bin/bash

  FIFO=fifo_test
  CI=$1

  [ -z "$CI" ] && echo "the control info should not be empty" && exit 

  echo $CI > $FIFO
  • 一個(gè)程序通過(guò)管道控制另外一個(gè)程序的工作

  $ chmod +x app.sh control.sh    #修改這兩個(gè)程序的可執(zhí)行權(quán)限,以便用戶(hù)可以執(zhí)行它們
  $ ./app.sh  #在一個(gè)終端啟動(dòng)這個(gè)應(yīng)用程序,在通過(guò)./control.sh發(fā)送控制碼以后查看輸出
  The CONTROL number is ONE, do something ...    #發(fā)送1以后
  The CONTROL number is ZERO, do something ...    #發(fā)送0以后
  The CONTROL number not recognized, do something else...  #發(fā)送一個(gè)未知的控制碼以后
  $ ./control.sh 1            #在另外一個(gè)終端,發(fā)送控制信息,控制應(yīng)用程序的工作
  $ ./control.sh 0
  $ ./control.sh 4343

這樣一種應(yīng)用架構(gòu)非常適合本地的多程序任務(wù)設(shè)計(jì),如果結(jié)合 web cgi,那么也將適合遠(yuǎn)程控制的要求。引入 web cgi 的唯一改變是,要把控制程序 ./control.sh 放到 webcgi 目錄下,并對(duì)它作一些修改,以使它符合 CGI 的規(guī)范,這些規(guī)范包括文檔輸出格式的表示(在文件開(kāi)頭需要輸出 content-tpye: text/html 以及一個(gè)空白行)和輸入?yún)?shù)的獲取 (web 輸入?yún)?shù)都存放在 QUERY_STRING 環(huán)境變量里頭)。因此一個(gè)非常簡(jiǎn)單的 CGI 控制程序可以寫(xiě)成這樣:

#!/bin/bash

FIFO=./fifo_test
CI=$QUERY_STRING

[ -z "$CI" ] && echo "the control info should not be empty" && exit 

echo -e "content-type: text/html\n\n"
echo $CI > $FIFO

在實(shí)際使用時(shí),請(qǐng)確保 control.sh 能夠訪(fǎng)問(wèn)到 fifo_test 管道,并且有寫(xiě)權(quán)限,以便通過(guò)瀏覽器控制 app.sh

http://ipaddress\_or\_dns/cgi-bin/control.sh?0

問(wèn)號(hào) ? 后面的內(nèi)容即 QUERY_STRING,類(lèi)似之前的 $1 。

這樣一種應(yīng)用對(duì)于遠(yuǎn)程控制,特別是嵌入式系統(tǒng)的遠(yuǎn)程控制很有實(shí)際意義。在去年的暑期課程上,我們就通過(guò)這樣一種方式來(lái)實(shí)現(xiàn)馬達(dá)的遠(yuǎn)程控制。首先,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的應(yīng)用程序以便控制馬達(dá)的轉(zhuǎn)動(dòng),包括轉(zhuǎn)速,方向等的控制。為了實(shí)現(xiàn)遠(yuǎn)程控制,我們?cè)O(shè)計(jì)了一些控制碼,以便控制馬達(dá)轉(zhuǎn)動(dòng)相關(guān)的不同屬性。

在 C 語(yǔ)言中,如果要使用有名管道,和 Shell 類(lèi)似,只不過(guò)在讀寫(xiě)數(shù)據(jù)時(shí)用 read,write 調(diào)用,在創(chuàng)建 fifo 時(shí)用 mkfifo 函數(shù)調(diào)用。

信號(hào)(Signal)

信號(hào)是軟件中斷,Linux 用戶(hù)可以通過(guò) kill 命令給某個(gè)進(jìn)程發(fā)送一個(gè)特定的信號(hào),也可以通過(guò)鍵盤(pán)發(fā)送一些信號(hào),比如 CTRL+C 可能觸發(fā) SGIINT 信號(hào),而 CTRL+\ 可能觸發(fā) SGIQUIT 信號(hào)等,除此之外,內(nèi)核在某些情況下也會(huì)給進(jìn)程發(fā)送信號(hào),比如在訪(fǎng)問(wèn)內(nèi)存越界時(shí)產(chǎn)生 SGISEGV 信號(hào),當(dāng)然,進(jìn)程本身也可以通過(guò) killraise 等函數(shù)給自己發(fā)送信號(hào)。對(duì)于 Linux 下支持的信號(hào)類(lèi)型,大家可以通過(guò) man 7 signal 或者 kill -l 查看到相關(guān)列表和說(shuō)明。

對(duì)于有些信號(hào),進(jìn)程會(huì)有默認(rèn)的響應(yīng)動(dòng)作,而有些信號(hào),進(jìn)程可能直接會(huì)忽略,當(dāng)然,用戶(hù)還可以對(duì)某些信號(hào)設(shè)定專(zhuān)門(mén)的處理函數(shù)。在 Shell 中,可以通過(guò) trap 命令(Shell 內(nèi)置命令)來(lái)設(shè)定響應(yīng)某個(gè)信號(hào)的動(dòng)作(某個(gè)命令或者定義的某個(gè)函數(shù)),而在 C 語(yǔ)言中可以通過(guò) signal 調(diào)用注冊(cè)某個(gè)信號(hào)的處理函數(shù)。這里僅僅演示 trap 命令的用法。

$ function signal_handler { echo "hello, world."; } #定義signal_handler函數(shù)
$ trap signal_handler SIGINT  #執(zhí)行該命令設(shè)定:收到SIGINT信號(hào)時(shí)打印hello, world
$ hello, world     #按下CTRL+C,可以看到屏幕上輸出了hello, world字符串

類(lèi)似地,如果設(shè)定信號(hào) 0 的響應(yīng)動(dòng)作,那么就可以用 trap 來(lái)模擬 C 語(yǔ)言程序中的 atexit 程序終止函數(shù)的登記,即通過(guò) trap signal_handler SIGQUIT 設(shè)定的 signal_handler 函數(shù)將在程序退出時(shí)執(zhí)行。信號(hào) 0 是一個(gè)特別的信號(hào),在 POSIX.1 中把信號(hào)編號(hào) 0 定義為空信號(hào),這常被用來(lái)確定一個(gè)特定進(jìn)程是否仍舊存在。當(dāng)一個(gè)程序退出時(shí)會(huì)觸發(fā)該信號(hào)。

$ cat sigexit.sh
#!/bin/bash

function signal_handler {
    echo "hello, world"
}
trap signal_handler 0
$ chmod +x sigexit.sh
$ ./sigexit.sh    #實(shí)際Shell編程會(huì)用該方式在程序退出時(shí)來(lái)做一些清理臨時(shí)文件的收尾工作
hello, world

作業(yè)和作業(yè)控制

當(dāng)我們?yōu)橥瓿梢恍?fù)雜的任務(wù)而將多個(gè)命令通過(guò) |,\>,<, ;, (,) 等組合在一起時(shí),通常這個(gè)命令序列會(huì)啟動(dòng)多個(gè)進(jìn)程,它們間通過(guò)管道等進(jìn)行通信。而有時(shí)在執(zhí)行一個(gè)任務(wù)的同時(shí),還有其他的任務(wù)需要處理,那么就經(jīng)常會(huì)在命令序列的最后加上一個(gè)&,或者在執(zhí)行命令后,按下 CTRL+Z 讓前一個(gè)命令暫停。以便做其他的任務(wù)。等做完其他一些任務(wù)以后,再通過(guò) fg 命令把后臺(tái)任務(wù)切換到前臺(tái)。這樣一種控制過(guò)程通常被成為作業(yè)控制,而那些命令序列則被成為作業(yè),這個(gè)作業(yè)可能涉及一個(gè)或者多個(gè)程序,一個(gè)或者多個(gè)進(jìn)程。下面演示一下幾個(gè)常用的作業(yè)控制操作。

創(chuàng)建后臺(tái)進(jìn)程,獲取進(jìn)程的作業(yè)號(hào)和進(jìn)程號(hào)

$ sleep 50 &
[1] 11137

把作業(yè)調(diào)到前臺(tái)并暫停

使用 Shell 內(nèi)置命令 fg 把作業(yè) 1 調(diào)到前臺(tái)運(yùn)行,然后按下 CTRL+Z 讓該進(jìn)程暫停

$ fg %1
sleep 50
^Z
[1]+  Stopped                 sleep 50

查看當(dāng)前作業(yè)情況

$ jobs            #查看當(dāng)前作業(yè)情況,有一個(gè)作業(yè)停止
[1]+  Stopped                 sleep 50
$ sleep 100 &     #讓另外一個(gè)作業(yè)在后臺(tái)運(yùn)行
[2] 11138         
$ jobs            #查看當(dāng)前作業(yè)情況,一個(gè)正在運(yùn)行,一個(gè)停止
[1]+  Stopped                 sleep 50
[2]-  Running                 sleep 100 &

啟動(dòng)停止的進(jìn)程并運(yùn)行在后臺(tái)

$ bg %1
[2]+ sleep 50 &

不過(guò),要在命令行下使用作業(yè)控制,需要當(dāng)前 Shell,內(nèi)核終端驅(qū)動(dòng)等對(duì)作業(yè)控制支持才行。

參考資料

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)