進(jìn)程概念及應(yīng)用
并發(fā)服務(wù)器端實(shí)現(xiàn)模型和方法:
- 多進(jìn)程服務(wù)器(Chap10&11)
- 多路復(fù)用服務(wù)器(Chap12)
- 多線程服務(wù)器(Chap18)
進(jìn)程:占用內(nèi)存空間的正在運(yùn)行的程序砚殿。
1個(gè)CPU中可能包含多個(gè)運(yùn)算設(shè)備(核)囤锉。核的個(gè)數(shù)與可同時(shí)運(yùn)行的進(jìn)程數(shù)相同。
可以通過(guò)調(diào)用fork函數(shù)創(chuàng)建進(jìn)程悴势。
#include <unistd.h>
pid_t fork(void); // 成功時(shí)返回進(jìn)程ID钱贯,失敗時(shí)返回-1
fork函數(shù)復(fù)制正在運(yùn)行的父進(jìn)程挫掏,父子進(jìn)程都將執(zhí)行fork函數(shù)之后的語(yǔ)句。但父進(jìn)程的fork返回值是子進(jìn)程ID秩命,子進(jìn)程的fork返回值是0尉共,應(yīng)利用此特點(diǎn)區(qū)分后續(xù)代碼的執(zhí)行流程。
# gcc fork.c -o fork
# ./fork
Parent Proc: [9, 23]
Child Proc: [13, 27]
進(jìn)程和僵尸進(jìn)程
下面的示例中弃锐,子進(jìn)程先return(或exit)袄友,但父進(jìn)程不知道,子進(jìn)程就變成了僵尸進(jìn)程(Z+)霹菊。直到父進(jìn)程終止剧蚣,子進(jìn)程才會(huì)與父進(jìn)程一起被銷(xiāo)毀。
# ./zombie &
[1] 19154
Child Process ID: 19156
Hi, I am a child process
# ps au
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
root 19154 0.0 0.0 4356 732 pts/3 S 06:48 0:00 ./zombie
root 19156 0.0 0.0 0 0 pts/3 Z 06:48 0:00 [zombie] <defunct>
# ps au
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
[1]+ Done ./zombie
為了銷(xiāo)毀子進(jìn)程旋廷,父進(jìn)程應(yīng)主動(dòng)請(qǐng)求獲取子進(jìn)程的返回值鸠按。方法之一就是調(diào)用如下函數(shù):
#include <sys/wait.h>
pid_t wait(int *statloc); // 成功時(shí)返回終止的子進(jìn)程ID,失敗時(shí)返回-1
調(diào)用此函數(shù)時(shí)饶碘,如果子進(jìn)程已終止目尖,那么子進(jìn)程return的返回值(或exit函數(shù)的參數(shù)值)將保存到statloc指向的內(nèi)存空間。statloc還包含其他信息扎运,可以用相應(yīng)的宏來(lái)判斷瑟曲。
# gcc wait.c -o wait
# ./wait &
[1] 5586
Child PID: 5588
Child PID: 5595
Child send 1: 3
Child send 1: 7
root@nsx:~/tcpip/Chap10 [master] # ps au
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
注意饮戳,wait并沒(méi)有傳入子進(jìn)程ID,它是按照子進(jìn)程結(jié)束(而非創(chuàng)建)的順序返回結(jié)果的测蹲。如果沒(méi)有已終止的子進(jìn)程莹捡,父進(jìn)程會(huì)在這里阻塞。
第二個(gè)主動(dòng)請(qǐng)求獲取子進(jìn)程返回值的方法:
#include <sys/wait.h>
/*
* @params
* pid: 子進(jìn)程ID扣甲,若傳遞-1篮赢,則與wait函數(shù)相同
* statloc: 同wait函數(shù)中的statloc
* options: 傳遞頭文件sys/wait.h中聲明的常量WNOHANG,即使沒(méi)有終止的子進(jìn)程也不會(huì)阻塞琉挖,而是返回0并退出函數(shù)
*/
pid_t waitpid(pid_t pid, int *statloc, int options); // 成功時(shí)返回終止的子進(jìn)程ID启泣,失敗時(shí)返回-1
waitpid不會(huì)阻塞父進(jìn)程,因此可以循環(huán)檢查示辈,并在期間處理別的工作寥茫。
# gcc waitpid.c -o waitpid
# ./waitpid
Do sth else.
Do sth else.
Do sth else.
Do sth else.
Do sth else.
Do sth else.
Child send: 24
信號(hào)處理
信號(hào)是在特定事件發(fā)生時(shí)由操作系統(tǒng)向進(jìn)程發(fā)送的消息。
#include <signal.h>
/*
* @params
* signo: 特殊情況信息(如SIGALRM代表已到通過(guò)alarm函數(shù)注冊(cè)的時(shí)間矾麻,SIGINT代表輸入CTRL+C纱耻,SIGCHILD代表子進(jìn)程終止)
* func: 發(fā)生該特殊情況時(shí)要調(diào)用的函數(shù)的指針
*/
void (*signal(int signo, void (*func)(int)))(int);
這個(gè)函數(shù)的返回值為函數(shù)指針,指向signal函數(shù)調(diào)用之前的信號(hào)處理函數(shù)(第二次調(diào)用signal函數(shù)的時(shí)候险耀,它的返回值就是第一次調(diào)用signal時(shí)傳入的信號(hào)處理函數(shù))弄喘。
#include <unistd.h>
unsigned int alarm(unsigned int seconds); // 返回0或以秒為單位的距SIGALRM信號(hào)發(fā)生所剩的時(shí)間
若alarm函數(shù)的參數(shù)為0代表取消之前對(duì)SIGALRM信號(hào)的預(yù)約。
# gcc signal.c -o signal
# ./signal
Wait...
^CCTRL+C pressed
Wait...
Time out!
Wait...
^CCTRL+C pressed
我們觀察到盡管主進(jìn)程的循環(huán)內(nèi)有100秒的sleep函數(shù)甩牺,但仍很快就結(jié)束了蘑志。因?yàn)槊恳粋€(gè)到達(dá)的信號(hào)都喚醒了主進(jìn)程,使其離開(kāi)阻塞狀態(tài)以處理信號(hào)贬派。
不過(guò)signal函數(shù)在UNIX系列的不同操作系統(tǒng)中可能存在區(qū)別急但,可以用sigaction函數(shù)來(lái)完全替代它。
#include <signal.h>
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
}
/*
* @params
* signo: 特殊情況信息搞乏,與signal函數(shù)相同
* act: 信號(hào)處理函數(shù)信息
* oldact: 獲取之前的信號(hào)處理函數(shù)指針波桩,若不需要?jiǎng)t傳遞0
*/
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact); // 成功時(shí)返回0, 失敗時(shí)返回-1
它的作用可以等同于signal函數(shù):
# gcc sigaction.c -o sigaction
# ./sigaction
Wait...
Time out!
Wait...
Time out!
Wait...
Time out!
利用sigaction函數(shù)消滅僵尸進(jìn)程:
# gcc remove_zombie.c -o rmzombie
# ./rmzombie
Child proc id: 9351
Child proc id: 9352
Wait...
I am child process
I am child process
Wait...
Enter signal handler...Remove proc id: 9352
Child send: 24
Enter signal handler...Remove proc id: 9351
Child send: 12
Wait...
Wait...
Wait...
我修改了原書(shū)中的例子查描,在waitpid函數(shù)處加了循環(huán)突委,因?yàn)閮蓚€(gè)子進(jìn)程終止的時(shí)間太過(guò)于接近導(dǎo)致waitpid函數(shù)只處理了一個(gè)信號(hào)(兩個(gè)信號(hào)都在信號(hào)處理函數(shù)執(zhí)行之前產(chǎn)生)。
基于多任務(wù)的并發(fā)服務(wù)器
每當(dāng)有客戶端請(qǐng)求鏈接時(shí)冬三,服務(wù)器端都創(chuàng)建子進(jìn)程以提供服務(wù)≡蹈浚可以結(jié)合第四章的回聲客戶端來(lái)運(yùn)行勾笆。
# gcc echo_mpserv.c -o mpserv
# ./mpserv 9190
Connected client 1
Begin exchanging data with client 1
Connected client 2
Begin exchanging data with client 2
Client disconnected...
Remove proc id: 30324
Child send: 0
Client disconnected...
Remove proc id: 30496
Child send: 0
第一次寫(xiě)完這段程序時(shí),我杯具地發(fā)現(xiàn)退出其中一個(gè)客戶端時(shí)桥滨,服務(wù)器開(kāi)始刷屏Remove proc id: 0
窝爪,因?yàn)槲以谇拔陌褀aitpid函數(shù)放在了while循環(huán)里弛车,而它卻一直收到0返回值因此不能退出循環(huán)。實(shí)際上0是期待的返回值蒲每,因?yàn)檫@個(gè)時(shí)候還有其他客戶端正在連接(意味著還有其他子進(jìn)程未結(jié)束)纷跛,waitpid就會(huì)返回0。-1是出現(xiàn)錯(cuò)誤時(shí)的返回值(如已經(jīng)沒(méi)有子進(jìn)程了)邀杏。前一個(gè)例子之所以沒(méi)有發(fā)現(xiàn)這個(gè)錯(cuò)誤是因?yàn)閮蓚€(gè)子進(jìn)程都在信號(hào)處理函數(shù)前就終止了贫奠,沒(méi)有給waitpid函數(shù)返回0的機(jī)會(huì)。
修改循環(huán)的判定條件為大于0望蜡,運(yùn)行成功唤崭,結(jié)果如上所示。
分割TCP的I/O程序
客戶端的父進(jìn)程負(fù)責(zé)接收數(shù)據(jù)脖律,創(chuàng)建子進(jìn)程發(fā)送數(shù)據(jù)谢肾。
# gcc echo_mpclient.c -o mpclnt
# ./mpclnt 127.0.0.1 9190
Connected
dear
Message from server: dear
Q
習(xí)題
- 下列關(guān)于進(jìn)程的說(shuō)法錯(cuò)誤的是?
a. 從操作系統(tǒng)的角度上說(shuō)小泉,進(jìn)程是程序運(yùn)行的單位芦疏。
b. 進(jìn)程根據(jù)創(chuàng)建方式建立父子關(guān)系。
c. 進(jìn)程可以包含其他進(jìn)程微姊,即一個(gè)進(jìn)程的內(nèi)存空間可以包含其他進(jìn)程酸茴。
d. 子進(jìn)程可以創(chuàng)建其他子進(jìn)程,而創(chuàng)建出來(lái)的子進(jìn)程還可以創(chuàng)建其子進(jìn)程柒桑,但所有這些進(jìn)程只與一個(gè)父進(jìn)程建立父子關(guān)系弊决。
cd。- 調(diào)用fork函數(shù)將創(chuàng)建子進(jìn)程魁淳,以下關(guān)于子進(jìn)程的描述錯(cuò)誤的是飘诗?
a. 父進(jìn)程銷(xiāo)毀時(shí)也會(huì)同時(shí)銷(xiāo)毀子進(jìn)程。
b. 子進(jìn)程是復(fù)制父進(jìn)程所有資源創(chuàng)建出的進(jìn)程界逛。
c. 父子進(jìn)程共享全局變量昆稿。
d. 通過(guò)fork函數(shù)創(chuàng)建的子進(jìn)程將執(zhí)行從開(kāi)始到fork函數(shù)調(diào)用為止的代碼。
cd息拜。不考慮主動(dòng)把子進(jìn)程變成守護(hù)進(jìn)程的情況溉潭,父進(jìn)程銷(xiāo)毀時(shí)也會(huì)同時(shí)銷(xiāo)毀子進(jìn)程。子進(jìn)程完全復(fù)制了父進(jìn)程的資源少欺,包括進(jìn)程上下文喳瓣、代碼區(qū)、數(shù)據(jù)區(qū)赞别、堆區(qū)畏陕、棧區(qū)、內(nèi)存信息仿滔、打開(kāi)文件的文件描述符惠毁、信號(hào)處理函數(shù)犹芹、進(jìn)程優(yōu)先級(jí)、進(jìn)程組號(hào)鞠绰、當(dāng)前工作目錄腰埂、根目錄、資源限制和控制終端等信息蜈膨,而子進(jìn)程與父進(jìn)程的區(qū)別有進(jìn)程號(hào)屿笼、資源使用情況和計(jì)時(shí)器等。全局變量位于數(shù)據(jù)區(qū)丈挟,也會(huì)被一起復(fù)制到子進(jìn)程的內(nèi)存空間刁卜。通過(guò)fork函數(shù)創(chuàng)建的子進(jìn)程將執(zhí)行從開(kāi)始調(diào)用fork函數(shù)到最后的代碼。- 創(chuàng)建子進(jìn)程時(shí)將復(fù)制父進(jìn)程的所有內(nèi)容曙咽,此時(shí)的復(fù)制對(duì)象也包含套接字文件描述符蛔趴。編寫(xiě)程序驗(yàn)證復(fù)制的文件描述符整數(shù)值是否與原文件描述符整數(shù)值相同。
與echo_client搭配運(yùn)行例朱。# gcc sockid.c -o sockid # ./sockid 9190 Connected client 1 server sock: 3, client sock: 4 server sock: 3, client sock: 4
- 請(qǐng)說(shuō)明進(jìn)程變?yōu)榻┦M(jìn)程的過(guò)程及預(yù)防措施孝情。
如果在父進(jìn)程中沒(méi)有注冊(cè)信號(hào)處理函數(shù)的話,子進(jìn)程的退出信號(hào)會(huì)傳遞給操作系統(tǒng)洒嗤,但操作系統(tǒng)并不會(huì)銷(xiāo)毀子進(jìn)程箫荡,需要傳遞給父進(jìn)程去銷(xiāo)毀。父進(jìn)程可以主動(dòng)發(fā)起請(qǐng)求渔隶,獲得子進(jìn)程的結(jié)束狀態(tài)值羔挡,然后調(diào)用wait或waitpid函數(shù)來(lái)正常終止子進(jìn)程。- 如果在未注冊(cè)SIGINT信號(hào)的情況下輸入Ctrl+C间唉,將由操作系統(tǒng)默認(rèn)的時(shí)間處理器終止程序绞灼。但如果直接注冊(cè)Ctrl+C信號(hào)的處理器,則程序不會(huì)終止呈野,而是調(diào)用程序員指定的事件處理器低矮。編寫(xiě)注冊(cè)處理函數(shù)的程序,完成如下功能:“輸入Ctrl+C時(shí)被冒,詢問(wèn)是否確定退出程序军掂,輸入Y則終止程序”。另外昨悼,編寫(xiě)程序使其每隔1秒輸出簡(jiǎn)單字符串蝗锥,并適用于上述時(shí)間處理器注冊(cè)代碼。
# gcc sigint.c -o sigint # ./sigint Waiting Waiting ^CPressed CTRL+C. Quit? Y/N N Waiting Waiting Waiting ^CPressed CTRL+C. Quit? Y/N Y
我的問(wèn)題
進(jìn)程號(hào)為1的進(jìn)程是什么率触?
init進(jìn)程玛追。