I/O 模型
I/O 復用
歡迎關注筆者,優質文章都在這里等你。
一個輸入操作通常包括兩個階段:
對于一個套接字上的輸入操作,第一步通常涉及等待數據從網絡中到達。當所等待數據到達時,它被復制到內核中的某個緩沖區。第二步就是把數據從內核緩沖區復制到應用進程緩沖區。
Unix 有五種 I/O 模型:
應用進程被阻塞,直到數據從內核緩沖區復制到應用進程緩沖區中才返回。
應該注意到,在阻塞的過程中,其它應用進程還可以執行,因此阻塞不意味著整個操作系統都被阻塞。因為其它應用進程還可以執行,所以不消耗 CPU 時間,這種模型的 CPU 利用率會比較高。
下圖中,recvfrom() 用于接收 Socket 傳來的數據,并復制到應用進程的緩沖區 buf 中。這里把 recvfrom() 當成系統調用。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
應用進程執行系統調用之后,內核返回一個錯誤碼。應用進程可以繼續執行,但是需要不斷的執行系統調用來獲知 I/O 是否完成,這種方式稱為輪詢(polling)。
由于 CPU 要處理更多的系統調用,因此這種模型的 CPU 利用率比較低。
使用 select 或者 poll 等待數據,并且可以等待多個套接字中的任何一個變為可讀。這一過程會被阻塞,當某一個套接字可讀時返回,之后再使用 recvfrom 把數據從內核復制到進程中。
它可以讓單個進程具有處理多個 I/O 事件的能力。又被稱為 Event Driven I/O,即事件驅動 I/O。
如果一個 Web 服務器沒有 I/O 復用,那么每一個 Socket 連接都需要創建一個線程去處理。如果同時有幾萬個連接,那么就需要創建相同數量的線程。相比于多進程和多線程技術,I/O 復用不需要進程線程創建和切換的開銷,系統開銷更小。
應用進程使用 sigaction 系統調用,內核立即返回,應用進程可以繼續執行,也就是說等待數據階段應用進程是非阻塞的。內核在數據到達時向應用進程發送 SIGIO 信號,應用進程收到之后在信號處理程序中調用 recvfrom 將數據從內核復制到應用進程中。
相比于非阻塞式 I/O 的輪詢方式,信號驅動 I/O 的 CPU 利用率更高。
應用進程執行 aio_read 系統調用會立即返回,應用進程可以繼續執行,不會被阻塞,內核會在所有操作完成之后向應用進程發送信號。
異步 I/O 與信號驅動 I/O 的區別在于,異步 I/O 的信號是通知應用進程 I/O 完成,而信號驅動 I/O 的信號是通知應用進程可以開始 I/O。
五大 I/O 模型比較
select/poll/epoll 都是 I/O 多路復用的具體實現,select 出現的最早,之后是 poll,再是 epoll。
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
有三種類型的描述符類型:readset、writeset、exceptset,分別對應讀、寫、異常條件的描述符集合。fd_set 使用數組實現,數組大小使用 FD_SETSIZE 定義。
timeout 為超時參數,調用 select 會一直阻塞直到有描述符的事件到達或者等待的時間超過 timeout。
成功調用返回結果大于 0,出錯返回結果為 -1,超時返回結果為 0。
fd_set fd_in, fd_out; struct timeval tv; // Reset the sets FD_ZERO( &fd_in ); FD_ZERO( &fd_out ); // Monitor sock1 for input events FD_SET( sock1, &fd_in ); // Monitor sock2 for output events FD_SET( sock2, &fd_out ); // Find out which socket has the largest numeric value as select requires it int largest_sock = sock1 > sock2 ? sock1 : sock2; // Wait up to 10 seconds tv.tv_sec = 10; tv.tv_usec = 0; // Call the select int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv ); // Check if select actually succeed if ( ret == -1 ) // report error and abort else if ( ret == 0 ) // timeout; no event detected else { if ( FD_ISSET( sock1, &fd_in ) ) // input event on sock1 if ( FD_ISSET( sock2, &fd_out ) ) // output event on sock2 }
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
pollfd 使用鏈表實現。
// The structure for two events struct pollfd fds[2]; // Monitor sock1 for input fds[0].fd = sock1; fds[0].events = POLLIN; // Monitor sock2 for output fds[1].fd = sock2; fds[1].events = POLLOUT; // Wait 10 seconds int ret = poll( &fds, 2, 10000 ); // Check if poll actually succeed if ( ret == -1 ) // report error and abort else if ( ret == 0 ) // timeout; no event detected else { // If we detect the event, zero it out so we can reuse the structure if ( fds[0].revents & POLLIN ) fds[0].revents = 0; // input event on sock1 if ( fds[1].revents & POLLOUT ) fds[1].revents = 0; // output event on sock2 }
比較
1. 功能
select 和 poll 的功能基本相同,不過在一些實現細節上有所不同。
2. 速度
select 和 poll 速度都比較慢。
3. 可移植性
幾乎所有的系統都支持 select,但是只有比較新的系統支持 poll。
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll_ctl() 用于向內核注冊新的描述符或者是改變某個文件描述符的狀態。已注冊的描述符在內核中會被維護在一棵紅黑樹上,通過回調函數內核會將 I/O 準備好的描述符加入到一個鏈表中管理,進程調用 epoll_wait() 便可以得到事件完成的描述符。
從上面的描述可以看出,epoll 只需要將描述符從進程緩沖區向內核緩沖區拷貝一次,并且進程不需要通過輪詢來獲得事件完成的描述符。
epoll 僅適用于 Linux OS。
epoll 比 select 和 poll 更加靈活而且沒有描述符數量限制。
epoll 對多線程編程更有友好,一個線程調用了 epoll_wait() 另一個線程關閉了同一個描述符也不會產生像 select 和 poll 的不確定情況。
// Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets. // The function argument is ignored (it was not before, but now it is), so put your favorite number here int pollingfd = epoll_create( 0xCAFE ); if ( pollingfd < 0 ) // report error // Initialize the epoll structure in case more members are added in future struct epoll_event ev = { 0 }; // Associate the connection class instance with the event. You can associate anything // you want, epoll does not use this information. We store a connection class pointer, pConnection1 ev.data.ptr = pConnection1; // Monitor for input, and do not automatically rearm the descriptor after the event ev.events = EPOLLIN | EPOLLONESHOT; // Add the descriptor into the monitoring list. We can do it even if another thread is // waiting in epoll_wait - the descriptor will be properly added if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, pConnection1->getSocket(), &ev ) != 0 ) // report error // Wait for up to 20 events (assuming we have added maybe 200 sockets before that it may happen) struct epoll_event pevents[ 20 ]; // Wait for 10 seconds, and retrieve less than 20 epoll_event and store them into epoll_event array int ready = epoll_wait( pollingfd, pevents, 20, 10000 ); // Check if epoll actually succeed if ( ret == -1 ) // report error and abort else if ( ret == 0 ) // timeout; no event detected else { // Check if any events detected for ( int i = 0; i < ret; i++ ) { if ( pevents[i].events & EPOLLIN ) { // Get back our connection pointer Connection * c = (Connection*) pevents[i].data.ptr; c->handleReadEvent(); } } }
epoll 的描述符事件有兩種觸發模式:LT(level trigger)和 ET(edge trigger)。
1. LT 模式
當 epoll_wait() 檢測到描述符事件到達時,將此事件通知進程,進程可以不立即處理該事件,下次調用 epoll_wait() 會再次通知進程。是默認的一種模式,并且同時支持 Blocking 和 No-Blocking。
2. ET 模式
和 LT 模式不同的是,通知之后進程必須立即處理事件,下次再調用 epoll_wait() 時不會再得到事件到達的通知。
很大程度上減少了 epoll 事件被重復觸發的次數,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
很容易產生一種錯覺認為只要用 epoll 就可以了,select 和 poll 都已經過時了,其實它們都有各自的使用場景。
1. select 應用場景
select 的 timeout 參數精度為 1ns,而 poll 和 epoll 為 1ms,因此 select 更加適用于實時性要求比較高的場景,比如核反應堆的控制。
select 可移植性更好,幾乎被所有主流平臺所支持。
2. poll 應用場景
poll 沒有最大描述符數量的限制,如果平臺支持并且對實時性要求不高,應該使用 poll 而不是 select。
3. epoll 應用場景
只需要運行在 Linux 平臺上,有大量的描述符需要同時輪詢,并且這些連接最好是長連接。
需要同時監控小于 1000 個描述符,就沒有必要使用 epoll,因為這個應用場景下并不能體現 epoll 的優勢。
需要監控的描述符狀態變化多,而且都是非常短暫的,也沒有必要使用 epoll。因為 epoll 中的所有描述符都存儲在內核中,造成每次需要對描述符的狀態改變都需要通過 epoll_ctl() 進行系統調用,頻繁系統調用降低效率。并且 epoll 的描述符存儲在內核,不容易調試。
資料來源 : github 作者 : CyC2018
對大廠架構設計,BAT面試題分享,編程語言理論或者互聯網圈逸聞趣事這些感興趣,歡迎關注筆者,沒有錯,干貨文章都在這里。
引言
FDS945-NL場效應管在高速發展的電子元件行業里,因其出色的性能和廣泛應用,已成為方案工程師、技術人員和制造購置的焦點。這款P溝MOSFET不但因其出色的電氣特性在市場中占有重要地位,并且在推進電子設計制造業技術革新方面發揮著主導地位。FDS945-NL不僅是電子元件行業的提高,都是現代電子技術發展的象征。
技術規格參數詳解
FDS945-NL的技術參數充分展示了其在高性能MOS管里的領先地位。它有60VP溝漏電源電流(Vdss)持續漏極電流做到50A(Id),主要應用于髙壓、大電流的使用場景。其導通電阻僅是20mΩ(在10V, 在50A環境下),這種低電阻特性顯著降低了高效導出時的能量損失,確保了系統在高性能工作情況下的穩定性和可靠性,并把FDS9945-NL推廣到電子設計里的挑選元件。
工作原理
FDS9945-NL工作原理是根據P溝MOSFET的關鍵系統。依據電場調整電流流動,進行更有效的電流調整。FDS945-NL不僅提升了電源靈活性,并且比傳統電子元件在電池管理和信號調節方面表現出更高的效率和穩定性。FDS9945-NL在高電流和高電壓情況下的出色表現,使FDS9945-NL在現代電子設計中具有獨特的優勢,主要適用于復雜而嚴苛的應用場景。
電路應用方案
FDS9945-NL在電子線路的設計和應用中起著重要作用。廣泛用于電源管理系統、電池充電器、信號增強器等關鍵領域。FDS9945-NL的出色電氣特性不僅提升了這些設備的綜合性能和品質,并且在處理大電流和高電壓問題時也表現出了其特點和優勢。該應用案例證明了FDS945-NL的多功能化,并顯示了其在提升電子產品性能層面的必要性。
結論
一般來說,FDS945-NL場效應管以其優質的技術參數和優良工作原理但在電子元件市場獲得普遍認可。這是方案工程師、技術人員和生產商在挑選高效元件時的理想選擇。FDS945-NL不僅促進了電子產業的技術創新,并且在提高產品特性方面發揮了重要作用。