1、概念說明
用戶空間和內(nèi)核空間
進程切換
進程的阻塞
文件描述符
緩存 IO
1.1榕订、用戶空間與內(nèi)核空間
現(xiàn)在操作系統(tǒng)都是采用虛擬存儲器劫恒,那么對32位操作系統(tǒng)而言两嘴,它的尋址空間(虛擬存儲空間)為4G(2的32次方)憔辫。操作系統(tǒng)的核心是內(nèi)核贰您,獨立于普通的應(yīng)用程序锦亦,可以訪問受保護的內(nèi)存空間庐冯,也有訪問底層硬件設(shè)備的所有權(quán)限展父。為了保證用戶進程不能直接操作內(nèi)核(kernel)栖茉,保證內(nèi)核的安全吕漂,操作系統(tǒng)將虛擬空間劃分為兩部分,一部分為內(nèi)核空間犬钢,一部分為用戶空間玷犹。針對linux操作系統(tǒng)而言歹颓,將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF)巍扛,供內(nèi)核使用撤奸,稱為內(nèi)核空間寂呛,而將較低的3G字節(jié)(從虛擬地址0x00000000到0xBFFFFFFF)瘾晃,供各個進程使用蹦误,稱為用戶空間强胰。
1.2偶洋、進程切換
為了控制進程的執(zhí)行玄窝,內(nèi)核必須有能力掛起正在CPU上運行的進程恩脂,并恢復(fù)以前掛起的某個進程的執(zhí)行趣斤。這種行為被稱為進程切換俩块。因此可以說玉凯,任何進程都是在操作系統(tǒng)內(nèi)核的支持下運行的漫仆,是與內(nèi)核緊密相關(guān)的歹啼。
從一個進程的運行轉(zhuǎn)到另一個進程上運行狸眼,這個過程中經(jīng)過下面這些變化:
1、保存處理機上下文岁钓,包括程序計數(shù)器和其他寄存器屡限。
2钧大、更新PCB信息啊央。
3瓜饥、把進程的PCB移入相應(yīng)的隊列乓土,如就緒趣苏、在某事件阻塞等隊列拦键。
4芬为、選擇另一個進程執(zhí)行媚朦,并更新其PCB询张。
5份氧、更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)蜗帜。
6厅缺、恢復(fù)處理機上下文。
1.3诀豁、進程的阻塞
正在執(zhí)行的進程舷胜,由于期待的某些事件未發(fā)生逞带,如請求系統(tǒng)資源失敗、等待某種操作的完成脸爱、新數(shù)據(jù)尚未到達或無新工作做等簿废,則由系統(tǒng)自動執(zhí)行阻塞原語(Block)族檬,使自己由運行狀態(tài)變?yōu)樽枞麪顟B(tài)〉チ希可見扫尖,進程的阻塞是進程自身的一種主動行為甩恼,也因此只有處于運行態(tài)的進程(獲得CPU)条摸,才可能將其轉(zhuǎn)為阻塞狀態(tài)屈溉。當(dāng)進程進入阻塞狀態(tài)子巾,是不占用CPU資源的线梗。
1.4怠益、文件描述符fd
文件描述符(File descriptor)是計算機科學(xué)中的一個術(shù)語,是一個用于表述指向文件的引用的抽象化概念烤咧。
文件描述符在形式上是一個非負(fù)整數(shù)抢呆。實際上昌阿,它是一個索引值懦冰,指向內(nèi)核為每一個進程所維護的該進程打開文件的記錄表。當(dāng)程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時内地,內(nèi)核向進程返回一個文件描述符瓤鼻。在程序設(shè)計中清焕,一些涉及底層的程序編寫往往會圍繞著文件描述符展開秸妥。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)突雪。
1.5咏删、緩存 IO
緩存 IO 又被稱作標(biāo)準(zhǔn) IO,大多數(shù)文件系統(tǒng)的默認(rèn) IO 操作都是緩存 IO辰狡。在 Linux 的緩存 IO 機制中宛篇,操作系統(tǒng)會將 IO 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中,也就是說,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中炉奴,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間瞻赶。
緩存 IO 的缺點:
數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和內(nèi)核進行多次數(shù)據(jù)拷貝操作璧南,這些數(shù)據(jù)拷貝操作所帶來的 CPU 以及內(nèi)存開銷是非常大的师逸。
2、Linux IO模型
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取动知,socket在linux系統(tǒng)被抽象為流,IO可以理解為對流的操作盒粮。對于一次IO訪問(以read舉例),數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中摊崭,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。
所以說阔墩,當(dāng)一個read操作發(fā)生時,它會經(jīng)歷兩個階段:
- 第一階段:等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)伞芹。
- 第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進程中 (Copying the data from the kernel to the process)唱较。
對于socket流而言:
- 第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達胸遇,然后被復(fù)制到內(nèi)核的某個緩沖區(qū)倍阐。
- 第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進程緩沖區(qū)峰搪。
網(wǎng)絡(luò)應(yīng)用需要處理的無非就是兩大類問題概耻,網(wǎng)絡(luò)IO尽纽,數(shù)據(jù)計算
弄贿。相對于后者,網(wǎng)絡(luò)IO的延遲呐萌,給應(yīng)用帶來的性能瓶頸大于后者。網(wǎng)絡(luò)IO的模型大致有如下幾種:
* 同步模型(synchronous IO)
* 阻塞IO(bloking IO)
* 非阻塞IO(non-blocking IO)
* 多路復(fù)用IO(multiplexing IO)
* 信號驅(qū)動式IO(signal-driven IO)
* 異步IO(asynchronous IO)
劃重點 :
同步IO和異步IO的區(qū)別就在于:數(shù)據(jù)拷貝的時候進程是否阻塞 阻塞IO和非阻塞IO的區(qū)別就在于:應(yīng)用程序的調(diào)用是否立即返回赠堵!
2.1、同步阻塞 IO(blocking IO)
2.1.1半等、場景
因為最近我和女友都比較忙憔狞,所以晚飯就沒時間自己做翘县,由我下班后買好,帶回家和女友一塊兒吃烁登。女友喜歡吃炒菜狼牺,我喜歡吃面條缅叠,而且女友喜歡的餐館A只賣炒菜,我喜歡的餐館B只賣面條,經(jīng)過最近幾天在AB兩家餐廳的買飯經(jīng)歷,對于如何“高效率”買飯单刁,我有一些心得。
第一天肺樟,我先去A餐館點了炒菜儡嘶,然后就百無聊賴的在那兒等,十幾分鐘后锦募,女友要的炒菜出鍋糊饱,然后我又去了B餐館點了自己喜歡吃的面过椎,也是經(jīng)過漫長的百無聊賴的等待竞惋,終于我的面也出鍋了拆宛,多么漫長且百無聊賴的等待浑厚,這就是典型的阻塞钳幅。
2.1.2 網(wǎng)絡(luò)模型
當(dāng)用戶進程調(diào)用了recv / recvfrom 這個系統(tǒng)調(diào)用牡属,kernel就開始了IO的第一個階段
:準(zhǔn)備數(shù)據(jù)(對于網(wǎng)絡(luò)IO來說,很多時候數(shù)據(jù)在一開始還沒有到達短蜕。比如,還沒有收到一個完整的UDP包扇雕。這個時候kernel就要等待足夠的數(shù)據(jù)到來)拓售。這個過程需要等待,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個過程的镶奉。而在用戶進程這邊础淤,整個進程會被阻塞(當(dāng)然,是進程自己選擇的阻塞)哨苛。第二個階段:當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了鸽凶,它就會將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存
,然后kernel返回結(jié)果建峭,用戶進程才解除block的狀態(tài)玻侥,重新運行起來。
所以亿蒸,blocking IO的特點就是在IO執(zhí)行的兩個階段都被block了凑兰。
優(yōu)點:能夠及時返回數(shù)據(jù),無等待
缺點:等待時間長
2.2祝懂、同步非阻塞 IO(nonblocking IO)
2.2.1票摇、場景描述
第二天,我先去A餐館點了炒菜砚蓬,但是我?guī)Я吮緯该牛缓笤谀莾哼吙磿叺龋⑶視r不時的問服務(wù)員炒菜好了沒有,十幾分鐘后祟剔,女友要的炒菜出鍋隔躲,然后我又去了B餐館點了自己喜歡吃的面,也是邊看書邊等物延,并且時不時的問服務(wù)員我點的面有沒有做好宣旱,經(jīng)過很長時間,終于我的面也出鍋了叛薯,等待時間雖長浑吟,但是我還是從書本中獲益匪淺。
2.2.2 網(wǎng)絡(luò)模型
同步非阻塞就是 “每隔一會兒瞄一眼進度條” 的輪詢(polling)方式
耗溜。在這種模型中组力,設(shè)備是以非阻塞的形式打開的。這意味著 IO 操作不會立即完成抖拴,read 操作可能會返回一個錯誤代碼燎字,說明這個命令不能立即滿足(EAGAIN 或 EWOULDBLOCK)。
在網(wǎng)絡(luò)IO時候阿宅,非阻塞IO也會進行recvform系統(tǒng)調(diào)用候衍,檢查數(shù)據(jù)是否準(zhǔn)備好,與阻塞IO不一樣洒放,"非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以進程不斷地有機會 '被' CPU光顧"蛉鹿。
也就是說非阻塞的recvform系統(tǒng)調(diào)用調(diào)用之后,進程并沒有被阻塞拉馋,內(nèi)核馬上返回給進程榨为,如果數(shù)據(jù)還沒準(zhǔn)備好,此時會返回一個error煌茴。進程在返回之后随闺,可以干點別的事情,然后再發(fā)起recvform系統(tǒng)調(diào)用蔓腐。重復(fù)上面的過程矩乐,循環(huán)往復(fù)的進行recvform系統(tǒng)調(diào)用。這個過程通常被稱之為輪詢回论。輪詢檢查內(nèi)核數(shù)據(jù)散罕,直到數(shù)據(jù)準(zhǔn)備好,再拷貝數(shù)據(jù)到進程傀蓉,進行數(shù)據(jù)處理欧漱。需要注意,拷貝數(shù)據(jù)整個過程葬燎,進程仍然是屬于阻塞的狀態(tài)误甚。
相比同步阻塞方式:
優(yōu)點:能夠在等待任務(wù)完成的時間里干其他活了(包括提交其他任務(wù)缚甩,也就>是 “后臺” 可以有多個任務(wù)在同時執(zhí)行)。
缺點:任務(wù)完成的響應(yīng)延遲增大了窑邦,因為每過一段時間才去輪詢一次read操>作擅威,而任務(wù)可能在兩次輪詢之間的任意時間完成。這會導(dǎo)致整體數(shù)據(jù)吞吐量>的降低冈钦。
2.3郊丛、IO 多路復(fù)用( IO multiplexing)
2.3.1、場景描述
第三天瞧筛,我先去A餐館點了炒菜厉熟,然后離開,又去了B餐館點了面條驾窟,然后我又去了位于兩家餐館中間的書店看書庆猫,在看書過程中,我時不時的打電話到兩家餐館確認(rèn)我的餐做好沒有绅络,很幸運,沒打幾次電話嘁字,在A餐館點的炒菜出鍋了恩急,我隨即去取了炒菜,再次回到書店看了會兒書纪蜒,打電話確認(rèn)B餐館點的面條也出鍋了衷恭,我又去了B餐館取了面條。這次等待的時間不長纯续,中間看書的環(huán)境也好随珠,但是老是要打電話給餐廳確認(rèn)還是挺不舒服的。
2.3.2 網(wǎng)絡(luò)模型
由于同步非阻塞方式需要不斷主動輪詢猬错,輪詢占據(jù)了很大一部分過程窗看,輪詢會消耗大量的CPU時間,而 “后臺” 可能有多個任務(wù)在同時進行倦炒,人們就想到了循環(huán)查詢多個任務(wù)的完成狀態(tài)显沈,只要有任何一個任務(wù)完成,就去處理它逢唤。如果輪詢不是進程的用戶態(tài)拉讯,而是有人幫忙就好了。那么這就是所謂的 “IO 多路復(fù)用”鳖藕。UNIX/Linux 下的 select魔慷、poll、epoll 就是干這個的(epoll 比 poll著恩、select 效率高院尔,做的事情是一樣的)蜻展。
IO多路復(fù)用有兩個特別的系統(tǒng)調(diào)用select、poll召边、epoll函數(shù)
铺呵。select調(diào)用是內(nèi)核級別的,select輪詢相對非阻塞的輪詢的區(qū)別在于---前者可以等待多個socket隧熙,能實現(xiàn)同時對多個IO端口進行監(jiān)聽片挂,當(dāng)其中任何一個socket的數(shù)據(jù)準(zhǔn)好了,就能返回進行可讀贞盯,然后進程再進行recvform系統(tǒng)調(diào)用音念,將數(shù)據(jù)由內(nèi)核拷貝到用戶進程,當(dāng)然這個過程是阻塞的躏敢。select或poll調(diào)用之后闷愤,會阻塞進程,與blocking IO阻塞不同在于件余,此時的select不是等到socket數(shù)據(jù)全部到達再處理, 而是有了一部分?jǐn)?shù)據(jù)就會調(diào)用用戶進程來處理讥脐。如何知道有一部分?jǐn)?shù)據(jù)到達了呢?監(jiān)視的事情交給了內(nèi)核啼器,內(nèi)核負(fù)責(zé)數(shù)據(jù)到達的處理旬渠。也可以理解為"非阻塞"吧。
I/O復(fù)用模型會用到select端壳、poll告丢、epoll函數(shù),這幾個函數(shù)也會使進程阻塞损谦,但是和阻塞I/O所不同的的岖免,這兩個函數(shù)可以同時阻塞多個I/O操作
。而且可以同時對多個讀操作照捡,多個寫操作的I/O函數(shù)進行檢測颅湘,直到有數(shù)據(jù)可讀或可寫時(注意不是全部數(shù)據(jù)可讀或可寫),才真正調(diào)用I/O操作函數(shù)麻敌。
對于多路復(fù)用栅炒,也就是輪詢多個socket。多路復(fù)用既然可以處理多個IO术羔,也就帶來了新的問題赢赊,多個IO之間的順序變得不確定了,當(dāng)然也可以針對不同的編號级历。
I/O多路復(fù)用的主要應(yīng)用場景如下:
- 服務(wù)器需要同時處理多個處于監(jiān)聽狀態(tài)或者多個連接狀態(tài)的套接字释移。
- 服務(wù)器需要同時處理多種網(wǎng)絡(luò)協(xié)議的套接字。
2.4寥殖、異步非阻塞 IO(asynchronous IO)
2.4.1 場景描述
我在公司分別向AB兩家餐廳訂了餐玩讳,并且讓他們做好以后給我送到公司涩蜘,然后我再將飯拎回家,在等待的過程中熏纯,我順手修補了我們系統(tǒng)中一個已知的bug同诫,完美。
2.4.2 網(wǎng)絡(luò)模型
相對于同步IO樟澜,異步IO不是順序執(zhí)行误窖。用戶進程進行aio_read系統(tǒng)調(diào)用之后,無論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好秩贰,都會直接返回給用戶進程霹俺,然后用戶態(tài)進程可以去做別的事情。等到socket數(shù)據(jù)準(zhǔn)備好了毒费,內(nèi)核直接復(fù)制數(shù)據(jù)給進程丙唧,然后從內(nèi)核向進程發(fā)送通知。IO兩個階段觅玻,進程都是非阻塞的想际。
在 Linux 中,通知的方式是 “信號”:
如果這個進程正在用戶態(tài)忙著做別的事(例如在計算兩個矩陣的乘積)溪厘,那就強行打斷之沼琉,調(diào)用事先注冊的信號處理函數(shù),這個函數(shù)可以決定何時以及如何處理這個異步任務(wù)桩匪。由于信號處理函數(shù)是突然闖進來的,因此跟中斷處理程序一樣友鼻,有很多事情是不能做的傻昙,因此保險起見,一般是把事件 “登記” 一下放進隊列彩扔,然后返回該進程原來在做的事妆档。
如果這個進程正在內(nèi)核態(tài)忙著做別的事,例如以同步阻塞方式讀寫磁盤虫碉,那就只好把這個通知掛起來了贾惦,等到內(nèi)核態(tài)的事情忙完了,快要回到用戶態(tài)的時候敦捧,再觸發(fā)信號通知须板。
如果這個進程現(xiàn)在被掛起了,例如無事可做 sleep 了兢卵,那就把這個進程喚醒习瑰,下次有 CPU 空閑的時候,就會調(diào)度到這個進程秽荤,觸發(fā)信號通知甜奄。
select柠横、poll、epoll簡介
epoll跟select都能提供多路I/O復(fù)用的解決方案课兄。在現(xiàn)在的Linux內(nèi)核里有都能夠支持牍氛,其中epoll是Linux所特有,而select則應(yīng)該是POSIX所規(guī)定烟阐,一般操作系統(tǒng)均有實現(xiàn)
select:
select本質(zhì)上是通過設(shè)置或者檢查存放fd標(biāo)志位的數(shù)據(jù)結(jié)構(gòu)來進行下一步處理搬俊。這樣所帶來的缺點是:
1、 單個進程可監(jiān)視的fd數(shù)量被限制曲饱,即能監(jiān)聽端口的大小有限悠抹。一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看扩淀。32位機默認(rèn)是1024個楔敌。64位機默認(rèn)是2048.
2、 對socket進行掃描時是線性掃描驻谆,即采用輪詢的方法卵凑,效率較低:當(dāng)套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍胜臊。這會浪費很多CPU時間勺卢。如果能給套接字注冊某個回調(diào)函數(shù),當(dāng)他們活躍時象对,自動完成相關(guān)操作黑忱,那就避免了輪詢,這正是epoll與kqueue做的勒魔。
3甫煞、需要維護一個用來存放大量fd的數(shù)據(jù)結(jié)構(gòu),這樣會使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時復(fù)制開銷大
poll:
poll本質(zhì)上和select沒有區(qū)別冠绢,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間抚吠,然后查詢每個fd對應(yīng)的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊列中加入一項并繼續(xù)遍歷弟胀,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設(shè)備楷力,則掛起當(dāng)前進程,直到設(shè)備就緒或者主動超時孵户,被喚醒后它又要再次遍歷fd萧朝。這個過程經(jīng)歷了多次無謂的遍歷。
它沒有最大連接數(shù)的限制延届,原因是它是基于鏈表來存儲的剪勿,但是同樣有一個缺點:
1、大量的fd的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核地址空間之間方庭,而不管這樣的復(fù)制是不是有意義厕吉。 2酱固、poll還有一個特點是“水平觸發(fā)”,如果報告了fd后头朱,沒有被處理运悲,那么下次poll時會再次報告該fd。
epoll:
epoll支持水平觸發(fā)和邊緣觸發(fā)项钮,最大的特點在于邊緣觸發(fā)班眯,它只告訴進程哪些fd剛剛變?yōu)榫托钁B(tài),并且只會通知一次烁巫。還有一個特點是署隘,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd亚隙,一旦該fd就緒磁餐,內(nèi)核就會采用類似callback的回調(diào)機制來激活該fd,epoll_wait便可以收到通知
epoll的優(yōu)點:
1阿弃、沒有最大并發(fā)連接的限制诊霹,能打開的FD的上限遠大于1024(1G的內(nèi)存上能監(jiān)聽約10萬個端口);
2渣淳、效率提升脾还,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降入愧。只有活躍可用的FD才會調(diào)用callback函數(shù)鄙漏;
即Epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān)棺蛛,因此在實際的網(wǎng)絡(luò)環(huán)境中泥张,Epoll的效率就會遠遠高于select和poll。
3鞠值、 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞渗钉;即epoll使用mmap減少復(fù)制開銷彤恶。
select、poll鳄橘、epoll 區(qū)別總結(jié):
總結(jié):
綜上声离,在選擇select,poll瘫怜,epoll時要根據(jù)具體的使用場合以及這三種方式的自身特點术徊。
1、表面上看epoll的性能最好鲸湃,但是在連接數(shù)少并且連接都十分活躍的情況下赠涮,select和poll的性能可能比epoll好子寓,畢竟epoll的通知機制需要很多函數(shù)回調(diào)。
2笋除、select低效是因為每次它都需要輪詢斜友。但低效也是相對的,視情況而定垃它,也可通過良好的設(shè)計改善