因為作業系統的資源是有限的,如果訪問資源的操作過多,必然會消耗過多的資源,而且如果不對這些操作加以區分,很可能造成資源訪問的衝突。所以,為了減少有限資源的訪問和使用衝突,unix/linux的設計哲學之一就是:對不同的操作賦予不同的執行等級,就是所謂特權的概念。簡單說就是有多大能力做多大的事,與系統相關的一些特別關鍵的操作必須由最高特權的程式來完成。intel的x86架構的cpu提供了0到3四個特權級,數字越小,特權越高,linux作業系統中主要採用了0和3兩個特權級,分別對應的就是核心態和使用者態。執行於使用者態的程序可以執行的操作和訪問的資源都會受到極大的限制,而執行在核心態的程序則可以執行任何操作並且在資源的使用上沒有限制。很多程式開始時執行於使用者態,但在執行的過程中,一些操作需要在核心許可權下才能執行,這就涉及到乙個從使用者態切換到核心態的過程
使用者態和核心態的轉換
a、系統呼叫
系統呼叫的本質其實也是中斷,相對於外圍裝置的硬中斷,這種中斷稱為軟中斷
b、異常
當cpu正在執行執行在使用者態的程式時,突然發生某些預先不可知的異常事件,這個時候就會觸發從當前使用者態執行的程序轉向核心態執行相關的異常事件,典型的如缺頁異常。
c、外圍裝置的中斷
當外圍裝置完成使用者的請求操作後,會像cpu發出中斷訊號,此時,cpu就會暫停執行下一條即將要執行的指令,轉而去執行中斷訊號對應的處理程式,如果先前執行的指令是在使用者態下,則自然就發生從使用者態到核心態的轉換
總結:核心態可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權。為了保證使用者程序不能直接操作核心(kernel),保證核心的安全,操心系統將虛擬空間劃分為兩部分,一部分為核心空間,一部分為使用者空間。
同步io需要主動讀寫資料,在讀寫過程中會阻塞,非同步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()
else:
try:
data = sock.recv(1024).decode()
if not data:
sock.close()
r_list.remove(sock)
continue
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)
大量參考: linux下的IO模型詳解
開門見山,linux下的如中io模型 阻塞io模型,非阻塞io模型,io復用模型,訊號驅動io模型,非同步io模型,見下圖 接下來一一講解這5種模型 阻塞型io 最簡單的一種io模型,簡單理解就是死等,即程序或執行緒一直等待莫格條件,不滿足則一直等待。非阻塞型io 應用程序與核心互動,目的未達到之前...
linux下的IO模型詳解
開門見山,linux下的如中io模型 阻塞io模型,非阻塞io模型,io復用模型,訊號驅動io模型,非同步io模型,見下圖 接下來一一講解這5種模型 阻塞型io 最簡單的一種io模型,簡單理解就是死等,即程序或執行緒一直等待莫格條件,不滿足則一直等待。非阻塞型io 應用程序與核心互動,目的未達到之前...
Linux下常見的IO模型
前三種都是同步,只有最後一種才是非同步io。簡介 程序會一直阻塞,直到資料拷貝完成。應用程式呼叫乙個io函式,導致應用程式阻塞,等待資料準備好。如果資料沒有準備好,一直等待。資料準備好了,從核心拷貝到使用者空間。執行完畢後,io函式會向應用程式返回成功響應,應用程式得到響應後,就不再阻塞,並進行後面...