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