linux下的io模型
1、用戶態和內核態
因為操作系統的資源是有限的,如果訪問資源的操作過多,必然會消耗過多的資源,而且如果不對這些操作加以區分,很可能造成資源訪問的沖突。所以,為了減少有限資源的訪問和使用沖突,Unix/Linux的設計哲學之一就是:對不同的操作賦予不同的執行等級,就是所謂特權的概念。簡單說就是有多大能力做多大的事,與系統相關的一些特別關鍵的操作必須由最高特權的程序來完成。Intel的X86架構的CPU提供了0到3四個特權級,數字越小,特權越高,Linux操作系統中主要采用了0和3兩個特權級,分別對應的就是內核態和用戶態。運行於用戶態的進程可以執行的操作和訪問的資源都會受到極大的限制,而運行在內核態的進程則可以執行任何操作並且在資源的使用上沒有限制。很多程序開始時運行於用戶態,但在執行的過程中,一些操作需要在內核權限下才能執行,這就涉及到一個從用戶態切換到內核態的過程
用戶態和內核態的轉換
a、系統調用
系統調用的本質其實也是中斷,相對於外圍設備的硬中斷,這種中斷稱為軟中斷
b、異常
當CPU正在執行運行在用戶態的程序時,突然發生某些預先不可知的異常事件,這個時候就會觸發從當前用戶態執行的進程轉向內核態執行相關的異常事件,典型的如缺頁異常。
c、外圍設備的中斷
當外圍設備完成用戶的請求操作後,會像CPU發出中斷信號,此時,CPU就會暫停執行下一條即將要執行的指令,轉而去執行中斷信號對應的處理程序,如果先前執行的指令是在用戶態下,則自然就發生從用戶態到內核態的轉換
總結:
內核態可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。
2、同步和異步的區別
同步io需要主動讀寫數據,在讀寫過程中會阻塞,異步io只需要讀寫完成的通知,讀寫操作由內核態完成
3、關於異步阻塞和異步非阻塞
異步阻塞,用戶進程(線程)發起讀寫操作,在原地等待內核態返回讀寫完成的結果。此時阻塞整個進程
異步非阻塞,用戶進程發起讀寫操作後,不在原地的等待內核態完成讀寫操作的結果。可以先去幹點別的事
4、同步的幾種io模型
以read為例
1、同步阻塞
1、進程發起read,進行recvfrom系統調用;
2、內核態準備數據
3、同時進程阻塞
4、阻塞直到數據從內核態copy到用戶態,內核返回結果,進程解除阻塞。
總結:準備數據和數據copy兩個階段都阻塞。
2、同步非阻塞
1、進程發起read操作,內核數據沒有準備好,立刻返回一個error.
2、用戶進程收到error,知道數據沒有準備好,於是再次發起read操作,直到數據準備好。
3、用戶進程收到數據準備好的信號,發送system call,copy數據,此時進程開始阻塞
4、數據copy完成,返回給用戶進程解除阻塞。
總結:數據準備階段,用戶進程不斷詢問內核數據準備好了沒,數據copy階段進程阻塞
3、io多路復用
1、用戶進程調用select,進程阻塞,同時內核會監聽所有select負責的socket.
2、當任何一個socket的數據準備好,select就會返回。
3、用戶進程調用read操作,將數據從內核copy到用戶進程。
總結:I/O 多路復用的特點是通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回
如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用多線程 + 阻塞 IO的web server性能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。
select、poll、epoll的區別:
select 是不斷輪詢去監聽的socket,socket個數有限制,一般為1024個;
poll還是采用輪詢方式去監聽,只不過沒有個數限制。
epoll並不用采用輪詢方式去監聽,而是當socket有變化時通過回調方式主動告知用戶進程。
select支持多平臺,epoll只支持linux平臺。
select實現ftp
import select
import socket
server = socket.socket()
server.bind((‘127.0.0.1‘, 9991))
server.listen(10)
server.setblocking(False)
r_list = [server, ]
w_list = []
w_data = {}
while True:
rl, wl, xl = select.select(r_list, w_list, [], 0.5)
print(wl)
for sock in rl:
if sock == server:
conn, addr = server.accept()
r_list.append(conn)
else:
try:
data = sock.recv(1024).decode()
if not data:
sock.close()
r_list.remove(sock)
continue
w_list.append(sock)
w_data[sock] = data.upper().encode()
except Exception as e:
print(e)
sock.close()
r_list.remove(sock)
selectors實現ftp
import selectors
import socket
def accept(obj, mask):
conn,addr = obj.accept()
sel.register(conn, selectors.EVENT_READ, read)
def read(obj,mask):
try:
data = obj.recv(1024).decode()
if not data:
sel.unregister(obj)
obj.close()
return
print(data)
obj.send(data.upper().encode())
except Exception as e:
print(e)
obj.close()
sel.unregister(obj)
server = socket.socket()
server.bind((‘127.0.0.1‘, 9990))
server.listen(10)
server.setblocking(False)
sel = selectors.DefaultSelector()
sel.register(server, selectors.EVENT_READ, accept)
while True:
events = sel.select()
for obj, mask in events:
callback = obj.data
callback(obj.fileobj, mask)
大量參考:
http://www.cnblogs.com/zingp/p/6863170.html
https://www.cnblogs.com/bakari/p/5520860.html
linux下的io模型