• 圍繞HugeTLB的極致優化

    作者簡介 宋牧春,字節跳動系統技術與工程團隊,Linux內核工程師。 內容簡介 介紹以一種創新的方式優化HugeTLB對應的struct page內存佔用。 相信大家對HugeTLB在虛擬化及DPDK等場景應用並不陌生,在動不動就上百GB的服務器上,輕輕鬆鬆預留上百GB HugeTLB。相信不少雲hotbuyhk也注意到HugeTLB的內存管理上存在一定的問題。既然有問題,為何upstream上遲遲看不到相關的優化patch呢? 答案很簡單:問題棘手。 Linux在內存管理方面已經發展了十幾年,即使某些機制不夠優秀,想大改也不是簡單的事情。內存管理貫徹整個Linux內核,與眾多子系統交互。究竟Linux在HugeTLB的管理上存在什麼問題呢? 如何管理物理內存 現在Linux Kernel主要以頁為單位管理內存,而頁的大小默認4 KB。為了方便管理物理內存,Linux為每個頁分配一個metadata結構體,即struct page結構,其大小通常64 Bytes。struct page可以簡單理解成一個數組,數組的index就是PFN(物理頁幀號)。我稱這段區域vmemmap。 4KB頁我們稱之為小頁,與之相反的是大頁。在x86-64平台,硬件支持2 MB和1 GB大頁。Linux為了方便用户使用大頁,提供2種不同的機制,分別是THP (Transparent Huge Page) 和 HugeTLB。HugeTLB經常出現在我們的工程實踐中,HugeTLB為我們為我們帶來不錯的性能提升。 但是也有一朵烏雲常伴其身。雖然2 MB的HugeTLB page理論上也只需要1個struct page結構,但是,在系統啓動之初,所有的物理內存均以4 KB為單位分配struct page結構。因此每個 HugeTLB page對應 512個struct page結構,佔用內存32 KB(摺合8個4 KB小頁)。 可能你會好奇這能有多少內存。針對嵌入式系統,確實不值一提。但是別忘了,我們有動不動就2 TB物理內存的服務器。 現在我們可以簡單的算一筆賬了。假設在一台1 TB的服務器上,我們分配1 TB的2 MB大頁(理想情況下),那麼struct page本身佔用的內存是多少呢?沒錯,是16 GB。如果有上千台,上萬台,甚至上十萬台機器呢?如果我們能夠優化掉16 GB的內存浪費或者儘可能的降低struct page的內存佔用,我們將會降低服務器平台成本。我們的目標就是儘量驅散這朵烏雲。 面臨的挑戰 我們試圖找到一種最簡單並且對其他模塊影響最小的設計方案,在這過程中我們遇到不少挑戰。 1. 不需要用户適配 理想情況下,我們的優化不應該涉及用户態的適配。如果引入一種全新的內存管理方式,所有的用户需要適配。我們的目標是開箱即用。 2. 不影響內核其他模塊功能 在確定不需要用户適配的前提下,我們預期所有的代碼修改只會集中於內核。我們知道內存管理的幾乎全是圍繞着struct page管理,各個不同子系統的模塊幾乎都和struct page息息相關。暴力的釋放所有的HugeTLB相關的struct page結構體是不合適的,否則將會影響內核各個內存子系統。既要釋放,但又不能釋放。這恐怕是最棘手和矛盾的問題了。 3. 代碼修改最小化 代碼量間接的決定了bug的數量。內存管理子系統修改代碼過多,勢必影響內核的穩定性。我們既要實現功能,又要以最少的代碼量實現。這不但可以降低bug出現的概率,同時也易於維護和理解。 初次探索 一種最簡單直接的方法浮出水面。那就是動態分配和釋放struct page。 HugeTLB的使用方法一般是先預留後使用。並且struct page只會被內核代碼訪問,我們傾向內核訪問struct page的概率較低。因此我們第一次提出的方案是壓縮解壓縮的方法。 我們知道HugeTLB對應的512個struct page對應的信息可以壓縮到 100 個字節左右,因此我們可以為每個HugeTLB準備一個全新的metadata結構體,然後將所有的信息壓縮到新的metadata結構體。並且將struct page區域對應的頁表的present清除,然後就可以將其對應的物理頁釋放。是不是和zram機制如出一轍? 內核在下次訪問HugeTLB的struct page的時候觸發page fault,在fault裏面分配struct page需要的物理頁,並解壓縮(從新的metadata結構體恢復數據)。 當內核使用完成後,會執行put_page操作。我們在put_page裏面做壓縮操作,並釋放vmemmap對應的物理頁。思路很直接,但是這裏面存在很多挑戰。 1. page fault裏面無法分配怎麼辦(例如:OOM)? 2. page fault可能發生在任何上下文,用GFP_NOWAIT分配內存?這隻會加重第一個問題。 3. 如果某一持有A鎖的路徑觸發page fault,page fault裏面也嘗試持有A鎖怎麼樣?只會死鎖。所以page fault的操作需要格外小心。 4. 壓縮和解壓縮操作如何做到原子?或者説壓縮操作如何和解壓縮操作互斥同步? 5. 每次put_page都需要壓縮操作,性能影響如何? 6. 如果某些內核路徑並沒有get操作訪問struct page(自然也不會put),壓縮的時機會是什麼時候? 我們列出了很多問題,但就第一個問題來説就很難解決。這不得不讓我們放棄了這個想法。我們只能另尋他路。換個思路或許柳暗花明。 另闢蹊徑 俗話説“知己知彼百戰不殆”。我們先詳細瞭解struct page是如何組織和管理的,清楚每一處細節,才可能運籌帷幄。 我們上面提到每個HugeTLB page對應512個struct page結構,而HugeTLB只使用前3個struct page結構存儲大頁相關的metadata。那麼其餘509個struct page是否完全沒有意義呢?如果沒有意義我們是不是就可以直接釋放這些內存。 然而事情並沒有那麼簡單。這些509個struct page會存儲第一個struct page的地址(struct page中compound_head字段)。如果第一個struct page稱之為head page的話,那麼其餘的struct page都是tail page。在Linux內核的內存管理的代碼中充斥着大量的代碼,這些代碼都可能試圖從tail page獲取head page。所以我們並不能單純的釋放這些內存。 上圖展示的3個struct page的結構體示意圖(第3個tail page至第511個struct page結構體使用的位域同圖中2nd tail page)。我們可以總結出以下特點: 1. struct page結構體的大小在大多數情況下是64字節,因此每個4 KB的物理頁可以存儲整數個數的struct page結構體。 2. 第2個tail page至第511個struct page結構體的內容完全一樣。 3. 內存管理的代碼中只會修改head page,1st tail page的2nd tail page的結構體,其餘的tail page結構體內存不會修改。 4. 每個2MB HugeTLB page對應512個struct page,內存佔用8個頁(4KB * 8)。 5. struct page所在的vmemmap區域和內核的線性映射地址不重合。 基於以上特點,我們可以提出全新的解決方案:共享映射,將HugeTLB對應的後7個頁的vmemmap虛擬地址映射到第1個vmemmap頁對應的物理頁幀。第1-2點是共享映射方案的基礎。基於第3點我們可以將這7個物理頁釋放,交給buddy系統管理。而第5點是buddy能夠管理這塊物理內存的基礎。內核通過線性地址訪問物理內存,所以這個地址不能和vmemmap共用。其原理如下圖所示。基於第3點,我們將共享映射屬性改成只讀,防止出現異常情況。 內存收益 經過上面的優化,我們成功的降低了服務器平台成本,並且收益不錯。針對1 GB和2 MB不同size的HugeTLB page,內存收益也同樣不同。簡單歸納表格如下: Total Size of HugeTLB Page HugeTLB Type Memory Gain 512 GB 1 GB ~8 GB 1024 GB 1 GB ~16 GB 512 GB 2 MB ~7 GB 1024 GB 2 MB ~14 GB 如果使用1 GB HugeTLB,內存收益約為HugeTLB總量的1.6%。如果使用2 MB HugeTLB,內存收益約為HugeTLB總量的1.4%。 因此,在我們1台1 TB內存的服務器上,如果使用1 GB大頁,struct page內存佔用優化提升接近100%。如果使用2MB大頁,struct page內存佔用優化提升約87.5%。 性能分析 我們知道vmemmap區域映射的單位是2 MB。但是我們需要以4 KB頁為單位修改頁表,因此必須修改vmemmap區域為小頁映射。這相當於在內核訪問vmemmap區域時,MMU會多訪問一級 PTE 頁表。但是有TLB的存在,所以查找的性能損失並不大。 但是我們同樣也有性能提升的地方,由於我們減少了vmemmap對應的物理頁。理論上來説,我們更容易命中cache。實際上也確實這樣,經過upstream的測試數據顯示,對HugeTLB page進行get_user_page操作性能可以提升接近 4 倍。 開源計劃 為了降低代碼review的難度,我們決定將全部patch拆分成3筆patchset。目前第一步基礎功能已經合入linux-next分支(代碼參考: [v23,0/9] Free some vmemmap pages of HugeTLB page,點擊文末左下角閲讀原文可達),不出意外的話,預計Linux 5.14會和大家見面。 後續我們繼續放出接下來的patchset。那麼接下來有哪些功能呢? 首先第一個功能是釋放7個page。什麼?這不是上面已經説的功能嗎?是的,但是我們的第一個patchset只釋放了6個page。所以在上面的patchset中,我們建立的映射關係其實如下圖所示。這才是最簡單的情況。因為我們head page和tail page的結構體內容其實是不一樣的,如果要實現上面的圖的映射關係,必然要有一些trick才行。另一組patchset是拆分vmemmap頁表。第一組patchset的實現並不包含拆分vmemmap頁表,而是系統啓動時使vmemmap頁表以PTE方式建立映射,而非PMD映射。

    Linux閲碼場 HugeTLB 內存

  • Linux內核頁表管理-那些鮮為人知的祕密

    1.開場白 環境: 處理器架構:arm64 內核源碼:linux-5.11 ubuntu版本:20.04.1 代碼閲讀工具:vim ctags cscope 通用操作系統,通常都會開啓mmu來支持虛擬內存管理,而頁表管理是在虛擬內存管理中尤為重要,本文主要以回答幾個頁表管理中關鍵性問題來解析Linux內核頁表管理,看一看頁表管理中那些鮮為人知的祕密。 2.頁表的作用是什麼? 1)地址轉換 將虛擬地址轉換為物理地址 2)權限管理 管理cpu對物理頁的訪問,如讀寫執行權限 3)隔離地址空間 隔離各個進程的地址空間,使其互不影響,提供系統的安全性 打開mmu後,對沒有頁表映射的虛擬內存訪問或者有頁表映射但是沒有訪問權限都會發生處理器異常,內核選擇殺死進程或者panic;通過頁表給一段內存設置用户態不可訪問, 這樣可以做到用户態的用户進程不能訪問內核地址空間的內容;而由於用户進程各有一套自己的頁表,所以彼此看不到對方的地址空間,更別提訪問,造成每個進程都認為自己擁有所有虛擬內存的錯覺;通過頁表給一段內存設置只讀屬性,那麼就不容許修改這段內存內容,從而保護了這段內存不被改寫;對應用户進程地址空間映射的物理內存,內核可以很方便的進行頁面遷移和頁面交換,而對使用虛擬地址的用户進程來説是透明的;通過頁表,很容易實現內存共享,使得一份共享庫很多進程都可以映射到自己地址空間使用;通過頁表,可以小內存加載大應用程序運行,在運行時按需加載和映射... 3.頁表的存放在哪? 頁表存放在物理內存中,打開mmu之後,如果需要修改頁表,需要將頁表所在的物理地址映射到虛擬地址才能訪問頁表(如內核初始化後會將物理內存線性映射,這樣通過物理地址和虛擬地址的偏移就可以獲得頁表物理地址對應的虛擬地址)。 4. 頁表項中存放是虛是實? 頁表基地址寄存器和各級頁表項中存放的都是物理地址,而不是虛擬地址。 5. 開啓mmu後地址轉換過程? 虛擬地址轉換物理地址的過程:打開mmu後,cpu訪問的都是虛擬地址,當cpu訪問一個虛擬地址的時候,會通過cpu內部的mmu來查詢物理地址,mmu首先通過虛擬地址在tlb中查找,如果找到相應表項,直接獲得物理地址;如果tlb沒有找到,就會通過虛擬地址從頁表基地址寄存器保存的頁表基地址開始查詢多級頁表,最終查詢到找到相應表項,會將表項緩存到tlb中,然後從表項中獲得物理地址。 6. Linux內核為何使用多級頁表? 1)使用一級頁表結構優劣: 優勢: 只需要2次訪問內存(一次訪問頁表,一次訪問數據),效率高,實現簡單 劣勢: 需要連續的大塊內存存放每個進程的頁表(如32位系統每個進程需要4M頁表),浪費內存,虛擬內存越大頁表越大,內存碎片化的時候很難分配到連續大塊內存,大多數虛擬內存並沒有使用。 2)使用多級頁表結構優劣: 優勢: 1.節省內存 2.可以按需分配各級頁表 3.可以離散存儲頁表 劣勢: 需要遍歷多級頁表,需要多次訪問內存,實現複雜度高點 3)Linux內核綜合考慮: 典型的以時間換空間,可以將各級頁表放到物理內存的任何地方,無論是硬件遍歷還是內核遍歷,比一級頁表更復雜,但是為了節省內存,內核選擇多級頁表結構。 7.減小多級頁表遍歷的優化? 1)mmu中添加tlb 來緩存最近訪問的頁表表項,根據程序的時間和空間的局部性原理,tlb能有很高的命中率。 2)使用巨型頁 減少訪存次數(如使用1G或2M巨型頁),可以減少tlb miss和缺頁異常。 8. 硬件做了哪些事情? 遍歷頁表,將va轉換為pa,頁面權限管理 涉及到的硬件為: mmu ->功能:查詢tlb或者遍歷頁表 tlb ->功能:緩存最近轉換的頁表條目 頁表基地址寄存器 如ttbr0_el1  ttbr1_el1 ->功能:存放頁表基地址(物理地址)作為mmu遍歷多級頁表的起點 mmu進行多級頁表遍歷時當發現虛擬地址的最高bit為1時使用 ttbr1_el1作為遍歷起點,最高bit為0時使用 ttbr0_el1作為遍歷起點。 9. 軟件做了哪些事情? 1)應用程序 訪問虛擬內存即可如執行指令、讀寫內存, 沒有權限管理頁表 不管虛擬內存如何轉換為物理內存,對應用來説透明。 2)Linux內核 填寫頁表,將頁表基地址告訴mmu 內核初始化建立內核頁表,實現缺頁異常等機制為用户任務按需分配並映射頁表。 當然,內核也可以遍歷頁表,如缺頁異常時遍歷進程頁表。 10. 內核中涉及到的頁表基地址? 內核: idmap_pg_dir    恆等映射頁表(va=pa 映射2M) init_pg_dir        粗粒度內核頁表 swapper_pg_dir   主內核頁表 用户: tsk->mm->pgd 用户進程fork的時候分配私有的pgd頁,用於保存pgd表項(僅僅分配了第一級頁表)。 11. 頁表填寫/切換時機 1)內核頁表填充 內核初始化過程: 物理地址 -> 恆等映射(建立恆等映射頁表和粗粒度內核頁表) ->打開mmu ->  paging_init(建立細粒度的內核頁表和內存線性映射)  -> ... 恆等映射階段: 將恆等映射頁表idmap_pg_dir  地址保存到ttbr0_el1 將 粗粒度內核頁表init_pg_dir 地址保存到ttbr1_el1 paging_init階段: 將內核主頁表swapper_pg_dir 地址保存到ttbr1_el1 paging_init之後丟棄idmap_pg_dir 和init_pg_dir 頁表的使用。 2)用户頁表填充 訪問時缺頁填充: 用户進程訪問已經申請的虛擬內存時,發生缺頁,缺頁處理程序中為進程分配各級頁表等物理頁並建立頁表映射關係。 進程切換時切換進程頁表: switch_mm的時候切換tsk->mm->pgd到ttbr0_el1以及asid 到ttbr1_el1,從而完成了進程地址空間切換。 12.頁表遍歷過程 下面以arm64處理器架構多級頁表遍歷作為結束(使用4級頁表,頁大小為4K): Linux內核中 可以將頁表擴展到5級,分別是頁全局目錄(Page Global Directory, PGD),  頁4級目錄(Page 4th Directory, P4D),   頁上級目錄(Page Upper Directory, PUD),頁中間目錄(Page Middle Directory, PMD),直接頁表(Page Table, PT),而支持arm64的linux使用4級頁表結構分別是 pgd,  pud, pmd, pt ,arm64手冊中將他們分別叫做L0,L1,L2,L3級轉換表,所以一下使用L0-L3表示各級頁表。 tlb miss時,mmu會進行多級頁表遍歷遍歷過程如下: 1.mmu根據虛擬地址的最高位判斷使用哪個頁表基地址寄存器作為起點:當最高位為0時,使用ttbr0_el1作為起點(訪問的是用户空間地址);當最高位為1時,使用ttbr1_el1作為起點(訪問的是內核空間地址) mmu從相應的頁表基地址寄存器中獲得L0轉換表基地址。 2.找到L0級轉換表,然後從虛擬地址中獲得L0索引,通過L0索引找到相應的表項(arm64中稱為L0表描述符,內核中叫做PGD表項),從表項中獲得L1轉換表基地址。 3.找到L1級轉換表,然後從虛擬地址中獲得L1索引,通過L1索引找到相應的表項(arm64中稱為L1表描述符,內核中叫做PUD表項),從表項中獲得L2轉換表基地址。 4.找到L2級轉換表,然後從虛擬地址中獲得L2索引,通過L2索引找到相應的表項(arm64中稱為L2表描述符,內核中叫做PUD表項),從表項中獲得L3轉換表基地址。 5.找到L3級轉換表,然後從虛擬地址中獲得L3索引,通過L3索引找到頁表項(arm64中稱為頁描述符,內核中叫做頁表項)。 6.從頁表項中取出物理頁幀號然後加上物理地址偏移(VA[11,0])獲得最終的物理地址。

    Linux閲碼場 內核 Linux 頁表

  • 技術解析:一文看懂 Anolis OS 國密生態|龍蜥專場

    編者注:本文系兩位演講者整理,他們在2021年阿里雲開發者大會的「開源操作系統社區和生態分論壇」上帶了分享,演講主題為《國密技術開發與實踐》。Anolis OS 國密是社區在 Anolis OS 上做的國密技術解決方案,非常歡迎業界有興趣的開發者能夠參與到 OpenAnolis 社區,為國內的基礎軟件生態添磚加瓦。 作者分別是阿里雲技術專家張天佳與螞蟻集團高級技術專家楊洋。張天佳主要負責Anolis OS上國密技術的開發和應用,參與實現了 libgcrypt 中的國密算法和 linux內核中的 SM2 算法;楊洋則主導開發了 BabaSSL,也是國內唯一的一個 OpenSSL maintainer,參與起草並推動 RFC8998 標準國際化。相信兩位技術人的乾貨分享,能給開發者們帶來一定的思考。以下為技術內容解析:國密——密碼算法的國產化 説到密碼算法,大家一定很熟悉 MD5,AES,RSA 這些通用的國際標準算法,這也是目前我們普遍採用的密碼學算法,它們在數據安全、通信、區塊鏈等眾多領域都有着廣泛的應用。 眾所周知,這些算法標準都是國外製定的,在某些情況下這會對國內信息安全有不利影響。當下有實力的國家,甚至有些大公司都制定了自己的算法標準。 顧名思義,國密就是密碼算法的國產化,跟其它領域一樣,密碼算法的國產化已經勢不可擋,這也是我們必須要做的事情。中國的國密算法為我們提供了一個新的選擇,在必要的場合中可以選擇替代那些國際主流算法,尤其是當下國際貿易衝突,技術封鎖不可忽視的大環境下,大規模推廣和採用國密算法將為國內重要的網絡基礎設施提供可靠的數據安全保障。 國密是什麼? 我是誰,從哪裏來? 國密是一個口語化的稱呼,官方名稱是國家商用密碼,簡稱商密,拼音縮寫是SM,這也是國密標準中具體算法名字的來源。國密是用於商用的、不涉及國家祕密的密碼技術。 國密標準完全由中國密碼管理局制定,主要的技術實現也基本是國內開發人員完成的,這對擺脱國外的密碼技術和產品依賴是非常有利的。 到哪裏去? 自2012 以來,SM2/3/4 的國密標準陸續公佈,目前國密技術生態基本處於一個正在逐步走向成熟的階段,但國內密碼基礎軟件在採用國密算法方面仍處於碎片化的狀態,比如我們經常可以看到各種個人或組織名義開源的支持國密算法的庫;此外這些開源項目的安全更新和社區活躍也都做的不好。國密的推廣仍然需要我們中國基礎軟件的開發者和用户共同努力。 2020年1月1日,《中華人民共和國密碼法》正式實施,從法律層面規範了國家商用密碼的應用和管理,這也為推廣和應用國密提供了必要的法律保障。 與國際算法的對比 這裏是國密算法和國際通用算法的一個對比,可以直觀地看到國密的一個基本情況: 針對各種常用的國際能用算法類型,比如對稱算法,公鑰算法和消息摘要算法,國密標準都定義了對應的相同功能的國密算法,比如 SM4 提供了與 AES 同樣的加密強度,並且支持各種加密模式;SM2 是基於橢圓曲線的公鑰算法,同時定義了非對稱加解密,數字簽名和密鑰交換標準,相對於 RSA,SM2 的密鑰更短,但支持的加密強度卻更高;SM3 是國密定義的消息摘要算法標準,摘要長度是固定的256位,強度等同於 SHA256。 除了基礎的算法,國密標準也定義了TLCP國密雙證書協議,用以支持國內的傳輸層安全協議。這裏還有一個好消息,今年三月份,TLS1.3 國密單證書協議正式被國際標準所承認,並且以 RFC8998 標準發佈,這意味着我們可以選擇在TLS1.3協議中使用完整的國密套件,目前我們也在聯繫正規瀏覽器hotbuyhk支持這個標準的實施和應用。 同時國密也定義了使用國密算法的 X509 證書,使用 SM3 哈希算法,SM2 算法作為數字簽名,證書類型是 SM2-with-SM3。 對開發者來説,國密提供了一個選擇,可以選擇從國際通用算法平滑遷移過來。除此之外,國密還有其它一些算法標準,是不太常用的,比如 SM9,ZUC 算法等。 BabaSSL 的前世今生 BabaSSL 是主打國密的密碼算法庫,與 OpenSSL 1.1.1 保持兼容,作為國密的密碼算法解決方案而誕生。 BabaSSL 是基於之前螞蟻集團和阿里集團內部的 OpenSSL 版本合併而來,並首次進行了開源。BabaSSL 的含義是:靈巧、輕快且靠譜的密碼學和 SSL/TLS 工具庫。 BabaSSL 的綠色商標,是基於阿里的橙色和螞蟻的藍色混合而來,也意味着我們希望將 BabaSSL 打造成一個靈活、小巧並且健壯的基礎密碼學庫。 BabaSSL 目前在阿里集團和螞蟻集團內部得到了非常廣泛的使用。從具體場景來看,有如下三個方面,分別是存儲、網絡和端上的設備。其中網絡服務的場景,是BabaSSL 最大的支撐場景,例如淘寶、天貓、阿里雲等各種涉及到鏈路加密的服務器端。此外移動端 App,例如支付寶手機 App 中集成了 BabaSSL 來實現多種密碼學的能力。 開源 BabaSSL 已經在去年的10月份進行了開源,目前代碼是託管在 OpenAnolis 上,當前開源的版本是 8.2.0,也是我們目前最新的穩定版本。目前 BabaSSL 在阿里內部使用的版本和開源版本之間存在一定的差異,我們目前正在逐步把內部版本的功能特性遷移到開源版本上進行開源,最終變成一個統一的開源版本,那麼屆時阿里內部也完全依賴於這個開源的版本,而不會再保留內部的閉源版本。 特色功能以下是 BabaSSL 當前最新穩定版本 8.2.0 的主要特色功能特性: 基於 OpenSSL 1.1.1,具備 OpenSSL 1.1.1的全部能力並且保持兼容 支持國密 SM2,  SM3和 SM4,並對 OpenSSL 1.1.1中所欠缺的 SM2 能力,比如 X509 證書的簽發和驗證功能進行了補全 GM/T 0024 和 TLCP 國密雙證書TLS協議 支持 RFC 8998:TLS 1.3 國密單證書 提供了對 IETF 正在標準化過程中的 Delegated Credentials 支持 IETF QUIC API 底層密碼學能力 更加完善的 SM2 算法支持,比如 X.509 證書籤發、驗籤的支持 正在申請軟件密碼模塊一級資質 與OpenSSL對比接下來是大家很關心的 BabaSSL 和 OpenSSL 這種老牌的密碼算法庫之間的區別: 從圖上可以看到一些主要區別: 對於一些新的密碼學技術標準,BabaSSL 會採取一種相對激進的策略快速跟進,比如在 IETF 中一些正在標準化流程中的技術方案,例如 delegated credentials, compact TLS 等,會進行原型的實現和快速跟進,而 OpenSSL則是相對保守,因為 OpenSSL 社區的策略是原則上只實現已經發布了的國際標準和國家標準。 在對於國密算法、國密協議、國密的監管合規、雲計算hotbuyhk的深度集成、以及國產化硬件等方面,BabaSSL 會進行更加深度和廣泛的支持,而 OpenSSL 則支持的比較有限。 對於API的易用程度,由於沒有歷史包袱,所以 BabaSSL 可以提供更加簡單易用的 API,而 OpenSSL 的 API 則相對複雜。對於資源受限的嵌入式設備,BabaSSL 會進行體積裁剪和內存使用量的規劃,OpenSSL 則明確表示沒有相關的計劃。 未來規劃 這個是一個後續 BabaSSL 未來的版本規劃和特性支持,基本上是每半年一個版本,涵蓋了多種新的密碼領域技術的支持,包括對IETF的幾個 RFC 草稿的實現、國產化硬件的支持以及未來對於後量子密碼學以及同態加密等前沿技術的支持: 支持 MPK Encrypted SNI Compressed Certificate Compact TLS SM算法優化 支持國產化 CPU 的國密算法指令集 體積裁剪,內存使用量優化 Tink API ZUC,SM9 PQC 同態加密算法 國密生態架構萬事俱備,有了基礎國密算法支持,我們便可以構建出一個圍繞國密算法展開的基礎軟件生態。這是一個國密生態的垂直場景,也是我們在 Anolis OS 上的國密生態架構,同時它也是一個全棧國密解決方案:從底層固件,內核,到基礎密碼學庫,在主要鏈路上做國密改造,最終形成一個完整的基於國密的安全信任鏈條。圖上右邊是一些垂直的國密應用場景,比如 SecureBoot,IMA,內核模塊簽名,文件完整性校驗等。 到目前為止,我們已經在 linux 內核,BabaSSL,libgcrypt,gnulib 等主流的基礎組件中支持了國密算法,這部分的工作都已經回饋到了上游開源社區,有興趣的開發者可以直接拿來使用或者作為參考,這些特性功能之後也會率先在 Anolis OS 上輸出,達到一個開箱即用的原生支持國密的 OS。 從中也能看到,國密生態涉及到的軟件棧非常多,形態也是各種各樣,要逐步完善這個生態,還有很長的路要走。近幾年的國際技術封鎖也給了我們做這件事的決心和動力。 目前我們已經和統信、海光等hotbuyhk有一些合作,也非常歡迎業界有興趣的開發者能夠參與到社區,一起來做這個事情,之後我們的工作都會在 Anolis 龍蜥社區以開源方式運作,秉着開放包容態度,繼續補充完善這個生態,最終達到的一個目標是:整個安全信任鏈是完全建立在國密算法之上。 國密在 IMA 和 modsign 的應用 我們知道,密碼學算法從來就是為安全服務的,我們來看兩個在安全領域具體國密改造的例子。 IMA 是 linux 內核提供一個文件完整性度量架構,用於檢測文件是否被惡意篡改,內核模塊簽名的目的是類似的,用於檢測模塊的發行源頭是否可信。它們都提供了自己的簽名工具,簽名工具依賴 BabaSSL 提供的 SM2 簽名文件的能力,用於在用户態做簽名。 文件簽名的驗證是在內核裏完成的,由於內核不能直接使用應用層的庫,為了支持在Linux內核裏驗證文件簽名,我們在內核裏實現了國密 SM2/3/4 算法以及國密證書的支持,用來驗證簽名是否合法。 通過對相應軟件棧的改造,我們完全基於國密算法構建了IMA和內核模塊簽名的安全機制,而這些之前都是由國際算法來保證的。

    Linux閲碼場 操作系統 阿里雲 OS

  • AK47所向披靡,內存泄漏一網打盡

    作者 / 嘗君、品文 編輯 /  芹菜 出品 / 雲巔論劍 青囊,喜歡運動T恤加皮褲的非典型程序猿。此時,他正目不轉睛注視着屏幕上一行行的代碼,內存泄漏這個問題已經讓他茶飯不思兩三天了,任憑偌大的雨滴捶打着窗户也無動於衷。就這麼靜悄悄地過了一會兒,突然間,他哼着熟悉的小曲,彷彿一切來的又那麼輕鬆又愜意。 是誰,在撩動我琴絃,那一段被遺忘的時光......初識內存泄漏小白的練級之路少不了前輩們的語重心長。從踏上linux內核之路開始,專家們就對青囊説——“遇到困難要學會獨立思考”、“最好的學習方式就是帶着問題看代碼”等等。可這次遇到的問題,讓青囊百思不得其解——機器總內存有200G,運行800多天,slabUnreclaim佔用 2G,且停掉業務進程,內存佔用並沒有降低。客户非得讓青囊給出合理解釋: 1.slab 2G內存是否存在泄漏?如果存在泄漏需要找到原因。 2.如不存在泄漏,需要找到這2G的使用者。 客户的問題很毒辣,是或不是都得給出合理解釋,需要用數據説話。帶着這些疑問,青囊開始了漫長的排查之路。正在青囊全身心投入工作的時候,專家走到他的身旁,靜悄悄的腳步並沒有被專心致志的青囊發現。專家的眼光放在了那一串串的代碼之上,眼裏流露出老父親般的慈祥。專家張口一句:“內存泄漏,這類問題不難,內核自帶kmemleak可以排查。況且200G的內存,slab Unreclaim才佔用2G,這根本就沒問題嘛。”青囊一下子抓到了救命稻草,眼裏泛着希望的光芒。一來這可能不是一個問題,二來也有排查工具kmemleak了。説幹就幹,青囊對kmemleak原理和使用進行了深入學習,kmemleak的使用總結起來就兩條指令: echo scan > /sys/kernel/debug/kmemleak cat /sys/kernel/debug/kmemleak 青囊迫不及待地登陸服務器執行命令——#echo scan > /sys/kernel/debug/kmemleak bash: /sys/kernel/debug/kmemleak: Permission denied 咦,居然提示沒權限?#ls -l /sys/kernel/debug/kmemleak ls: cannot access /sys/kernel/debug/kmemleak: No such file or directory 查看系統配置,原來線上環境根本就沒有打開kmemleak。# CONFIG_DEBUG_KMEMLEAK is not set 青囊立馬想到,可以重新編譯內核使能kmemleak,再讓客户來複現問題,但客户狠狠地甩了一句,“你們的目標不是提供永不停機的計算服務嗎!?”此時此刻,青囊燃起的希望又破滅了。青囊自我安慰了一番,接着跑到專家邊上想請教。他一臉沮喪地説着問題背景,專家聽罷,無可奈何地揮了揮手:“你玩鬥地主都是直接上來就王炸的嗎?這種問題應該是自己思考解決的”。青囊只好灰頭土臉地回工位。痛定思痛,他憋着口氣,一定要搞懂內存管理,搞懂slab內存分配。於是,青囊又開始了自己的鑽研之路。sysAK——內存泄漏無處遁逃 有了slab分配器系統的學習,以及對內存泄漏特徵的思考,等到再一次登陸機器時,青囊信心滿滿志在必得。想想畢竟有備而來,這次一定讓內存泄漏無處可逃。排查過程大致可以分為四步:1. 確認泄漏的slab通過slabtop  -s -a ,找到使用objects最多且不可回收的slab OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 419440512 419440512 100% 0.03K 3276879 128 13107516K kmalloc-32 810303 401712 49% 0.10K 20777 39 83108K buffer_head 417600 362397 86% 1.05K 13920 30 445440K ext4_inode_cache 267596 241915 90% 0.57K 9557 28 152912K radix_tree_node 216699 154325 71% 0.19K 10319 21 41276K dentry 155958 37367 23% 0.04K 1529 102 6116K ext4_extent_status 可以看到kmalloc-32的active object達到了4億多個,佔用內存在1.3G左右,泄漏嫌疑最大,那就拿kmalloc-32來開刀了。2. 啓用crash實時分析kmalloc-32的內存3.查找kmalloc-32內存頁 crash> kmem -S kmalloc-32 | tailffffea000c784e00 ffff88031e138000 0 128 111 17ffffea00242d0240 ffff88090b409000 0 128 126 2ffffea00436c5800 ffff8810db160000 0 128 127 1ffffea0018b1df40 ffff88062c77d000 0 128 126 2ffffea005947d1c0 ffff881651f47000 0 128 126 2ffffea004d463080 ffff8813518c2000 0 128 126 2ffffea0030644100 ffff880c19104000 0 128 126 2 4.dump內存內容 這裏可以隨機選擇多個page來分析,這裏選擇ffff88031e138000來分析。crash> rd ffff88031e138000 -Sffff88031e138000: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138010: dead000000200200 ffffffff002856f7 .. ......V(.....-------ffff88031e138020: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138030: dead000000200200 ffffffff00284a2a .. .....*J(.....------ffff88031e138040: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138050: dead000000200200 00303038002856f7 .. ......V(.800.------ffff88031e138060: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138070: dead000000200200 ffffffff00284a29 .. .....)J(..... 可以看到每個object的內容大體相似,但並沒有預期的順利,沒有看到函數名或者變量名,可青囊知道 ffff882f59bade00 這也是個slab內存地址,於是進一步打印其內容crash> x /20a 0xffff882f59bade000xffff882f59bade00: 0x460656b7 0xffffffff81685b60 0xffff882f59bade10: 0x63ea63ea00000001 0xffff882f59bade180xffff882f59bade20: 0xffff882f59bade18 0x00xffff882f59bade30: 0x0 0xffff882f59bade380xffff882f59bade40: 0xffff882f59bade38 0x7fb27fb2 0xffffffff81685b60明顯是內核只讀段的地址。sym 這個地址,原來是inotify_fsnotify_ops全局變量,從而推出泄漏結構體是struct fsnotify_event_private_data,然後結合fsnotify_event_private_data分配和釋放的代碼,在釋放內存時存在不正確的判斷邏輯,導致分配的內存沒有添加到鏈表,失去釋放的機會,從而導致泄漏。分析到這裏,青囊終於確認這是一起內存泄漏,而且泄漏的函數也定位到了,這下算是可以給客户一個滿意的答案了。客户問題得到了解決,青囊也對內存管理有了深刻的認識,還形成了自己的一套分析方法。但是青囊心裏也清楚,泄漏的內存不是每次都能找到函數名或者可視字符,手工使用crash查看的內存樣本也不一定夠,還要對內存地址比較敏感。於是青囊想把這套分析方法提煉成工具,可以對內存泄漏這類問題實施快速一鍵診斷,且不要懂內存知識,人人都可以上手分析。抱着這樣的理念,實現了5個核心功能: 自動判斷系統是否存在泄漏。 自動判斷是slab, vmalloc還是alloc page泄漏。 掃描全局內存,找到內存中slab object最多,且內容相似度最高的object。 動態採集內存的分配和釋放。 計算動態採集地址的內容與存量object的內容相似度,但達到一定相似度時,則對動態地址進行標記。 青囊把工具命名為sysAK,寓意像AK47一樣,能夠對系統問題快速定位。隨着青囊的學習成長,sysAK後續也會加入更多的功能,實現對操作系統全方位的監控,診斷和修復。 手握sysAK,青囊也蠢蠢欲動想驗證自己的工具,於是找專家們要來正在處理的“內存泄漏問題”。專家們只見青囊輕輕地敲擊了一條指令sysak memleak靜靜等待了200秒後,屏幕輸出了令人為之一振的結果:未釋放內存彙總:次數 標記次數 函數66 62 bond_vminfo_add 0x7c/0x200 [bonding]109 0 memleak_max_object 0x3f7/0x7e0 [mem]33 0 inet_bind_bucket_create 0x21/0x701 0 copy_fs_struct 0x22/0xb01 0 tracepoint_add_probe 0xf8/0x430 slab: kmalloc-64 object地址: 0xffff88003605e000 相似object數量: 593975泄漏函數: bond_vminfo_add 0x7c/0x200 [bonding] 結果直接顯示bond_vminfo_add函數存在泄漏,因為它分配的地址與內存中的59萬個object高度相似。專家們看到這個結果,半信半疑地回去review代碼——bond_vminfo_add函數竟然真存在泄漏。大家對青囊投來了詫異的眼神,滿是讚許。此刻的暢快,像極了遊戲通關之後的感覺。通過自己的努力,最終順利解決了問題,這個過程,只是説起來都覺得充滿了成就感。這次經歷,也鼓舞着青囊繼續前行。一個程序猿的自我修養——別輕易放過bug 。(完)

    Linux閲碼場 內存泄漏 內存

  • ARMv9刷屏 —— 號稱十年最大變革,Realm機密計算技術有什麼亮點?

    作者 / 乾越 編輯 /  芹菜 出品 / 雲巔論劍 ARMv9的新聞刷屏了。ARMv9號稱十年以來最重大變革,因此讓我們看下ARMv9中機密計算相關的新特性Realm。(注:本文是對Introducing the Confidential Compute Architecture的部分翻譯和個人註解,本文圖均來自anandtech.com網站。)背景在過去的幾年裏,我們看到安全問題和硬件安全漏洞已經成為了新聞熱點。許多處理器側信道漏洞,如幽靈、熔燬以及與它們有關的側通道攻擊,都表明有必要重新思考如何在處理器架構層面解決安全問題。Arm想要解決這個問題的方法是:通過引入Arm機密計算體系結構(Arm CCA),重新設計敏感應用程序的工作方式。一句話亮點總結Arm CCA基於Armv9 Realm Mangement Extension(RME,簡稱Realm),將敏感應用和OS隔離在Realm中;Realm比機密虛擬機更加通用,既支持機密虛擬機形態,也支持機密OS形態。 High Level設計 Arm CCA基於Armv9 Realm Mangement Extension,將敏感應用和OS隔離在Realm中:從這張圖可以總結出以下幾個要點: 1. Non-Secure World、Secure World和Realm之間是相互隔離的。 現有材料中沒有詳細解釋這種隔離是如何實現的,大概率還是基於硬件的地址空間隔離技術。 對Realm的隔離要看兩個方面:運行在Realm中的敏感應用也可能是租户部署的惡意應用,因此對Realm的隔離也是必要的,即雙向隔離。 2. Realm可以運行OS(簡稱Realm OS),也就是説Realm提供了高特權級的支持,可以運行EL1特權軟件。 Realm OS的形態可以有多種: 不一定非要是經過裁剪和安全加固過的Linux內核,也可以為Realm設計的TEE OS,或者由支持其他機密計算的OS技術實現演進過來額外支持Realm的LibOS(如Enarx、Occlum、Graphene等);但這種TEE OS不能是支持TrustZone的TEE OS,後面會討論這個話題。 TEE OS目前的一種發展趨勢是縮小TCB、減少Rich OS潛在的攻擊面進而提升整體的安全性;但在是否需要提供良好的業務邏輯兼容性上存在分歧: 1) 一種方案是不考慮對業務的兼容性,以安全為先,可以適度犧牲性能和兼容性。 2)另一種方案還是重視對存量業務的兼容性,以兼容性為先,可以適度犧牲性能和安全性。PS:Unikernel又有機會了!3.  EL2運行Realm Manager,負責管理Realm的調度和資源分配。可以預見這部分會由Arm CCA firmware架構來支持(類似ATF,或直接在ATF中進行擴展來支持)。 從目前的資料來看:Realm Manager是Arm新寫的,其代碼量大概是Hypervisor大小的十分之一。 Realm Manager和TDX中的SEAM Module很像:在處理器架構級為該功能模塊提供了一個新的運行模式;同時該功能模塊承擔了Realm生命週期和資源管理的功能,系統中其他不可信的組件不能替代其功能和角色。 4. TrustZone對Realm也是不可信的。也就是説Realm不是像TrustZone那樣只解決計算資源隔離的問題,而是解決更進一步的敏感數據隔離的問題。安全威脅模型這張圖説明了Realm的安全威脅模型,可以看出它具備典型的機密計算技術的特點:從這張圖可以總結出以下幾個要點:1. 這裏的hardware manufacturer 指的是外設的硬件設備提供商,而不是處理器本身的硬件提供商(比如Arm或SoChotbuyhk)。2. Realm Manager不屬於Realm的一部分,但它是用户TCB的一部分。 用法 由於Realm具備運行完整OS的能力,所以看上去可類比TDX的trust domain以及SEV/CSV的機密虛擬機,但下面的用法中則揭示了Realm相比機密虛擬機形態更為通用的一面:從這張圖可以總結出以下幾個要點:1. 由於TrustZone中的TEE OS不是通用OS,而是結合TrustZone深度定製過的,因此無法將TEE OS直接加載到Realm中並運行,這也打破了原先認為Realm會基於TrustZone架構進行迭代的假設;但適配了OP-TEE的TA是可以運行在Realm中的,只要Realm OS能夠支持OP-TEE的TA API。換句話説,這張圖可能也暗示了Arm接下來會在Realm OS中提供對TA的支持,當然也可能這張圖只是展示Realm的兼容性能力;此外,在Realm中運行Android應用也存在上述的可能性。2. Realm Manager本質上充當了類似Hypervisor管理VM的角色,只不過Realm Manager管理的對象是Realm。 當Realm運行VM的時候,可以認為把Hypervisor中涉及到Realm安全性的邏輯挪到了Realm Manager中,而把不涉及其安全性的部分保留在傳統Hypervisor中。 3. Realm僅僅是專門為運行敏感應用而提供的硬件TEE,它的使用者可以將自己環境內的工作負載通過Realm Manager將敏感應用 OS一起加載到Realm中,甚至是將一個完整的虛擬機加載到Realm中,因此ealm不是機密虛擬機,而是泛用性更高的通用型機密計算運行環境底座。綜上所述,Realm技術不僅大幅度降低了敏感應用對信任的需求以及用户在適配Realm的成本,而且OS自身的安全性問題對Realm應用來説將變得非常透明(但Realm應用對外提供的服務以及Realm OS對外暴露的接口的安全性依舊需要重視)。此外,因為關鍵性應用能夠安全地在任何支持CCA的系統中運行,而當今公司或企業往往需要使用具有各種安全合規的授權軟件棧的專用設備才能實現這種安全性,因此這種技術也能降低用户在安全上所投入的成本。 slide中沒有體現出來的要點 Realm中的應用能夠attest Realm Manager以確保它是可信的。內存加密。這個是機密計算的必備能力。目前的資料沒有顯示出Realm提供的這種通用運行能力是如何支持Realm與IO設備間的交互的。據説Confidential IO問題還沒有在Realm 1.0中得到解決,也許會在下一代技術中解決。 後續 目前Arm僅僅提供了關於CCA如何運作的high level解釋,有關該機制究竟如何運作的更多細節將在今年夏天晚些時候公佈。(完)

    Linux閲碼場 slide ARMv9 Realm

  • 為什麼我的進程被kill掉了

    先來看段代碼: 這段代碼非常簡單,就是先用mmap的方式,為該進程分配10GiB的虛擬內存,然後再用page寫的方式,讓操作系統為這10GiB虛擬內存,分配對應的物理內存,最後sleep,等待我們測試。 運行下: 沒啥問題,和我們預期的一樣,正常執行。 打開另一個終端,執行以下命令,看下它的內存佔用: 上圖中的VSZ指的是虛擬內存,RSS指的是物理內存,單位都是KiB,所以該進程虛擬內存和物理內存的使用,都約等於10GiB,沒問題。 我們再開個終端,再執行下這個程序: 第二次執行這個程序也沒問題,但奇怪的是,此時第一次執行的那個程序卻被kill掉了: 這是為什麼呢? 上面我們説到,該程序的邏輯是分配10GiB的物理內存,所以運行兩次,也就是要分配20GiB的物理內存。 但在我們的測試機器上,物理內存一共才16GiB,所以,運行兩個這樣的進程肯定是不行的。 在第二次執行該程序,且向操作系統申請物理內存時,操作系統會發現,物理內存已經沒有了。 此時,為了防止整個系統crash掉,linux內核會觸發 OOM/Out of Memory killing 機制,即按照一定的規則選擇一個進程,將其kill掉,以便回收物理內存,以此來保證機器整體的穩定運行。 同時,該kill事件,也會被記錄到內核日誌中,且可通過dmesg命令等方式查看。 比如上面第一個進程被kill掉的事件記錄如下: 看上面紅色字體行,該行是説,進程14134因為out of memory被linux內核kill掉了,該進程正是上面我們第一次執行的那個程序。 linux內核的oom killing機制,其實是一種棄車保帥的做法,因為如果我們不kill掉某進程,來釋放物理內存的話,那很有可能會導致後續系統級別的crash,兩害相權取其輕,操作系統只能這樣處理,歸根結底,是我們對進程使用物理內存的規劃不足,才導致了這種情況。 那為什麼不在第二次執行該程序時,在調用mmap分配虛擬內存時就直接報錯,返回無法分配內存呢? 這是因為,經過多年觀察,linux內核的開發人員發現,絕大部分程序在分配了很大的虛擬內存之後,在大部分時間裏,並不會一直使用這麼多的物理內存。 所以,為了更合理更高效的利用物理內存資源,linux內核允許虛擬內存的overcommit,即,例如在上面執行mmap分配虛擬內存時,linux內核並不會嚴格檢查,所有運行中的進程分配的虛擬內存加起來,是否超過了整個物理內存大小。 這也就解釋了為什麼上面第二次運行該程序時,mmap是沒有報錯的。 但是,雖然mmap的虛擬內存分配成功了,但當真正使用該內存時,比如上面的寫內存,此時要分配物理內存,則是有可能失敗的,因為虛擬內存的overcommit,很可能導致後續的物理內存不足。 如果真的發生了這種情況,就會觸發linux內核的oom killing機制,即linux內核中的oom killer會按一定的規則,選一個進程,將其kill掉,這個上面我們已經演示過了。 那為什麼不kill掉第二個進程,而是kill掉第一個呢? 這個和linux內核中oom killer的選擇策略有關,我們直接看源碼: 當進程請求操作系統為其分配物理內存時,如果此時物理內存已經沒有了,則會觸發上圖中的out_of_memory函數。 該函數中,會使用select_bad_process選擇要被kill掉的進程,然後使用oom_kill_process將其kill掉,來釋放物理內存。 在看select_bad_process之前,我們先看下oom_kill_process。 該函數調用了__oom_kill_process: 在上面的函數中,通過向victim進程發送SIGKILL這個signal(我們平時使用的kill -9命令,就是用的這個signal),將其kill掉,然後該kill事件,會被記錄到內核日誌中。 注意,這裏記錄的日誌格式,正好和我們上面用dmesg輸出的,14134進程被kill掉事件日誌格式完全一樣。 kill掉進程的過程就是這樣,我們再來看下select_bad_process函數是如何選擇要被kill掉進程的: 在該函數中,會遍歷系統中的所有進程,然後使用oom_evaluate_task這個函數,對各個進程進行評估: oom_evaluate_task函數中,會使用oom_badness,計算某進程badness的點數,點數越高,越容易被kill掉。 如果badness的點數是LONG_MIN這個特殊值,則直接跳過該進程,即該進程不會成為被kill掉的對象,如果badness點數小於之前選擇進程的badness點數,同樣也跳過該進程,即被kill掉的進程badness點數要是最大的。 遍歷中選擇的進程,及其badness的點數,會被賦值到oc->chosen和oc->chosen_points裏,oc->chosen最終指向的進程,就是上面oom_kill_process裏kill掉的進程。 我們再來看下badness點數是如何計算的: 該函數主體邏輯分成兩部分,一部分是,在某些情況下,該進程的badness點數直接返回LONG_MIN,即不會被kill掉。 這些情況包括,oom_score_adj的值為OOM_SCORE_ADJ_MIN,即-1000,或者該進程已經在被kill的過程中了,或者該進程在vfork過程中。 該函數邏輯的另外一部分就是計算進程的badness點數,其大致計算規則為: points = 該進程佔用的物理內存總數  總物理內存 * oom_score_adj值的千分比。 oom_score_adj的值,是進程獨有的,是可以通過寫 /proc/[pid]/oom_score_adj 的方式調整的,取值範圍為 -1000 到 1000。 該值越大,進程總的badness點數就會越大,進程也就越容易被kill掉。 該值越小,進程總的badness點數就會越小,該進程也就越不容易被kill掉。 上面我們還提到oom_score_adj有一個特殊值為OOM_SCORE_ADJ_MIN,即-1000,表示該進程不能被kill掉。 各進程的oom_score_adj的值默認為0。 綜上可知,linux內核中oom killer選擇被kill進程的方式,就是看各進程badness點數的大小。 默認情況下,因為各進程的oom_score_adj的值都為0,所以進程佔用的物理內存越大,其badness點數也就越大,其也就越容易被kill掉。 這也就解釋了,為什麼上面在第二次執行那個程序時,被kill掉的是第一次執行的那個進程,而不是第二次執行的進程,因為第一次執行的那個進程,佔用的物理內存更大。 其實,調整linux內核中oom killer行為的方式有很多,不止修改oom_score_adj值這一種方法。 比如,通過修改 /proc/sys/vm/panic_on_oom 的值,可以讓整個系統在物理內存不夠時,直接panic,而不是選擇性的kill掉某個進程。 比如,通過修改 /proc/sys/vm/overcommit_memory 的值,可以使上面第二次執行的測試程序,在使用mmap分配虛擬內存時,就直接報錯,説內存不夠。 比如,通過修改 /proc/[pid]/oom_adj 值的方式,同樣可以達到修改 /proc/[pid]/oom_score_adj 的目的,不過這個在內核2.6.36版本之後已經不推薦使用。 oom killer行為調整的相關參數,其具體詳解可以看proc的man文檔: //man.archlinux.org/man/proc.5 聊了這麼多,那理解linux內核的oom killer機制,對於我們實際應用有哪些幫助呢? 我們假設以下場景: 假如,我們有一台機器,上面跑着一個非常重要的服務,比如數據庫,或者某個應用進程等。 它非常耗內存,但是正常情況下,它使用的物理內存肯定不會高於實際總物理內存大小。 有一天我們需要在這台機器上執行一項任務,如果這個任務也比較耗內存,那很可能在執行這項任務時,整台機器的物理內存就完全不夠用了,此時,就會觸發linux內核的oom killing機制。 又因為在不調整oom_score_adj值的情況下,linux內核中的oom killer默認kill掉的,就是佔用物理內存最多的那個進程,一般來説,就是我們數據庫進程,或其他應用進程,假設這個進程又是線上的一個重要服務,那它被kill掉了,你想一下這會是多麼嚴重的一個事故。 那怎麼避免呢? 此時,我們就可以使用上面提到的,用於調整進程badness點數的,oom_score_adj 這個參數。 比如,我們可以通過 echo -1000 > /proc/[pid]/oom_score_adj 命令,將oom_score_adj的值設置為-1000,即該進程不能被kill掉。 又比如,還是通過上面的echo命令,將oom_score_adj的值修改為一個較小的值,來降低它被kill掉的概率。 但是,這些方法其實都不是完美的解決方式。 雖然該機器上的這個重要服務不被kill掉了,但操作系統為了保證整個系統不crash,還是會kill掉其他各種進程。 如果那些進程不重要還好,萬一重要的話,還是會相當嚴重的。 甚至,如果操作系統找不到可以kill掉的進程,那整個系統就會crash,這個就更嚴重了。 所以,最好的方式,還是人為去避免物理內存不足的情況,在機器上跑各種程序時,要提前對整個物理內存的使用,有個規劃和預判,最好是能預留出一些內存,以防各種誤操作。 好了,該篇文章就講這些內容,如果以後你發現你的進程,莫名奇妙就沒有了,可以通過dmesg等方式看下內核日誌,確定下你的進程是否被oom kill掉了。

    Linux閲碼場 進程 代碼 虛擬內存

  • 宋寶華:為什麼numactl內存綁定對代碼段不起作用

    本文目錄 閲讀本文大約需要10分鐘 numactl內存綁定中代碼段的問題 代碼段為什麼沒有進入指定的numa節點 內核內存管理一個改進方向建議 numactl內存綁定中代碼段的問題 在一個典型的NUMA架構Linux服務器中,我們常常使用類似 numactl -N 1 -m 1 ./a.out 類似的命令來綁定一定進程的memory,比如上面的例子,進程的memory被綁定到NUMA1。 但是這個時候,我們用numastat命令去查看進程a.out的內存分佈,很可能會發現它有少部分內存不在NUMA1: 有極少量0.75MB在NUMA0。這是不是説numactl -m 1沒有起作用呢?瞎猜沒用,眼見為實,我們來調查一下這個在NUMA0的內存屬於進程的哪一部分。 基本上可以看出,有3個地方有位於N0的內存,比如: 開始地址是0x40000的,文件背景為/root/a.out的部分; 開始地址是0x7fb9afc000,文件背景為/lib/aarch64-linux-gnu/libc-2.23.so的部分; 開始地址為0x7fb9c42000,文件背景為/lib/aarch64-linux-gnu/ld-2.23.so的部分。 如果我們進一步探究,會發現上面這三段,都是代碼段: 為什麼會這樣呢?看起來numactl -m對代碼段不起作用? 代碼段為啥沒進入指定numa? 原因其實是比較清晰的。上述代碼段對應的內存,在Linux內核中,都屬於有文件背景的頁面,受page cache機制管理。 想象一個場景,如果a.out曾經運行過一次(其實我開機後已經在沒有用numactl綁定內存的情況下,運行過一次a.out,上面的數據是第二次運行a.out的時候採集的),然後系統也加載了一些動態庫,那麼a.out本身的代碼段,庫的代碼段可能進入到了numa節點m,從而在內存命中。接下來,如果我們用numactl -m./a.out去運行a.out並綁定numa節點n,勢必要再次需要a.out的代碼段以及a.out依賴的動態庫的代碼段。但是前一次,這些代碼段都進入了page cache(位於NUMA node m),所以第2次在numa node n運行的時候,其實是命中了numa node m裏面的內存。 假設我們運行4個a.out,這4個a.out分別運行於4個不同的numa,然後a.out依賴a.out的代碼段、libx.so代碼段,liby.so代碼段。那麼,完全有可能出現下圖的情況,a.out的代碼段位於numa0,libcx.so代碼段位於numa1,liby.so的代碼段位於numa2,這樣4份運行中的a.out,都各自有跨NUMA的代碼段內存訪問,這樣在icache替換的時候,都需要跨NUMA訪問內存。 內核為什麼這樣做呢?原因在於,page cache的管理機制是以inode為單位的,每個page inode唯一!一個inode(比如a.out對應的inode)的page cache在內存命中的情況下,內核會直接用這部分page cache。這個page cache,不會為每個NUMA單獨複製一份。從page cache的管理角度來講,這沒有問題。 我們把前面的a.out kill掉,然後drop一次cache,再看a.out的內存分佈,發現在node0的部分減少了(0.75->0.63) 為什麼呢?因為我drop掉部分page cache後(echo 3也不可能drop掉全部的所有的代碼段,畢竟這裏面很多代碼是“活躍”代碼),我們再運行a.out並綁定numa1的時候,這次這些沒有命中的代碼段page cache,會進入到numa1。 如果我們重啓系統,開機第一次運行a.out就綁定numa1呢?這個時候,我們會看到a.out的代碼段在numa1: 然後我們把a.out kill掉,第二次綁定numa node0運行a.out,會發現這次的a.out的代碼段還是在numa node1而不是node0: 原因是它命中了第一次運行a.out已經進入node1的代碼段page cache。 初戀為什麼如此刻骨銘心,你終究還是錯過了那個人,而多少年以後,常常回想起來,你依然淚流滿面?因為,它命中了你的page cache。但是終究,一個人,一生可能不會只運行一次a.out。我們終究也要學會放手,把全部的愛,獻給你身邊與你相濡以沫的那個人。 內存管理的改進方向 2020年8月,我在Linux內核裏面提交和合入了per-numa CMA的支持: dma-contiguous: provide the ability to reserve per-numa CMA //git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b7176c261cdbc 這樣讓每個NUMA裏面的外設申請連續內存的時候,可以申請到本NUMA的近地址內存,而不用跑到遠端去,從而提高I/O的性能: 考慮到代碼段以及其他page cache的跨NUMA特點,這裏我想提一個可能性,就是per-numa Page cache。內核可以支持讓關鍵的代碼段,文件背景頁面,在每個NUMA單獨獲得一份page cache: 它的缺點是顯而易見的,page cache可能會用多份內存。它的優點也是顯而易見的,就是代碼段不用跨NUMA了。這屬於典型的以空間換時間! 這個事情行不行得通呢?技術上是行得通的,實踐上,我是不敢做的,因為需要大量的benchmark,加上patch至少得發20,30個版本,前後一兩年至少的。別的不説,宋牧春童鞋的省vmemmap內存的patch已經發到了22版。 [PATCH v22 0/9] Free some vmemmap pages of HugeTLB page //lore.kernel.org/lkml/20210430031352.45379-1-songmuchun@bytedance.com/ 要是幹這個page cache的優化,不得至少發個30版?通常這種有利於全世界,而不利於自己的KPI的事情,是沒有多少工程師願意投入的 :-) 細思恐極,這需要極大的耐心、投入和奉獻精神。 那麼,前期是不是可以從一個小點開始優化呢?我覺得是可能的。 比如a.out本身在numa0運行,kill後再在numa1運行,這個時候,內核感知到a.out獨一份,沒有share的情況,是不是直接在內核態把page cache直接migrate到numa1呢?我這裏還是打個嘴炮就好,把想象空間留給讀者。

    Linux閲碼場 代碼 numactl 內存

  • 不會讀代碼的程序員,不是好廚師

    [導讀] 大家好,我是逸珺。 某日一好友,調侃我的筆名,説讀起來好像“抑菌”!嗨,還真是,不管了,“抑菌”就“抑菌”吧。 寫號以來,有小夥伴問:如何能快速提升編程能力?這感覺永遠沒有正確答案,每個人都有自己的套路,今天就來聊聊我對這個問題的看法: 學會高效讀代碼,就是一個不錯的辦法。閲讀代碼,可能和寫代碼一樣重要! 為什麼要會讀代碼? 考慮這樣一些場景: Case 1: 你還在讀書,照着教程,照着例子,學習編程。剛開始,大概率是先讀別人的代碼,理解別人的代碼,而非一上來,就開始寫。 這是我YY的一個學寫代碼的學習模型,所以讀了,理解了,在自己就可以發揮了,然後書本上、他人的知識,就流進了自己的腦瓜了。 Case 2: 一個職場新人,一進公司,就加入一個項目組,那項目代碼真是海了去了!然後老大可能給你一個小小的活,在現有基礎上,添加一個小功能。項目經驗少的童鞋,一下就傻眼了,特麼的,這代碼這麼多行,文件幾百上千!該從何入手呢?別説改了,看都看不懂!完了,試用期是不是就要被幹掉?! Case 3: 你進了一個小公司,技術管理混亂,前任已閃人,你受命接任一個一坨翔一樣的項目,那代碼寫的真是雲裏霧裏,工期又緊,老闆又逼着出貨,怎麼辦?閃人?可是下家會更好麼?跳槽往往是從一個坑裏,跳到另一個坑裏。所以讀吧,總是要讀的。。。 Case n: ...... 學校往往教授的是如何寫代碼,可能從沒有教如何讀代碼。 然而,理想很豐滿,現實很骨感!工作中,你寫代碼的時間可能只佔工作時間很少很少的一部分,大部分時間你可能都是在閲讀已有的代碼,當然除非這個項目從0到1都是你一個人幹,可即便是自己寫代碼,也是漸進增長、不斷迭代的,也需要不斷反覆閲讀自己寫的代碼。 再者,編程與寫文章,有異曲同工之處。編程與寫作相似之處,都是用語言表達寫作者的想法。 對於如何提升寫作,古人曾講:熟讀唐詩三百首,不會作詩也會吟。回想學生時代,老師也常説:讀書破萬卷,下筆如有神!強調寫作需要大量閲讀,讀的多了,寫作能力也會相應提升。閲讀之於寫作,相輔相成,互為促進。 那麼大量閲讀別人的代碼,也能提升自己的編程水平。閲讀代碼,個人覺得會有這樣些好處: 博採眾長 優秀的源碼,就如傳世佳作一樣,值得反覆揣摩,細細品味。其編寫技巧、設計範式、架構思想,都具有極大的學習借鑑價值。比如一些優秀的開源項目:Linux內核、lwIP、u-boot等等。這些作品都彙集了全球優秀頂級程序員的思想智慧。都是非常優秀的作品,廣為流傳,廣為應用。如果能花些時間去閲讀理解一下其代碼,一定是大有裨益的。 正如牛頓所説:如果我能比別人看的更遠,只因為我站在巨人的肩上。 解決難題 編程生涯中,總會遇到一些感動束手無策的場景。github,搜索都已無能為力的時候。如果説還沒遇到,那一定是機緣未到~ 比如做Linux編程的時候,遇到某個API出錯,或許在網上查找半天,都找不到答案。實在找不到答案了,嘗試讀一讀內核底層相關代碼,有時候就能發現問題的原因。 開闊視野 很多時候,日常工作內容或許只是很小的領域,修復一些小的bug,修改一些小的功能等。如果只專注這些小的點,個人成長一定會受到侷限。 如果能善於發現一些新的感興趣的領域,並去閲讀相關的代碼,則一定會提升自己的編程能力的。 所以為什麼要讀代碼呢? 找bug review別人的代碼 學習 維護等 如何閲讀代碼呢? 這裏聊聊我的一些體會,也未必都對,也未必適合其他的朋友。分享以作交流,如有其他想法,也歡迎大家留言交流。 先粗後細 我一般拿到一份別人的代碼,會先去找這個項目的入口,先梳理個大概的脈絡。如單片機程序,一般會從下面幾個角度先掃一遍: main在哪裏? 開了幾個任務? 哪些是關鍵任務,主要功能鏈是怎麼樣的? 任務間如何協作的?任務的執行週期是如何安排的? 使用哪些硬件外設? 使用了哪些中斷?中斷與哪些任務發生了交互? 從軟件角度看,大致有哪些子系統? 是否有關鍵算法? 是否使用開源組件? ...... 先不關心很細的函數具體怎麼寫,數據結構是如何設計的?這樣,我大致能先有一個總體認識,然後在對自己感興趣的進行細讀。當然如果是review別人的代碼則就另當別論了。 如果是Linux應用程序,或者C 應用程序,我也大致採用差不多的思路,先讀個大概,然後再細讀。比如對一個Linux應用程序,會先了解這些方面的概要信息: 入口在哪個文件?一般都是main函數。 是否支持命令行傳啓動參數? 是否是守護進程? 開了哪些線程? 大致有哪些子系統? 使用了哪些開源組件? 是否使用驅動,是否有通訊等? ...... 如果項目採用cmake或者makefile進行組織的,那麼先閲讀下makefile也會是瞭解項目概要信息的一個比較好的切入點。 善做筆記 在閲讀代碼的概要信息的時候,我比較喜歡做做筆記,畫畫圖。在閲讀代碼的時候,我比較喜歡先去研究代碼中的數據結構。數據結構往往會體現作者抽象問題、對問題建模的一些思路,並使用UML圖畫出來,剛開始可能都不去看每個函數是怎麼實現的,只關心與這些數據結構相關有哪些函數以及數據結構間關係。 “Bad programmers worry about the code. Good programmers worry about data structures and their relationships.” — Linus Torvalds 或許,有的朋友會説,UML不會。不會沒關係,用你習慣自己能看懂的方式都可以,而且即便是用UML也不必過分糾結繪製的圖是否嚴謹。甚至拿支筆在筆記上手繪也可以。不過個人更建議,儘量寫電子筆記,更容易保存和查閲。 閲讀某一個具體函數時,如果函數內或者模塊內具有狀態機,如果這部分是需要仔細理解的時候,我就會將其狀態機圖,先繪製出來。比如,之前寫的modbus協議中的狀態圖: 這樣做有個好處,邊繪圖邊去理解代碼,就會加速對代碼的理解,對我來説,我如果只用兩隻眼睛盯着看,和一邊看一比畫圖效率會低很多。 這樣做還有一個好處,可以將理解以圖的形式記錄下來,如果光用圖還不能表達清楚的時候,我還會再加點文字描述。時間過了很久之後,再來看代碼,可能之前的理解全忘了,可是如果有這樣一份圖文並茂的筆記,我就會很快找回記憶。 善用工具 比如source insght, vs code等工具,都是提高閲讀代碼效率的好工具。儘量熟悉如何使用鍵盤控制閲讀跳轉,用熟了,效率倍增。 另外,還有些工具,可以自動將代碼轉化成類圖等,比如visual studio,可以自動繪製類圖,Enterprise Architect也具有根據代碼生成類圖的功能。具有此類功能的軟件還有很多。有興趣可以搜索一下。 多多調試 如果遇到有的代碼,怎麼看也理解不了。這時候可以試着加些打印日誌,運行調試一下,也可以使用調試工具進行斷點、單步調試,觀察程序運行的軌跡,數據的變化情況,可能就找到了突破口。 或者嘗試對原有的代碼,做些小的修改,來印證理解,也是不錯的方法。 一個經常調試的程序猿,鍵盤上F10,F11這些鍵大都壞的比較快。 總結一下 把自己閲讀代碼的一些體會分享一下,每個人都會有適合自己的方法。利用適合自己的方法,高效的閲讀代碼,是提升編程的一個行之有效的辦法。 如果我講的這些,如對你有所啓發,也不妨點個贊或者再看,小小的鼓勵一下我。當然你如願意擴散分享,那就感激不盡啦。

    嵌入式客棧 源碼 代碼 程序員

  • C語言中return、break、continue用法和區別

    作者 | strongerHuang 微信公眾號 | 嵌入式專欄 C語言中 return、 break、continue 是我們常用的三個“流程控制”關鍵字。 你能熟練使用這三個關鍵字嗎?下面來講講這三個關鍵字,以及相關的內容。 0概述 大部分編程語言中都存在return、 break、continue關鍵字,它們的作用有相似之處,有“流程控制”的功能。 剛開始編程的時候,可能很多人都會搞混它們的關係,特別是 break 和 continue 很多人都沒搞明白。 這三個關鍵字的大概意思:return:跳出當前正在執行函數。break:在循環體內,結束整個循環過程。continue:結束本次的循環,直接進行下一次的循環。 1return return:跳出當前正在執行函數。 使用方法:return (表達式);其中,(表達式)是可以省略的。 1.有返回類型return通常都是帶有返回類型的,比如返回int型變量: int Fun(void){ int rtn; //函數代碼; return rtn;} 這裏可以返回變量、結構體、指針等。 強調兩點:a.return不能返回數組.b.return不能返回指向(函數內)局部變量的類型; 2.無返回類型有些情況下,return是無返回類型的。 比如,當某個條件成立,需要結束執行本函數: void Fun(void){ int rtn; //函數代碼; if(條件成立) return; //函數代碼;} 強調兩點: a.void 空類型因為函數的返回類型為void(空類型),所以,這裏 return 是不帶任何值的。(帶有返回數據,就會報錯) b.void * 任意類型指針 void *Fun(void) 這是一個“返回任意類型指針”的指針函數(也是一個函數,只是它返回類型是指針)。 比如uCOS郵箱部分的函數: 2break break:在循環體內,結束整個循環過程,然後執行循環之後的代碼。 break常用語 for、 while 和 switch 語句中。比如: for(i=0; i<100; i ){ //代碼 if(條件成立) break; //跳出for循環 //代碼} switch(num){ case 1: //代碼 break; case 2: //代碼 break;} 1.break只挑出當前循環 如果有兩層、甚至多層嵌套的for循環,break只跳出它當前所在那個for循環,外層的for循環依然會繼續循環。 比如: int a=0;int i=0;int j=0; for(i=0;i<=9;i ){ for(j=0;j<=9;j ) { break; a ; //這裏a 不會執行; } a ; //這裏a 會執行;}printf("%d",a); 內層那個a 不會執行,所以最後輸出結果為:10 2.case如果沒有 break 會依順序執行 如果 switch 沒有break,比如: switch(num){ case 1: //代碼 //沒有break; case 2: //代碼 //沒有break; case 3: //代碼 //沒有break;} 如果num=2,沒有break,則case 2 和 case 3都會被執行。 相信有很多人都在這裏踩過坑。 3continue continue:結束本次的循環,直接進行下一次的循環。 和break類似,continue也是用於循環語句中,只是這裏剛好和break相反,這裏是繼續執行下一次循環(而不是跳出循環)。 比如:for(i=0; i<100; i ){ //代碼1 if(條件成立) continue; //執行下一次循環 //代碼2} 如果條件成立,則下面 代碼2 不會被執行。 1.continue不用於switch語句中 break用於switch語句中,但continue通常不用於switch語句中。 2.break 和continue 主要區別 break 語句是結束整個循環過程,不再判斷執行循環的條件是否成立。 continue 語句則只結束本次循環,而不是終止整個循環。

    嵌入式客棧 C語言 break continue

  • 加個默認參數還是重載呢?

    [導讀] 在開發比較大型的C 項目的時候,這樣一些場景你或許會遇到:1.維護別人寫的代碼;2.老闆要你在加個功能;3.項目需要持續發佈,功能在不斷添加;等等,很多時候,我們可能需要對一些類原有函數增加參數。此時,你很容易就能想到的辦法就是重載一下,或者修改原函數。本文就來分享一下在實際開發中的切身體驗。 直接改原函數 比如這樣的簡單栗子(栗子僅為説明思路): class point{ public: point(double x,double y); ~point(); draw(); private: double x; double y; }; 其中的draw方法在其最初的版本時,就是簡單的將(x,y)座標處繪製一個點,至於這個點長啥樣不管。可是忽然有一天,討厭的光頭產品經理跑過來説,這點黑漆麻咖的,太特麼難看了!他想給這個點上點色。但是有的地方呢,他又覺得黑漆漆的還蠻好看。於是乎,就把draw改成這樣了: //假定用一個32bit 16進制值來表示RGB。 draw(unsigned int color); 完了,一看代碼,發現只有很少幾個地方需要特定的顏色顯示,大部分默認就好了,可是原來函數已經變了啊,沒辦法我只能吭呲吭呲的把所有調用的地方都改一遍,泥馬,好都地方要改呀~ 改着改着,一想C 這麼牛逼的語言,一定還有其他的方案來應對類似的場景。一拍腦門,想起來,可以重載個函數嘛。 於是乎....... 重載draw 這麼一想,原類就變成這樣了: class point{ public: point(double x,double y); ~point(); draw(); draw(unsigned int color); private: double x; double y; }; 哇,很爽!就幾個調用的地方需要替換,啪啪一頓替換,提交上線,完事。端起杯子喝水,一邊看着自己的代碼,還有沒有其他的辦法呢? 重載雖然爽,但是功能如果不斷迭代,重載原來越多,大部分修改都很小的改動,可是類卻變的越來越胖了!而且重載的代碼裏大部分內容與原函數基本一致,僅僅添加了一個顏色指定!心裏隱隱覺得好似這樣整也不是很爽。突然間,腦瓜裏想起好像C 可以支持默認參數這一説。於是乎,又一頓敲..... 修改原函數 又繼續回到老路上,把原來的類一頓改,變成這個鳥樣: #define DEFAULT_COLOR  (0x00FFFFFF) class point{ public: point(double x,double y); ~point(); draw(unsigned int color=DEFAULT_COLOR); private: double x; double y; }; 好嘛,就幾個地方需要改,就把對應的地方替換一下: pt.draw(0xxxxxx); //其他地方,啥也不用改 pt.draw(); 0xxxxxx為光頭產品經理想要的顏色,其他的需要顯示原顏色的很多地方不動,編譯運行,效果一樣!想着這下可以了。那麼什麼是C 的默認參數呢? 何為函數默認參數? C 函數默認參數,是指函數聲明中提供的值,如果函數的調用者未提供帶有默認值的參數值,則該值由編譯器自動分配。 我不清楚C 的設計者設計默認參數是否是出於這樣的應用場景考慮,但是個人認為默認參數確實在本文類似的場景中表現的比重載更為優雅。讓類不會因為不斷迭代變的因為一些簡單沒必要增加重載函數的時候大顯身手。 那麼使用默認參數,需要注意些什麼呢? 默認參數不同於常量參數,因為常量參數不能更改,而默認參數可以根據需要覆蓋。 調用函數為其提供值時,默認參數將被覆蓋。如果調用者不給定參數,編譯器將聲明中的默認值傳入調用的地方。 將默認值用於函數定義中的參數後,在相同作用域中該參數的所有後續參數都必須具有默認值。也可以説默認參數是從右到左分配的。例如,以下函數定義無效,因為默認變量z的後續參數不是默認變量。 int sum(int x, int y, int z=0, int w) 什麼是相同作用域呢?比如這樣也是可以的: { void f(int n, int k = 1); void f(int n = 0, int k); // OK: k的默認值在前一個函數的聲明中指定了 } 默認參數是在函數聲明中指定的,因此在函數體實現的地方就不能還帶着默認值,這樣編譯會報錯!比如 point::draw(unsigned int color=DEFAULT_COLOR) { ...... } 虛擬函數的重載不會從基類聲明中獲取默認參數,並且在調用虛擬函數時,將根據對象的靜態類型來確定默認參數。

    嵌入式客棧 重載 C 默認參數

  • 硬件的總體設計

    1、硬件框圖 首先,總體設計是需要交付一張硬件框圖的。有的人有強迫症,需要用visio畫出很多細節。有的人,覺得大概把主要的核心器件放一下,示意一下就可以了。自然總體框圖的美觀程度不代表硬件的絕對水平。 當然除了表示出芯片,還需要表示出芯片之間的接口。考慮接口的時候,就需要考慮接口的速率。很久以前看到有同事,想在I2C上走視頻信號,應該是沒有仔細考慮視頻信號的帶寬,和I2C總線能夠承載的最大速率。 工欲善其事,必先利其器是説:工匠想要使他的工作做好,一定要先讓工具鋒利。比喻要做好一件事,準備工作非常重要。語出《論語·衞靈公》:子貢問為仁。子曰:“工欲善其事,必先利其器。居是邦也,事其大夫之賢者,友其士之仁者。”  這個準備工作,當然不只是指工具,還有動手之前的策劃、思考、評估、方案設計、架構設計等。 硬件工程師動手畫原理圖之前,需要畫的一個東東就是,用“猥瑣”(Visio)畫出電路板方案框圖。 2、業務模型 硬件知識背景的開發人員,比較容易先關注芯片選型和接口的選擇。其實一個總體設計,應該先從電路板(或者説 產品的角度)怎麼使用的角度開始思考。特別是電信設備的電路板,我們需要考慮對外接口,需要考慮數據在整個硬件系統裏面的流向,同時需要考慮在電路板內部,數據的流向和走向。 所以,業務模型決定的,數據通信的帶寬,也決定了FPGA、DSP等芯片外掛的DDR的帶寬和容量。同時業務模型,也決定了處理器的能力需求。 如果是非服務器模型,我們一般用浮點計算和整點計算的算力對芯片的規格進行約束,如果是服務器模型,我們需要使用Intel定義的一些算力評估方法對處理器能力進行評估。 所以業務決定硬件,而不是先選定硬件再看能夠承載什麼樣的業務。脱離業務模型的評估硬件需求,都是耍流氓。 4、關鍵器件分析 我們研發一個新項目的時候,不可避免的會使用我們沒有使用過的器件,甚至是整個公司都沒有使用過的器件。 我們根據設計需求選定了基本方案之後,需要對新器件、關鍵器件進行應用分析,輸出應用分析報告,並召集相關人員、管理者、有經驗的工程師進行評審和分析。 方案分析過程中需要參考和更新器件的Bug List和設計注意事項。同時收集案例,並給出應對解決措施,提前預防已知問題。 關鍵器件不僅僅侷限於新引入的器件,還包括複雜的器件、開發人員及項目組經驗積累都不足的器件。 這個關鍵器件的分析,我們需要對器件的datasheet進行分析、尋找並Demo板、查閲廠家的errata,尋找已經產品化或者已經完成過的設計,部分電路的改動,需要動手做電路實驗、或者做電路仿真,進行驗證。 5、預佈局、結構設計、熱設計(焦灼與迭代) 我們根據需求,明確了必要的功能、性能、然後明確了關鍵器件,也就是電路板上面大功率器件、新器件、複雜器件都已經敲定了。那麼我們可以做一個預佈局,明確電路板尺寸,形狀的大致想法。 我們為了實現一個準確的預佈局,需要輸出一個預佈局原理圖,要求原理圖至少包含主要器件、電源、熱敏感器件和接插件。 預佈局需要把電路板的信號流向、器件功能、主要器件的電源管腳分佈、整個電路板的電源的基本分佈和流向。 然後把相關的訴求,提供給ID工程師,給一個基本的ID設想,然後由結構工程師進行結構設計的細化。 如果是成熟的機框,則這個過程相對簡單一些,例如ATCA、VPX這種標準機框。一些個人消費電子產品這個過程反覆的可能性非常大。 為了避免這個過程,硬件工程師在完成初步的預佈局,之後應該把自己的設計訴求,全部記錄在《結構要素圖設計説明書》中,提供給結構和ID工程師。 硬件工程師需要提供單板佈局和器件熱耗(功耗)表、各器件散熱參數。數字器件要注意提供儘量準確的功耗數據,特別是DDR/FPGA等器件要根據使用場景進行計算。提供的器件的功耗需要準確,避免過設計,也避免散熱風險。 當存在工程挑戰的時候,硬件需要在預佈局、結構設計、熱設計之間進行焦灼和迭代。最終達到一個最佳的平衡的效果,有時候不得已還需要跟需求進行平衡。 6、硬件與軟件 應用決定軟件、軟件決定硬件。所以硬件選型,特別是CPU的選型,需要充分考慮軟件需求。小公司,需要考慮軟件工程師的開發能力和已經具備的技術儲備。大公司,需要考慮公司的軟件路標、OS的選型規劃、軟件開發工作量、軟件的歸一化、商用OS的供應商現狀等等。

    嵌入式客棧 硬件 總體設計

  • 開發板給電腦自動分配IP?電腦與開發板直連

    [導讀] 在做一個ZYNQ項目時,需要實現嵌入式Linux與Windows電腦直連。可能會有盆友會遇到類似的需求,所以整理分享一下。 問題描述 基於ZYNQ芯片設計的一塊嵌入式板子,板上運行Linux需要將大量的數據通過網口傳輸給電腦。這裏借用黑金AX7010開發板示例,事實上也是這樣做實驗的。最開始系統是這樣工作的: 板子連以及電腦網口都連接在路由器的LAN口,路由器自動給電腦與板子分配IP地址,板子與電腦在同一個子網裏。從而實現了數據通信。這樣用是能用,就是設備總是需要帶一個路由器,這樣很不方便。有沒有辦法直連呢?就像下面這樣: 這樣就需要配置板子與電腦的IP地址在同一個網段內,有沒有什麼辦法能夠自動分配IP地址給板子以及電腦呢?經過一些搜索,發現了這麼一個非常棒的開源組件Avahi。下面就來分享一下Avahi。 何為Avahi? Avahi is a system which facilitates service discovery on a local network via the mDNS/DNS-SD protocol suite. This enables you to plug your laptop or computer into a network and instantly be able to view other people who you can chat with, find printers to print to or find files being shared. Compatible technology is found in Apple MacOS X (branded "Bonjour" and sometimes "Zeroconf"). Avahi is primarily targetted at Linux systems and ships by default in most distributions. It is not ported to Windows at this stage, but will run on many other BSD-like systems. The primary API is D-Bus and is required for usage of most of Avahi, however services can be published using an XML service definition placed in /etc/avahi/services. 翻譯一下: Avahi 是一個通過 mDNS/DNS-SD 協議實現在局域網發現服務的系統。從而使您能夠將筆記本電腦或計算機連接到網絡,並立即能夠查看可以與之會話的其他機器、查找局域網打印機或查找正在共享的文件。在 Apple MacOS X中存在與之兼容的技術(品牌為 “Bonjour”,也稱為“Zeroconf” 零配置)。 Avahi 主要針對 Linux 系統,並在大多數發行版中默認提供。目前還不支持Windows,但可以在許多其他類似 BSD 的系統上運行。主要 API 基於D-Bus實現,並可以在/etc/avahi/services 中的 XML 文件中定義發佈服務。 那麼什麼是mDNS/DNS-SD呢?什麼又是零配置呢? mDNS協議 mDNS是multicast DNS的縮寫,也即是多播DNS協議。 什麼是DNS呢?簡單打個比方就是相當於電話簿,比如要給某人打電話,往往很難記住某個人的電話號碼,因此需要去電話簿查這個人的號碼,然後再撥過去。計算機間通信IP需要知道目標機的IP地址,也是常常使用主機名或者某個站點的域名進行訪問,但是就通信協議而言則是需要對應機器的IP地址。因為DNS系統就被設計出來了。 DNS(Domain Name Server) 是指域名系統,是用於管理域名與IP間對應關係的軟件系統。那麼問題來了,mDNS與常規的DNS又有何差異呢? 和DNS一樣,mDNS也是將域名解析為IP地址,不同的是mDNS在局域網級別運行,這與全局級別運行的常規DNS不同。它與零配置(Zeroconf)網絡中的DNS-SD(DNS-Service Discovery)協議結合使用。零配置網絡不需要手動操作。此外,零配置網絡不依賴DNS服務器和DHCP服務器進行操作。 DNS-SD協議 DNS-SD又是個什麼東西呢?用來做什麼的呢?DNS-SD允許客户端獲取服務實例及其服務類型的命名列表,並使用標準DNS查詢消息將這些服務解析為主機名。mDNS協議在RFC6762中定義,DNS-SD協議在RFC6763中定義。mDNS有多種實現版本,比如Bonjor,Avahi,Windows等。 怎麼使能Avahi 對於ZYNQ系統使用petalinux-tools編譯構建Linux內核以及根文件系統,使用起來非常方便。 首先在已經建立的petalinux工程運行根文件系統配置命令: petalinux-config -c rootfs 這樣得到如下的配置界面: 2.選擇 Filesystem Packages進入: 3.選擇network進入下一級配置界面: 4.進入avahi配置界面進入: 如上圖這樣配置,保存退出,就完成avahi配置了。 5.編譯部署 petalinux-build 再運行如下命令,生成部署鏡像文件: petalinux-package --boot --fsbl ./images/linux/zynq_fsbl.elf --fpga xxxx.bit --u-boot --force xxxx.bit是PL的FPGA所需要的bit文件。 6.運行測試 將BOOT.BIN以及image.ub文件拷貝進SD卡,將板子與電腦網口用網線直連,然後系統上電運行,可見到如下類似的內核打印信息: INIT: Entering runlevel: 5 Configuring network interfaces... IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready udhcpc: started, v1.29.2 run-parts: /etc/udhcpc.d/00avahi-autoipd: exit status 1 udhcpc: sending discover random: app: uninitialized urandom read (4 bytes read) Server is listening... udhcpc: sending discover macb e000b000.ethernet eth0: link up (1000/Full) IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready udhcpc: sending discover random: avahi-autoipd: uninitialized urandom read (4 bytes read) "169.254.3.140" broadcastDatagram udhcpc: no lease, forking to background done. random: dbus-uuidgen: uninitialized urandom read (12 bytes read) random: dbus-uuidgen: uninitialized urandom read (8 bytes read) Starting system message bus: dbus. Starting internet superserver: inetd. Starting syslogd/klogd: done * Starting Avahi mDNS/DNS-SD Daemon: avahi-daemon ...done. * Starting Avahi Unicast DNS Configuration Daemon: avahi-dnsconfd ...done. 檢查板子的IP地址: root@idaq:~# ifconfig eth0  Link encap:Ethernet  HWaddr 00:0A:35:00:1E:53 inet6 addr: fe80::20a:35ff:fe00:1e53/64 Scope:Link UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 RX packets:39 errors:0 dropped:0 overruns:0 frame:0 TX packets:63 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:4132 (4.0 KiB)  TX bytes:8953 (8.7 KiB) Interrupt:27 Base address:0xb000 eth0:avahi Link encap:Ethernet  HWaddr 00:0A:35:00:1E:53 inet addr:169.254.3.140  Bcast:169.254.255.255  Mask:255.255.0.0 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 Interrupt:27 Base address:0xb000 再檢查電腦的IP地址: 從而開發板IP地址為169.254.3.140,電腦的IP為169.254.50.229,子網掩碼都是255.255.0.0,所以電腦與開發板處於同一個子網中。剩下做應用就是利用socket進行TCP/UDP通信了。 這裏對avahi的幾個配置項做個簡要的介紹: avahi-dbg:使能這個選項就將avahi相關的調試符號編譯進去了。 libavahi-glib:將avahi引擎編譯成glib庫接口以支持GTK /GNOME libavahi-client:編譯avahi client庫 libavahi-core:編譯avahi引擎編譯成庫,將完整的 mDNS/DNS-SD棧嵌入到應用軟件中的 API。這僅適用於嵌入式設備的開發人員。 avahi-dev:多播/單播 DNS-SD框架 avahi-dnsconfd:mDNS/DNS-SD配置守護進程的單播DNS服務器。avahi-dnsconfd 連接到正在運行的avahi-daemon,併為本地 LAN 根據腳本 /etc/avahi/dnsconfd.action發送單播DNS服務。如此mDNS 就以類似DHCP的方式配置單播 DNS 服務。 avahi-autoipd:avahi-autoipd 實現了 IPv4LL,即“IPv4 鏈路本地地址的動態配置協議”(IETF RFC3927),是一種無需中央服務器即可從鏈路本地 169.254.0.0/16 範圍自動配置 IP 地址的協議。主要用於缺少DHCP服務器的ad-hoc網絡。avahi-autoipd可以用作獨立的地址分配器或用作DHCP客户端的插件,如果找不到DHCP服務器,它可以用作後備解決方案。 avahi-utils:該配置項將使能多個實用程序,可以使用這些命令行與Avahi守護程序交互,包括髮布、瀏覽和發現服務。 libavahi-common:該配置項使能Avahi 通用庫。 avahi-daemon:Avahi mDNS/DNS-SD守護進程實現了Zeroconf 架構。該守護進程使用mDNS/DNS-SD註冊本地 IP 地址和靜態服務,併為本地程序提供兩個進程間通信的API,以利用 avahi-daemon維護的 mDNS 記錄緩存。 avahi-daemon 在系統啓動時解釋其配置文件 /etc/avahi/avahi-daemon.conf 並從 /etc/avahi/services/*.service 讀取 XML ,這些信息可能被定義為靜態 DNS-SD服務。如果在 avahi-daemon.conf 中啓用 publish-resolv-conf-dns-servers,文件 /etc/resolv.conf 也將被讀取。 libavahi-gobject:使能Avahi的GObject 接口庫。 總結一下 如果你使用buildroot進行系統構建,Buildroot也已經內置了Avahi的支持,配置也基本類似。當然你也可以直接下載源碼進行交叉編譯,不過個人不建議這樣使用。 總之,利用Avahi可以方便實現零配置網絡,實現嵌入式Linux與電腦直連,自動分配IP地址給電腦只是一個比較簡單實用的應用,利用其庫還可以開發出更為複雜的應用。

    嵌入式客棧 芯片 開發板 IP

  • 阿里雲 IoT 企業物聯網平台 MQTT 通訊模式

    阿里雲 IoT企業物聯網平台為不同場景的硬件提供了多種通信模式,例如設備到雲,雲到設備,設備到設備之間的通信。儘管不同業務場景設備和交互行為差異很大,但是大多數底層數據流通信模型都可以歸類為三種MQTT模式:點對點模式,廣播模式和同步調用模式。 當我們基於阿里雲IoT企業物聯網平台搭建硬件端到App端物聯網業務時,完整業務數據鏈路的架構示意圖如下: 設備與IoT物聯網平台之間的通信: 設備通過MQTT協議的Publish,發送數據到物聯網平台,如圖①。 設備通過MQTT協議的Subscribe,實時監聽物聯網平台下發的指令,如圖⑤ 。 業務服務器和IoT物聯網平台之間的通信: 數據流轉到業務服務器 通過配置物聯網平台的規則引擎,可以將數據實時寫入到 數據庫,業務服務器直接從數據庫獲取數據,如圖②。 通過配置物聯網平台的規則引擎,可以使用AMQP協議, 將數據實時推送到業務服務器,如圖③。 業務系統下發控制指令到設備 業務服務器通過調用物聯網平台API,下發控制指令,如圖④。 點對點模式 點對點通信模式是設備在MQTT中基本的消息發送和消息接收模式。 設備上報消息給雲端 設備端可通過MQTT的Publish指令,傳入設備的DeviceName 後,可以上報消息到物聯網平台。如下圖所示 同一產品下海量設備上報的消息可以通過雲產品流轉匯總接收。如下圖所示 雲端下發消息到設備端 雲端下發消息給設備的能力,可通過調用物聯網平台提供的Pub 接口,傳入目標設備的DeviceName來實現。如下圖所示 設備與設備間通信M2M 接入同一個企業物聯網實例的設備A和設備B之間可以通過雲平台規則引擎的Topic流轉規則來實現設備間通信(M2M),如下圖所示 消息廣播模式 在線設備全量廣播消息 針對同一產品下全量在線設備廣播消息的場景,物聯網平台 提供了廣播接口,可最大每分鐘調用1次。 指定Topic的全量設備廣播消息 相同產品下的設端可以訂閲相同的廣播Topic (/broadcast/${productKey}/xxx),業務服務器調用物聯網平台提供的PubBroadcast接口,向已訂閲廣播Topic 的在線設備推送廣播消息。 一個廣播Topic最多支持被1000個 設備訂閲。 當設備數量超過1千台,您可以對設備進行分組。例如,如果您有5,000個設備,您可以將設備按每組1,000個,而分成5組(例如 /broadcast/${productKey}/group1)。您需要分5次調用PubBroadcast接口,以便實現全量設備(即使設備當前離線)廣播的目的。 同步消息RRPC模式 MQTT協議是基於Pub/Sub的異步通信模式,不適用於服務端同步 控制設備端返回結果的場景。物聯網平台基於MQTT協議制定了一套請求和響應的同步機制,無 需改動MQTT協議即可實現同步通信。物聯網平台提供API給服務端, 設備端只需要按照固定的格式回覆PUB消息,服務端使用API,即 可同步獲取設備端的響應結果。 同步RRPC具體流程如下: 設備端訂閲RRPC相關Topic。 業務服務器調用物聯網平台的RRPC接口。 物聯網平台收到服務器端RRPC調用請求,向設備下發一條 RRPC請求消息。 設備端接收到RRPC消息,執行業務指令,向IoT平台回覆一條 RRPC響應消息。 物聯網平台提取設備上報RRPC響應的消息ID,和業務系統的RRPC請求消息匹配。 物聯網平台將響應結果作為RRPC的響應返回給指定服務器。 在阿里雲 IoT企業物聯網平台上,組合使用以上三種模式,即可輕鬆搞定物聯網場景消息通信需求。

    嵌入式客棧 IoT 阿里雲 MQTT

  • 互聯網黑話,你知道多少

    作為一個新手程序員,你是否經常聽到一些奇奇怪怪的詞,比如痛點、抓手、場景、生態、頭部、腰部、擊穿、打法、聲量、藍海、紅海、賦能…今天,華妹就給大家普及一下互聯網行業的黑話,學會了逼格滿滿,升職加薪不是夢喲! 那麼什麼是互聯網黑話呢?我們做一個簡短的模擬對話: -“我們接下來將在幾號幾點在XX發佈什麼內容,會用到XX社交媒體賬號……” -“我覺得你沒有想清楚這個策劃。” -“我們先開篇預熱,拉高期待值……然後借勢發酵,引起圍觀,為營銷賦能……” -“對呀,你看這就清晰多了。” 這是一個策劃和一個互聯網客户的對話。描述同一件事情,策劃前面用大白話差點被斃稿,換上後面的互聯網“黑話”就成功過稿了。這種好好的話不能正常説,非要弄一些看起來高大上的詞彙來表達意思,就是當前被熱議的互聯網黑話現象的真實寫照。 ...... 對於互聯網黑話,大家的態度不一,有人認為這是企業文化和互聯網精神的一種體現,讓職場小白快速成長為老手,甚至互聯網人也在不斷創造和強化黑話.也有人認為這只是故作深沉、“不説人話”,實際上毫無意義,徒增溝通成本。 其實在其他行業,也各有黑話,例如: 搬磚——從事研究物質空間位置轉移的科研項目 網絡管理員——全解析數據交互式處理,專業數據網絡課題處理分析,網絡物理層應用研究開發 氣保焊工——通過加熱和低強匹配金屬填充,使異種材質達到原子間結合而形成永久性連接 安裝路由器交換機——控制從各省匯聚到全國骨幹網上數以萬計的 IP 數據包的存儲與正確轉發 ...... 有人説“互聯網公司最擅長重新發明已經存在的東西” 有人説“每天賦能、賦能搞得一身負能” 有人説“這算是文化的一種流失,將正確的打碎,造成認知上的斷裂” 但是,黑話千千萬,互聯網行業一直向前的本質是不會變的。 加油,程序員! 互聯網黑話小知識,你get了嗎? 互聯網黑話 藍海市場=人少魚多還沒火爆 紅海市場=人多魚少很難搞 風口=從藍海變紅海 用户畫像=大部分用户喜歡啥關心啥愛看啥 用户痛點=咋讓用户上癮 佈局=畫餅 垂直細分=把10分成1 1 1..... 新零售=線上線下和物流結合在一起 共享經濟=租賃 AII  in=賭一把 下沉=分銷 商業化運營=能掙錢的都做 社羣營銷=微信羣賣課 轉化=變成自己的 精細化=加人 發力=投錢 打法=花錢的方法 閉環=做PPT時畫個圈兒 壁壘=你説啥我聽不懂 IP運營=搞版權 PGC=專業人寫的 聯動=多人蔘與 商業模式=賺錢方式 PUSH=手機通知欄的垃圾消息 OGC=職業的寫的 UGC=用户寫的內容 羣控=一台電腦控制上百部手機 KOL=網紅大V KOC=微商和羣主 私域流量=朋友圈 Vlog=有人對着鏡頭説話 MCN=網紅經紀公司 洗稿=抄襲 互推=資源互換粉絲共享 灰度發佈=發佈體驗版 硬廣=只有廣告 軟文=有點廣告 內容電商=軟廣花式廣告 跳出率 沒看完 網感=上網時間長短 內容迭代=根據時間再改一版 孵化=自己搞 追熱點=蹭流量,博眼球 全網發佈=各個平台,都發一遍 分發=一個一個發 矩陣=搞一堆號 賦能=幫忙助力 圈層傳播=全網發佈

    華清遠見武漢中心 互聯網 程序員 黑話

  • 編程真的會讓人頭禿

    編程不可怕,禿頭才尷尬 在微博搜索“脱髮”,能找到上萬則熱門話題,這數量是什麼水平?仔細再看,程序員佔去一大片從萬萬千脱髮人羣中,“禿”顯出來,一躍成禿頭的重災區! 程序員脱髮多不僅是因為這個職業,其實更多的是因為程序員男性居多。而男性脱髮多是以雄脱為主,這是殺傷力的一種脱髮類型,而且程序員的職業特點也導致脱髮發生或雄脱提前。精神壓力大。程序員敲代碼時往往需要注意力高度集中,而且不能犯錯誤,不僅用腦多、而且壓力非常的大。人在精神高度集中和思考時,大腦對血氧的需求增多,因此血液會優先供應大腦,使頭皮的血供相對不足。這會造成局部血液循環不通暢的現象,就導致頭皮吸收不到足夠的養分,所以就造成頭髮生長環境的改變和營養不良的情況,降低了頭髮生長的質量,那麼頭髮就無法健康的生長,同時生長的頭髮也很容易掉落。而且,程序員思考問題時候也會導致身體分泌雄性激素來維持大腦的活躍度,所以程序員體內DHT偏高,更容易導致脱髮。熬夜和不規律作息。程序員需要長期的面對電腦,電子產品的輻射對於人體的傷害還是很大的,尤其是日積月累的情況下。而且還經常會面臨熬夜、飲食不規率等,這些也都是導致脱髮的因素,有脱髮基因的還會使雄脱提前。 為此華妹想告訴以後將會成為程序猿的大佬們:雖説脱髮難以缺席,但可以遲到。以後你們就會知道禿頭真的是一件很恐怖的事情,要學會早睡早起,保持運動,外面的世界還有星辰大海等着你們去征服。

    華清遠見武漢中心 編程 程序員 脱髮

首頁  上一頁  1 2 3 4 5 6 7 8 9 10 下一頁 尾頁
發佈文章