鴻蒙是一個面向場景的智能操作系統。很多人剛開始把它與Linux相比,這是不對的,首先Linux只是一個內核,在Linux之上我們開發者還需要做很多的操作,比如驅動開發和應用開發才能讓用戶能夠正常的操作。鴻蒙的LiteOS才是用來對標Linux的,值得注意的是LiteOS和Linux是一樣的,都是宏內核而不是之前宣傳的微內核,鴻蒙的微內核可能要到過段時間才會發布。那么鴻蒙對標的產品是什么呢?是安卓和Windows。這也讓安卓特別的難受,因為與它正在開發的Funchsia系統在地位上有較大的吻合,都是面向IOT設備的操作系統。我們可以看看這張圖,在鴻蒙的整個框架中內核只是占比較小的一部分,而內核這部分里還分了兩個內核子系統:Linux和LiteOS。所以內核位于鴻蒙就像心臟位于人體,非常重要但占比很小。如果把內核類比成系統就好像把心臟類比成人一樣,不合適。
系統基本能力子系統集:為分布式應用在 HarmonyOS 多設備上的運行、調度、遷移等操作提供了基礎能力,由分布式軟總線、分布式數據管理、分布式任務調度、方舟多語言運行時、公共基礎庫、多模輸入、圖形、安全、AI 等子系統組成。其中,方舟運行時提供了 C / C++ / JS 多語言運行時和基礎的系統類庫,也為使用方舟編譯器靜態化的 Java 程序(即應用程序或框架層中使用 Java 語言開發的部分)提供運行時。
基礎軟件服務子系統集:為 HarmonyOS 提供公共的、通用的軟件服務,由事件通知、電話、多媒體、DFX、MSDP & DV 等子系統組成。
增強軟件服務子系統集:為 HarmonyOS 提供針對不同設備的、差異化的能力增強型軟件服務,由智慧屏專有業務、穿戴專有業務、IoT 專有業務等子系統組成。
硬件服務子系統集:為 HarmonyOS 提供硬件服務,由位置服務、生物特征識別、穿戴專有硬件服務、IoT 專有硬件服務等子系統組成。
剛剛講了一個宏內核和微內核的概念,那什么是宏內核什么是微內核呢?
這一張圖就是用來區分宏內核和微內核的,中間有一條橫線,橫線上面是運行在應用的,叫應用(用戶)態,應用態是受操作系統限制的,只能在指定的內存空間中運行。橫線下面是運行在內核里面的,也叫內核態。最左邊的是宏內核,中間是微內核,最右邊的是混合內核。那么宏內核和微內核有什么區別,其實就是劃分的問題,簡單的說,宏內核把大大小小的事情都劃分到內核里面去處理,比如VFS虛擬文件系統,系統調用,文件系統等等統統塞進內核,只有應用程序才運行在用戶態。而微內核則是相反,除了核心的內核,統統扔到用戶態里面去執行,這兩個是完全的極端,這也帶來明顯不一樣的效果。首先比較明顯的不同就是微內核比較容易擴展,且內核驅動之間互不干擾,不會出現像宏內核那樣,一個崩都得死的現象。微內核里面比較有名的就是我們的國貨之光RT-Thread。那么比如說Mac OS X使用的就是比較中庸的混合內核,讓驅動程序員來決定這些東西到底放在用戶態還是內核態。這樣在保護內核同時給開發帶來比較大的靈活性。有些功能它需要用極致的處理速度,并且程序上穩定不會太大改變,那么就可以把這塊的驅動程序沉到內核態,有些東西它不需要很快的執行速度,且因為業務問題需要經常的變動,那么可以扔到用戶態。
那既然Linux是宏內核,LiteOS搞了半天還是宏內核,為啥鴻蒙還要搞一個LiteOS呢?
Linux的強大在于它支持的硬件非常多,但是它過于龐大,啟動慢、耗電,這些缺點導致它不適合用在資源比較受限的物聯網硬件設備領域。
Liteos-a為物聯網而生,與其他RTOS(實時操作系統)不同,LiteOS支持MMU,支持內核/APP空間隔離、支持各個APP空間隔離,系統更健壯;啟動快,省電。等下我們可以稍微體驗一下他開機能有多快,快到我都覺得他是關機其實只是待機。
除了Liteos-a,還有一個Liteos-m,后者運行在沒有MMU的芯片上,也就是運行在MCU上,就是傳統的單片機。
講回鴻蒙系統,鴻蒙有啥不一樣的呢?官方的回答是:作為面向未來的全場景分布式OS:它具有多端統一OS、硬件虛擬化互助(也就是分布式)以及一次開發多端部署的優點。
什么叫多端統一OS ?鴻蒙在開發者大會上提出1+8+N的一個硬件生態理念,就是圍繞著以手機為中心,開展8個領域的華為自研產品,包括 PC、平板、車機、運動健康、穿戴、AR/VR、智慧大屏、智能音響。同時打造大量的IoT設備,也就是里面的N,比如:耳機,打印機,電子秤等等。而這些設備都將使用鴻蒙OS來開發。全部使用鴻蒙OS就能為第二步:硬件虛擬化互助帶來可能性。
什么叫硬件虛擬化互助,即分布式任務調度?我們先想象這么一個場景:假設我們開車使用車上的車載導航到達目的地附近,接著要使用手機導航到目的地需要幾個步驟:首先打開手機里的導航軟件-》輸入目的地-》選擇步行導航-》開始導航。現在我們覺得這些操作合情合理,但是如果手機和車載都搭載了鴻蒙系統,這個步驟將會縮短成一步:拿出手機就可以繼續導航,真正實現無縫銜接,而這一過程里車載上的導航將會移到手機上,并顯示。這就是為應用提供多設備協同的能力。
那這個分布式架構有什么優勢?以及它是如何實現硬件虛擬化互助的這個功能呢?
分布式架構在設計之初就考慮了多設備移植和部署的這個需求,這也就剛剛說的第三點好處(一次開發多端部署),它是分布式架構設計帶來的一個結果之一,只要一次開發就可以在上面所說的1+8+N上跑自己的應用程序。那么在鴻蒙底層也為這種協同工作帶來一些組件上的支持。像我們現在的分立式設備,兩個不同的設備是如何建立連接的?需要用到藍牙或者wifi,以及一些相關的協議。那么這是建立在雙方有藍牙或者wifi的前提下,以及協議必須相同的前提下,如果其中一方不支持,那么它們就不可能建立起連接,所以在開發過程中,你既要跟APP廠家近進行協同,又要跟另外一個硬件廠家進行協同,這樣的成本其實是非常的高昂的。所以鴻蒙的出現首先要降低這種協同上的成本。我覺得也是大家以后會使用鴻蒙比較重要的原因之一。
那么鴻蒙是如何做到分布式智能互聯的呢?這里有一張圖,這張圖模擬了兩個設備之間的互聯情況,我們假設設備1是一個手機,設備2是一個手表。那么這兩個設備是如何連接的呢?鴻蒙提供兩種解決方案,一種就是傳統的物理層連接,也就是通過藍牙-藍牙、wifi-wifi等方式進行連接。一種就是軟總線連接,
什么叫軟總線?傳統的連接方式要求雙方必須要有相同的傳輸設備,比如都有藍牙設備。但是軟總線可以做到使用設備1由藍牙設備發送數據,設備2使用wifi接收,在開發過程中不再需要關心網絡協議差異。鴻蒙
所以我們可以使用軟總線在兩個設備之間快速的通信。在軟總線之上是鴻蒙提供的一個分布式執行框架,這一套框架可以允許我們通過軟總線與多個設備進行連接。同時分布式執行框架也是用來區分數據要發給哪個設備,假如說,我一臺手機連接好幾個智能手表,那么怎么知道我手機上的一張照片是發給哪個智能手表的?就可以在分布式執行框架進行判斷。在往上走是用戶程序框架,這個框架是鴻蒙給APP開發著提供的統一的調用接口,這樣APP開發者就不需要去考慮這個分布式互聯是如何實現的。在開發中,你要運行到設備1和設備2的應用是一起開發的,也就是說你一次就可以開發完兩個設備上的應用。
設備1里還有一個FA和設備2里面還有一個AA,它們分別是什么呢?AA(Atomic Ability)是不帶界面的功能單元,AA是鴻蒙中不可缺少的且不能分割的能力,所以也叫元能力。而FA(Feature Ability)就是帶界面的AA。AA和FA都是由鴻蒙框架去實現的,然后通過統一的接口供開發者使用。那么這些AA和FA是什么呢?這些都是一些簡單的小功能,當我們要去實現一個功能的時候,需要去調用不同的AA或者FA。比如說我要實現一個拍照功能,或者音樂播放的功能就需要調用這兩個AA或者FA。同時不同的設備之前也可以相互調用AA或者FA,也就是跨設備調用。我們可以想象一下,現在的我們開發應用要調用某個設備,比如說攝像頭,那必須是這個設備擁有攝像頭這個功能,所以在開發過程中程序員必須充分了解這點。但是在鴻蒙里它是面向場景的操作系統,開發者無需考慮自己運行的這個設備是否有攝像頭,只需要考慮當前環境下會出現哪些帶有攝像頭的設備然后調用它的AA。
那么實現這些功能的原理還是剛剛說的軟總線和分布式調度。
那么至此北向的應用開發就是這些內容的介紹。現在就講一下
鴻蒙LiteOS和LINUX比較
基礎知識
LiteOS與Linux的啟動區別
LiteOS以上電后會跳轉到reset_vector復位向量表這里,在reset_vector中會執行關中斷、設置ICache、重定位、
然后會看到PAGE_TABLE_SET,這是一個宏,用來設置頁表的 ,設置完頁表之后就會啟動MMU,一旦啟動MMU,CPU就沒辦法通過物理地址訪問設備。只能用虛擬地址
啟動MMU之后初始化棧、然后調用main函數
那Linux在啟動內核的時候做了哪些操作呢?如下圖,可以看出還是非常的相似的
MMU
先舉個例子,編寫兩個測試demo:main_world.c和main_leleen.c
1、main_world.c
#include 《stdio.h》
int main(void)
{
printf(“Hello World!!!!!\r\n”);
}
2、main_leleen.c
#include 《stdio.h》
int main(void)
{
printf(“Hello Leleen!!!!!\r\n”);
}
將其進行編譯得到可執行文件:
arm-linux-gnueabihf-gcc -g -o main_world main_world.c
arm-linux-gnueabihf-gcc -g -o main_leleen main_leleen.c
再將其進行反編譯
arm-linux-gnueabihf-objdump -S -d main_world 》 main_world.txt
arm-linux-gnueabihf-objdump -S -d main_leleen 》 main_leleen.txt
會發現他們的起始地址都是一樣的:0x0102c8
很顯然,這個地址不應該是物理地址,理由有一點是可以確認的:當前我們的應用程序并沒有在開發板子上跑起來。所以這個地址應該是一個虛擬的地址。
上面是通過代碼來查看地址,那如果在硬件上,假設當前RAM運行了多個APP,它們的運行狀態保存在RAM中,此時它們的地址應該各不相同,比如app1的地址是addr4,app2所處的地址是addr1。這些地址是app所處的物理地址。所以在編譯某個app時,需要單獨指定它的鏈接地址,也就是指定它去哪里運行。如下面這張圖,但這是一個不可能完成的任務。因為app眾多且運行時保存在哪個地址是完全不可以預料的。所以這里必須引入虛擬地址。CPU只需要通過虛擬地址就能控制某個應用程序。
除此之外,引入虛擬地址還有一個作用,在資源少的硬件上運行容量大的應用,比如說一個10G的應用在只有512M的RAM上運行。應用程序不可能一次性跑完,它會分段加載,假如說先運行片段1,那么就會先把片段1的程序先加載到RAM上運行,依次這樣操作,當運行到片段5的時候,此時RAM已經滿了, 這個時候,系統會將RAM中先運行的片段1擦除,然后將片段5拷貝到運來片段1的RAM空間上運行。
CPU是如何處理和管理這些虛擬地址,這就是MMU的工作,MMU是硬件,不是軟件。對內存的管理屬于硬件管理,從應用程序的角度看,MMU是完全透明且不被感知的,所以在日常開發中都不會引起注意。
當CPU發出0x0102c8這個虛擬地址的時候,會通過MMU進行分析。MMU通過分析CPU發來的虛擬地址去調用不同的APP。既然MMU會去分析虛擬地址并轉換成物理地址,那么在MMU內部一定存在一個虛擬地址和物理地址的對應關系,這個對應關系就是頁表。
鴻蒙使用的二級頁表,頁表的分析有點麻煩,時間問題我也沒有很仔細的去理解,為了方便后面的理解,我自己暫且把它理解一本新華字典,當CPU發過來虛擬地址的時候,MMU就去翻這本新華字典,先從偏旁部首的目錄開始找,找到了就跳到那一頁查看是哪個地址,以及這個地址有哪些權限。
那如何使用MMU?大致需要以下步驟:在內存中創建頁表-》把頁表基地址告訴MMU-》啟動MMU
所以等下在LiteOS移植前需要先去設置它的MMU的頁表,讓它指向開發板內存的首地址,并且設置大小。這樣就能建立虛擬地址和物理地址的映射,LiteOS就能正確的去訪問物理地址。
最后MMU除了管理虛擬地址和物理地址之外,還有一種功能就是保護內存,防止數據被篡改。比如說我們寫的一個APP,當CPU發出APP的虛擬地址給MMU時,MMU會判斷這個APP是否有權限訪問內核空間,當發現只是一個普通的APP的時候,只可以讓它訪問自己的空間,而不能訪問其他內核空間和其他APP的空間。
在reset_vector_up.S中設置MMU的地方:SYS_MEM_BASE是物理地址,KERNEL_VMM_BASE是虛擬地址,SYS_MEM_BASE點進去SYS_MEM_BASE指向-》DDR_MEM_ADDR,我們需要更改DDR_MEM_ADDR,讓它指向6ull的內存基地址(物理地址)0x80000000
那0x80000000這個值是從哪里來的呢?需要去6ull的芯片手冊查找,同時我們要找到等會要實現的串口物理地址
找到了這兩個值就可以設置頁表了
將PERIPH_PMM_BASE設置為0x02020000,SYS_MEM_BASE設置為0x80000000
但這么設置會有問題,因為LiteOS的映射是段映射,也就是說必須是整M的映射,所以我們將PERIPH_PMM_BASE設置為0x02000000
那么PERIPH_PMM_BASE的物理地址會映射到PERIPH_DEVICE_BASE,打開PERIPH_DEVICE_BASE,以后就可以通過
UART_Type* uart0 = IO_DEVICE_ADDR(0x02020000);來獲取設備的虛擬地址
設置完這些我們就可以來實現串口輸出了
串口輸出的實現
PRINT_RELEASE -》 LOS_LkPrint(los_printf.c)-》OsVprintf(los_printf.c)-》UartPuts(amba_pl011.c)-》UartPutsReg(amba_pl011.c)-》UartPutStr(amba_pl011.c)
amba_pl011.c這是華為用于他們開發板的代碼,要使用它得去修改這個代碼
修改UartPutsReg
STATIC VOID UartPutcReg(UINTPTR base, CHAR c) { UART_Type *uartRegs = (UART_Type *)base; while (!((uartRegs-》USR2) & (1《《3))); /*等待上個字節發送完畢*/ uartRegs-》UTXD = (unsigned char)c; }
設置完這些我們要進行編譯一下,首先先進入liteos_a所在的文件夾下,拷貝一下3518的配置文件到我們的目錄下配置一下
cp tools/build/config/debug/hi3518ev300_clang.config .config
make clean
make -j 8
中斷子系統
當我們點擊觸摸屏等外設的時候,會產生一個中斷,這個中斷是如何到達CPU的呢?在ARM芯片上由一個通用的中斷控制器,叫GIC。這個中斷控制器主要是來決定發過來的中斷哪個優先級更高,發送給CPU,如下圖
這張圖是GIC的詳細流程,里面有很多細節,簡單來看就是下面這張
那么CPU如何去訪問GIC?CPU發送虛擬地址到MMU從而訪問到外設,比如DDR、UART,同樣的,CPU要訪問GIC也必須通過MMU去訪問,通過芯片手冊可以知道GIC的物理地址是0xa00000,這個地址比我們剛剛設置的URAT的物理地址還要低,所以我們等會要去設置PERIPH_PMM_BASE,將它設置成0x00a00000。那么映射多大空間呢?因為我們現在只使用到了URAT和中斷,那么PERIPH_PMM_SIZE可以設置為0x00a00000 - 0x02000000 = 0x1600000
那么有哪些使用虛擬地址去調用GIC呢?
LITE_OS_SEC_TEXT_INIT INT32 main(VOID);
LITE_OS_SEC_TEXT_INIT VOID OsSystemInfo(VOID);
CHAR *HalIrqVersion(VOID);
UINT32 pidr = GIC_REG_32(GICD_PIDR2V2);
#define GIC_REG_32(reg) (*(volatile UINT32 *)((UINTPTR)(GIC_BASE_ADDR + (reg))))
我們要去設置這個GIC_BASE_ADDR
將#define GIC_BASE_ADDR IO_DEVICE_ADDR(0x10300000)
改為#define GIC_BASE_ADDR IO_DEVICE_ADDR(0xa00000)
那中斷產生的時候是從哪里進來的呢?總要有一個入口吧?其實不管什么處理器,都有中斷(異常)向量表,在中斷總入口中,所有的中斷都會調用到這里,也就是說,作為一個管家,它需要分辨出調用它的是哪個中斷,然后調用對應的中斷函數。而這對應的中斷函數由我們提供。如何提供這樣的函數呢?可能是LiteOS在這一塊還不夠完善或者為了減少我們開發者的使用成本,內部機制中還兼容著老款Linux的體系,使用request_irq,也可以使用鴻蒙LiteOS自己使用的OsalRegisterIrq,接下來我們分別講講從Linux和鴻蒙是如何處理中斷的。
先來講講Linux是如何處理中斷的。
在講中斷之前,我們先引入一個生活場景,假如我們現在回家口很渴,想燒水來喝,于是就去廚房燒了水,那么我們有以下幾種等待方式,1、盯著這壺水硬等,我就站在熱水壺旁邊看著它燒開;2、同樣在旁邊,但是我刷起了抖音,等它燒開發出響聲的時候我在過去沖。3、我先去洗個澡,20分鐘后這壺水肯定燒開了;4、回到客廳開始刷劇,等到水燒開的時候它就會發出響聲,同時熱水壺還是智能的,可以通過手機APP通知我燒開了。
這4個操作分別對應著我們程序中的“查詢機制”、“休眠-喚醒機制”、“poll方式”、“異步通知”,除了第一種,剩下的都是我們常說的中斷機制。
了解了中斷機制,我們來看看CPU通常會發生哪些意想不到的異常而產生的中斷: ① 指令未定義 ② 指令、數據訪問有問題 ③ SWI(軟中斷) ④ 快中斷 ⑤ 中斷。那么這些是如何而來的?不管是uboot、Linux還是鴻蒙,都會有像上面那樣的異常向量表,每一個跳轉都對應著一個異常中斷。
Linux中的中斷向量表長這樣(entry-armv.S)
我們引入一個場景,當前有一個進程A,在他運行中產生了一個中斷
那么進程間的切換時候,中斷還會做什么事情呢?1、保存當前A進程的現場,2、執行中斷,3、恢復A現場。這切換過程中保存的數據全部放在棧中。那么當A產生中斷的時候,這個時候能不能再產生一個中斷呢?并不可以。我們假設可以,當A在執行中斷程序的時候,發生了另外一個中斷2,此時要保存中斷程序中的現場,然后跳去2執行中斷函數,當2執行中斷函數的時候又產生了3中斷,……周而復始,保存在棧中的數據會越來越多,最后把整個棧都撐爆了。
除此之外,中斷處理函數還必須快速開始快速結束,如果中斷函數里面有大量的耗時運算,此時進程A會處于一種假死狀態,這種現象是非常難以忍受的。但很顯然,在顯示開發中很難避免會在中斷函數中處理一些耗時的操作,那么應該怎么辦?先把這個問題放一邊。
我們來看看硬件上是如何產生一個中斷的。假設一個GPIO0中接有兩個外部物理設備,設備1和設備2,當設備1從高電平變成低電平的時候產生一個中斷信號,同理,如果設備2也這樣操作,也會產生一個中斷信號,中斷信號通過GIC通用中斷控制器傳給CPU來執行不同的中斷處理函數。那么CPU如何去知道哪個設備觸發了這個中斷呢?CPU可以去GIC中讀取寄存器的值來確定是哪個模塊發生了中斷。假設知道了是A號發生了中斷,A號中斷對應的是GPIO的中斷,但GPIO中又有很多GPIO中斷,假設除了剛剛說的那個,還有GPIO0、1、2都可以產生中斷。那么還要繼續讀取GPIO中的寄存器進行判斷是誰出發了中斷,假設讀到了剛剛說的那個B號中斷。那么對于這個B號中斷呢,它有可能是設備1發出的中斷,有可能是設備2發出的中斷。那么就需要去調用設備1的中斷處理函數和設備2的中斷處理函數來判斷下。
上面可以看到有兩個中斷,一個是A號中斷和B號中斷。它們在Linux中是通過一個irq_desc數組進行保存的,在irq_desc數組中有兩個比較關鍵的點,一個是 handle_irq,一個是 irqaction。
A號中斷的handle_irq主要會做哪些事情呢?首先會讀取GPIO的寄存器確認是GPIO發出來的中斷。當確認了GPIO是由GPIO發出的中斷的時候回去調用GPIO的中斷處理函數handle_irq,那么GPIO是如何知道是設備1還是設備2發出的中斷呢?很簡單粗暴,直接調用兩個設備的處理函數,由它們的處理函數handler來判斷自己有沒有發出中斷信號。
那么還有一個irqaction結構體,這個action結構體是用來做什么的呢? 當調用 request_irq、 request_threaded_irq 注冊中斷處理函數時,內核就會構造一個 irqaction 結構體。在里面保存 handler函數,如果是共享中斷,里面會放有多個設備的處理函數講他們鏈起來。當指定到GPIO處理函數的時候,系統會去irqaction下講設備1和設備2的處理函數都執行一遍。
我們接著往下看會看到handler下還有一個thread_fun。還記得我們剛才留下的一個問題嗎?如果我必須在中斷處理函數中執行很耗時間的操作該怎么辦?我們可以在handler中執行一些最核心的代碼,在這一部分執行的代碼是不可以被再次中斷的,然后講不那么重要但需要時間去運算的操作放在thread_fun去操作。當handler執行完成之后就會去調用thread,當thread判斷有機會執行時候就會去調用thread_fun里面的函數。
上面就是使用request_irq注冊一個中斷處理函數的一個大致概念,下面鴻蒙的部分我們分析它的調用流程即可,因為會發現鴻蒙LiteOS把這一塊處理的十分的簡單
假設系統發生中斷,那么會跳來執行b OsIrqHandler。 在OsIrqHandler中會保存環境然后禁止中斷等等,然后調用HalIrqHandler,在HalIrqHandler中
UINT32 iar = GIC_REG_32(GICC_IAR);//讀取GIC里面的寄存器,就可以知道你發生的是哪一號中斷。
然后調用OsInterrupt(vector);來處理這一號中斷。如何處理呢?
在OsInterrupt里面使用vector取出它的hook函數,這個hook函數是什么呢?在我們注冊的調用OsHwiCreateShared的時候會將hook指向我們的我們的處理函數
總結一下
講了中斷這個概念,是為了解決一個疑問,就是鴻蒙LiteOS是如何實現多任務實時操作系統的,所謂的多任務實時操作系統就是在同一個時間內同時存在多個任務在運行。這里的同時是相對我們人感知的時間,實際上在操作系統中是通過不斷的任務切換來分配CPU資源的,只是這種切換的速度足夠的快,以至于人感受不到。那誰來觸發這個中斷呢?時鐘中斷,時鐘中斷每隔一段時間就會觸發一次來判斷當前任務執行的時間片是否用完,如果用完就切換到下一個任務。當一個任務的時間全部用完時候就從執行隊列里面徹底的刪除。
那么鴻蒙是怎么做到這一點的呢?
打開arm_generic_timer.c
在HalClockInit中使用HalClockFreqRead(VOID)讀取時鐘的頻率。
HalClockFreqRead(VOID)中通過一條協處理的匯編指令來讀取當前的時鐘頻率
mrc:協處理器,MCR指令用于將ARM處理器寄存器中的數據傳送到協處理器寄存器中
獲取到時鐘的頻率之后LOS_HwiCreate(OS_TICK_INT_NUM, MIN_INTERRUPT_PRIORITY, 0, OsTickEntry, 0);來注冊中斷處理函數。OsTickEntry是系統時鐘的滴答中斷,每隔十毫秒產生一次,多任務的調度就依賴于這個系統中斷。
在OsTickEntry中會調用到OsTickHandler();里面會使用OsTaskScan對多個任務進行系統掃描,如果當前任務執行的時間已經用完就使用LOS_ListDelete移除出去0
字符設備
在這里我們用內存來模擬存儲設備,存儲設備就是塊設備,在Linux中字符設備和塊設備,LiteOS也是采用這種模式。
那什么叫字符設備。簡單的說一個字節一個字節進行讀寫操作的設備,不能隨機讀取設備中的某一數據、讀取數據要按照先后數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制臺和LED等
當我們應用想去訪問某個硬件的時候:使用open、read、write、ioctrl等,比如我要操作一盞燈,如下代碼:
int main(){ int fd1,fd2; int val = 1; fd1 = open(“dev/led”,O_RDWR); write(fd1,&val,4); }
這里的open、write是誰實現的,在應用層下還有一層:C庫,由它來實現,它和應用一起都屬于應用層,那么C庫是怎么進入到內核的呢?當我們調用open、read的時候實際上是執行了一個swi val指令,這條指令會產生一個異常(也就是中斷),當產生這個異常時就會觸發內核的異常處理函數。
那么誰來處理這個異常指令呢,在內核里面有一個系統調用接口,它用來處理swi val指令,通過value來判斷中斷發生的原因調用對應的處理函數。比如說應用層調用open的時候傳進來的val值為1,read的值為2 ,那么系統調用就會根據1和2分別調用sys_open,sys_read,這個sys_open,sys_read隸屬于VFS虛擬文件系統這一層
那么當我應用使用open(“/dev/led0”,XXX)的時候,系統是如何判斷“/dev/led0”是字符設備還是后面要說的塊設備呢?
在Linux中我們可以通過ls -l來列出當前目錄下的所有東西,cd到/dev/目錄下,這里存放設備中的驅動程序。
使用ls -l就可以看到這些設備的詳細信息,最左側c、d、b代表的是對應的設備類型:c表示字符設備、d表示目錄、b表示塊設備。系統通過這個來判斷要調用的是那種類型的設備驅動。但是有這個還不夠,區分完是字符設備驅動后,我們怎么才能知道是哪個驅動程序呢?這就需要主設備號,內核通過一個散列表(哈希表)來記錄設備編號,哈希表由數組和鏈表組成,里面存放著一堆驅動程序的結構體file_operation。系統通過主設備號通過一個計算公式獲得這個數組的下標,取出結構體。然后就執行這個file_operation下的驅動程序。那誰來填充這個數組呢?當編寫完驅動程序里file_operation中的所有函數的時候,會使用register_chrdev(主設備號,次設備號,“/dev/led0“,file_operation),向上注冊這個結構體。
塊設備
那么什么叫做塊設備驅動呢?
塊設備對應的就是磁盤、FLASH,你要去讀寫它的時候需要以一個扇區為讀寫單位。對于這個設備、Linux里面采用了一個叫做電梯原理的算法,如下圖,假設先要讀取扇區1的數據、然后到扇區3中寫數據、再到扇區2讀數據。如果使用字符設備驅動,就是這么個流程,此時磁頭要跳轉兩次,效率很低,塊設備將其進行優化,同個扇區先操作,其他扇區的操作先放到隊列里面,等執行完后再跳轉到另一個扇區進行操作,這樣整體的操作就會提高。
上面是硬盤,那么對于FLASH,塊設備是如何進行操作的呢
假設我要去 扇區0中寫數據,寫完之后再去扇區1寫數據,那么使用字符設備會有怎么個操作呢,因為FLASH要先擦除才能夠寫,但擦除是整塊整塊擦除的,那么為了保證其他的數據不被意外擦除,就要先把整塊數據讀到一個buffer里面,修改buffer里的扇區0,然后擦除整塊,再把這塊buffer燒寫進去,寫扇區1也是如此
整個流程即:讀整塊數據到buffer ---》 修改扇區0的數據 ---》 擦除塊里面的數據 ---》 燒寫數據到塊里面 ---》 讀整塊數據到buffer ---》 修改扇區1的數據 ---》 擦除塊里面的數據 ---》 燒寫數據到塊里面
這樣非常的麻煩,那么塊設備是如何去操作的呢?
首先,先把所有操作放入隊列,然后進行優化:讀整塊數據到buffer ---》 修改扇區0和1的數據 ---》 擦除塊里面的數據 ---》 燒寫數據到塊里面
上面的操作和生活中的坐電梯很像,這次先把要上去的通通一層一層的送到,等上電梯的這批人都上樓了,再到各層把要下樓的乘客運下去,這就是電梯算法(Noop)。所以可以先總結一下塊設備的操作是:先把所有的“讀寫”操作放入隊列里面,再其優化后再執行。
然而Linux對IO的操作不止使用了這一種算法,還有電梯算法里還包含了另外兩種常見的算法CFQ 和DeadLine。
那么塊設備驅動的框架是什么呢?如下圖:
假設我現在要通過open、read、write一個普通的文本文件“1.txt”,操作這個文本文件其實最終是操作某個硬件,那對這個文本文件的讀寫怎么轉換成對塊設備也就是硬盤或FLASH等里面扇區的讀寫?這里面肯定需要一個轉換,這里就需要一個文件系統:vfat、ext2、ext3、yaffs等等各種文件系統。
那么ll_rw_block主要是干什么的呢?就是我們上面說的:把“讀寫”放入隊列 -- 》 調用隊列的處理函數,優化執行
Linux源碼分析:
那么Linux是如何寫塊設備驅動程序
1、分配gendisk:alloc_disk
2、分配/設置隊列:request_queue_t
blk_init_queue
3、設置gendisk其他信息
4、注冊:add_disk
HDF分析
再來回顧一下鴻蒙的整個框架,我們剛剛講了應用層框架是如何實現跨平臺調度的,也就是系統基本能力子系統集中的分布式任務調度和軟總線。也講了Linux和LiteOS內核的一些知識點和差異,那么在右下角還有一個HDF(鴻蒙驅動框架),這個HDF是什么呢?它的全稱是Harmony Driver Framework。HDF的提出也是為剛剛所說的一次開發多端部署做準備的,舉個例子,我開發了一套點燈的應用程序,那么我在不同板子上可能使用的是不同的內核,那么肯定就會有不同的庫。HDF就是抽象出這些不同中的共性。
我們可以回憶一下單片機開發的一個場景,當你要去操作某個引腳,那么需要做到的步驟就是:查閱手冊(這是必須操作)-》在程序中指定這個引腳然后初始化它-》賦值。當我們變更了芯片或者板子的時候,引腳的作用可能會發生改變,這時候,你又需要去修改程序中的引腳。這樣是非常麻煩的。在Linux3.x之前的內核源碼中,也是這樣存在大量對板級細節信息描述的代碼。這些代碼充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目錄,這種編寫方式被Linux之父托瓦茲怒斥為辣雞代碼。自此之后,Linux內核引入了設備樹機制以描述計算機板機底層硬件信息。
編輯:hfy
-
Linux
+關注
關注
87文章
11345瀏覽量
210392 -
操作系統
+關注
關注
37文章
6892瀏覽量
123742 -
鴻蒙系統
+關注
關注
183文章
2638瀏覽量
66707
發布評論請先 登錄
相關推薦
評論