紅黑樹linux
⑴ Handler消息機制(一):linux的epoll機制
在linux 沒有實現epoll事件驅動機制之前,我們一般選擇用select或者poll等IO多路復用的方法來實現並發服務程序。在linux新的內核中,有了一種替換它的機制,就是epoll。
相比select模型, poll使用鏈表保存文件描述符,因此沒有了監視文件數量的限制 ,但其他三個缺點依然存在。
假設我們的伺服器需要支持100萬的並發連接,則在__FD_SETSIZE 為1024的情況下,則我們至少需要開辟1k個進程才能實現100萬的並發連接。除了進程間上下文切換的時間消耗外,從內核/用戶空間大量的無腦內存拷貝、數組輪詢等,是系統難以承受的。因此,基於select模型的伺服器程序,要達到10萬級別的並發訪問,是一個很難完成的任務。
由於epoll的實現機制與select/poll機制完全不同,上面所說的 select的缺點在epoll上不復存在。
設想一下如下場景:有100萬個客戶端同時與一個伺服器進程保持著TCP連接。而每一時刻,通常只有幾百上千個TCP連接是活躍的(事實上大部分場景都是這種情況)。如何實現這樣的高並發?
在select/poll時代,伺服器進程每次都把這100萬個連接告訴操作系統(從用戶態復制句柄數據結構到內核態),讓操作系統內核去查詢這些套接字上是否有事件發生,輪詢完後,再將句柄數據復制到用戶態,讓伺服器應用程序輪詢處理已發生的網路事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的並發連接。
epoll的設計和實現與select完全不同。epoll通過在Linux內核中申請一個簡易的文件系統(文件系統一般用什麼數據結構實現?B+樹)。把原先的select/poll調用分成了3個部分:
1)調用epoll_create()建立一個epoll對象(在epoll文件系統中為這個句柄對象分配資源)
2)調用epoll_ctl向epoll對象中添加這100萬個連接的套接字
3)調用epoll_wait收集發生的事件的連接
如此一來,要實現上面說是的場景,只需要在進程啟動時建立一個epoll對象,然後在需要的時候向這個epoll對象中添加或者刪除連接。同時,epoll_wait的效率也非常高,因為調用epoll_wait時,並沒有一股腦的向操作系統復制這100萬個連接的句柄數據,內核也不需要去遍歷全部的連接。
當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。eventpoll結構體如下所示:
每一個epoll對象都有一個獨立的eventpoll結構體,用於存放通過epoll_ctl方法向epoll對象中添加進來的事件。這些事件都會掛載在紅黑樹中,如此,重復添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為樹的高度)。
而所有 添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關系,也就是說,當相應的事件發生時會調用這個回調方法 。這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中。
在epoll中,對於每一個事件,都會建立一個epitem結構體,如下所示:
當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶。
epoll結構示意圖
通過紅黑樹和雙鏈表數據結構,並結合回調機制,造就了epoll的高效。
events可以是以下幾個宏的集合:
EPOLLIN:觸發該事件,表示對應的文件描述符上有可讀數據。(包括對端SOCKET正常關閉);
EPOLLOUT:觸發該事件,表示對應的文件描述符上可以寫數據;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP: 表示對應的文件描述符被掛斷;
EPOLLET:將EPOLL設為邊緣觸發(EdgeTriggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT: 只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。
示例:
ET(EdgeTriggered) :高速工作模式,只支持no_block(非阻塞模式)。在此模式下,當描述符從未就緒變為就緒時,內核通過epoll告知。然後它會假設用戶知道文件描述符已經就緒,並且不會再為那個文件描述符發送更多的就緒通知,直到某些操作導致那個文件描述符不再為就緒狀態了。(觸發模式只在數據就緒時通知一次,若數據沒有讀完,下一次不會通知,直到有新的就緒數據)
LT(LevelTriggered) :預設工作方式,支持blocksocket和no_blocksocket。在LT模式下內核會告知一個文件描述符是否就緒了,然後可以對這個就緒的fd進行IO操作。如果不作任何操作,內核還是會繼續通知!若數據沒有讀完,內核也會繼續通知,直至設備數據為空為止!
1.我們已經把一個用來從管道中讀取數據的文件句柄(RFD)添加到epoll描述符
2. 這個時候從管道的另一端被寫入了2KB的數據
3. 調用epoll_wait(2),並且它會返回RFD,說明它已經准備好讀取操作
4. 然後我們讀取了1KB的數據
5. 調用epoll_wait(2)……
ET工作模式:
如果我們在第1步將RFD添加到epoll描述符的時候使用了EPOLLET標志,在第2步執行了一個寫操作,第三步epoll_wait會返回同時通知的事件會銷毀。因為第4步的讀取操作沒有讀空文件輸入緩沖區內的數據,因此我們在第5步調用epoll_wait(2)完成後,是否掛起是不確定的。epoll工作在ET模式的時候,必須使用非阻塞套介面,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
只有當read(2)或者write(2)返回EAGAIN時(認為讀完)才需要掛起,等待。但這並不是說每次read()時都需要循環讀,直到讀到產生一個EAGAIN才認為此次事件處理完成,當read()返回的讀到的數據長度小於請求的數據長度時(即小於sizeof(buf)),就可以確定此時緩沖中已沒有數據了,也就可以認為此事讀事件已處理完成。
LT工作模式:
LT方式調用epoll介面的時候,它就相當於一個速度比較快的poll(2),並且無論後面的數據是否被使用,因此他們具有同樣的職能。
當調用 epoll_wait檢查是否有發生事件的連接時,只是檢查 eventpoll對象中的 rdllist雙向鏈表是否有 epitem元素而已,如果 rdllist鏈表不為空,則把這里的事件復制到用戶態內存中,同時將事件數量返回給用戶。因此,epoll_wait的效率非常高。epoll_ctl在向 epoll對象中添加、修改、刪除事件時,從 rbr紅黑樹中查找事件也非常快,也就是說,epoll是非常高效的,它可以輕易地處理百萬級別的並發連接。
1.減少用戶態和內核態之間的文件句柄拷貝;
2.減少對可讀可寫文件句柄的遍歷。
https://cloud.tencent.com/developer/information/linux%20epoll%E6%9C%BA%E5%88%B6
https://blog.csdn.net/u010657219/article/details/44061629
https://jiahao..com/s?id=1609322251459722004&wfr=spider&for=pc
⑵ linuxpollread區別
`poll` 和 `epoll` 都是 Linux 中用於實現 I/O 多路復用的機制,可以讓一個進程同時監聽多個文件描述符上的事件。它們的主要區別在於實現方式和性能表現。
`poll` 函數是最早引入到 Linux 內核中的 I/O 多路復用機制,它使用一個 `pollfd` 結構體數組來存儲需喚爛要監聽的文件描述符以及每個文件描述符上需要監聽的事件類型。當調用 `poll` 函數時,內核會遍歷所有的 `pollfd` 結構體,檢查每個文件描述符上是否有事件發生,如果有則將該事件通知給應用程序。但是當需要監聽大量文件描述符時,由於 `poll` 函數是線性掃描整個數組來查找就緒文件描述符,因此效率較低。
而 `epoll` 函數則採用了基於事件驅動(Event-driven)的方式來實現 I/O 多路復用。它使用一個紅黑樹(Red-Black Tree)來存儲需要監聽的文件描述符,並通過回調函數機制來通知應用程序哪些文件描述符爛穗上發生了事件。相比於 `poll` 函飢鏈卜數,`epoll` 函數在處理大量並發連接時具有更高的效率和更低的系統開銷。
總結一下:
- `poll` 是最早引入到 Linux 內核中的 I/O 多路復用機制,`epoll` 是後來引入的。
- `poll` 使用一個數組來存儲需要監聽的文件描述符,效率較低;而 `epoll` 使用紅黑樹和回調函數機制,效率更高。
- `epoll` 可以同時監聽大量文件描述符上的事件,並且可以支持邊緣觸發(Edge Triggered)和水平觸發(Level Triggered)兩種模式。
⑶ 為什麼STL和linux都使用紅黑樹作為平衡樹的實現
紅黑樹是平衡二叉樹的一種,它有很好的性質,拿凳褲樹中的結點都是有序的,而且因為它本身就是平衡的,所以查找也不會出現非常惡劣的情粗譽況,基於二叉樹的操消簡作的時間復雜度是O(log(N))。Linux內核在管理vm_area_struct時就是採用了紅黑樹來維護內存塊的....
⑷ linux內核設計與實現
Linux內核的設計與實現是一個很大的話題,我這里簡單概括一下:
Linux內核採用模塊化設計,將內核功能劃分為多個相對獨立的模塊,如文件系統模塊、網路模塊、進程調度模塊等。這種模塊化設計使世鋒得Linux內核具有良好的可擴展性和可維護性。
Linux內核採用層次化設計,從上到下主要分為用戶空間、搜前晌系統調用介面、內核空間。用戶空間和內核空間通過系統調用介面交互。內核空間中又分為多層,從上到下主要分為進程調度層、文件系統層、網路層、設備驅動層等。
Linux內核提供強大的抽象能力和通用機制。如通過文件抽象統一了對文件、網路套接字、管道等的訪問;通過緩沖 CACHE 抽象實現了文件緩沖、目錄緩沖、頁緩沖等。這些抽象和機制使得上層文件系統和網路協議悔脊的實現變得更加簡單。
Linux內核採用先進的數據結構,如雙向鏈表、散列表、紅黑樹、堆等,這些數據結構使得Linux內核在性能和復雜度上都有很好的表現。
Linux內核實現了先進的演算法和機制,如時間共享進程調度演算法、虛擬內存管理演算法、TCP擁塞控制演算法等,這些演算法機制是Linux內核性能優良和功能強大的基石。
Linux內核有著非常清晰和簡潔的源代碼,這也是它受歡迎的一個重要原因。簡潔清晰的代碼易於維護和二次開發。
這是Linux內核設計與實現的一個簡單概括,實際上每個方面都可以講述很多,希望對你有所幫助。如果你有任何其他問題,歡迎在回復中提出。
⑸ 一文讀懂Linux任務間調度原理和整個執行過程
在前文中,我們分析了內核中進程和線程的統一結構體task_struct,並分析進程、線程的創建和派生的過程。在本文中,我們會對任務間調度進行詳細剖析,了解其原理和整個執行過程。由此,進程、線程部分的大體框架就算是介紹完了。本節主要分為三個部分:Linux內核中常見的調度策略,調度的基本結構體以及調度發生的整個流程。下面將詳細展開說明。
Linux 作為一個多任務操作系統,將每個 CPU 的時間劃分為很短的時間片,再通過調度器輪流分配給各個任務使用,因此造成多任務同時運行的錯覺。為了維護 CPU 時間,Linux 通過事先定義的節拍率(內核中表示為 HZ),觸發時間中斷,並使用全局變數 Jiffies 記錄了開機以來的節拍數。每發生一次時間中斷,Jiffies 的值就加 1。節拍率 HZ 是內核的可配選項,可以設置為 100、250、1000 等。不同的系統可能設置不同的數值,可以通過查詢 /boot/config 內核選項來查看它的配置值。
Linux的調度策略主要分為實時任務和普通任務。實時任務需求盡快返回結果,而普通任務則沒有較高的要求。在前文中我們提到了task_struct中調度策略相應的變數為policy,調度優先順序有prio, static_prio, normal_prio, rt_priority幾個。優先順序其實就是一個數值,對於實時進程來說,優先順序的范圍是 0 99;對於普通進程,優先順序的范圍是 100 139。數值越小,優先順序越高。
實時調度策略主要包括以下幾種
普通調度策略主要包括以下幾種:
首先,我們需要一個結構體去執行調度策略,即sched_class。該類有幾種實現方式
普通任務調度實體源碼如下,這裡麵包含了 vruntime 和權重 load_weight,以及對於運行時間的統計。
在調度時,多個任務調度實體會首先區分是實時任務還是普通任務,然後通過以時間為順序的紅黑樹結構組合起來,vruntime 最小的在樹的左側,vruntime最多的在樹的右側。以CFS策略為例,則會選擇紅黑樹最左邊的葉子節點作為下一個將獲得 CPU 的任務。而這顆紅黑樹,我們稱之為運行時隊列(run queue),即struct rq。
其中包含結構體cfs_rq,其定義如下,主要是CFS調度相關的結構體,主要有權值相關變數、vruntime相關變數以及紅黑樹指針,其中結構體rb_root_cached即為紅黑樹的節點
對結構體dl_rq有類似的定義,運行隊列由紅黑樹結構體構成,並按照deadline策略進行管理
對於實施隊列相應的rt_rq則有所不同,並沒有用紅黑樹實現。
下面再看看調度類sched_class,該類以函數指針的形式定義了諸多隊列操作,如
調度類分為下面幾種:
隊列操作中函數指針指向不同策略隊列的實際執行函數函數,在linux/kernel/sched/目錄下,fair.c、idle.c、rt.c等文件對不同類型的策略實現了不同的函數,如fair.c中定義了
以選擇下一個任務為例,CFS對應的是pick_next_task_fair,而rt_rq對應的則是pick_next_task_rt,等等。
由此,我們來總結一下:
有了上述的基本策略和基本調度結構體,我們可以形成大致的骨架,下面就是需要核心的調度流程將其拼湊成一個整體,實現調度系統。調度分為兩種,主動調度和搶占式調度。
說到調用,逃不過核心函數schele()。其中sched_submit_work()函數完成當前任務的收尾工作,以避免出現如死鎖或者IO中斷等情況。之後首先禁止搶占式調度的發生,然後調用__schele()函數完成調度,之後重新打開搶占式調度,如果需要重新調度則會一直重復該過程,否則結束函數。
而__schele()函數則是實際的核心調度函數,該函數主要操作包括選取下一進程和進行上下文切換,而上下文切換又包括用戶態空間切換和內核態的切換。具體的解釋可以參照英文源碼注釋以及中文對各個步驟的注釋。
其中核心函數是獲取下一個任務的pick_next_task()以及上下文切換的context_switch(),下面詳細展開剖析。首先看看pick_next_task(),該函數會根據調度策略分類,調用該類對應的調度函數選擇下一個任務實體。根據前文分析我們知道,最終是在不同的紅黑樹上選擇最左節點作為下一個任務實體並返回。
下面來看看上下文切換。上下文切換主要干兩件事情,一是切換任務空間,也即虛擬內存;二是切換寄存器和 CPU 上下文。關於任務空間的切換放在內存部分的文章中詳細介紹,這里先按下不表,通過任務空間切換實際完成了用戶態的上下文切換工作。下面我們重點看一下內核態切換,即寄存器和CPU上下文的切換。
switch_to()就是寄存器和棧的切換,它調用到了 __switch_to_asm。這是一段匯編代碼,主要用於棧的切換, 其中32位使用esp作為棧頂指針,64位使用rsp,其他部分代碼一致。通過該段匯編代碼我們完成了棧頂指針的切換,並調用__switch_to完成最終TSS的切換。注意switch_to中其實是有三個變數,分別是prev, next, last,而實際在使用時,我們會對last也賦值為prev。這里的設計意圖需要結合一個例子來說明。假設有ABC三個任務,從A調度到B,B到C,最後C回到A,我們假設僅保存prev和next,則流程如下
最終調用__switch_to()函數。該函數中涉及到一個結構體TSS(Task State Segment),該結構體存放了所有的寄存器。另外還有一個特殊的寄存器TR(Task Register)會指向TSS,我們通過更改TR的值,會觸發硬體保存CPU所有寄存器在當前TSS,並從新的TSS讀取寄存器的值載入入CPU,從而完成一次硬中斷帶來的上下文切換工作。系統初始化的時候,會調用 cpu_init()給每一個 CPU 關聯一個 TSS,然後將 TR 指向這個 TSS,然後在操作系統的運行過程中,TR 就不切換了,永遠指向這個 TSS。當修改TR的值得時候,則為任務調度。
更多Linux內核視頻教程文本資料免費領取後台私信【 內核大禮包 】自行獲取。
在完成了switch_to()的內核態切換後,還有一個重要的函數finish_task_switch()負責善後清理工作。在前面介紹switch_to三個參數的時候我們已經說明了使用last的重要性。而這里為何讓prev和last均賦值為prev,是因為prev在後面沒有需要用到,所以節省了一個指針空間來存儲last。
至此,我們完成了內核態的切換工作,也完成了整個主動調度的過程。
搶占式調度通常發生在兩種情況下。一種是某任務執行時間過長,另一種是當某任務被喚醒的時候。首先看看任務執行時間過長的情況。
該情況需要衡量一個任務的執行時間長短,執行時間過長則發起搶占。在計算機裡面有一個時鍾,會過一段時間觸發一次時鍾中斷,通知操作系統時間又過去一個時鍾周期,通過這種方式可以查看是否是需要搶占的時間點。
時鍾中斷處理函數會調用scheler_tick()。該函數首先取出當前CPU,並由此獲取對應的運行隊列rq和當前任務curr。接著調用該任務的調度類sched_class對應的task_tick()函數進行時間事件處理。
以普通任務隊列為例,對應的調度類為fair_sched_class,對應的時鍾處理函數為task_tick_fair(),該函數會獲取當前的調度實體和運行隊列,並調用entity_tick()函數更新時間。
在entity_tick()中,首先會調用update_curr()更新當前任務的vruntime,然後調用check_preempt_tick()檢測現在是否可以發起搶占。
check_preempt_tick() 先是調用 sched_slice() 函數計算出一個調度周期中該任務運行的實際時間 ideal_runtime。sum_exec_runtime 指任務總共執行的實際時間,prev_sum_exec_runtime 指上次該進程被調度時已經佔用的實際時間,所以 sum_exec_runtime - prev_sum_exec_runtime 就是這次調度佔用實際時間。如果這個時間大於 ideal_runtime,則應該被搶佔了。除了這個條件之外,還會通過 __pick_first_entity 取出紅黑樹中最小的進程。如果當前進程的 vruntime 大於紅黑樹中最小的進程的 vruntime,且差值大於 ideal_runtime,也應該被搶佔了。
如果確認需要被搶占,則會調用resched_curr()函數,該函數會調用set_tsk_need_resched()標記該任務為_TIF_NEED_RESCHED,即該任務應該被搶占。
某些任務會因為中斷而喚醒,如當 I/O 到來的時候,I/O進程往往會被喚醒。在這種時候,如果被喚醒的任務優先順序高於 CPU 上的當前任務,就會觸發搶占。try_to_wake_up() 調用 ttwu_queue() 將這個喚醒的任務添加到隊列當中。ttwu_queue() 再調用 ttwu_do_activate() 激活這個任務。ttwu_do_activate() 調用 ttwu_do_wakeup()。這裡面調用了 check_preempt_curr() 檢查是否應該發生搶占。如果應該發生搶占,也不是直接踢走當前進程,而是將當前進程標記為應該被搶占。
由前面的分析,我們知道了不論是是當前任務執行時間過長還是新任務喚醒,我們均會對現在的任務標記位_TIF_NEED_RESCUED,下面分析實際搶占的發生。真正的搶占還需要一個特定的時機讓正在運行中的進程有機會調用一下 __schele()函數,發起真正的調度。
實際上會調用__schele()函數共有以下幾個時機
從系統調用返回用戶態:以64位為例,系統調用的鏈路為do_syscall_64->syscall_return_slowpath->prepare_exit_to_usermode->exit_to_usermode_loop。在exit_to_usermode_loop中,會檢測是否為_TIF_NEED_RESCHED,如果是則調用__schele()
內核態啟動:內核態的執行中,被搶占的時機一般發生在 preempt_enable() 中。在內核態的執行中,有的操作是不能被中斷的,所以在進行這些操作之前,總是先調用 preempt_disable() 關閉搶占,當再次打開的時候,就是一次內核態代碼被搶占的機會。preempt_enable() 會調用 preempt_count_dec_and_test(),判斷 preempt_count 和 TIF_NEED_RESCHED 是否可以被搶占。如果可以,就調用 preempt_schele->preempt_schele_common->__schele 進行調度。
本文分析了任務調度的策略、結構體以及整個調度流程,其中關於內存上下文切換的部分尚未詳細敘述,留待內存部分展開剖析。
1、調度相關結構體及函數實現
2、schele核心函數
⑹ 關於Linux下的select/epoll
select這個系統調用的原型如下
第一個參數nfds用來告訴內核 要掃描的socket fd的數量+1 ,select系統調用最大接收的數量是1024,但是如果每次都去掃描1024,實際上的數量並不多,則效率太低,這里可以指定需要掃描的數量。 最大數量為1024,如果需要修改這個數量,則需要重新編譯Linux內核源碼。
第2、3、4個參數分別是readfds、writefds、exceptfds,傳遞的參數應該是fd_set 類型的引用,內核會檢測每個socket的fd, 如果沒有讀事件,就將對應的fd從第二個參數傳入的fd_set中移除,如果沒有寫事件,就將對應的fd從第二個參數的fd_set中移除,如果沒有異常事件,就將對應的fd從第三個參數的fd_set中移除 。這里我們應該 要將實際的readfds、writefds、exceptfds拷貝一份副本傳進去,而不是傳入原引用,因為如果傳遞的是原引用,某些socket可能就已經丟失 。
最後一個參數是等待時間, 傳入0表示非阻塞,傳入>0表示等待一定時間,傳入NULL表示阻塞,直到等到某個socket就緒 。
FD_ZERO()這個函數將fd_set中的所有bit清0,一般用來進行初始化等。
FD_CLR()這個函數用來將bitmap(fd_set )中的某個bit清0,在客戶端異常退出時就會用到這個函數,將fd從fd_set中刪除。
FD_ISSET()用來判斷某個bit是否被置1了,也就是判斷某個fd是否在fd_set中。
FD_SET()這個函數用來將某個fd加入fd_set中,當客戶端新加入連接時就會使用到這個函數。
epoll_create系統調用用來創建epfd,會在開辟一塊內存空間(epoll的結構空間)。size為epoll上能關注的最大描述符數,不夠會進行擴展,size只要>0就行,早期的設計size是固定大小,但是現在size參數沒什麼用,會自動擴展。
返回值是epfd,如果為-1則說明創建epoll對象失敗 。
第一個參數epfd傳入的就是epoll_create返回的epfd。
第二個參數傳入對應操作的宏,包括 增刪改(EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD) 。
第三個參數傳入的是 需要增刪改的socket的fd 。
第四個參數傳入的是 需要操作的fd的哪些事件 ,具體的事件可以看後續。
返回值是一個int類型,如果為-1則說明操作失敗 。
第一個參數是epfd,也就是epoll_create的返回值。
第二個參數是一個epoll_event類型的指針,也就是傳入的是一個數組指針。 內核會將就緒的socket的事件拷貝到這個數組中,用戶可以根據這個數組拿到事件和消息等 。
第三個參數是maxevents,傳入的是 第二個參數的數組的容量 。
第四個參數是timeout, 如果設為-1一直阻塞直到有就緒數據為止,如果設為0立即返回,如果>0那麼阻塞一段時間 。
返回值是一個int類型,也就是就緒的socket的事件的數量(內核拷貝給用戶的events的元素的數量),通過這個數量可以進行遍歷處理每個事件 。
一般需要傳入 ev.data.fd 和 ev.events ,也就是fd和需要監控的fd的事件。事件如果需要傳入多個,可以通過按位與來連接,比如需要監控讀寫事件,只需要像如下這樣操作即可: ev.events=EPOLLIN | EPOLLOUT 。
LT(水平觸發), 默認 的工作模式, 事件就緒後用戶可以選擇處理和不處理,如果用戶不處理,內核會對這部分數據進行維護,那麼下次調用epoll_wait()時仍舊會打包出來 。
ET(邊緣觸發),事件就緒之後, 用戶必須進行處理 ,因為內核把事件打包出來之後就把對應的就緒事件給清掉了, 如果不處理那麼就緒事件就沒了 。ET可以減少epoll事件被重復觸發的次數,效率比LT高。
如果需要設置為邊緣觸發只需要設置事件為類似 ev.events=EPOLLIN | EPOLLET 即可 。
select/poll/epoll是nio多路復用技術, 傳統的bio無法實現C10K/C100K ,也就是無法滿足1w/10w的並發量,在這么高的並發量下,在進行上下文切換就很容易將伺服器的負載拉飛。
1.將fd_set從用戶態拷貝到內核態
2.根據fd_set掃描內存中的socket的fd的狀態,時間復雜度為O(n)
3.檢查fd_set,如果有已經就緒的socket,就給對應的socket的fd打標記,那麼就return 就緒socket的數量並喚醒當前線程,如果沒有就緒的socket就繼續阻塞當前線程直到有socket就緒才將當前線程喚醒。
4.如果想要獲取當前已經就緒的socket列表,則還需要進行一次系統調用,使用O(n)的時間去掃描socket的fd列表,將已經打上標記的socket的fd返回。
CPU在同一個時刻只能執行一個程序,通過RR時間片輪轉去切換執行各個程序。沒有被掛起的進程(線程)則在工作隊列中排隊等待CPU的執行,將進程(線程)從工作隊列中移除就是掛起,反映到java層面的就是線程的阻塞。
什麼是中斷?當我們使用鍵盤、滑鼠等IO設備的時候,會給主板一個電流信號,這個電流信號就給CPU一個中斷信號,CPU執行完當前的指令便會保存現場,然後執行鍵盤/滑鼠等設備的中斷程序,讓中斷程序獲取CPU的使用權,在中斷程序後又將現場恢復,繼續執行之前的進程。
如果第一次沒檢測到就緒的socket,就要將其進程(線程)從工作隊列中移除,並加入到socket的等待隊列中。
socket包含讀緩沖區+寫緩沖區+等待隊列(放線程或eventpoll對象)
當從客戶端往伺服器端發送數據時,使用TCP/IP協議將通過物理鏈路、網線發給伺服器的網卡設備,網卡的DMA設備將接收到的的數據寫入到內存中的一塊區域(網卡緩沖區),然後會給CPU發出一個中斷信號,CPU執行完當前指令則會保存現場,然後網卡的中斷程序就獲得了CPU的使用權,然後CPU便開始執行網卡的中斷程序,將內存中的緩存區中的數據包拿出,判斷埠號便可以判斷它是哪個socket的數據,將數據包寫入對應的socket的讀(輸入)緩沖區,去檢查對應的socket的等待隊列有沒有等待著的進程(線程),如果有就將該線程(進程)從socket的等待隊列中移除,將其加入工作隊列,這時候該進程(線程)就再次擁有了CPU的使用許可權,到這里中斷程序就結束了。
之後這個進程(線程)就執行select函數再次去檢查fd_set就能發現有socket緩沖區中有數據了,就將該socket的fd打標記,這個時候select函數就執行完了,這時候就會給上層返回一個int類型的數值,表示已經就緒的socket的數量或者是發生了錯誤。這個時候就再進行內核態到用戶態的切換,對已經打標記的socket的fd進行處理。
將原本1024bit長度的bitmap(fd_set)換成了數組的方式傳入 ,可以 解決原本1024個不夠用的情況 ,因為傳入的是數組,長度可以不止是1024了,因此socket數量可以更多,在Kernel底層會將數組轉換成鏈表。
在十多年前,linux2.6之前,不支持epoll,當時可能會選擇用Windows/Unix用作伺服器,而不會去選擇Linux,因為select/poll會隨著並發量的上升,性能變得越來越低,每次都得檢查所有的Socket列表。
1.select/poll每次調用都必須根據提供所有的socket集合,然後就 會涉及到將這個集合從用戶空間拷貝到內核空間,在這個過程中很耗費性能 。但是 其實每次的socket集合的變化也許並不大,也許就1-2個socket ,但是它會全部進行拷貝,全部進行遍歷一一判斷是否就緒。
2.select/poll的返回類型是int,只能代表當前的就緒的socket的數量/發生了錯誤, 如果還需要知道是哪些socket就緒了,則還需要再次使用系統調用去檢查哪些socket是就緒的,又是一次O(n)的操作,很耗費性能 。
1.epoll在Kernel內核中存儲了對應的數據結構(eventpoll)。我們可以 使用epoll_create()這個系統調用去創建一個eventpoll對象 ,並返回eventpoll的對象id(epfd),eventpoll對象主要包括三個部分:需要處理的正在監聽的socket_fd列表(紅黑樹結構)、socket就緒列表以及等待隊列(線程)。
2.我們可以使用epoll_ctl()這個系統調用對socket_fd列表進行CRUD操作,因為可能頻繁地進行CRUD,因此 socket_fd使用的是紅黑樹的結構 ,讓其效率能更高。epoll_ctl()傳遞的參數主要是epfd(eventpoll對象id)。
3.epoll_wait()這個系統調用默認會 將當前進程(線程)阻塞,加入到eventpoll對象的等待隊列中,直到socket就緒列表中有socket,才會將該進程(線程)重新加入工作隊列 ,並返回就緒隊列中的socket的數量。
socket包含讀緩沖區、寫緩沖區和等待隊列。當使用epoll_ctl()系統調用將socket新加入socket_fd列表時,就會將eventpoll對象引用加到socket的等待隊列中, 當網卡的中斷程序發現socket的等待隊列中不是一個進程(線程),而是一個eventpoll對象的引用,就將socket引用追加到eventpoll對象的就緒列表的尾部 。而eventpoll對象中的等待隊列存放的就是調用了epoll_wait()的進程(線程),網卡的中斷程序執行會將等待隊列中的進程(線程)重新加入工作隊列,讓其擁有佔用CPU執行的資格。epoll_wait()的返回值是int類型,返回的是就緒的socket的數量/發生錯誤,-1表示發生錯誤。
epoll的參數有傳入一個epoll_event的數組指針(作為輸出參數),在調用epoll_wait()返回的同時,Kernel內核還會將就緒的socket列表添加到epoll_event類型的數組當中。
⑺ 在linux操作系統內核實現里經常使用的紅黑樹
在linux操作系統內核實現里經常使用的紅黑樹如下:
二叉樹,按中序遍歷後為一遞增數組,自平衡意味著樹的高度有一個上限,對於紅黑樹,其為2log(n+1),所以時間復雜度為最差為Olog(n)。
賦予二叉搜索樹自平衡特性的方法有多種,紅黑樹通過一下4條約束實現自平衡:
Every node is either red or black.
All NIL nodes (figure 1) are considered black.
A red node does not have a red child.
Everypathfrom a given node to any of its descendant NIL nodes goes through the same number of black nodes.
其中根節點為黑色。
紅黑樹的搜索與二叉搜索樹無異,但是插入和刪除可能會違背上述四條原則。需要用到左旋右旋操作。左旋右旋上圖,可以看到左旋右旋本身不改變二叉搜索樹的特性,旋轉後必要時改變節點的顏色可消除插入或者刪除帶來的紅沖突和黑沖突,有時紅黑樹的重新平衡需要迭代進行。
紅黑樹比較適合的應用場景:
需要動態插入、刪除、查找的場景,包括但不限於:
某些資料庫的增刪改查,比如select * from xxx where 這類條件檢索。
linux內核中進程通過紅黑樹組織管理,便於快速插入、刪除、查找進程的task_struct。
linux內存中內存的管理:分配和回收。用紅黑樹組織已經分配的內存塊,當應用程序調用free釋放內存的時候,可以根據內存地址在紅黑樹中快速找到目標內存塊。
hashmap中(key,value)增、刪、改查的實現;java 8就採用了RBTree替代鏈表。
Ext3文件系統,通過紅黑樹組織目錄項。
⑻ linux如何打開進程式控制制塊
linux如何打開進程式控制制塊[linux內核]4.進程式控制制塊和進程創建纖盯_H4ppyD0g的博客-CSDN博客
進程式控制制塊PCB的定義位置在/linux3.18.6/include/linux/sched.h#1234, 起名為 struct task_struct 下圖為進程結構體示意圖 1.1 進程狀態信息 在PCB中的變數定義:volatile long state;操作系統...
CSDN技術社區2021-11-23
Linux PCB(進程式控制制塊)詳解_BadSleepC的博客-CSDN博客_linux pcb
可中斷的等待狀態 不可中斷的等待狀態 處於這種狀態的進程,要麼正在運行、要麼正准備運行。正在運行的進程就是當前進程(由current所指向的進程)...
CSDN技術社區2022-03-14
Linux下的進程式控制制塊(PCB)碼農教程
本文章向大家介紹Linux下的進程式控制制塊(PCB),主要包括Linux下的進程式控制制塊(PCB)使用實例、應用技巧、基本知識點總結和需要注意事項,具有一定的參考價值,需要的朋友可以參考一下。
碼農網2020-06-01
其他人還搜了
linux殺進程
linux查看進程命令
linux如何kill進程
linux進程間通信
linux進程式控制制塊pcb01
什麼叫進程式控制制塊
linux查看進程佔用內存
linux進程式控制制塊數據結構
2021-04-10 Linux系統下通過內核模塊顯示進程式控制制塊信息_ONEIROPLUS的博客-CSDN博客
Linux系統下通過內核模塊顯示進程式控制制塊信息 前言 本機為微軟 Surface pro4,為 64 位,所用操作系統為 Windos 10。本機虛擬機版本為 Oracle VM VirtualBox 6.1.8,所用操作系統是使用 Ubuntu18...
CSDN技術社區2021-04-10
【Linux】進李豎纖程式控制制(詳細解析)SouLinya的博客-CSDN博客
進程調用fork函數,當控制轉移到內核中的fork代碼後,內核做: 分配新的內存塊和內核數據結構(主要有:鏈表、隊列、映射和紅黑樹。給子進程 將父進程部分數據結構內容拷貝至子進程 將子進程...
CSDN技術社區2022-12-10
Linux進程式控制制-碼農教程
在Linux中,每個進程在創建時都會被分配一個數據結構,稱為進程式控制制塊(PCB,Process Control Block),描述進程的運動變化過程,與進程是一一對應的關系。通常PCB包含以下信息: 進程標識符:每個...
碼農網2022-07-22
linux進程式控制制與管理命令_進程式控制制塊的內容-Java架構師必看
進程調用fork,當控制轉移到內核中的fork代碼後,內核做: 分配新的內存塊和內核數據結構給子進程。將父進程部分數據結構內容拷貝至子進程。添加子進程到系統進程列表當中。fork返回,開始調度...
Java架構師必看2022-08-08
Linux系哪仿統中的進程式控制制塊.doc-淘豆網
在linux 中每一個進程都由task_struct 數據結構來定義.task_struct就是我們通常所說的PCB.她是對進程式控制制的唯一手段也是最有效的手段.當我們調用fork()時,系統會為我們產生一個task_struct結構...
淘豆網
linux系統中的進程式控制制塊.doc-螞蟻文庫
在LINUX中每一個進程都由TASK_STRUCT數據結構來定義TASK_STRUCT就是我們通常所說的PCB她是對進程式控制制的唯一手段也是最有效的手段當我們調用FORK時,系統會為我們產生一個TASK_STRUCT結構。然後從...
螞蟻文庫
Linux下進程描述(1)—進程式控制制塊【轉】阿里雲開發者社區
每個進程在內核中都有⼀個進程式控制制塊(PCB)來維護進程相關的信息,Linux內核的 進程式控制制塊是task_struct結構體。現在我們全⾯了解⼀下其中都有哪些信息。在Linux中,這個結構叫做task_struct。...
阿里雲開發者社區2017-12-27
相關搜索
linux查詢進程
linux殺死進程命令
什麼是進程式控制制塊
linux進程式控制制塊pcb02
linux進程式控制制塊的結構體
linux中新程序如何開啟進程
linux如何殺死進程
linux殺死進程
linux如何關閉進程
linux系統查看所有進程命令
⑼ 如何利用linux內核中的紅黑樹庫,調試和運行紅黑樹
1、 初識紅黑樹
從網上搜索了許多仔模紅黑樹的介紹,這些文章中主要介紹了紅黑樹的性質,念碧緩然後就是紅黑樹的旋轉如下示意圖。
左旋、右旋,旋轉過程中爸爸變成了兒子,兄弟變成了孫子;紅的變成黑的,黑的變成紅的。經過一系列的旋轉,就把我慧裂旋轉的暈頭轉向了,腦子里攪成了一團漿糊。相信,沒有學過二叉樹的同學肯定會遇到和我一樣窘況。
⑽ Linux進程的調度
上回書說到 Linux進程的由來 和 Linux進程的創建 ,其實在同一時刻只能支持有限個進程或線程同時運行(這取決於CPU核數量,基本上一個進程對應一個CPU),在一個運行的操作系統上可能運行著很多進程,如果運行的進程占據CPU的時間很長,就有可能導致其他進程餓死。為了解決這種問題,操作系統引入了 進程調度器 來進行進程的切換,輪流讓各個進程使用CPU資源。
1)rq: 進程的運行隊列( runqueue), 每個CPU對應一個 ,包含自旋鎖(spinlock)、進程數量、用於公平調度的CFS信息結構、當前運行的進程描述符等。實際的進程隊列用紅黑樹來維護(通過CFS信息結構來訪問)。
2)cfs_rq: cfs調度的進程運行隊列信息 ,包含紅黑樹的根結點、正在運行的進程指針、用於負載均衡的葉子隊列等。
3)sched_entity: 把需要調度的東西抽象成調度實體 ,調度實體可以是進程、進程組、用戶等。這里包含負載權重值、對應紅黑樹結點、 虛擬運行時vruntime 等。
4)sched_class:把 調度策略(演算法)抽象成調度類 ,包含一組通用的調度操作介面。介面和實現是分離,可以根據調度介面去實現不同的調度演算法,使一個Linux調度程序可以有多個不同的調度策略。
1) 關閉內核搶占 ,初始化部分變數。獲取當前CPU的ID號,並賦值給局部變數CPU, 使rq指向CPU對應的運行隊列 。 標識當前CPU發生任務切換 ,通知RCU更新狀態,如果當前CPU處於rcu_read_lock狀態,當前進程將會放入rnp-> blkd_tasks阻塞隊列,並呈現在rnp-> gp_tasks鏈表中。 關閉本地中斷 ,獲取所要保護的運行隊列的自旋鎖, 為查找可運行進程做准備 。
2) 檢查prev的狀態,更新運行隊列 。如果不是可運行狀態,而且在內核態沒被搶占,應該從運行隊列中 刪除prev進程 。如果是非阻塞掛起信號,而且狀態為TASK_INTER-RUPTIBLE,就把該進程的狀態設置為TASK_RUNNING,並將它 插入到運行隊列 。
3)task_on_rq_queued(prev) 將pre進程插入到運行隊列的隊尾。
4)pick_next_task 選取將要執行的next進程。
5)context_switch(rq, prev, next)進行 進程上下文切換 。
1) 該進程分配的CPU時間片用完。
2) 該進程主動放棄CPU(例如IO操作)。
3) 某一進程搶佔CPU獲得執行機會。
Linux並沒有使用x86 CPU自帶的任務切換機制,需要通過手工的方式實現了切換。
進程創建後在內核的數據結構為task_struct , 該結構中有掩碼屬性cpus_allowed,4個核的CPU可以有4位掩碼,如果CPU開啟超線程,有一個8位掩碼,進程可以運行在掩碼位設置為1的CPU上。
Linux內核API提供了兩個系統調用 ,讓用戶可以修改和查看當前的掩碼:
1) sched_setaffinity():用來修改位掩碼。
2) sched_getaffinity():用來查看當前的位掩碼。
在下次task被喚醒時,select_task_rq_fair根據cpu_allowed里的掩碼來確定將其置於哪個CPU的運行隊列,一個進程在某一時刻只能存在於一個CPU的運行隊列里。
在Nginx中,使用了CPU親和度來完成某些場景的工作:
worker_processes 4;
worker_cpu_affinity 0001001001001000;
上面這個配置說明了4個工作進程中的每一個和一個CPU核掛鉤。如果這個內容寫入Nginx的配置文件中,然後Nginx啟動或者重新載入配置的時候,若worker_process是4,就會啟用4個worker,然後把worker_cpu_affinity後面的4個值當作4個cpu affinity mask,分別調用ngx_setaffinity,然後就把4個worker進程分別綁定到CPU0~3上。
worker_processes 2;
worker_cpu_affinity 01011010;
上面這個配置則說明了兩個工作進程中的每一個和2個核掛鉤。