子曰:“飯疏食,飲水,曲肱而枕之,樂亦在其中矣。不義而富且貴,于我如浮雲。” 《論語》:述而篇
百篇部落格系列篇.本篇為:
v19.xx 鴻蒙核心源碼分析(位圖管理篇) | 誰能一分錢分兩半用
基礎工具相關篇為:
- v01.12 鴻蒙核心源碼分析(雙向連結清單) | 誰是核心最重要結構體
- v19.04 鴻蒙核心源碼分析(位圖管理) | 誰能一分錢分兩半花
- v20.03 鴻蒙核心源碼分析(用棧方式) | 程式運作場地由誰提供
- v31.02 鴻蒙核心源碼分析(定時器) | 哪個任務的優先級最高
- v34.04 鴻蒙核心源碼分析(原子操作) | 誰在為原子操作保駕護航
- v35.03 鴻蒙核心源碼分析(時間管理) | 誰是核心基本時間機關
先看四個宏定義,程序和線程(線程就是任務)最高和最低優先級定義,[0,31]區間,即32級,優先級用于排程,CPU根據這個來決定先運作哪個程序和任務。
#define OS_PROCESS_PRIORITY_HIGHEST 0 //程序最高優先級
#define OS_PROCESS_PRIORITY_LOWEST 31 //程序最低優先級
#define OS_TASK_PRIORITY_HIGHEST 0 //任務最高優先級,軟時鐘任務就是最進階任務,見于 OsSwtmrTaskCreate
#define OS_TASK_PRIORITY_LOWEST 31 //任務最低優先級
為何程序和線程都是32個優先級?
回答這個問題之前,先回答另一個問題,為什麼人類幾乎所有的文明都是用十進制的計數方式。答案掰手指就知道了,因為人有十根手指頭。瑪雅人的二十進制那是把腳指頭算上了,但其實也算是十進制的表示。
這是否說明一個問題,認知受環境的影響,方向是怎麼簡單/友善怎麼來。這也可以解釋為什麼人類語言發音包括各種方言對媽媽這個詞都很類似,因為嬰兒說mama是最容易的。 注意認識這點很重要!
而計算機的世界是二進制的,是是非非,清清楚楚,特别的簡單,二進制已經最簡單了,到底啦,不可能有更簡單的了。還記得雙向連結清單篇中說過的嗎,因為簡單是以才不簡單啊,大道若簡,計算機就靠着這01碼,表述萬千世界。
但人類的大腦不擅長存儲,二進制太長了數到100就撐爆了大腦,記不住,為了記憶和運算友善,程式設計常用靠近10進制的 16進制來表示 ,0x9527ABCD 看着比 0011000111100101010100111舒服多了。
應用開發和核心開發有哪些差別?
差別還是很大的,這裡隻說一點,就是對位的控制能力,核心會出現大量的按位運算(&,|,~,^) , 一個變量的不同位表達不同的含義,但這在應用程式員那是很少看到的,他們用的更多的是邏輯運算(&&,||,!)
#define OS_TASK_STATUS_INIT 0x0001U //初始化狀态
#define OS_TASK_STATUS_READY 0x0002U //就緒狀态的任務都将插入就緒隊列
#define OS_TASK_STATUS_RUNNING 0x0004U //運作狀态
#define OS_TASK_STATUS_SUSPEND 0x0008U //挂起狀态
#define OS_TASK_STATUS_PEND 0x0010U //阻塞狀态
這是任務各種狀态(注者後續将比如成貼标簽)表述,将它們還原成二進制就是:
0000000000000001 = 0x0001U
0000000000000010 = 0x0002U
0000000000000100 = 0x0004U
0000000000001000 = 0x0008U
0000000000010000 = 0x0010U
發現二進制這邊的差別沒有,用每一位來表示一種不同的狀态,1表示是,0表示不是。
這樣的好處有兩點:
1.可以多種标簽同時存在 比如 0x07 = 0b00000111,對應以上就是任務有三個标簽(初始,就緒,和運作),程序和線程在運作期間是允許多種标簽同時存在的。
2.節省了空間,一個變量就搞定了,如果是應用程式員要實作這三個标簽同時存在,習慣上要定義三個變量的,因為你的排他性顆粒度是一個變量而不是一個位。
而對位的管理/運算就需要有個專門的管理器:位圖管理器 (見源碼 los_bitmap.c )
什麼是位圖管理器?
直接上部分代碼,代碼關鍵地方都加了中文注釋,簡單說就是對位的各種操作,比如如何在某個位上設1?如何找到最高位為1的是哪個位置?這些函數都是有大用途的。
//對狀态字的某一标志位進行置1操作
VOID LOS_BitmapSet(UINT32 *bitmap, UINT16 pos)
{
if (bitmap == NULL) {
return;
}
*bitmap |= 1U << (pos & OS_BITMAP_MASK);//在對應位上置1
}
//對狀态字的某一标志位進行清0操作
VOID LOS_BitmapClr(UINT32 *bitmap, UINT16 pos)
{
if (bitmap == NULL) {
return;
}
*bitmap &= ~(1U << (pos & OS_BITMAP_MASK));//在對應位上置0
}
/********************************************************
雜項算術指令
CLZ 用于計算操作數最高端0的個數,這條指令主要用于一下兩個場合
計算操作數規範化(使其最高位為1)時需要左移的位數
确定一個優先級掩碼中最高優先級
********************************************************/
//擷取狀态字中為1的最高位 例如: 00110110 傳回 5
UINT16 LOS_HighBitGet(UINT32 bitmap)
{
if (bitmap == 0) {
return LOS_INVALID_BIT_INDEX;
}
return (OS_BITMAP_MASK - CLZ(bitmap));
}
//擷取狀态字中為1的最低位, 例如: 00110110 傳回 2
UINT16 LOS_LowBitGet(UINT32 bitmap)
{
if (bitmap == 0) {
return LOS_INVALID_BIT_INDEX;
}
return CTZ(bitmap);//
}
位圖在哪些地方應用?
核心很多子產品在使用位圖,這裡隻說程序和線程子產品,還記得開始的問題嗎,為何程序和線程都是32個優先級?因為他們的優先級是由位圖管理的,管理一個UINT32的變量,是以是32級,一個位一個級别,最高位優先級最低。
UINT32 priBitMap; /**< BitMap for recording the change of task priority, //任務在執行過程中優先級會經常變化,這個變量用來記錄所有曾經變化
the priority can not be greater than 31 */ //過的優先級,例如 ..01001011 曾經有過 0,1,3,6 優先級
這是任務控制塊中對排程優先級位圖的定義,注意一個任務的優先級在運作過程中可不是一成不變的,核心會根據運作情況而改變它的,這個變量是用來儲存這個任務曾經有過的所有優先級曆史記錄。
比如 任務A的優先級位圖是 00000001001011 ,可以看出它曾經有過四個排程等級記錄,那如果想知道優先級最低的記錄是多少時怎麼辦呢?
诶,上面的位圖管理器函數 UINT16 LOS_HighBitGet(UINT32 bitmap) 就很有用啦 ,它傳回的是1在高位出現的位置,可以數一下是 6
因為任務的優先級0最大,是以最終的意思就是A任務曾經有過的最低優先級是6
一定要了解位圖的操作,核心中大量存在這類代碼,尤其到了彙編層,對寄存器的操作大量的出現。
比如以下這段彙編代碼。
MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @禁止中斷并切到管理模式
LDRH R1, [R0, #4] @将存儲器位址為R0+4 的低16位資料讀入寄存器R1,并将R1的高16 位清零
ORR R1, #OS_TASK_STATUS_RUNNING @或指令 R1=R1|OS_TASK_STATUS_RUNNING
STRH R1, [R0, #4] @将寄存器R1中的低16位寫入以R0+4為位址的存儲器中
程式設計執行個體
對資料實作位操作,本執行個體實作如下功能:
- 某一标志位置1。
- 擷取标志位為1的最高bit位。
- 某一标志位清0。
- 擷取标志位為1的最低bit位。
#include "los_bitmap.h"
#include "los_printf.h"
static UINT32 Bit_Sample(VOID)
{
UINT32 flag = 0x10101010;
UINT16 pos;
dprintf("\nBitmap Sample!\n");
dprintf("The flag is 0x%8x\n", flag);
pos = 8;
LOS_BitmapSet(&flag, pos);
dprintf("LOS_BitmapSet:\t pos : %d, the flag is 0x%0+8x\n", pos, flag);
pos = LOS_HighBitGet(flag);
dprintf("LOS_HighBitGet:\t The highest one bit is %d, the flag is 0x%0+8x\n", pos, flag);
LOS_BitmapClr(&flag, pos);
dprintf("LOS_BitmapClr:\t pos : %d, the flag is 0x%0+8x\n", pos, flag);
pos = LOS_LowBitGet(flag);
dprintf("LOS_LowBitGet:\t The lowest one bit is %d, the flag is 0x%0+8x\n\n", pos, flag);
return LOS_OK;
}
結果驗證
Bitmap Sample!
The flag is 0x10101010
LOS_BitmapSet: pos : 8, the flag is 0x10101110
LOS_HighBitGet:The highest one bit is 28, the flag is 0x10101110
LOS_BitmapClr: pos : 28, the flag is 0x00101110
LOS_LowBitGet: The lowest one bit is 4, the flag is 0x00101110
百篇部落格分析.深挖核心地基
- 給鴻蒙核心源碼加注釋過程中,整理出以下文章。内容立足源碼,常以生活場景打比方盡可能多的将核心知識點置入某種場景,具有畫面感,容易了解記憶。說别人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆诘屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切.确實有難度,自不量力,但已經出發,回頭已是不可能的了。 😛
- 與代碼有bug需不斷debug一樣,文章和注解内容會存在不少錯漏之處,請多包涵,但會反複修正,持續更新,v**.xx 代表文章序号和修改的次數,精雕細琢,言簡意赅,力求打造精品内容。
按功能子產品:
基礎工具 | 加載運作 | 程序管理 | 編譯建構 |
---|---|---|---|
雙向連結清單 位圖管理 用棧方式 定時器 原子操作 時間管理 | ELF格式 ELF解析 靜态連結 重定位 程序映像 | 程序概念 Fork 特殊程序 程序回收 信号生産 信号消費 Shell編輯 Shell解析 | 編譯環境 編譯過程 環境腳本 建構工具 gn應用 忍者ninja |
程序通訊 | 記憶體管理 | 前因後果 | 任務管理 |
自旋鎖 互斥鎖 信号量 事件控制 消息隊列 | 記憶體配置設定 記憶體彙編 記憶體映射 記憶體規則 實體記憶體 | 總目錄 排程故事 記憶體主奴 源碼注釋 源碼結構 靜态站點 | 時鐘任務 任務排程 排程隊列 排程機制 線程概念 并發并行 CPU 系統調用 任務切換 |
檔案系統 | 硬體架構 | ||
檔案概念 索引節點 挂載目錄 根檔案系統 字元裝置 VFS 檔案句柄 管道檔案 | 彙編基礎 彙編傳參 工作模式 寄存器 異常接管 彙編彙總 中斷切換 中斷概念 中斷管理 |
百萬漢字注解.精讀核心源碼
四大碼倉中文注解 . 定期同步官方代碼
鴻蒙研究站( weharmonyos ) | 每天死磕一點點,原創不易,歡迎轉載,請注明出處。若能支援點贊更好,感謝每一份支援。