I/O(Input/Output)輸入輸出,總體圖
一.操作系統(tǒng)與設備之間的IO
簡單來說(詳細的請看《現(xiàn)代操作系統(tǒng)》)在扰,操作系統(tǒng)通過設備驅(qū)動程序訪問IO設備。方式有:
(1)輪詢方式:
CPU主動在各種設備中輪詢檢查狀態(tài)雷客,有數(shù)據(jù)就IO芒珠。
(2)中斷方式:
設備有數(shù)據(jù)的時候,發(fā)出中斷搅裙,由CPU決定要不要響應中斷皱卓,然后中斷,去處理設備的IO部逮。CPU不用經(jīng)常輪詢設備狀態(tài)娜汁。被動接收中斷就行。
(3)DMA直接存儲器訪問方式:
如果1個字節(jié)的數(shù)據(jù)中斷一次兄朋,傳1KB的數(shù)據(jù)得中斷1024次掐禁,太浪費CPU時間,于是有了DMA方式颅和,CPU只需要把開頭和結(jié)束的地址告訴DMA傅事,中間由DMA完成數(shù)據(jù)IO。CPU從字節(jié)干預解放到數(shù)據(jù)塊的干預峡扩。
(4)通道控制方式:
DMA方式只能控制一個設備的一塊數(shù)據(jù)蹭越,多塊數(shù)據(jù)還是要CPU干預多次。于是有了通道來控制IO有额,它比DMA更強大般又,能控制多塊數(shù)據(jù)彼绷,多個設備的IO,更加解放了CPU參與IO過程茴迁。
二.操作系統(tǒng)與用戶進程間的IO
(進程中的線程才是CPU基本的執(zhí)行/調(diào)度單元寄悯,下面用線程舉例,用socket舉例)
設備來的數(shù)據(jù)放在內(nèi)核cache中堕义,需要用戶線程去內(nèi)核cache中取數(shù)據(jù)猜旬,復制到自己進程的cache中。有5中讀取數(shù)據(jù)方式:
(1)阻塞:
用戶線程調(diào)用某些系統(tǒng)函數(shù)去內(nèi)核取數(shù)據(jù)倦卖,直到數(shù)據(jù)到達內(nèi)核cache前洒擦,該線程處于阻塞狀態(tài),等待數(shù)據(jù)到達怕膛。
(2)非阻塞
用戶線程去取數(shù)據(jù)熟嫩,不管內(nèi)核cache有沒有數(shù)據(jù),都直接返回褐捻,可能拿到數(shù)據(jù)掸茅,也可能拿不到,不會使線程進入阻塞態(tài)柠逞。
(3)IO多路復用
多路就是一個線程管理多路IO昧狮,線程還是被阻塞調(diào)用,其中一路或幾路IO有數(shù)據(jù)了就返回板壮。需要線程遍歷全部IO逗鸣,判斷是哪個IO有數(shù)據(jù)。
例如 socket 的 select() 函數(shù)绰精,線程調(diào)用 select() 進入阻塞態(tài)撒璧,任何一個IO有數(shù)據(jù)了,線程就退出阻塞態(tài)茬底,獲得機會繼續(xù)執(zhí)行沪悲。
(4)信號驅(qū)動IO
給一個IO注冊一個信號和信號觸發(fā)的回調(diào)函數(shù),一旦信號被觸發(fā)阱表,回調(diào)函數(shù)里讀取數(shù)據(jù)殿如。
例如給 socket 注冊一個“可讀”的信號,當數(shù)據(jù)來了最爬,可讀的時候涉馁,信號被觸發(fā),執(zhí)行回調(diào)函數(shù)從內(nèi)核cache復制數(shù)據(jù)到用戶空間爱致。
(5)異步IO
異步IO中烤送,操作系統(tǒng)完成了數(shù)據(jù)從內(nèi)核到用戶空間的拷貝后,以信號的方式通知用戶線程可以下一步操作糠悯。省去了用戶線程阻塞下來拷貝數(shù)據(jù)的過程帮坚。
IO管理
假設一臺服務器需要被1萬個客戶端連接妻往。方法有:
(1)單路:
最簡單的一個線程管理一個客戶端的socket IO,那么需要1萬的線程试和,假設每個線程占內(nèi)存3MB讯泣,需要300G內(nèi)存,單臺服務器沒那么大的內(nèi)存阅悍,并且操作系統(tǒng)最大線程數(shù)有限制好渠,unix下一個進程好像是最多只能開 4096 個線程。
(2)IO 多路復用:
socket一旦多起來节视,單路IO 就扛不住了拳锚,需要一個線程管理多個 socket IO,下面都是在一個線程內(nèi)的情況寻行。
(2.1)select
一個線程管理多個socket IO霍掺,調(diào)用 select() 進入阻塞態(tài),任何一個IO有數(shù)據(jù)則返回拌蜘,由于不知道是哪個 socket 有數(shù)據(jù)抗楔,需要遍歷所有 socket fd 去判斷,當1萬個 socket 大部分都是有IO的時候拦坠,效率較高,如果只是那么幾百個有IO剩岳,此方法效率較低贞滨。
(2.2)epoll 和 kqueue
epoll 是 linux 下的,kqueue 是 unix 下的拍棕。
由于 select 需要遍歷全部的 socket fd晓铆,效率較低,于是有了 epoll, kqueue 方式绰播,kqueue 管理多個IO骄噪,阻塞調(diào)用等待函數(shù),當有一個或多個IO事件蠢箩,kqueue 直接返回多個IO事件的 socket fd链蕊,不需要遍歷全部 socket fd,效率較高谬泌。
假設一個 socket 連接的對象是 3 kb滔韵,8G的內(nèi)存可以管理 280w 個連接。
- select掌实,epoll陪蜻,kqueue 原理
已知的情況
內(nèi)核中注冊 socket 的 IO 中斷處理的回掉函數(shù),有 IO 了會回調(diào)該函數(shù)贱鼻。
select:
select 管理多個 socket宴卖,select 收到一個來自網(wǎng)卡 IO 中斷就返回滋将,不知道這個中斷對應是哪個 socket fd 的。需要用戶線程遍歷判斷症昏。
epoll:
epoll 收到一個 IO 中斷随闽,會去查找這個中斷對應哪個 socket fd。
epoll 中建立一個紅黑樹(平衡二叉樹的一種)齿兔,紅黑樹查找很高效橱脸。
用戶注冊感興趣的 socket 事件,就是把這個 socket fd 插入到紅黑樹中分苇,用中斷號做key添诉,可以理解為(中斷號,socket fd)的二元組医寿。
用戶移除事件就是栏赴,刪除樹上的某個節(jié)點。
然后收到一個IO中斷靖秩,epoll 把網(wǎng)卡數(shù)據(jù)拷貝到內(nèi)核cache须眷,根據(jù)中斷號在紅黑樹中查找對應的 fd,把 fd 加入到就緒鏈表中沟突,準備返回給用戶線程花颗。用戶直接得到就緒的 fd。
kqueue:
收到 socket IO 中斷去哈希表中查找對應的 socket fd惠拭,再把它放到一個鏈表里扩劝,返回。
用戶注冊一個感興趣的事件职辅,就是往哈希表中添加一個 fd棒呛。
一些優(yōu)化
Memory Map (mmap)
設備的數(shù)據(jù)到內(nèi)核cache,再到用戶空間域携,中間需要把內(nèi)核數(shù)據(jù)復制到用戶空間簇秒,比較耗時,那么直接用 mmap 把用戶空間的地址映射到內(nèi)核空間秀鞭,省去了內(nèi)核數(shù)據(jù)到用戶空間的拷貝
if((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED) err_sys("mmap error for input");
if((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fout, 0)) == MAP_FAILED) err_sys("mmap error for output");
memcpy(dst, src, statbuf.st_size);
參考文獻:
使用 kqueue 在 FreeBSD 上開發(fā)高性能應用服務器
epoll 底層實現(xiàn)源碼分析
Kqueue: A generic and scalable event notification facility
FreeBSD Kqueue的實現(xiàn)原理
操作系統(tǒng)-IO系統(tǒng)
操作系統(tǒng)-IO模型