在 Linux 中、一切都是文件、硬件設備是文件、管道是文件、網絡套接字也是文件。
for
https://juejin.cn/post/6844904103437582344
fork 進程的一些問題
fork 函數比較特殊、一次調用會返回兩次。在父進程和子進程都會返回。
每個進程在內核中都是一個 taskstruct 結構、fork 的時候、內核把內核中原來的 task_struct 拷貝了一份
還創建了一個全新的地址空間和堆棧
進程的地址空間沒有拷貝,子進程和父進程是在共享內存空間的。都是映射到同一個物理內存頁上的
內核把這些頁面都設置成只讀、如果你們是讀的話、不會有問題、但是有一方嘗試寫入、就會觸發異常。內核發現異常后再分配一個新的頁面讓你們分開使用。這個就是寫時復制。
線程的棧
線程的棧默認是 8M、實際上一開始分配給棧并不是那么大的空間。會按需自動增大。直到大于 8M 就會棧溢出。
進程間如何通信
信號
信號時 Linux 上的一種軟中斷通信機制、可以向制定進程發送通知。總共有 64 種信號、不過信號只能作為通知使用。沒辦法傳輸數據。
socket 套接字
網絡通信使用。可以將連接的地址設置為 127.0.0.1 作為本地計算機通信。這樣子的話、不需要經過網卡、因為 127.0.0.1 是本地回環地址,數據在協議棧就轉發。 但是抓包可以抓到、因為在虛擬的回環網卡lo上抓到。
匿名管道
單向。滿了會阻塞
需要有親緣關系的進程繼承指揮才能通信。
命名管道
只要指明管道名稱就能通信。不需要進程間有關系。
消息隊列
共享內存
IO 多路復用
自己寫了一個 web 服務進程。監聽某個端口、如何處理客戶端的連接請求。(單線程 while 去接受處理客戶端連接請求)
當客戶連接成功之后、但是一直不傳輸數據、線程會一直卡在那里、并且沒法再處理其他客戶的連接請求了
多線程去應對
每一個連接過來都去創建一個新的線程去處理。
select 模型
越來越多的連接過來了、線程越來越多、終于頂唔住了。。。。
I/O 多路復用
有個叫 select 的函數、你把文件描述符批量傳給他,平時他阻塞在那里、只要其中有一個有消息來、它就會返回,你這個時候去檢查睡來消息,并去處理就行了。
select 函數是怎么做到的?
它會遍歷所有的文件描述符、把你掛入與這些文件描述符相關聯的設備等待隊列中、如何交出執行權進入阻塞,等后面這些設備來消息、然后通過回調函數通知你。把你喚醒。
但是 select 函數底層時使用位圖數組來存儲要管理的文件描述符的、容量有上線。最多只能同時處理 1024 個文件描述符。
poll
但是 poll 和 select 一樣。慢!!
為啥慢?
1、 每次返回后不告訴程序到底是哪一個文件描述符有消息、需要程序一個個遍歷。耽擱了不少時間
2、每次調用他們的時候、都要把所有的文件描述符從用戶態地址空間拷貝到內核中,這樣子經常拷貝也費時間。
epoll
epoll 這個多路復用模型、不需要每次拷貝全部的數據、只需要增減就行。因為它內部采用紅黑樹來管理監聽的文件描述符,所以查找起來很快。而且它內部還有一個隊列、所有就緒的文件描述符都會進入中國隊列。程序不再需要遍歷所有的文件描述符去找來消息的那個了。
像訪問內存一樣讀寫文件
因為磁盤太慢了、所以在內核空間中給每一個要讀取的文件建立一個數據結構。里面記錄了已經緩存的文件數據塊信息。從硬盤讀過來的數據就緩存到內存。并記錄到這個數據結構中。
以后讀取文件的時候、先通過這個數據結構去查詢、查到就直接拷貝給應用、查不到才去找磁盤要。
CPU的局部性原理在這里也適用。
在寫文件的時候是先寫到這個緩存里面、并不會立即同步刷到磁盤的、這時候突然斷電、緩存的數據就會丟掉了。
sync 函數、只要你調用它、就會馬上進行同步、寫入硬盤。
內存映射文件
讀取文件時需要進行兩次拷貝、第一次從磁盤拷貝到內核的緩存頁中、第二次把它從緩存中拷貝到應用程序的緩沖區中。
寫的時候也是同理。
把文件的數據緩存頁映射到用戶態地址空間、這樣用戶態地址空間的緩沖區和緩存頁就能映射到同一個物理內存頁。
再進一步的話就是
在進程的地址空間劃分一塊區域和文件內容簡歷映射關系、等到應用程序訪問這部分區域的時候、會發生缺頁中斷錯誤、這時我們把數據從硬盤讀取到緩存頁中、再把緩存頁和進程中缺頁中斷的頁面關聯起來。
這樣子對應用無感。不用再使用 read、write、fseek 這樣麻煩地讀寫文件。
這個 api 叫 mmap 內存映射文件。
協程
A:Java 線程執行阻塞函數時被操作系統掛起、切換到別的線程。
B:線程切換是否需要成本?如果大量線程頻繁切換、成本又當如何?
A:如果擔心這個問題、那就不用阻塞函數、通過異步回調進行。
B:異步回調確實不用阻塞、不過它有兩點不好、其一就是割裂了原來的業務代碼、其二就是回調地獄。
協程
線程可以在執行函數遇到阻塞后,保存執行的上下文、轉而執行別處到代碼。待阻塞請求完成后、再回去繼續執行。
線程是有操作系統統一調度管理的。那么在一個線程中、同樣可以抽象出多個執行流、由線程來統一調度管理、這線程智商抽象的執行流就是協程。
線程時操作系統在調度管理、那線程里抽出來的執行流、也就是協程、該怎么調度管理?
OS 通過時鐘中斷和系統調用進入內核來剝奪線程的執行權、那線程如何剝奪協程的執行權來實現調度管理?
協程:協作式程序。它會主動交出執行權。
Java19 已經支持了虛擬線程(協程)、或者使用第三方協程框架 Quasar
https://zhuanlan.zhihu.com/p/425978232
https://book.douban.com/subject/36428782/