一次Linux C++面試問題筆記
子程序會繼承父程序哪些資料
fork()會產生一個和父程序完全相同的子程序,但子程序在此後多會exec系統呼叫,出於效率考慮,linux中引入了“寫時複製“技術,只有程序空間的各段的內容要發生變化時,才會將父程序的內容複製一份給子程序。
在fork之後exec之前兩個程序用的是相同的物理空間(記憶體區),子程序的程式碼段、資料段、堆疊都是指向父程序的物理空間,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。
1、如果不exec,核心會給子程序的資料段、堆疊段分配相應的物理空間(至此兩者有各自的程序空間,互不影響),而程式碼段繼續共享父程序的物理空間(兩者的程式碼完全相同)。
2、如果exec,由於兩者執行的程式碼不同,子程序的程式碼段也會分配單獨的物理空間。
每個程序都有自己的虛擬地址空間,不同程序的相同的虛擬地址顯然可以對應不同的實體地址。因此地址相同(虛擬地址)而值不同沒什麼奇怪。
例如父程序malloc的指標指向0x12345678, fork 後,子程序中的指標也是指向0x12345678,但是這兩個地址都是虛擬記憶體地址 (virtual memory),經過記憶體地址轉換後所對應的 實體地址是不一樣的。所以兩個進城中的這兩個地址相互之間沒有任何關係。
linux為了提高 fork 的效率,採用了 copy-on-write 技術,fork後,這兩個虛擬地址實際上指向相同的實體地址(記憶體頁),只有任何一個程序試圖修改這個虛擬地址裡的內容前,兩個虛擬地址才會指向不同的實體地址(新的實體地址的內容從原實體地址中複製得到)
c++容器底層的資料結構
Vector
:其底層資料結構是陣列,新建的Vector會分配一片連續的記憶體空間.
map與multimap
: 這兩個關聯容器的底層資料結構均為紅黑樹,map與multimapdoi是STL中的關聯容器、提供一對一key-value的資料處理能力; map與multimap的區別在於,multimap允許關鍵字重複,而map不允許重複。
set與multiset
set與multiset有序儲存元素,這兩種容器的底層實現與map一樣都是紅黑樹,所以能實現O(lgn)的查詢,插入,刪除操作。
set與multiset的區別在於是否允許重複;
priority_queue
若設二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層所有的結點都連續集中在最左邊,這就是完全二叉樹。
堆又分大小堆:就是保證根節點是所有資料中最大/小,並且盡力讓大/小的節點在上方,優先佇列即是這種結構.
list
底層資料結構為雙向連結串列,特點是支援快速的增刪。
queue
單向佇列,為先入先出原則。
deque
雙向佇列,其對比queue可以實現在頭尾兩端高效的插入和刪除操作。
select、poll、epoll的區別
epoll沒用過,還真不知道他們有什麼區別。只知道poll相比select沒有檔案描述符的限制。
select/poll
採用輪詢的方式掃描檔案描述符,檔案描述符數量越多,效能越差;單個程序能夠監視的檔案描述符的數量存在最大限制,通常是1024,(在linux核心標頭檔案中,有這樣的定義:#define __FD_SETSIZE 1024),當然可以更改數量,
核心 / 使用者空間記憶體拷貝問題,select需要複製大量的控制代碼資料結構,產生巨大的開銷;
select返回的是含有整個控制代碼的陣列,應用程式需要遍歷整個陣列才能發現哪些控制代碼發生了事件;
select的觸發方式是水平觸發,應用程式如果沒有完成對一個已經就緒的檔案描述符進行IO操作,那麼之後每次select呼叫還是會將這些檔案描述符通知程序。
相比select模型,poll使用連結串列儲存檔案描述符,因此沒有了監視檔案數量的限制,但其他三個缺點依然存在。
拿select模型為例,假設我們的伺服器需要支援100萬的併發連線,則在__FD_SETSIZE 為1024的情況下,則我們至少需要開闢1k個程序才能實現100萬的併發連線。除了程序間上下文切換的時間消耗外,從核心/使用者空間大量的無腦記憶體拷貝、陣列輪詢等,是系統難以承受的。因此,基於select模型的伺服器程式,要達到10萬級別的併發訪問,是一個很難完成的任務。
epoll
由於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萬個連線的控制代碼資料,核心也不需要去遍歷全部的連線。
參考資料 :深度理解select、poll和epoll
Makefile如何輸出除錯資訊
1、輸出列印資訊的方法是:
(warning xxxxx),$(error xxxxx)
例如 $(info, “here add the debug info”).
2、輸出列印變數值的方法是:$(warning $(XXX))
例如 $(info, $(ROOT_DIR) )