本系列文章討論P(yáng)erl的三個(gè)關(guān)鍵功能:進(jìn)程(Processes)、管道(Pipes)和信號(hào)(Signals)。通過建立一個(gè)新進(jìn)程,Perl程序可以運(yùn)行另一個(gè)程序甚至是它自己的拷貝。管道允許Perl腳本剛其它進(jìn)行交換數(shù)據(jù),而信號(hào)使Perl腳本監(jiān)視和控制其它進(jìn)程成為可能。本文討論的是其中的:信號(hào)。 信號(hào)( signal )正如文件句柄,理解信號(hào)是網(wǎng)絡(luò)編程的基礎(chǔ)。信號(hào)是通過操作系統(tǒng)發(fā)送給你的程序的一個(gè)消息,告訴它發(fā)生了重要的事情。信號(hào)可以指示程序自身的一個(gè)錯(cuò)誤,比如嘗試除0。事件要求立刻反應(yīng),例如用戶嘗試終止這個(gè)程序,或者一個(gè)非關(guān)鍵信息,如程序啟動(dòng)后,終止一個(gè)子進(jìn)程。 常見信號(hào)POSIX標(biāo)準(zhǔn)定義了19個(gè)信號(hào)。每一個(gè)信號(hào)都擁有一個(gè)小的整數(shù)和一個(gè)符號(hào)名。我們?cè)谙旅娴谋砀耧@示它們。
按照慣例, 在一個(gè)進(jìn)程中使用TERM 和 KILL 來結(jié)束另一個(gè)進(jìn)程。默認(rèn)的, TERM讓程序直接結(jié)束,但程序可以搭配一個(gè)信號(hào)句柄給 TERM,用來攔截結(jié)束請(qǐng)求以及可能在退出執(zhí)行一些清理工作。 相比之下,KILL 信號(hào)是無法捕獲的,它會(huì)強(qiáng)制進(jìn)程立刻結(jié)束。比如,當(dāng)UINX系統(tǒng)關(guān)機(jī)時(shí),shutdown(關(guān)機(jī))進(jìn)程首先發(fā)送 TERM 給所有正在運(yùn)行的進(jìn)程,給機(jī)會(huì)給它們進(jìn)行清理。如果少數(shù)進(jìn)程在幾秒后依然在運(yùn)行,那么它將發(fā)送 KILL。 捕獲信號(hào)你可以通過在全局哈希 %SIG 中添加一個(gè)信號(hào)句柄來捕獲一個(gè)信號(hào)。使用你想捕獲的信號(hào)名作為哈希的鍵。例如,使用 $SIG{INT} 來獲取或設(shè)置 INT 信號(hào)句柄。使用引用作為值:一個(gè)匿名函數(shù)或指向已命名函數(shù)的引用。例如,下面的例子是一個(gè)設(shè)定 INT 信號(hào)句柄的小腳本。當(dāng)我們按下中斷鍵的時(shí)候,它打印一條短信息并增加計(jì)數(shù)器。腳本如此運(yùn)行下去,直到計(jì)數(shù)到三次中斷,說明真正要結(jié)束了。在下面的例子中,當(dāng)我們按下Ctrl+C時(shí),打印一條“別打斷我!”的信息。 #!/usr/bin/perl #文件:interrupt.pl #1 ############################################## # (c) 2011 LoRui(i@, www.) # ############################################## use strict; #2 use warnings; my $interruptions = 0; #3 $SIG{INT} = \&hanlde_interruptions; #4 while($interruptions < 3) { #5 print "休息一下……\n"; #6 sleep 5; #7 } #8 sub hanlde_interruptions { #9 $interruptions++; #10 warn "別打斷我!你已經(jīng)打斷我 $interruption 次了!\n"; #11 } #12 來看看這個(gè)腳本的詳細(xì)信息。
對(duì)于很短的信號(hào)處理程序,你可以使用匿名函數(shù)進(jìn)行處理。比如,下面的代碼片段和剛剛的行將,但我們不需要給信號(hào)處理程序命名: $SIG{INT} = sub { $interruptions++; warn "別打斷我!你已經(jīng)打斷我 $interruption 次了!\n"; }; 除了引用代碼之外, %SIG 接受兩個(gè)特例。字符串“DEFAULT”恢復(fù)默認(rèn)信號(hào)的行為。例如,將 $SIG{INT} 設(shè)定為“DEFAULT”,將讓 INT 信號(hào)再次結(jié)束當(dāng)前腳本。字符串“IGNORE”將讓該信號(hào)完全被忽略。 $SIG{TERM} = $SIG{HUP} = $SIG{INT} = \&handler sub handler { my $sig = shift; warn "處理 $sig 信號(hào).\n"; } 處理 PIPE 異常現(xiàn)在我們擁有了處理PIPE異常所需要的知識(shí)?;叵搿禤erl進(jìn)程、管道和信號(hào)之二:管道》里的示例代碼 write_ten.pl 和 read_three.pl中那恐怖的PIPE錯(cuò)誤。write_ten.pl打開一個(gè)到read_three.pl的管道并嘗試向其寫入10行文本,但read_three.pl只期望接受3行之后就退出并結(jié)束管道。write_ten.pl 并不知道到對(duì)方的連接已經(jīng)被關(guān)閉,嘗試寫入第4行的時(shí)候, PIPE 信號(hào)產(chǎn)生了。 #!/usr/bin/perl #文件:write_ten_ph.pl #1 ############################################## # (c) 2011 LoRui(i@, www.) # ############################################## use strict; #2 use warnings; my $ok = 1; #3 $SIG{PIPE} = sub { undef $ok }; open(PIPE, "| read_three.pl") or die ("無法打開管道:$!"); select PIPE; $| = 1; select STDOUT; my $count = 0; for($_ = 1; $ok && $_ <= 10; $_++) { warn "寫入第 $_ 行\(zhòng)n"; print PIPE "這是第 $_ 行\(zhòng)n" and $count++; sleep 1; } close PIPE or die "無法關(guān)閉管道:$!"; print "共寫入 $count 行文本\n"; $SIG{PIPE} = sub { undef $ok }; 當(dāng)接受到一個(gè)PIPE信號(hào)時(shí),該處理程序?qū)⑷∠?ok的定義,讓其為假。 % write_ten_ph.pl 寫入第 1 行 read_three.pl 獲取:這是第 1 行 寫入第 2 行 read_three.pl 獲?。哼@是第 2 行 寫入第 3 行 read_three.pl 獲取:這是第 3 行 寫入第 4 行 共寫入 3 行文本 另一種常用方法是設(shè)置 $SIG{INT} 為“IGNORE”,以完整的忽略 PIPE信號(hào)?,F(xiàn)在,我們的職責(zé)是檢查出了什么錯(cuò),我們可以通過 print() 的返回值進(jìn)行檢測。如果 print() 返回假值,我們退出循環(huán)。 #!/usr/bin/perl #文件:write_ten_i.pl #1 ############################################## # (c) 2011 LoRui(i@, www.) # ############################################## use strict; #2 use warnings; $SIG{PIPE} = 'IGNORE'; #3 open(PIPE, "| read_three.pl") or die "無法打開管道:$!"; #4 select PIPE; $| = 1; select STDOUT; #5 my $count = 0; #6 for(1..10) { #7 warn "寫入第 $_ 行\(zhòng)n"; #8 if(print PIPE "這是第 $_ 行\(zhòng)n") { #9 $count++; #10 } else { #11 warn "寫入數(shù)據(jù)時(shí)有錯(cuò)誤發(fā)生:$!\n"; #12 last; #13 } #14 sleep 1; #15 } #16 close PIPE or die "無法關(guān)閉管道:$!"; #17 print "共寫入 $count 行文本\n"; #18 運(yùn)行結(jié)果: % write_ten_i.pl 寫入第 1 行 read_three.pl 獲取:這是第 1 行 寫入第 2 行 read_three.pl 獲?。哼@是第 2 行 寫入第 3 行 read_three.pl 獲?。哼@是第 3 行 寫入第 4 行 寫入數(shù)據(jù)時(shí)有錯(cuò)誤發(fā)生:Broken pipe 共寫入 3 行文本 注意,如果失敗,錯(cuò)誤信息中的 $! 處將顯示 “Broken pipe”。如果你希望將這個(gè)錯(cuò)誤與其它I/O錯(cuò)誤分別處理,我們可以通過正則表達(dá)式來明確地測試它的值?;蛴酶玫姆椒ǎ和ㄟ^它的數(shù)字值與EPIPE的錯(cuò)誤常量進(jìn)行比對(duì),例如: use Errno ':POSIX'; ... unless (print PIPE "這是第 $_ 行\(zhòng)n") { # 處理寫入錯(cuò)誤 last if $! == EPIPE; # PIPE錯(cuò)誤,終止循環(huán) die "I/O 錯(cuò)誤: $!"; # 其它錯(cuò)誤,打印錯(cuò)誤信息 } 發(fā)送信號(hào)Perl腳本可以使用 kill() 函數(shù)發(fā)送一個(gè)信號(hào)到其它進(jìn)程。 $count = kill($signal, @processes)kill() 函數(shù)發(fā)送信號(hào)$signal給一個(gè)或多個(gè)進(jìn)程。你可以通過數(shù)字(比如:2)或符號(hào)名(比如: INT)來指定要發(fā)送的信號(hào)。@processes是將信號(hào)發(fā)送過去的一個(gè)或多個(gè)進(jìn)程的PID列表。成功執(zhí)行信號(hào)的進(jìn)程數(shù)將作為 kill() 函數(shù)的結(jié)果返回。 一個(gè)進(jìn)程只能發(fā)送一個(gè)信號(hào)給其它進(jìn)程,并且需要對(duì)應(yīng)的權(quán)限。一般而言,進(jìn)程以普通用戶權(quán)限運(yùn)行,那么該進(jìn)程也只能給普通用戶權(quán)限及普通用戶以下權(quán)限運(yùn)行的進(jìn)程發(fā)送信號(hào)。是的,以root或超級(jí)用戶權(quán)限運(yùn)行的進(jìn)程可以給任何進(jìn)程發(fā)送信號(hào)。 kill INT => $$; # 等效于 kill('INT',$$) 讓慢的系統(tǒng)調(diào)用超時(shí)當(dāng)Perl執(zhí)行系統(tǒng)調(diào)用時(shí),信號(hào)可能產(chǎn)生。大多數(shù)情況下,Perl自動(dòng)重啟并嚴(yán)密監(jiān)控調(diào)用。 $slept = sleep([$seconds])根據(jù)指定的秒數(shù)暫停,或一直暫停直到接收到一個(gè)信號(hào)。如果沒有給定參數(shù),該函數(shù)將永遠(yuǎn)暫停。 sleep() 將返回其實(shí)際暫停的秒數(shù)。 另一個(gè)例外是四個(gè)參數(shù)的 select(),它可用于定時(shí)等待,直到一個(gè)或多個(gè)設(shè)定的I/O文件句柄準(zhǔn)備就緒。該函數(shù)將在以后文章中描述。 有時(shí)候,自動(dòng)重啟系統(tǒng)調(diào)用不是你想要的。比如,一個(gè)應(yīng)用程序提示用戶輸入密碼,并嘗試從標(biāo)準(zhǔn)輸入讀取用戶輸入。你可能希望,讀取工作在一段時(shí)間后超時(shí)退出,以避免用戶已離開,程序卻還在等待輸入。下面的代碼片段看上去好像能勝任這個(gè)工作: my $timed_out = 0; $SIG{ALRM} = sub { $timed_out = 1 }; print STDERR "輸入密碼: "; alarm (5); # 5秒超時(shí) my $password = <STDIN>; alarm (0); print STDERR "操作已超時(shí)\n" if $timed_out; 這里我們使用 alarm() 函數(shù)來設(shè)計(jì)定時(shí)器。當(dāng)定時(shí)器過期,操作系統(tǒng)生成一個(gè) ALRM信號(hào),我們攔截這個(gè)信號(hào)并進(jìn)行處理:設(shè)置全局變量 $timed_out 為真。在這個(gè)代碼里,我們用5秒超時(shí)來調(diào)用 alarm() 函數(shù),然后從標(biāo)準(zhǔn)輸入讀取一行。讀取完成之后,我們以零為參數(shù)再次調(diào)用 alarm(),以關(guān)閉定時(shí)器。就是說,用戶要在5秒鐘的時(shí)間內(nèi)輸入密碼,否則定時(shí)器將失效,我們也不再重啟該程序。 $seconds_lef = alarm($seconds)為把 ALRM 信號(hào)在$seconds秒之后傳遞給進(jìn)程做準(zhǔn)備。如果參數(shù)為零,將使定時(shí)器失效。 Perl自動(dòng)重啟使用系統(tǒng)調(diào)用變慢的問題中,包括 <>。即使鬧鐘停止了,我們還停留在<>調(diào)用,等待用戶的鍵盤輸入。 print STDERR "輸入密碼: "; my $password = eval { local $SIG{ALRM} = sub { die "超時(shí)\n" }; alarm (5); # 5秒超時(shí) return <STDIN>; }; alarm (0); print STDERR "操作已超時(shí)\n" if $@ =~ /timeout/; 這個(gè)程序中,我們命名 eval{} 塊讓 ALRM處理程序局部化(localize)。eval{} 塊設(shè)定鬧鐘,跟前面一樣,嘗試從 STDIN 讀取。如果在 <> 返回之前已超時(shí),用戶輸入將從eval{} 塊返回,并賦值給 $password。 |
|