當前位置:首頁 » 編程軟體 » epoll編程

epoll編程

發布時間: 2023-03-08 17:36:39

1. 伺服器程序源代碼分析之二:php-fpm

php作為排名top2 互聯網開發工具,非常流行,可以參考:中國最大的25個網站採用技術選型方案

php這個名稱實際上有兩層含義

直接定義:

php-fpm從php5.3.3開始已經進入到php源代碼包,之前是作為patch存在的

很少人會去讀php本身源代碼,我6年前解決php內存泄露問題的時候做了些研究,最近再查看了一番,發現php的開發者很有誠意,這是一款非常出色的伺服器軟體,支持如下

linux伺服器上,如果不設置 events.mechanism ,那麼默認就是採用epoll,所以

php-fpm的IO模型&並發處理能力和nginx是完全一致

nginx以性能卓越聞名,大部分程序員都認為php效率低下,看了源代碼,才知道這是傳奇啊

在高性能部署的時候,大家往往會針對性的優化nginx 。我自己之前部署php程序也犯了錯誤,8G內存的server,php-fpm的max children都會設置128+,現在看來太多了,參考nginx的部署:

php-fpm配置為 3倍 cpu core number就可以了

php-fpm穩定性比nginx稍差 這是因為php-fpm內置了一個php解析器,php-fpm進程就和php程序捆綁了,如果php腳本寫得不好,有死循環或者阻塞在某個遠端資源上,會拖累載入它的php-fpm進程

而nginx和後端應用伺服器之間通過網路連接,可以設置timeout,不容易堵死的

php-fpm的fastcgi是短連接 我原以為是長連接的,看了代碼才知道也是短連接,處理一個request就關閉掉

php-fpm介面採用fastcgi 非常遺憾,php-fpm和fastcgi完全綁定了,無法獨立使用 。只能部署在支持http-fcgi協議轉換程序背後(nginx)。其實可以考慮在php-fpm代碼包裡面引入http協議支持,這樣php-fpm可以獨立運行,讓nodejs無話可說

php-fpm等同於OpenResty OpenResty是一個國人開發的nginx模塊,就是在nginx引入lua解釋器. 實際上,它和php-fpm的唯一差別就是一個採用php語法,一個用lua,所以OpenResty要作為nginx增強包使用還可以,要選擇它作為一個主要編程工具,沒有任何必要

從架構上來說,php-fpm已經做到最好,超過大多數 python部署工具,我再也不黑它了

2. 面試必問的epoll技術,從內核源碼出發徹底搞懂epoll

epoll是linux中IO多路復用的一種機制,I/O多路復用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。當然linux中IO多路復用不僅僅是epoll,其他多路復用機制還有select、poll,但是接下來介紹epoll的內核實現。

events可以是以下幾個宏的集合:

epoll相比select/poll的優勢

epoll相關的內核代碼在fs/eventpoll.c文件中,下面分別分析epoll_create、epoll_ctl和epoll_wait三個函數在內核中的實現,分析所用linux內核源碼為4.1.2版本。

epoll_create用於創建一個epoll的句柄,其在內核的系統實現如下:

sys_epoll_create:

可見,我們在調用epoll_create時,傳入的size參數,僅僅是用來判斷是否小於等於0,之後再也沒有其他用處。
整個函數就3行代碼,真正的工作還是放在sys_epoll_create1函數中。

sys_epoll_create -> sys_epoll_create1:

sys_epoll_create1 函數流程如下:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc:


sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags:

linux內核中,current是個宏,返回的是一個task_struct結構(我們稱之為進程描述符)的變數,表示的是當前進程,進程打開的文件資源保存在進程描述符的files成員裡面,所以current->files返回的當前進程打開的文件資源。rlimit(RLIMIT_NOFILE) 函數獲取的是當前進程可以打開的最大文件描述符數,這個值可以設置,默認是1024。

相關視頻推薦:

支撐億級io的底層基石 epoll實戰揭秘

網路原理tcp/udp,網路編程epoll/reactor,面試中正經「八股文」

學習地址:C/C++Linux伺服器開發/後台架構師【零聲教育】-學習視頻教程-騰訊課堂

需要更多C/C++ Linux伺服器架構師學習資料加群 812855908 獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

__alloc_fd的工作是為進程在[start,end)之間(備註:這里start為0, end為進程可以打開的最大文件描述符數)分配一個可用的文件描述符,這里就不繼續深入下去了,代碼如下:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags -> __alloc_fd:

然後,epoll_create1會調用anon_inode_getfile,創建一個file結構,如下:

sys_epoll_create -> sys_epoll_create1 -> anon_inode_getfile:

anon_inode_getfile函數中首先會alloc一個file結構和一個dentry結構,然後將該file結構與一個匿名inode節點anon_inode_inode掛鉤在一起,這里要注意的是,在調用anon_inode_getfile函數申請file結構時,傳入了前面申請的eventpoll結構的ep變數,申請的file->private_data會指向這個ep變數,同時,在anon_inode_getfile函數返回來後,ep->file會指向該函數申請的file結構變數。

簡要說一下file/dentry/inode,當進程打開一個文件時,內核就會為該進程分配一個file結構,表示打開的文件在進程的上下文,然後應用程序會通過一個int類型的文件描述符來訪問這個結構,實際上內核的進程裡面維護一個file結構的數組,而文件描述符就是相應的file結構在數組中的下標。

dentry結構(稱之為「目錄項」)記錄著文件的各種屬性,比如文件名、訪問許可權等,每個文件都只有一個dentry結構,然後一個進程可以多次打開一個文件,多個進程也可以打開同一個文件,這些情況,內核都會申請多個file結構,建立多個文件上下文。但是,對同一個文件來說,無論打開多少次,內核只會為該文件分配一個dentry。所以,file結構與dentry結構的關系是多對一的。

同時,每個文件除了有一個dentry目錄項結構外,還有一個索引節點inode結構,裡面記錄文件在存儲介質上的位置和分布等信息,每個文件在內核中只分配一個inode。 dentry與inode描述的目標是不同的,一個文件可能會有好幾個文件名(比如鏈接文件),通過不同文件名訪問同一個文件的許可權也可能不同。dentry文件所代表的是邏輯意義上的文件,記錄的是其邏輯上的屬性,而inode結構所代表的是其物理意義上的文件,記錄的是其物理上的屬性。dentry與inode結構的關系是多對一的關系。

sys_epoll_create -> sys_epoll_create1 -> fd_install:

總結epoll_create函數所做的事:調用epoll_create後,在內核中分配一個eventpoll結構和代表epoll文件的file結構,並且將這兩個結構關聯在一塊,同時,返回一個也與file結構相關聯的epoll文件描述符fd。當應用程序操作epoll時,需要傳入一個epoll文件描述符fd,內核根據這個fd,找到epoll的file結構,然後通過file,獲取之前epoll_create申請eventpoll結構變數,epoll相關的重要信息都存儲在這個結構裡面。接下來,所有epoll介面函數的操作,都是在eventpoll結構變數上進行的。

所以,epoll_create的作用就是為進程在內核中建立一個從epoll文件描述符到eventpoll結構變數的通道。

epoll_ctl介面的作用是添加/修改/刪除文件的監聽事件,內核代碼如下:

sys_epoll_ctl:

根據前面對epoll_ctl介面的介紹,op是對epoll操作的動作(添加/修改/刪除事件),ep_op_has_event(op)判斷是否不是刪除操作,如果op != EPOLL_CTL_DEL為true,則需要調用_from_user函數將用戶空間傳過來的event事件拷貝到內核的epds變數中。因為,只有刪除操作,內核不需要使用進程傳入的event事件。

接著連續調用兩次fdget分別獲取epoll文件和被監聽文件(以下稱為目標文件)的file結構變數(備註:該函數返回fd結構變數,fd結構包含file結構)。

接下來就是對參數的一些檢查,出現如下情況,就可以認為傳入的參數有問題,直接返回出錯:

當然下面還有一些關於操作動作如果是添加操作的判斷,這里不做解釋,比較簡單,自行閱讀。

在ep裡面,維護著一個紅黑樹,每次添加註冊事件時,都會申請一個epitem結構的變數表示事件的監聽項,然後插入ep的紅黑樹裡面。在epoll_ctl裡面,會調用ep_find函數從ep的紅黑樹裡面查找目標文件表示的監聽項,返回的監聽項可能為空。

接下來switch這塊區域的代碼就是整個epoll_ctl函數的核心,對op進行switch出來的有添加(EPOLL_CTL_ADD)、刪除(EPOLL_CTL_DEL)和修改(EPOLL_CTL_MOD)三種情況,這里我以添加為例講解,其他兩種情況類似,知道了如何添加監聽事件,其他刪除和修改監聽事件都可以舉一反三。

為目標文件添加監控事件時,首先要保證當前ep裡面還沒有對該目標文件進行監聽,如果存在(epi不為空),就返回-EEXIST錯誤。否則說明參數正常,然後先默認設置對目標文件的POLLERR和POLLHUP監聽事件,然後調用ep_insert函數,將對目標文件的監聽事件插入到ep維護的紅黑樹裡面:

sys_epoll_ctl -> ep_insert:

前面說過,對目標文件的監聽是由一個epitem結構的監聽項變數維護的,所以在ep_insert函數裡面,首先調用kmem_cache_alloc函數,從slab分配器裡面分配一個epitem結構監聽項,然後對該結構進行初始化,這里也沒有什麼好說的。我們接下來看ep_item_poll這個函數調用:

sys_epoll_ctl -> ep_insert -> ep_item_poll:

ep_item_poll函數裡面,調用目標文件的poll函數,這個函數針對不同的目標文件而指向不同的函數,如果目標文件為套接字的話,這個poll就指向sock_poll,而如果目標文件為tcp套接字來說,這個poll就是tcp_poll函數。雖然poll指向的函數可能會不同,但是其作用都是一樣的,就是獲取目標文件當前產生的事件位,並且將監聽項綁定到目標文件的poll鉤子裡面(最重要的是注冊ep_ptable_queue_proc這個poll callback回調函數),這步操作完成後,以後目標文件產生事件就會調用ep_ptable_queue_proc回調函數。

接下來,調用list_add_tail_rcu將當前監聽項添加到目標文件的f_ep_links鏈表裡面,該鏈表是目標文件的epoll鉤子鏈表,所有對該目標文件進行監聽的監聽項都會加入到該鏈表裡面。

然後就是調用ep_rbtree_insert,將epi監聽項添加到ep維護的紅黑樹裡面,這里不做解釋,代碼如下:

sys_epoll_ctl -> ep_insert -> ep_rbtree_insert:

前面提到,ep_insert有調用ep_item_poll去獲取目標文件產生的事件位,在調用epoll_ctl前這段時間,可能會產生相關進程需要監聽的事件,如果有監聽的事件產生,(revents & event->events 為 true),並且目標文件相關的監聽項沒有鏈接到ep的准備鏈表rdlist裡面的話,就將該監聽項添加到ep的rdlist准備鏈表裡面,rdlist鏈接的是該epoll描述符監聽的所有已經就緒的目標文件的監聽項。並且,如果有任務在等待產生事件時,就調用wake_up_locked函數喚醒所有正在等待的任務,處理相應的事件。當進程調用epoll_wait時,該進程就出現在ep的wq等待隊列裡面。接下來講解epoll_wait函數。

總結epoll_ctl函數:該函數根據監聽的事件,為目標文件申請一個監聽項,並將該監聽項掛人到eventpoll結構的紅黑樹裡面。

epoll_wait等待事件的產生,內核代碼如下:

sys_epoll_wait:

首先是對進程傳進來的一些參數的檢查:

參數全部檢查合格後,接下來就調用ep_poll函數進行真正的處理:

sys_epoll_wait -> ep_poll:

ep_poll中首先是對等待時間的處理,timeout超時時間以ms為單位,timeout大於0,說明等待timeout時間後超時,如果timeout等於0,函數不阻塞,直接返回,小於0的情況,是永久阻塞,直到有事件產生才返回。

當沒有事件產生時((!ep_events_available(ep))為true),調用__add_wait_queue_exclusive函數將當前進程加入到ep->wq等待隊列裡面,然後在一個無限for循環裡面,首先調用set_current_state(TASK_INTERRUPTIBLE),將當前進程設置為可中斷的睡眠狀態,然後當前進程就讓出cpu,進入睡眠,直到有其他進程調用wake_up或者有中斷信號進來喚醒本進程,它才會去執行接下來的代碼。

如果進程被喚醒後,首先檢查是否有事件產生,或者是否出現超時還是被其他信號喚醒的。如果出現這些情況,就跳出循環,將當前進程從ep->wp的等待隊列裡面移除,並且將當前進程設置為TASK_RUNNING就緒狀態。

如果真的有事件產生,就調用ep_send_events函數,將events事件轉移到用戶空間裡面。

sys_epoll_wait -> ep_poll -> ep_send_events:

ep_send_events沒有什麼工作,真正的工作是在ep_scan_ready_list函數裡面:

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list:

ep_scan_ready_list首先將ep就緒鏈表裡面的數據鏈接到一個全局的txlist裡面,然後清空ep的就緒鏈表,同時還將ep的ovflist鏈表設置為NULL,ovflist是用單鏈表,是一個接受就緒事件的備份鏈表,當內核進程將事件從內核拷貝到用戶空間時,這段時間目標文件可能會產生新的事件,這個時候,就需要將新的時間鏈入到ovlist裡面。

僅接著,調用sproc回調函數(這里將調用ep_send_events_proc函數)將事件數據從內核拷貝到用戶空間。

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list -> ep_send_events_proc:

ep_send_events_proc回調函數循環獲取監聽項的事件數據,對每個監聽項,調用ep_item_poll獲取監聽到的目標文件的事件,如果獲取到事件,就調用__put_user函數將數據拷貝到用戶空間。

回到ep_scan_ready_list函數,上面說到,在sproc回調函數執行期間,目標文件可能會產生新的事件鏈入ovlist鏈表裡面,所以,在回調結束後,需要重新將ovlist鏈表裡面的事件添加到rdllist就緒事件鏈表裡面。

同時在最後,如果rdlist不為空(表示是否有就緒事件),並且由進程等待該事件,就調用wake_up_locked再一次喚醒內核進程處理事件的到達(流程跟前面一樣,也就是將事件拷貝到用戶空間)。

到這,epoll_wait的流程是結束了,但是有一個問題,就是前面提到的進程調用epoll_wait後會睡眠,但是這個進程什麼時候被喚醒呢?在調用epoll_ctl為目標文件注冊監聽項時,對目標文件的監聽項注冊一個ep_ptable_queue_proc回調函數,ep_ptable_queue_proc回調函數將進程添加到目標文件的wakeup鏈表裡面,並且注冊ep_poll_callbak回調,當目標文件產生事件時,ep_poll_callbak回調就去喚醒等待隊列裡面的進程。

總結一下epoll該函數: epoll_wait函數會使調用它的進程進入睡眠(timeout為0時除外),如果有監聽的事件產生,該進程就被喚醒,同時將事件從內核裡面拷貝到用戶空間返回給該進程。

熱點內容
java返回this 發布:2025-10-20 08:28:16 瀏覽:741
製作腳本網站 發布:2025-10-20 08:17:34 瀏覽:1005
python中的init方法 發布:2025-10-20 08:17:33 瀏覽:712
圖案密碼什麼意思 發布:2025-10-20 08:16:56 瀏覽:874
怎麼清理微信視頻緩存 發布:2025-10-20 08:12:37 瀏覽:773
c語言編譯器怎麼看執行過程 發布:2025-10-20 08:00:32 瀏覽:1120
郵箱如何填寫發信伺服器 發布:2025-10-20 07:45:27 瀏覽:346
shell腳本入門案例 發布:2025-10-20 07:44:45 瀏覽:224
怎麼上傳照片瀏覽上傳 發布:2025-10-20 07:44:03 瀏覽:910
python股票數據獲取 發布:2025-10-20 07:39:44 瀏覽:869