操作系統基本概念
首先來來說下操作系統,嗯,操作系統是計算機硬件的管理軟件,是對計算機硬件的抽象,操作系統將應用程序分為用戶態和內核態,例如驅動程序就位於內核態,而我們寫的一般程序都是用戶態,包括web服務器這些,應用程序無法直接操控硬件,只能通過系統調用,通過操作系統驅動io硬件,通過操作系統管理進程。
接下來說下文件的概念,在操作系統中,文件是對i/o的一種抽象,文件大體包括三類
普通的文件:包括二進制文件和文本文件
目錄:就是普通文件的一組鏈表
套接字文件:用來與另一個進程進行跨網絡通信的文件
套接字文件就是通常說的socket,還有值得注意的是無論打開什么文件,內核都會返回給應用程序一個文件描述符。當關閉文件后,內核釋放資源,同時回收文件描述符。
進程的內存模型
每個進程都有獨立的上下文,它擁有完整的虛擬內存空間。
CPU執行進程,總是在不斷對進程的切換中,這種叫時分復用,而且時間很快,從而讓人有一種進程並行的感覺,即單個cpu在一個時刻只能做一件事
I/O流程
說下應用程序讀文件的大致流程(寫文件也差不多),當一個進程想要向磁盤或者接受網絡數據時,它會先發起系統調用(可以通過異常等方式),然后將程序控制權交給操作系統,
操作系統向指定的文件發起讀的操作,返回給程序一個文件操作符,然后接下來就是比較有意思的地方了,因為文件讀出來是需要時間的,文件讀出來后會存到內核的緩沖區中(DMA),然后中斷提醒CPU,CPU再由內核緩沖區讀取到用戶進程中,在這個過程中,這段時間里,用戶進程可以有阻塞,非阻塞,同步,異步各種狀態
linux的I/O模型
網絡IO的本質是socket的讀取,socket在linux系統被抽象為流,IO可以理解為對流的操作。對於一次IO訪問(以read舉例),數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。所以說,當一個read操作發生時,它會經歷兩個階段:
第一階段:等待數據准備 (Waiting for the data to be ready)。 第二階段:將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)。
對於socket流而言,
第一步:通常涉及等待網絡上的數據分組到達,然后被復制到內核的某個緩沖區。 第二步:把數據從內核緩沖區復制到應用進程緩沖區。
linux的五種網絡i/o模型
同步的概念就是在數據復制到用戶進程的這段時間內,用戶進程是不干活
異步是在這段時間內,用戶進程會繼續執行它后續的工作
先在同步異步的基礎上進行簡單的分類
同步模型(synchronous IO)
- 阻塞IO(bloking IO)
- 非阻塞IO(non-blocking IO)
- 多路復用IO(multiplexing IO)
- 信號驅動式IO(signal-driven IO)
異步IO(asynchronous IO)
接下來進行分類的介紹
阻塞I/O
阻塞i/o就是整個過程用戶進程都是阻塞,它發起系統調用后就被掛起了,直到數據被搬運到緩沖區中,然后數據從緩沖區讀進用戶進程,它才被喚醒,真個過程它都處於掛起狀態(什么都不干)
非阻塞i/o
用戶進程發起系統調用后,它沒有被掛起,而是繼續執行,但它要不斷輪詢看數據是否運到內核了,數據到了內核后,用戶進程將數據從內核讀取到用戶進程
多路復用I/O
多路復用I/O比較復雜,它整個過程也是阻塞的,但不同的是它可以阻塞多個i/o,同時阻塞多個socket連接,有epoll,,poll,select等,epoll是linux最高效的,多路復用的特點是通過一種機制一個進程能同時等待多個IO文件描述符,內核監視這些文件描述符(套接字描述符),其中的任意一個進入讀就緒狀態,select, poll,epoll函數就可以返回。
select,poll,epoll都是內核狀態的函數調用
用戶進程發起系統調用后,處於掛起狀態,同時監聽多個socket連接,只要有其中有一個數據到達內核,用戶進程就被喚醒工作,然后將數據從內核讀取到用戶進程,其實就是由epoll,select同時監聽多個io對象,當io對象發生變化的時候,就通知用戶進程讀寫數據,進行操作
即多個io對象復用一個進程,這樣可以很充分的利用阻塞的這段時間
IO多路復用是同步阻塞模式
異步驅動I/O
這個理論上是最好的,但在linux系統中很難實現
信號驅動i/o
這個很少使用到
異步IO
異步io在linux中很難實現,但也有一種模擬異步io的方法即多線程和同步阻塞io進行模擬,設置一個主線程,用其它線程進行同步io操作,當io完成時通知主線程去讀取進程中的數據,進行后續操作,因為是同一個進程,所以可以共享內存資源。進而實現類似異步io的效果,在linux中有libev,libeio這樣的異步io實現庫,而在windows,則使用了iocp,可以說異步io的核心就是在子線程上執行io操作,在執行完畢后通知調用者提取相關數據。只不過linux是用戶層的線程池,而iocp是內核的線程池。
Node模型
首先說下常見的模型要么是單進程多線程,要么是多進程單線程,node是屬於后者
node中最重的就是包含了libuv這個,node的所有io操作都是通過它來實現的,libuv實現了異步IO,libuv中包含一個事件隊列(可以理解為就是主線程),如果是網絡io,它會使用epoll這種io多路復用的方式(在linux中)對io進行處理,而對於磁盤的io操作,它會采用多線程+阻塞io的方式進行io操作,它讀寫完數據后就將數據返回給js引擎。從而實現io操作。
最后提一點epoll這種io多路復用模型使用的很廣,redis,nginx,都不同程度使用了它,它2者也可以歸為多進程單線程這種模型。