一、進(jìn)程的簡介
1、什么是進(jìn)程,進(jìn)程的概念
??進(jìn)程是一個(gè)動(dòng)態(tài)過程,而非靜態(tài)文件,它是程序的一次運(yùn)行過程,當(dāng)應(yīng)用程序被加載到內(nèi)存中運(yùn)行之后它就稱為了一個(gè)進(jìn)程,當(dāng)程序運(yùn)行結(jié)束后也就意味著進(jìn)程終止,這就是進(jìn)程的一個(gè)生命周期。
2、進(jìn)程狀態(tài)
??Linux 系統(tǒng)下進(jìn)程通常存在 6 種不同的狀態(tài),分為:就緒態(tài)、運(yùn)行態(tài)、僵尸態(tài)、 可中斷睡眠狀態(tài)(淺度睡眠)、不可中斷睡眠狀態(tài)(深度睡眠)以及暫停態(tài)。
??? 就緒態(tài)(Ready) : 指該進(jìn)程滿足被 CPU 調(diào)度的所有條件但此時(shí)并沒有被調(diào)度執(zhí)行,只要得到 CPU就能夠直接運(yùn)行;意味著該進(jìn)程已經(jīng)準(zhǔn)備好被 CPU 執(zhí)行,當(dāng)一個(gè)進(jìn)程的時(shí)間片到達(dá),操作系統(tǒng)調(diào)度程序會(huì)從就緒態(tài)鏈表中調(diào)度一個(gè)進(jìn)程;
??? 運(yùn)行態(tài): 指該進(jìn)程當(dāng)前正在被 CPU 調(diào)度運(yùn)行,處于就緒態(tài)的進(jìn)程得到 CPU 調(diào)度就會(huì)進(jìn)入運(yùn)行態(tài);
??? 僵尸態(tài): 僵尸態(tài)進(jìn)程其實(shí)指的就是僵尸進(jìn)程,指該進(jìn)程已經(jīng)結(jié)束、但其父進(jìn)程還未給它“收尸”;
??? 可中斷睡眠狀態(tài): 可中斷睡眠也稱為淺度睡眠,表示睡的不夠“死”,還可以被喚醒,一般來說可以通過信號來喚醒;
??? 不可中斷睡眠狀態(tài): 不可中斷睡眠稱為深度睡眠,深度睡眠無法被信號喚醒,只能等待相應(yīng)的條件成立才能結(jié)束睡眠狀態(tài)。把淺度睡眠和深度睡眠統(tǒng)稱為等待態(tài)(或者叫阻塞態(tài)) ,表示進(jìn)程處于一種等待狀態(tài),等待某種條件成立之后便會(huì)進(jìn)入到就緒態(tài);所以,處于等待態(tài)的進(jìn)程是無法參與進(jìn)程系統(tǒng)調(diào)度的。
??? 暫停態(tài): 暫停并不是進(jìn)程的終止,表示進(jìn)程暫停運(yùn)行,一般可通過信號將進(jìn)程暫停,譬如 SIGSTOP信號;處于暫停態(tài)的進(jìn)程是可以恢復(fù)進(jìn)入到就緒態(tài)的,譬如收到 SIGCONT 信號。一個(gè)新創(chuàng)建的進(jìn)程會(huì)處于就緒態(tài),只要得到 CPU 就能被執(zhí)行。以下列出了進(jìn)程各個(gè)狀態(tài)之間的轉(zhuǎn)換關(guān)系,如下所示:
3、什么是進(jìn)程號
??Linux 系統(tǒng)下的每一個(gè)進(jìn)程都有一個(gè)進(jìn)程號(process ID,簡稱 PID),進(jìn)程號是一個(gè)正數(shù),用于唯一標(biāo)
識系統(tǒng)中的某一個(gè)進(jìn)程。
4、進(jìn)程間的通信方法(IPC)
??①管道通信:分別為有名管道和無名管道。
??②信號。
??③共享內(nèi)存。
??④消息隊(duì)列。
??⑤信號量。
??⑥套接字(socket)。
二、 fork()創(chuàng)建子進(jìn)程
三、父、 子進(jìn)程間的文件共享
??調(diào)用 fork()函數(shù)之后,子進(jìn)程會(huì)獲得父進(jìn)程所有文件描述符的副本,這些副本的創(chuàng)建方式類似于 dup(),
這也意味著父、子進(jìn)程對應(yīng)的文件描述符均指向相同的文件表,如下圖所示:
??由此可知,子進(jìn)程拷貝了父進(jìn)程的文件描述符表,使得父、子進(jìn)程中對應(yīng)的文件描述符指向了相同的文件表, 也意味著父、子進(jìn)程中對應(yīng)的文件描述符指向了磁盤中相同的文件,因而這些文件在父、子進(jìn)程間實(shí)現(xiàn)了共享,譬如,如果子進(jìn)程更新了文件偏移量,那么這個(gè)改變也會(huì)影響到父進(jìn)程中相應(yīng)文件描述符的位置偏移量。
1、實(shí)驗(yàn)1
??父進(jìn)程打開文件之后,然后 fork()創(chuàng)建子進(jìn)程,此時(shí)子進(jìn)程繼承了父進(jìn)程打開的文件描述符(父進(jìn)程文件描述符的副本) ,然后父、子進(jìn)程同時(shí)對文件進(jìn)行寫入操作。
??上圖測試結(jié)果可知,此種情況下,父、子進(jìn)程分別對同一個(gè)文件進(jìn)行寫入操作,結(jié)果是接續(xù)寫,不管是父進(jìn)程,還是子進(jìn)程,在每次寫入時(shí)都是從文件的末尾寫入, 很像使用了 O_APPEND 標(biāo)志的效果。 其原因也非常簡單, 圖 9.6.1 中便給出了答案,子進(jìn)程繼承了父進(jìn)程的文件描述符,兩個(gè)文件描述符都指向了一個(gè)相同的文件表,意味著它們的文件偏移量是同一個(gè)、綁定在了一起,相互影響,子進(jìn)程改變了文件的位置偏移量就會(huì)作用到父進(jìn)程,同理,父進(jìn)程改變了文件的位置偏移量就會(huì)作用到子進(jìn)程。
2、實(shí)驗(yàn)2
??父進(jìn)程在調(diào)用 fork()之后,此時(shí)父進(jìn)程和子進(jìn)程都去打開同一個(gè)文件,然后再對文件進(jìn)行寫入操作。
??上圖測試結(jié)果可知,這種文件共享方式實(shí)現(xiàn)的是一種兩個(gè)進(jìn)程分別各自對文件進(jìn)行寫入操作,因?yàn)楦?、子進(jìn)程的這兩個(gè)文件描述符分別指向的是不同的文件表,意味著它們有各自的文件偏移量,一個(gè)進(jìn)程修改了文件偏移量并不會(huì)影響另一個(gè)進(jìn)程的文件偏移量,所以寫入的數(shù)據(jù)會(huì)出現(xiàn)覆蓋的情況。
四、使用execl函數(shù)執(zhí)行新程序
??當(dāng)子進(jìn)程的工作不再是運(yùn)行父進(jìn)程的代碼段,而是運(yùn)行另一個(gè)新程序的代碼,那么這個(gè)時(shí)候子進(jìn)程可以通過 exec 函數(shù)來實(shí)現(xiàn)運(yùn)行另一個(gè)新的程序。而execl函數(shù)是exec族函數(shù)里面的其中一個(gè)員,但它是比較常使用的那個(gè)。
??1、先創(chuàng)建新的程序hello.c文件。
??2、創(chuàng)建一個(gè)execl.c文件,功能:創(chuàng)建子進(jìn)程,并且將子進(jìn)程繼承的父進(jìn)程的程序替換成新的程序,并運(yùn)行。
??上圖測試現(xiàn)象可知,execl函數(shù)是可以替換子進(jìn)行的程序,然后執(zhí)行新的程序的。
五、關(guān)于終端上對進(jìn)程的一些指令操作
??指令:ps -aux
??其功能是查看系統(tǒng)進(jìn)程的詳細(xì)信息。
??指令: | grep
??其功能是管道篩選信息。
??指令:kill -9 [pid]
??其功能是殺死,結(jié)束對應(yīng)進(jìn)程。
六、孤兒進(jìn)程
??父進(jìn)程先于子進(jìn)程結(jié)束,也就是意味著,此時(shí)子進(jìn)程變成了一個(gè)“孤兒”,我們把這種進(jìn)程就稱為孤兒進(jìn)程;因?yàn)樽舆M(jìn)程在結(jié)束后需要父進(jìn)程將子進(jìn)程的一些資源釋放掉,而父進(jìn)程先于子進(jìn)程結(jié)束的話,那么系統(tǒng)就會(huì)讓init進(jìn)程接管父進(jìn)程職責(zé),釋放結(jié)束后的子進(jìn)程資源。 在 Linux 系統(tǒng)當(dāng)中,所有的孤兒進(jìn)程都自動(dòng)成為 init 進(jìn)程(進(jìn)程號為 1)的子進(jìn)程, 換言之,某一子進(jìn)程的父進(jìn)程結(jié)束后,該子進(jìn)程調(diào)用 getppid()將返回 1, init 進(jìn)程變成了孤兒進(jìn)程的“養(yǎng)父”。
??孤兒進(jìn)程程序?qū)崿F(xiàn)與測試:
??由上圖測試結(jié)果可知,在紅色框框中,父進(jìn)程還未結(jié)束;在黃色框框中,由于子進(jìn)程sleep阻塞了3秒,父進(jìn)程先于子進(jìn)程結(jié)束了,然后子進(jìn)程的父進(jìn)程(pid=10604)變成了父進(jìn)程(pid=2122),在右邊框框可知,pid:2122是/sbin/upstart其實(shí)就是init進(jìn)程。
七、僵尸進(jìn)程
??進(jìn)程結(jié)束之后,通常需要其父進(jìn)程為其“收尸”,回收子進(jìn)程占用的一些內(nèi)存資源,父進(jìn)程通過調(diào)用wait()(或其變體 waitpid()、 waitid()等)函數(shù)回收子進(jìn)程資源,歸還給系統(tǒng)。
??如果子進(jìn)程先于父進(jìn)程結(jié)束,此時(shí)父進(jìn)程還未來得及給子進(jìn)程“收尸”,那么此時(shí)子進(jìn)程就變成了一個(gè)僵尸進(jìn)程。 子進(jìn)程結(jié)束后其父進(jìn)程并沒有來得及立馬給它“收尸”, 子進(jìn)程處于“曝尸荒野”的狀態(tài),在這么一個(gè)狀態(tài)下,我們就將子進(jìn)程成為僵尸進(jìn)程;至于名字由來,肯定是對電影情節(jié)的一種效仿!
??當(dāng)父進(jìn)程調(diào)用 wait()(或其變體,下文不再強(qiáng)調(diào))為子進(jìn)程“收尸”后,僵尸進(jìn)程就會(huì)被內(nèi)核徹底刪除。另外一種情況,如果父進(jìn)程并沒有調(diào)用 wait()函數(shù)然后就退出了,那么此時(shí) init 進(jìn)程將會(huì)接管它的子進(jìn)程并自動(dòng)調(diào)用 wait(), 故而從系統(tǒng)中移除僵尸進(jìn)程。
??如果父進(jìn)程創(chuàng)建了某一子進(jìn)程,子進(jìn)程已經(jīng)結(jié)束,而父進(jìn)程還在正常運(yùn)行,但父進(jìn)程并未調(diào)用 wait()回收子進(jìn)程,此時(shí)子進(jìn)程變成一個(gè)僵尸進(jìn)程。 首先來說,這樣的程序設(shè)計(jì)是有問題的,如果系統(tǒng)中存在大量的僵尸進(jìn)程,它們勢必會(huì)填滿內(nèi)核進(jìn)程表,從而阻礙新進(jìn)程的創(chuàng)建。需要注意的是,僵尸進(jìn)程是無法通過信號將其殺死的,即使是“一擊必殺”信號 SIGKILL 也無法將其殺死,那么這種情況下,只能殺死僵尸進(jìn)程的父進(jìn)程(或等待其父進(jìn)程終止),這樣 init 進(jìn)程將會(huì)接管這些僵尸進(jìn)程,從而將它們從系統(tǒng)中清理掉!所以,在我們的一個(gè)程序設(shè)計(jì)中,一定要監(jiān)視子進(jìn)程的狀態(tài)變化,如果子進(jìn)程終止了,要調(diào)用 wait()將其回收,避免僵尸進(jìn)程。
??僵尸進(jìn)程程序?qū)崿F(xiàn)與測試:
??通過命令可以查看到子進(jìn)程 10997 依然存在,可以看到它的狀態(tài)欄顯示的是“Z”(zombie,僵尸),表示它是一個(gè)僵尸進(jìn)程。 僵尸進(jìn)程無法被信號殺死,大家可以試試,要么等待其父進(jìn)程終止、要么殺死其父進(jìn)程,讓 init 進(jìn)程來處理,當(dāng)我們殺死其父進(jìn)程之后,僵尸進(jìn)程也會(huì)被隨之清理。
1、監(jiān)視子進(jìn)程
??在很多應(yīng)用程序的設(shè)計(jì)中,父進(jìn)程需要知道子進(jìn)程于何時(shí)被終止,并且需要知道子進(jìn)程的終止?fàn)顟B(tài)信息,是正常終止、還是異常終止亦或者被信號終止等,意味著父進(jìn)程會(huì)對子進(jìn)程進(jìn)行監(jiān)視。而這可以通過系統(tǒng)調(diào)用 wait()以及其它變體來監(jiān)視子進(jìn)程的狀態(tài)改變。
??wait()函數(shù):
??對于許多需要?jiǎng)?chuàng)建子進(jìn)程的進(jìn)程來說,有時(shí)設(shè)計(jì)需要監(jiān)視子進(jìn)程的終止時(shí)間以及終止時(shí)的一些狀態(tài)信息,在某些設(shè)計(jì)需求下這是很有必要的。 系統(tǒng)調(diào)用 wait()可以等待進(jìn)程的任一子進(jìn)程終止,同時(shí)獲取子進(jìn)程的終止?fàn)顟B(tài)信息,其函數(shù)原型如下所示:
??使用wait函數(shù)監(jiān)視子進(jìn)程程序?qū)崿F(xiàn)與測試:
??實(shí)驗(yàn)現(xiàn)象可知,父進(jìn)程也是經(jīng)過2秒的等待后才進(jìn)行對子進(jìn)程收尸處理的,說明wait()函數(shù)能起到監(jiān)視和減少子進(jìn)程變成僵尸進(jìn)程發(fā)生。
八、守護(hù)進(jìn)程
1、何為守護(hù)進(jìn)程
??守護(hù)進(jìn)程(Daemon) 也稱為精靈進(jìn)程,是運(yùn)行在后臺的一種特殊進(jìn)程,它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些事情的發(fā)生, 主要表現(xiàn)為以下兩個(gè)特點(diǎn):
??? 長期運(yùn)行。 守護(hù)進(jìn)程是一種生存期很長的一種進(jìn)程,它們一般在系統(tǒng)啟動(dòng)時(shí)開始運(yùn)行,除非強(qiáng)行終止,否則直到系統(tǒng)關(guān)機(jī)都會(huì)保持運(yùn)行。 與守護(hù)進(jìn)程相比,普通進(jìn)程都是在用戶登錄或運(yùn)行程序時(shí)創(chuàng)建,在運(yùn)行結(jié)束或用戶注銷時(shí)終止,但守護(hù)進(jìn)程不受用戶登錄注銷的影響,它們將會(huì)一直運(yùn)行著、直到系統(tǒng)關(guān)機(jī)。
??? 與控制終端脫離。 在 Linux 中,系統(tǒng)與用戶交互的界面稱為終端,每一個(gè)從終端開始運(yùn)行的進(jìn)程都會(huì)依附于這個(gè)終端,這是上一小節(jié)給大家介紹的控制終端,也就是會(huì)話的控制終端。當(dāng)控制終端被關(guān)閉的時(shí)候, 該會(huì)話就會(huì)退出, 由控制終端運(yùn)行的所有進(jìn)程都會(huì)被終止, 這使得普通進(jìn)程都是和運(yùn)行該進(jìn)程的終端相綁定的; 但守護(hù)進(jìn)程能突破這種限制,它脫離終端并且在后臺運(yùn)行, 脫離終端的目的是為了避免進(jìn)程在運(yùn)行的過程中的信息在終端顯示并且進(jìn)程也不會(huì)被任何終端所產(chǎn)生的信息所打斷。
??守護(hù)進(jìn)程是一種很有用的進(jìn)程。 Linux 中大多數(shù)服務(wù)器就是用守護(hù)進(jìn)程實(shí)現(xiàn)的,譬如, Internet 服務(wù)器inetd、 Web 服務(wù)器 httpd 等。同時(shí),守護(hù)進(jìn)程完成許多系統(tǒng)任務(wù),譬如作業(yè)規(guī)劃進(jìn)程 crond 等。
??守護(hù)進(jìn)程 Daemon,通常簡稱為 d,一般進(jìn)程名后面帶有 d 就表示它是一個(gè)守護(hù)進(jìn)程。守護(hù)進(jìn)程與終端無任何關(guān)聯(lián),用戶的登錄與注銷與守護(hù)進(jìn)程無關(guān)、不受其影響,守護(hù)進(jìn)程自成進(jìn)程組、自成會(huì)話,即pid=gid=sid。通過命令"ps -ajx"查看系統(tǒng)所有的進(jìn)程,如下所示:
??TTY 一欄是問號?表示該進(jìn)程沒有控制終端,也就是守護(hù)進(jìn)程,其中 COMMAND 一欄使用中括號[]括起來的表示內(nèi)核線程,這些線程是在內(nèi)核里創(chuàng)建,沒有用戶空間代碼,因此沒有程序文件名和命令行,通常采用 k 開頭的名字,表示 Kernel。
2、編寫守護(hù)進(jìn)程程序
??編寫守護(hù)進(jìn)程一般包含如下幾個(gè)步驟:
??1) 創(chuàng)建子進(jìn)程、終止父進(jìn)程
??父進(jìn)程調(diào)用 fork()創(chuàng)建子進(jìn)程,然后父進(jìn)程使用 exit()退出,這樣做實(shí)現(xiàn)了下面幾點(diǎn)。第一,如果該守護(hù)進(jìn)程是作為一條簡單地 shell 命令啟動(dòng),那么父進(jìn)程終止會(huì)讓 shell 認(rèn)為這條命令已經(jīng)執(zhí)行完畢。第二,雖然子進(jìn)程繼承了父進(jìn)程的進(jìn)程ID,但它有自己獨(dú)立的進(jìn)程ID,這保證了子進(jìn)程不是一個(gè)進(jìn)程組的組長進(jìn)程,這是下面將要調(diào)用 setsid 函數(shù)的先決條件!
??2) 子進(jìn)程調(diào)用 setsid 創(chuàng)建會(huì)話
??這步是關(guān)鍵,由于之前子進(jìn)程并不是進(jìn)程組的組長進(jìn)程,所以調(diào)用 setsid()會(huì)使得子進(jìn)程創(chuàng)建一個(gè)新的會(huì)話,子進(jìn)程成為新會(huì)話的首領(lǐng)進(jìn)程,同樣也創(chuàng)建了新的進(jìn)程組、子進(jìn)程成為組長進(jìn)程,此時(shí)創(chuàng)建的會(huì)話將沒有控制終端。 所以這里調(diào)用 setsid 有三個(gè)作用:讓子進(jìn)程擺脫原會(huì)話的控制、讓子進(jìn)程擺脫原進(jìn)程組的控制和讓子進(jìn)程擺脫原控制終端的控制。在調(diào)用 fork 函數(shù)時(shí),子進(jìn)程繼承了父進(jìn)程的會(huì)話、進(jìn)程組、控制終端等,雖然父進(jìn)程退出了,但原先的會(huì)話期、進(jìn)程組、控制終端等并沒有改變,因此,那還不是真正意義上使兩者獨(dú)立開來。 setsid 函數(shù)能夠使子進(jìn)程完全獨(dú)立出來,從而脫離所有其他進(jìn)程的控制。
??3) 將工作目錄更改為根目錄
??子進(jìn)程是繼承了父進(jìn)程的當(dāng)前工作目錄,由于在進(jìn)程運(yùn)行中,當(dāng)前目錄所在的文件系統(tǒng)是不能卸載的,這對以后使用會(huì)造成很多的麻煩。因此通常的做法是讓“/”作為守護(hù)進(jìn)程的當(dāng)前目錄,當(dāng)然也可以指定其它目錄來作為守護(hù)進(jìn)程的工作目錄。
??4) 重設(shè)文件權(quán)限掩碼 umask
??文件權(quán)限掩碼 umask 用于對新建文件的權(quán)限位進(jìn)行屏蔽。由于使用 fork 函數(shù)新建的子進(jìn)程繼承了父進(jìn)程的文件權(quán)限掩碼,這就給子進(jìn)程使用文件帶來了諸多的麻煩。因此, 把文件權(quán)限掩碼設(shè)置為 0, 確保子進(jìn)程有最大操作權(quán)限、這樣可以大大增強(qiáng)該守護(hù)進(jìn)程的靈活性。設(shè)置文件權(quán)限掩碼的函數(shù)是 umask,通常的使用方法為 umask(0)。
??5) 關(guān)閉不再需要的文件描述符
??子進(jìn)程繼承了父進(jìn)程的所有文件描述符, 這些被打開的文件可能永遠(yuǎn)不會(huì)被守護(hù)進(jìn)程(此時(shí)守護(hù)進(jìn)程指的就是子進(jìn)程,父進(jìn)程退出、子進(jìn)程成為守護(hù)進(jìn)程) 讀或?qū)?,但它們一樣消耗系統(tǒng)資源,可能導(dǎo)致所在的文件系統(tǒng)無法卸載,所以必須關(guān)閉這些文件, 這使得守護(hù)進(jìn)程不再持有從其父進(jìn)程繼承過來的任何文件描述符。
??6) 將文件描述符號為 0、 1、 2 定位到/dev/null
??將守護(hù)進(jìn)程的標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出以及標(biāo)準(zhǔn)錯(cuò)誤重定向到/dev/null,這使得守護(hù)進(jìn)程的輸出無處顯示、也無處從交互式用戶那里接收輸入。
??7) 其它:忽略 SIGCHLD 信號
??處理 SIGCHLD 信號不是必須的, 但對于某些進(jìn)程,特別是并發(fā)服務(wù)器進(jìn)程往往是特別重要的,服務(wù)器進(jìn)程在接收到客戶端請求時(shí)會(huì)創(chuàng)建子進(jìn)程去處理該請求,如果子進(jìn)程結(jié)束之后,父進(jìn)程沒有去 wait 回收子進(jìn)程,則子進(jìn)程將成為僵尸進(jìn)程;如果父進(jìn)程 wait 等待子進(jìn)程退出,將又會(huì)增加父進(jìn)程的負(fù)擔(dān)、也就是增加服務(wù)器的負(fù)擔(dān),影響服務(wù)器進(jìn)程的并發(fā)性能,在 Linux 下,可以將 SIGCHLD 信號的處理方式設(shè)置為SIG_IGN,也就是忽略該信號,可讓內(nèi)核將僵尸進(jìn)程轉(zhuǎn)交給 init 進(jìn)程去處理,這樣既不會(huì)產(chǎn)生僵尸進(jìn)程、又省去了服務(wù)器進(jìn)程回收子進(jìn)程所占用的時(shí)間。
??從上圖可知, daemon進(jìn)程成為了一個(gè)守護(hù)進(jìn)程,與控制臺脫離,當(dāng)關(guān)閉當(dāng)前控制終端時(shí),daemon進(jìn)程并不會(huì)受到影響,依然會(huì)正常繼續(xù)運(yùn)行;
??守護(hù)進(jìn)程可以通過終端命令行啟動(dòng),但通常它們是由系統(tǒng)初始化腳本進(jìn)行啟動(dòng),譬如/etc/rc*或/etc/init.d/*等。
補(bǔ)充知識點(diǎn):
九、進(jìn)程間的通信方法實(shí)現(xiàn)
1、管道通信
①無名管道通信
??pipe無名管道通信只能作用在有親緣關(guān)系的進(jìn)程之間通信,詳情如下圖所示:
??pipe是一種單向數(shù)據(jù)傳輸?shù)墓艿?,其參?shù)pipefd[2]是個(gè)整形數(shù)組,而pipefd[0]和pipefd[1]分別是創(chuàng)建的讀管道的文件描述符和寫管道的文件描述符。
??使用pipe函數(shù)實(shí)現(xiàn)父進(jìn)程與子進(jìn)程間的通信及測試:
??從上圖可知,當(dāng)子進(jìn)程讀管道數(shù)據(jù)時(shí),如果管道沒有數(shù)據(jù)就會(huì)一直阻塞。只有管道有數(shù)據(jù)可讀,子進(jìn)程才會(huì)通,如下圖所示:
??在父進(jìn)程中向管道寫入數(shù)據(jù),然后子進(jìn)程向管道讀取數(shù)據(jù)。
②有名管道通信
??mkfifo有名管道通信可以作用在無親緣關(guān)系的進(jìn)程之間通信,詳情如下圖所示:
??mkfifo其實(shí)就是創(chuàng)建了一個(gè)實(shí)實(shí)在在的fifo文件,然后進(jìn)程間的通信就是通過對創(chuàng)建的fifo文件進(jìn)行操作,其fifo文件和塊設(shè)備文件一樣,都是不占用字節(jié)的,如下圖所示:
??使用mkfifo函數(shù)實(shí)現(xiàn)兩個(gè)無親緣關(guān)系的進(jìn)程間的通信及測試:
??讀fifo數(shù)據(jù)的進(jìn)程程序:
??寫fifo數(shù)據(jù)的進(jìn)程程序:
??現(xiàn)象:
2、信號通信
??信號的基本概念:信號是事件發(fā)生時(shí)對進(jìn)程的通知機(jī)制,也可以把它稱為軟件中斷。信號與硬件中斷的相似之處在于能夠打斷程序當(dāng)前執(zhí)行的正常流程, 其實(shí)是在軟件層次上對中斷機(jī)制的一種模擬。 大多數(shù)情況下,是無法預(yù)測信號達(dá)到的準(zhǔn)確時(shí)間,所以,信號提供了一種處理異步事件的方法。
信號的目的是用來通信的
??一個(gè)具有合適權(quán)限的進(jìn)程能夠向另一個(gè)進(jìn)程發(fā)送信號,信號的這一用法可作為一種同步技術(shù),甚至是進(jìn)程間通信(IPC)的原始形式。 信號可以由“誰”發(fā)出呢? 以下列舉的很多情況均可以產(chǎn)生信號:
??? 硬件發(fā)生異常,即硬件檢測到錯(cuò)誤條件并通知內(nèi)核,隨即再由內(nèi)核發(fā)送相應(yīng)的信號給相關(guān)進(jìn)程。硬件檢測到異常的例子包括執(zhí)行一條異常的機(jī)器語言指令,諸如,除數(shù)為 0、數(shù)組訪問越界導(dǎo)致引用了無法訪問的內(nèi)存區(qū)域等,這些異常情況都會(huì)被硬件檢測到,并通知內(nèi)核、然后內(nèi)核為該異常情況發(fā)生時(shí)正在運(yùn)行的進(jìn)程發(fā)送適當(dāng)?shù)男盘栆酝ㄖM(jìn)程。
??? 用于在終端下輸入了能夠產(chǎn)生信號的特殊字符。 譬如在終端上按下 CTRL + C 組合按鍵可以產(chǎn)生中斷信號(SIGINT),通過這個(gè)方法可以終止在前臺運(yùn)行的進(jìn)程;按下 CTRL + Z 組合按鍵可以產(chǎn)生暫停信號(SIGCONT),通過這個(gè)方法可以暫停當(dāng)前前臺運(yùn)行的進(jìn)程。
??? 進(jìn)程調(diào)用 kill()系統(tǒng)調(diào)用可將任意信號發(fā)送給另一個(gè)進(jìn)程或進(jìn)程組。 當(dāng)然對此是有所限制的,接收信號的進(jìn)程和發(fā)送信號的進(jìn)程的所有者必須相同,亦或者發(fā)送信號的進(jìn)程的所有者是 root 超級用戶。
??? 用戶可以通過 kill 命令將信號發(fā)送給其它進(jìn)程。 kill 命令想必大家都會(huì)使用,通常我們會(huì)通過 kill命令來“殺死”(終止)一個(gè)進(jìn)程,譬如在終端下執(zhí)行"kill -9 xxx"來殺死 PID 為 xxx 的進(jìn)程。 kill命令其內(nèi)部的實(shí)現(xiàn)原理便是通過 kill()系統(tǒng)調(diào)用來完成的。
??? 發(fā)生了軟件事件,即當(dāng)檢測到某種軟件條件已經(jīng)發(fā)生。 這里指的不是硬件產(chǎn)生的條件(如除數(shù)為 0、引用無法訪問的內(nèi)存區(qū)域等),而是軟件的觸發(fā)條件、觸發(fā)了某種軟件條件(進(jìn)程所設(shè)置的定時(shí)器已經(jīng)超時(shí)、進(jìn)程執(zhí)行的 CPU 時(shí)間超限、進(jìn)程的某個(gè)子進(jìn)程退出等等情況)。
??進(jìn)程同樣也可以向自身發(fā)送信號,然而發(fā)送給進(jìn)程的諸多信號中,大多數(shù)都是來自于內(nèi)核。
??以上便是可以產(chǎn)生信號的多種不同的條件,總的來看,信號的目的都是用于通信的,當(dāng)發(fā)生某種情況下,通過信號將情況“告知”相應(yīng)的進(jìn)程,從而達(dá)到同步、通信的目的。
信號由誰處理、怎么處理
??信號通常是發(fā)送給對應(yīng)的進(jìn)程,當(dāng)信號到達(dá)后, 該進(jìn)程需要做出相應(yīng)的處理措施,通常進(jìn)程會(huì)視具體信號執(zhí)行以下操作之一:
??? 忽略信號。也就是說,當(dāng)信號到達(dá)進(jìn)程后,該進(jìn)程并不會(huì)去理會(huì)它、直接忽略,就好像是沒有出該信號,信號對該進(jìn)程不會(huì)產(chǎn)生任何影響。事實(shí)上,大多數(shù)信號都可以使用這種方式進(jìn)行處理,但有兩種信號卻決不能被忽略,它們是 SIGKILL 和 SIGSTOP,這兩種信號不能被忽略的原因是:它們向內(nèi)核和超級用戶提供了使進(jìn)程終止或停止的可靠方法。另外,如果忽略某些由硬件異常產(chǎn)生的信號,則進(jìn)程的運(yùn)行行為是未定義的。
??? 捕獲信號。 當(dāng)信號到達(dá)進(jìn)程后,執(zhí)行預(yù)先綁定好的信號處理函數(shù)。為了做到這一點(diǎn),要通知內(nèi)核在某種信號發(fā)生時(shí),執(zhí)行用戶自定義的處理函數(shù),該處理函數(shù)中將會(huì)對該信號事件作出相應(yīng)的處理,Linux 系統(tǒng)提供了 signal()系統(tǒng)調(diào)用可用于注冊信號的處理函數(shù)。
??? 執(zhí)行系統(tǒng)默認(rèn)操作。 進(jìn)程不對該信號事件作出處理,而是交由系統(tǒng)進(jìn)行處理,每一種信號都會(huì)有其對應(yīng)的系統(tǒng)默認(rèn)的處理方式。需要注意的是,對大多數(shù)信號來說,系統(tǒng)默認(rèn)的處理方式就是終止該進(jìn)程。
信號是異步的
??信號是異步事件的經(jīng)典實(shí)例,產(chǎn)生信號的事件對進(jìn)程而言是隨機(jī)出現(xiàn)的,進(jìn)程無法預(yù)測該事件產(chǎn)生的準(zhǔn)確時(shí)間,進(jìn)程不能夠通過簡單地測試一個(gè)變量或使用系統(tǒng)調(diào)用來判斷是否產(chǎn)生了一個(gè)信號,這就如同硬件中斷事件,程序是無法得知中斷事件產(chǎn)生的具體時(shí)間,只有當(dāng)產(chǎn)生中斷事件時(shí),才會(huì)告知程序、然后打斷當(dāng)前程序的正常執(zhí)行流程、跳轉(zhuǎn)去執(zhí)行中斷服務(wù)函數(shù),這就是異步處理方式。
??信號本質(zhì)上是 int 類型數(shù)字編號,這些信號在<signum.h>頭文件中定義, 每個(gè)信號都是以 SIGxxx 開頭,這里就自己去查了。
①信號的分類
Ⅰ、可靠信號與不可靠信號:
??不可靠信號主要指的是進(jìn)程可能對信號做出錯(cuò)誤的反應(yīng)以及信號可能丟失(處理信號時(shí)又來了新的信號,則導(dǎo)致信號丟失)。
??在 Linux 系統(tǒng)下使用"kill -l"命令可查看到所有信號,如下所示:
??括號" ) "前面的數(shù)字對應(yīng)該信號的編號,編號 1~31 所對應(yīng)的是不可靠信號,編號 34~64 對應(yīng)的是
可靠信號,從圖中可知,可靠信號并沒有一個(gè)具體對應(yīng)的名字,而是使用了 SIGRTMIN+N 或 SIGRTMAXN 的方式來表示。
Ⅱ、實(shí)時(shí)信號與非實(shí)時(shí)信號:
??實(shí)時(shí)信號與非實(shí)時(shí)信號其實(shí)是從時(shí)間關(guān)系上進(jìn)行的分類,與可靠信號與不可靠信號是相互對應(yīng)的, 非實(shí)時(shí)信號都不支持排隊(duì),都是不可靠信號;實(shí)時(shí)信號都支持排隊(duì),都是可靠信號。 實(shí)時(shí)信號保證了發(fā)送的多個(gè)信號都能被接收, 實(shí)時(shí)信號是 POSIX 標(biāo)準(zhǔn)的一部分,可用于應(yīng)用進(jìn)程。
②信號的發(fā)送函數(shù)及使用測試實(shí)驗(yàn)
Ⅰ、raise()函數(shù)
??可以在終端中敲man 3 raise可知raise函數(shù)的功能,如下圖所示:
??由上圖可知,raise(sig)等價(jià)于kill(getpid(), sig),也就是說其功能就是自己給自己發(fā)送信號(實(shí)質(zhì)是先將信號和pid號發(fā)給內(nèi)核,內(nèi)核再發(fā)送該信號給對應(yīng)的pid進(jìn)程或直接處理)。
??這里我做一個(gè)簡單自己殺死自己的實(shí)驗(yàn),如下圖所示:
Ⅱ、kill()函數(shù)
??這里不過多介紹該函數(shù)了,可以在man手冊查詢。
??這里我做一個(gè)簡單 “進(jìn)程kill” 殺死手動(dòng)指定的 “進(jìn)程a” 的實(shí)驗(yàn),如下圖所示:
Ⅱ、alarm()函數(shù)
??這里不過多介紹該函數(shù)了,可以在man手冊查詢。
??這里我做一個(gè)簡單鬧鐘定時(shí)程序,當(dāng)時(shí)間到了發(fā)出鬧鐘時(shí)間就終止程序的實(shí)驗(yàn),如下圖所示:
③信號的接收函數(shù)及使用測試實(shí)驗(yàn)
Ⅰ、pause()函數(shù)
??pause()系統(tǒng)調(diào)用可以使得進(jìn)程暫停運(yùn)行、進(jìn)入休眠狀態(tài),直到進(jìn)程捕獲到一個(gè)信號為止,詳細(xì)信息可以可以在man手冊查詢。
??這里我做一個(gè)簡單鬧鐘定時(shí)使程序退出pause狀態(tài),這次鬧鐘程序不是用默認(rèn)的信號處理函數(shù),當(dāng)時(shí)間到了發(fā)出鬧鐘時(shí)間就觸發(fā)pause函數(shù),讓程序繼續(xù)往下走,如下圖所示:
④信號的處理函數(shù)及使用測試實(shí)驗(yàn)
Ⅰ、signal()函數(shù)
??當(dāng)進(jìn)程接收到內(nèi)核或用戶發(fā)送過來的信號之后,根據(jù)具體信號可以采取不同的處理方式:忽略信號、捕獲信號或者執(zhí)行系統(tǒng)默認(rèn)操作。
??忽略信號:
??系統(tǒng)默認(rèn):
??捕獲信號:
3、共享內(nèi)存
①共享內(nèi)存的概念
??共享內(nèi)存就是映射一段能被其它進(jìn)程所訪問的內(nèi)存, 這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建, 但其它的多個(gè)進(jìn)程都可以訪問, 使得多個(gè)進(jìn)程可以訪問同一塊內(nèi)存空間。共享內(nèi)存是最快的 IPC 方式, 它是針對其它進(jìn)程間通信方式運(yùn)行效率低而專門設(shè)計(jì)的, 它往往與其它通信機(jī)制, 譬如結(jié)合信號量來使用, 以實(shí)現(xiàn)進(jìn)程間的同步和通信。
②shmget函數(shù)和ftok函數(shù)
Ⅰ、shmget函數(shù)
??這里我截取了man手冊的描述,它是一個(gè)向系統(tǒng)申請分配一個(gè)共享內(nèi)存空間的函數(shù),如下圖所示:注意:參數(shù)key值除了可以使用文檔描述的 IPC_PRIVATE 這個(gè)宏外,還可以使用ftok函數(shù)獲取這個(gè)key值
Ⅱ、ftok函數(shù)
Ⅲ、shmat、shmdt、shmctl函數(shù)
??上述說過,使用shmget函數(shù)申請分配共享內(nèi)存成功后,它會(huì)返回共享內(nèi)存標(biāo)識符,它和文件標(biāo)識符是類似的,但是這個(gè)不便于我們?nèi)ゲ僮鬟@個(gè)共享內(nèi)存,所以在Linux應(yīng)用中經(jīng)常會(huì)使用共享內(nèi)存映射到進(jìn)程地址上,方便開發(fā)者直接對共享內(nèi)存進(jìn)行讀寫操作。
??shmat、shmdt、shmctl函數(shù)分別是創(chuàng)建共享內(nèi)存映射、刪除共享內(nèi)存映射(但不會(huì)刪除內(nèi)核中的共享內(nèi)存對象)、操作共享內(nèi)存對象。
Ⅳ、使用shmget函數(shù)實(shí)現(xiàn)以共享內(nèi)存方式的父子進(jìn)程間的通信及測試實(shí)驗(yàn)
??我們先了解一下,使用系統(tǒng)自帶的密鑰IPC_PRIVATE和使用ftok函數(shù)自己獲取的密鑰 向系統(tǒng)申請分配共享內(nèi)存的區(qū)別,圖下兩張圖的實(shí)驗(yàn)所示:
??由上面兩張實(shí)驗(yàn)結(jié)果對比可知,使用系統(tǒng)自帶的密鑰IPC_PRIVATE和使用ftok函數(shù)自己獲取的密鑰 向系統(tǒng)申請分配共享內(nèi)存的區(qū)別前者共享內(nèi)存描述符是為0的,而后者的共享描述符是不為0的。
??好,有了上面的了解,正式做要實(shí)現(xiàn)的實(shí)驗(yàn),如下圖所示:
4、消息隊(duì)列
①消息隊(duì)列的概述
??消息隊(duì)列是消息的鏈表, 存放在內(nèi)核中并由消息隊(duì)列標(biāo)識符標(biāo)識, 消息隊(duì)列克服了信號傳遞信息少、 管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺陷。 消息隊(duì)列包括 POSIX 消息隊(duì)列和 System V 消息隊(duì)列。
??而 POSIX 消息隊(duì)列和 System V 消息隊(duì)列異同點(diǎn):
目的不同:POSIX 消息隊(duì)列主要用于進(jìn)程間通信,而 System V 消息隊(duì)列可以進(jìn)行進(jìn)程間和線程間的通信。
接口不同:POSIX 消息隊(duì)列使用 mq_open(), mq_send(), mq_receive()等函數(shù)進(jìn)行操作,而 System V 消息隊(duì)列主要使用msgget(), msgsnd(), msgrcv()等函數(shù)進(jìn)行操作。
大小限制不同:POSIX 消息隊(duì)列沒有固定的消息大小限制,但是 System V 消息隊(duì)列有固定的消息大小限制。
使用方式不同:POSIX 消息隊(duì)列的使用方式更為靈活,可以使用文件描述符來操作消息隊(duì)列;而 System V 消息隊(duì)列需要通過>鍵值來找到消息隊(duì)列。
發(fā)送和接收消息的方式不同:POSIX 消息隊(duì)列可以設(shè)置優(yōu)先級,支持非阻塞方式和阻塞方式;而 System V 消息隊(duì)列僅支持阻
塞方式,且不支持消息的優(yōu)先級。
刪除消息隊(duì)列方式不同:POSIX 消息隊(duì)列可以使用 mq_close()和mq_unlink()函數(shù)刪除消息隊(duì)列,而刪除 System V 消息隊(duì)列需>要使用 msgctl()函數(shù)。
相同點(diǎn):都是進(jìn)程通信的一種手段。都可以實(shí)現(xiàn)消息的發(fā)送和接收,保證進(jìn)程間的異步通信。都可以使用信號量機(jī)制實(shí)現(xiàn)消息的>同步和互斥。
??消息隊(duì)列是 UNIX 下不同進(jìn)程之間實(shí)現(xiàn)共享資源的一種機(jī)制, UNIX 允許不同進(jìn)程將格式化的數(shù)據(jù)流以消息隊(duì)列形式發(fā)送給任意進(jìn)程, 有足夠權(quán)限的進(jìn)程可以向隊(duì)列中添加消息,被賦予讀權(quán)限的進(jìn)程則可以讀走隊(duì)列中的消息。
②使用消息隊(duì)列實(shí)現(xiàn)兩個(gè)無情緣關(guān)系之間的進(jìn)程通信
Ⅰ、關(guān)于消息隊(duì)列創(chuàng)建時(shí)用的key值的知識
Ⅱ、實(shí)現(xiàn)實(shí)驗(yàn)及測試
程序:
結(jié)果:
5、信號量
①信號量的概述
??信號量是一個(gè)計(jì)數(shù)器, 與其它進(jìn)程間通信方式不大相同, 它主要用于保護(hù)臨界資源,控制多個(gè)進(jìn)程間或一個(gè)進(jìn)程內(nèi)的多個(gè)線程間對共享資源的訪問, 相當(dāng)于內(nèi)存中的標(biāo)志,進(jìn)程可以根據(jù)它判定是否能夠訪問某些共享資源,同時(shí),進(jìn)程也可以修改該標(biāo)志, 除了用于共享資源的訪問控制外,還可用于進(jìn)程同步。
??它常作為一種鎖機(jī)制, 防止某進(jìn)程在訪問資源時(shí)其它進(jìn)程也訪問該資源, 因此, 主要作為進(jìn)程間以及同一個(gè)進(jìn)程內(nèi)不同線程之間的同步手段。 Linux 提供了一組精心設(shè)計(jì)的信號量接口來對信號量進(jìn)行操作,它們聲明在頭文件 sys/sem.h 中。
②使用信號量實(shí)現(xiàn)進(jìn)程間的通信
Ⅰ、使用sleep阻塞方式,使父進(jìn)程先于子進(jìn)程運(yùn)行
Ⅱ、在上一個(gè)實(shí)驗(yàn)基礎(chǔ)上,使用信號量的阻塞方式,使子進(jìn)程先于父進(jìn)程運(yùn)行
semaphore.c文章來源:http://www.zghlxwxcb.cn/news/detail-423818.html
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
//信號量操作函數(shù)semctl中參數(shù)2聯(lián)合體
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int main(int argc, char *argv[])
{
pid_t pid; //進(jìn)程號
key_t key; //密鑰key
int semid; //信號量集標(biāo)識符
union semun my_sem_union; //聯(lián)合體,目的是初始化信號量的值
struct sembuf sop; //信號量結(jié)構(gòu)體數(shù)組,semop函數(shù)使用
int ret;
key = ftok("./my_keyfile", 24678); //獲取key值
if(key == -1) {
printf("ftok error\n");
exit(-1);
} else {
printf("ftok ok,the key is %#X\n",key);
}
/* 向系統(tǒng)申請創(chuàng)建信號量,參數(shù)1:密鑰key
參數(shù)2:信號量個(gè)數(shù) 參數(shù)3:權(quán)限 */
semid = semget(key, 1, 0777 | IPC_CREAT);
if(semid == -1) {
printf("semget error\n");
exit(-1);
} else {
printf("semid is %d\n", semid);
}
//初始化信號量
my_sem_union.val = 0; //信號量值為0
ret = semctl(semid,0,SETVAL,my_sem_union); //初始化第0個(gè)信號量的值
if(ret == -1) {
printf("semctl error\n");
exit(-1);
}
pid = fork(); //創(chuàng)建子進(jìn)程
if(pid == -1) {
printf("fork failed\n");
exit(-1);
}
//父進(jìn)程
if(pid > 0) {
int status; //子進(jìn)程結(jié)束狀態(tài)
sop.sem_num = 0; //信號量編號為0
sop.sem_op = -1; //分配資源,-1操作
sop.sem_flg = 0; //阻塞操作
ret = semop(semid, &sop, 1); //參數(shù)3:1個(gè)信號量集
if(ret == -1) {
printf("semop failed\n");
exit(-1);
}
printf("I am father\n");
wait(&status); //監(jiān)視子進(jìn)程,等待退出
}
//子進(jìn)程
if(pid == 0) {
sleep(2); //休眠2秒,讓父進(jìn)程先運(yùn)行
sop.sem_num = 0; //信號量編號為0
sop.sem_op = 1; //分配資源,-1操作
sop.sem_flg = 0; //阻塞操作
ret = semop(semid, &sop, 1); //參數(shù)3:1個(gè)信號量集
printf("I am son\n");
}
exit(0);
}
實(shí)驗(yàn)現(xiàn)象:文章來源地址http://www.zghlxwxcb.cn/news/detail-423818.html
到了這里,關(guān)于Linux應(yīng)用程序開發(fā):進(jìn)程的一些事兒的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!