目前網(wǎng)站開發(fā)有什么缺點(diǎn)北京seo專業(yè)團(tuán)隊(duì)
一、進(jìn)程創(chuàng)建
fork函數(shù)
在Linux中fork函數(shù)是非常重要的函數(shù),它從已存在進(jìn)程中創(chuàng)建一個新進(jìn)程,原進(jìn)程為父進(jìn)程
fork函數(shù)的功能:
- 分配新的內(nèi)存和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程
- 將父進(jìn)程部分?jǐn)?shù)據(jù)結(jié)構(gòu)內(nèi)容拷貝至子進(jìn)程
- 添加子進(jìn)程到系統(tǒng)的進(jìn)程列表中
- fork返回,開始調(diào)度器調(diào)度?
fork函數(shù)的返回值:
- 子進(jìn)程返回0
- 父進(jìn)程返回子進(jìn)程的pid
- 創(chuàng)建進(jìn)程失敗返回
?寫實(shí)拷貝
在進(jìn)程地址空間中,我們解釋了父子進(jìn)程的數(shù)據(jù)相同地址不同值的問題,那么現(xiàn)在我們來談?wù)凮S是具體怎么實(shí)現(xiàn)的,下面給大家畫個草圖
fork的常規(guī)用法
- 一個父進(jìn)程希望復(fù)制自己,使父子進(jìn)程同時(shí)執(zhí)行不同的代碼段。例如,父進(jìn)程等待客戶端請求,生成子進(jìn)程來處理請求
- 一個進(jìn)程要執(zhí)行一個不同的程序。例如子進(jìn)程從fork返回后,調(diào)用exec函數(shù)
fork調(diào)用失敗的原因
- 系統(tǒng)中有太多的進(jìn)程
- 實(shí)際用戶的進(jìn)程數(shù)超過了限制
?二、進(jìn)程終止
進(jìn)程退出場景
- 代碼運(yùn)行完畢,結(jié)果正確
- 代碼運(yùn)行完畢,結(jié)果不正確
- 代碼異常終止
為什么要介紹這個?因?yàn)橄到y(tǒng)創(chuàng)建進(jìn)程本質(zhì)是讓進(jìn)程去完成一些工作,那么系統(tǒng)當(dāng)然有必要去了解工作的結(jié)果,如果成功了,那么萬事大吉,如果代碼錯誤,不管是代碼運(yùn)行完畢結(jié)果錯誤還是出現(xiàn)異常提前終止,操作系統(tǒng)都需要知道原因,那么如何知道進(jìn)程失敗的原因呢?
1.代碼運(yùn)行完畢,結(jié)果不正確
相信大家在寫C語言的時(shí)候,main方法里總是會寫return 0;這個語句,現(xiàn)在我們應(yīng)該明白,其實(shí)這就是告訴父進(jìn)程,該進(jìn)程工作順利完成,同時(shí)我們也或多或少在控制臺的黑窗口中見過某某程序返回值不為0的情況。這些返回值統(tǒng)一叫做退出碼,對應(yīng)一些數(shù)字,而每一個數(shù)字對應(yīng)一個字符串
當(dāng)然這個退出碼也是可以自定義的
這個和C語言中學(xué)的errno(錯誤碼)這個全局變量很相似(大家可以去查查C的文檔),只不過退出碼是記錄進(jìn)程跑完后的結(jié)果是否正確及錯誤的原因,錯誤碼記錄庫函數(shù)/系統(tǒng)接口,即函數(shù)運(yùn)行失敗的原因
?2.代碼異常終止
?上面兩個程序都是異常終止,操作系統(tǒng)檢測到進(jìn)程異常通過信號直接殺掉進(jìn)程(因?yàn)椴僮飨到y(tǒng)是進(jìn)程的管理者)
?總結(jié):
1.進(jìn)程是否異常,看有沒有收到信號
2.進(jìn)程運(yùn)行結(jié)果是否正確和錯誤的原因,看退出碼
(進(jìn)程異常結(jié)束后,退出碼就沒有意義了)
3.進(jìn)程常見的退出方法
正常終止(可以通過echo $?查看進(jìn)程的退出碼):
- 從main返回(執(zhí)行return n等同于執(zhí)行exit(n),因?yàn)檎{(diào)用main的運(yùn)行時(shí)函數(shù)會將main的返回值當(dāng)做 exit的參數(shù))---這里的return僅限于main函數(shù)中的,其他函數(shù)的return不具有結(jié)束進(jìn)程的功能,這里就不做過多介紹了
- 調(diào)用exit(庫函數(shù))
- 調(diào)用_exit(系統(tǒng)接口)
1.exit---庫函數(shù)
?2._exit---系統(tǒng)調(diào)用接口
上面的代碼運(yùn)行結(jié)果和exit一樣,就是將exit函數(shù)換成了_exit函數(shù),結(jié)論和上面一樣
那么這兩個函數(shù)有什么不同呢?我們來看下面這段代碼
當(dāng)結(jié)束進(jìn)程時(shí),exit函數(shù)會將緩沖區(qū)中的內(nèi)容刷新,而_exit不會,這個現(xiàn)象其實(shí)可以推導(dǎo)出緩沖區(qū)不在操作系統(tǒng)中,因?yàn)閑xit就是封裝的_exit,這個后面的章節(jié)會講,這里先得出結(jié)論
三、進(jìn)程等待
通過wait/waitpid,讓父進(jìn)程對子進(jìn)程進(jìn)行資源回收的等待過程
進(jìn)程等待的原因:
- 子進(jìn)程退出,父進(jìn)程如果不管不顧,就可能造成僵尸進(jìn)程的問題,造成內(nèi)存泄漏(進(jìn)程一旦變成僵尸狀態(tài),就無法被殺死)
- 父進(jìn)程需要知道子進(jìn)程的運(yùn)行結(jié)果,通過進(jìn)程等待獲取子進(jìn)程的退出信息---不是必須的,但是系統(tǒng)需要提供這樣的功能
?如何進(jìn)行等待???
1.wait方法
pid_t wait(int*status);返回值:成功返回被等待進(jìn)程pid,失敗返回-1參數(shù):輸出型參數(shù),獲取子進(jìn)程退出狀態(tài),不關(guān)心則可以設(shè)置成為NULL
代碼一:
代碼二:
上面兩個代碼說明兩件事:
1.進(jìn)程等待能回收子進(jìn)程的僵尸狀態(tài)
2.父進(jìn)程必須在wait上進(jìn)行阻塞等待,直到子進(jìn)程運(yùn)行結(jié)束變成僵尸狀態(tài),wait回收
2.waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);返回值:1.正常返回收集到的子進(jìn)程的進(jìn)程ID2.如果設(shè)置了選項(xiàng)WNOHANG,而發(fā)現(xiàn)沒有子進(jìn)程可收集,返回03.如果調(diào)用中出錯,則返回-1,這時(shí)errno回被設(shè)置為相應(yīng)的值來表明錯誤原因參數(shù):pid:1) -1,等待任何一個子進(jìn)程,與wait等效2)>0,等待進(jìn)程ID和pid相等的子進(jìn)程status:1) WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真(查看進(jìn)程是否是正常退出)2)WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼(查看進(jìn)程的退出碼)options:1)0:默認(rèn)阻塞等待2)WNOHANG: 若pid指定的子進(jìn)程沒有結(jié)束,則waitpid()函數(shù)返回0,不予以等待。若正常結(jié)束,則返回該子進(jìn)程的ID-----上面兩個選項(xiàng)最重要,其他的options,請自行查閱文檔
waitpid(-1,NULL,0)和wait(NULL)等價(jià),這里就不演示了
下面來講講waitpid的后面兩個參數(shù)(wait的參數(shù)和waitpid的第二個參數(shù)一樣)
1.status
這個status輸出型參數(shù)的值很奇怪,但是我將它用位運(yùn)算分割成兩個數(shù)字之后,我們就能理解了10是退出碼,0代表進(jìn)程沒有出現(xiàn)異常,這個現(xiàn)象和它的底層設(shè)計(jì)有關(guān)
?將10和0帶入上面的規(guī)則,就會發(fā)現(xiàn)status=2560
上面演示的是正常退出的情況,下面演示一個進(jìn)程異常被殺死的情況
這里再次強(qiáng)調(diào):當(dāng)進(jìn)程異常時(shí),退出碼就沒有意義了!!!
(擴(kuò)展:父進(jìn)程等待子進(jìn)程處于阻塞狀態(tài)時(shí),本質(zhì)其實(shí)是父進(jìn)程的pcb鏈入了子進(jìn)程pcb的等待隊(duì)列。父進(jìn)程需要獲取到子進(jìn)程的退出狀態(tài)就意味著子進(jìn)程的pcb中存有這兩個數(shù)字,而wait和waitpid函數(shù)作為系統(tǒng)調(diào)用接口,將輸出型參數(shù)status用這兩個數(shù)字拼接后返回)
當(dāng)然如果你對status的組成不是很了解,也可以用WIFEXITED和WEXITSTATUS這兩個宏替代
異常的情況就留給讀者自己去實(shí)驗(yàn)了?
多個子進(jìn)程的創(chuàng)建和等待
(這里僅是截取了最后的運(yùn)行結(jié)果)
我們發(fā)現(xiàn)子進(jìn)程的結(jié)束時(shí)間并非按創(chuàng)建的時(shí)間順序,還是得看系統(tǒng)是如何調(diào)度的
2.options
0:阻塞等待,子進(jìn)程不結(jié)束,不返回值,父進(jìn)程只能一直等,不能做其他事情
WNOHANG:非阻塞等待,無論子進(jìn)程是否結(jié)束,都返回結(jié)果,如果子進(jìn)程結(jié)束,返回子進(jìn)程ID,如果子進(jìn)程沒結(jié)束,返回0,一般需要重復(fù)調(diào)用,即輪詢,父進(jìn)程在等待時(shí)可以做自己的一些工作
?非阻塞等待:
?四、進(jìn)程的程序替換
程序替換的用法和本質(zhì)
當(dāng)我們用fork創(chuàng)建子進(jìn)程時(shí),子進(jìn)程執(zhí)行的都是父進(jìn)程代碼塊,如果我們要讓子進(jìn)程執(zhí)行新的程序呢?即不再執(zhí)行父進(jìn)程的代碼塊,我們該怎么辦?這就是程序替換的意義,我們用exec*這類的函數(shù)接口實(shí)現(xiàn)程序替換
下面,我們先來見識一下程序替換
我們在解決上面的問題之前,先看一下execl函數(shù)的聲明
既然是替換程序,那么我們當(dāng)然能執(zhí)行被替換過來的ls命令,這個很容易理解,但是為什么第二個打印語句沒有執(zhí)行呢?因?yàn)榇a被全部替換了,自然無法執(zhí)行最后的打印語句。
那么代碼被替換了,進(jìn)程是不是也被替換了呢?
很顯然,子進(jìn)程的pid沒有改變,也就是說沒有創(chuàng)建新的進(jìn)程,只是單純的程序替換
(多進(jìn)程的替換和寫時(shí)拷貝原理一樣,單一進(jìn)程的程序替換就是將新程序覆蓋原程序)
我們來說說這個execl函數(shù)的返回值,它只有在替換失敗的時(shí)候才會右返回值,替換成功就沒有返回值,其實(shí)想一想也確實(shí)合理,當(dāng)它執(zhí)行成功,后面的代碼就不執(zhí)行了,還要這個返回值干嘛呢?當(dāng)然正常來說,它執(zhí)行失敗我們也不接收它的返回值,因?yàn)樗鼒?zhí)行任務(wù)失敗我們直接結(jié)束進(jìn)程就行
可能有人好奇它的返回值,這里演示一下
程序替換還有一些其他的接口,全是以exec開頭的函數(shù)接口,如下
用法介紹
上面這些函數(shù)有興趣可以自己回去試試,這里就不演示了,用法都很相似?
既然能替換系統(tǒng)命令,那么能不能替換成我們寫的程序呢?畢竟系統(tǒng)命令本質(zhì)也是我們寫的程序
下面我們來試試看
很顯然,我們用exec*這種類型的接口實(shí)現(xiàn)了對我們自己寫的程序的替換
那么我們能不能用它對其他語言所寫的程序進(jìn)行替換呢?
當(dāng)然可以,因?yàn)樗沁M(jìn)程的程序替換,無論是什么語言在Linux中運(yùn)行都會變成進(jìn)程,那么同為進(jìn)程,為什么只有C++寫的程序能被替換呢?所以exec*接口也能替換其他語言寫的程序
下面寫個bash腳本語言給大家見識一下
?
環(huán)境變量
1.當(dāng)我們進(jìn)行程序替換的時(shí)候,子進(jìn)程對應(yīng)的環(huán)境變量,是可以直接從父進(jìn)程繼承來的,證明如下
當(dāng)我們在調(diào)用mytest這個進(jìn)程的時(shí)侯,本質(zhì)是bash創(chuàng)建了一個子進(jìn)程執(zhí)行mytest這個程序,而后mytest中又創(chuàng)建了子進(jìn)程process,而環(huán)境變量具有全局屬性,所以bash的子進(jìn)程都能繼承這些環(huán)境變量,而一旦mytest繼承了這些環(huán)境變量,同理process也同樣能繼承mytest的環(huán)境變量,這只是猜測,下面是實(shí)驗(yàn)證明
2.環(huán)境變量被子進(jìn)程繼承是一種默認(rèn)的行為,不受程序替換的影響
在學(xué)習(xí)進(jìn)程地址空間時(shí),我們學(xué)過命令行參數(shù)和環(huán)境變量也在進(jìn)程地址空間中,當(dāng)我們創(chuàng)建子進(jìn)程時(shí),環(huán)境變量當(dāng)然也自動隨著進(jìn)程地址空間拷貝給了子進(jìn)程,而程序替換并沒有改變環(huán)境變量,說明程序替換不會改變環(huán)境變量
3.子進(jìn)程獲得的環(huán)境變量有兩種方法:
a.從父進(jìn)程原封不動的傳遞給子進(jìn)程---1)什么都不做? ?2)通過execle/execvpe傳遞環(huán)境變量表environ
b.我們也能用execle/execvpe傳遞我們自己寫的環(huán)境變量
c.如果想新增一些環(huán)境變量給子進(jìn)程,同上,在父進(jìn)程中putenv
講了這么多,還有一個函數(shù)沒介紹
在見過exec*的眾多函數(shù)接口后,我們會發(fā)現(xiàn)他們的功能基本一樣,只是單純的使用方式不同,其實(shí)他們本質(zhì)都是對execve這個系統(tǒng)接口的封裝,以適應(yīng)不同的場景需求而已