contributed by <tina0405
>
github: tina0405/raspberry-pi3-mini-os
item | specfication |
---|---|
SoC | Broadcom BCM2837 |
CPU | 1.2 GHz 64-bit quad-core ARM Cortex-A53 |
GPU | Dual Core VideoCore IV® Multimedia Co-Processor; Open GL ES 2.0; hardware-accelerated OpenVG; 1080p60 H.264 high-profie decode |
記憶體 | 1GB LPDDR2(和 GPU 共享) |
視訊輸出 | Composite RCA; HDMI |
音訊輸出 | 3.5 mm jack; HDMI(1.3 & 1.4) |
儲存 | microSD |
USB | USB 2.0 x 4 |
Ethernet | 10/100 RJ45 |
Wireless | 802.11n |
Bluetooth | Bluetooth 4.1; Bluetooth Low Energy(BLE) |
GPIO | 40-pin 2.54 mm (100 mil) |
Raspbian 是基本官方提供的作業系統我會選擇這個是因為想利用它內部提供的 bootcode.bin 和 start.elf 幫助我完成 bootloader 階段,好讓心力放在 kernel 實作部份。
參考 Ahmed El-Arabawy 的 Embedded Systems: Lecture 7 (page 46-47)
首先當我們將板子 power on 後, ARM 的處理器目前是 off 的狀態,而 SDRAM 是 disable 的狀態, 而是利用 GPU 來進行 start booting
第 1 階段 bootloader
第 2 階段 bootloader
他做兩件事
第 3 階段 bootloader
是 start.elf, 讀 kernel image(kernel.img), configuration 檔 (config.txt), 和 kernel command line 參數 (cmdline.txt), 把這些都載入記憶體然後再喚醒 ARM 核心img
檔
df
顯示可使用之檔案儲存空間及檔案數目/dev/sdf1
/dev/sdf
,避免再運行過程中有其他的寫入
dd
: 意為 data description, 能夠將輸入寫到標準輸出中
if
= input file ;of
= output fileof
應該要填入 SD 卡的位置 of=/dev/sdf在 SD 安裝完後 raspbian 後先別急著把其他除了 bootloader 的檔案刪掉,我們可以先用原本的 kernel 測試 serial port 是否正常能使用。
因為使用實驗室的 raspberry pi, 在一年前廠商所附的 USB 轉 Serial port 線被學長燒掉,目前是用同類型的 PL2303HX 晶片代替, 雖然從 6 pin 變 4 pin, 但如果只是要連接 Serial port 其實很夠用。
晶片上和板子上的 TX 接 RX, RX 接 TX
在 boot/config.txt 文件中打開 enable_uart=1
(如下)
改好後,拿出 SD 卡插到板子上,接上 serial port, hdmi, 開啟電源
電腦端下指令和 ttyUSB0 做傳輸,就成功了!
接下來的內容我會參考兩份 github 的開放資源做學習, 瞭解其操作後,接著再去針對我的 kernel 做進一步的設計
程式碼放在 我的github 上,首先我們需要一個 linker script 去幫我們放置以下的程式,我們先定一個 .text.boot
段, 之後有關開機的 initial 內容就放置在這。
and x0, x0,#0xFF
, 如果是 Core0 就抓出來做初始化,因為初始化只會做一次, 其他的處理器就去 proc_hold
裡等待。master label
後是清空 bbs 段config.txt
kernel_old=1
規定 kernel image 被載入 address 0.disable_commandline_tags=1
GPU 不會傳遞任何command line 的參數給 booted image。-Map
參數能讓我們了解其配置,指令放至 Makefile 裡了。
寫一個 Makefile 能夠加速開發流程, 裡面的 $(wildcard *.c)
是取出當前目錄的 .c 檔,$(wildcard *.s)
是取出當前目錄的 .s 檔,好將他們都編成 .o 檔,再將全部的 .o 檔 link 起來。
搬進 SD 卡的 FAT32 的第1分區 /boot, 此時可以留下前面提到的開機流程需要的幾個檔案即可。
程式碼,延續上一篇 hello world!
這次來控制 4 顆核心,首先我們不再把其他三顆核心放到 proc_hold
,而是讓他們進入 setup_stack
, 在這至少要確保不會 override 核心的 image 檔,用 #LOW_MEMORY
先給 4MB , 而 stack pointer 的配置如下:
boot.S 的 memzero
只需收兩個參數,一是開始地址,二是所需空間。
memzero
subs
: set flag, 通常下一步會去比較 Z flagb.gt
: 1100 = GT - Z clear, and either N set and V set, or N clear and V set (>)xzr
: 是設計來取出零常數量的暫存器,通常用 wzr/xzr 來表示kernel.c
'\0'
,不然會停止不了,只有 core 0 會做 uart_init(),因為硬體只有一個,其他核心則是先以 delay 的方式做等待,避免互相搶佔 uart 硬體資源,之後如果能實作 mutex 就可以先把資源鎖住,等用完再釋放。以下是結果
程式碼首先在暫存器沒有任何設定之下先單純測試 exception level,但在 kernel 內至少要有簡易的 print 可以將結果印再螢幕上 , 拿了 gnu 的 printf 來做輸出,文中提到只要將 putc
放入函式中 init_printf(NULL,putc)
即可利用, 如果照以上程式碼做的話,會得到 exception level
: 3
每個支持 ARM.v8 architecture 的 ARM 處理器,都有 4 exception levels(EL).
EL0
通常 user process 應該要不能拿到別的 process 的資料,為了達到這些行為, 作業系統會將 user process 設定在 EL0, 但在這個 exception level, process 可以擁有自己的虛擬記憶體, 不能用指令去改變虛擬記憶體的設定, 所以為了確保 process 獨立,作業系統要準備分開的虛擬記憶體空間給每個 process ,而且在處理 user process 前,處理器應該要設定成 EL0EL1
作業系統層級,可以拿到控制虛擬記憶體設定的那些暫存器, 也可以拿到系統暫存器EL2
給 host OS 使用 ,guest OS 只能使用 EL1
.EL3
hardware level.利用 CurrentEL
暫存器,來瞭解目前的 exception level, 但因為有 2 個保留 bit 在右邊,所以右移2個
然後再在 C 中使用, 這時候已經可以把 printf 拿來用了。
在 ARM 的架構裡, 在沒有更高 level 的軟體支援的前提下,不能增加程式自己的 exception level ,其實這個設定是有原因的,如果每個程式都可以任意改變 EL 設定的話,這樣他就有可能拿到別支程式的 data。 很重要的一點是,Current EL 只有在例外產生的時候才能改變,但這種情況只有在執行違法指令(例如在嘗試拿取不存在的記憶體位置,或除以 0) 也是有 application 可以故意執行 svc
指令來產生例外。 硬體產生的中斷也可被認為是一種特別形式的例外。 然而例外被產生跟著以下步驟發生(這邊所定義的例外,是處理 EL n 的情形)
eret
指令. 這個指令重新儲存處理器狀態(從 SPSR_ELn
暫存器裡取得新狀態),然後再從 ELR_ELn
裡拿到地址恢復執行。程式碼,一般而言作業系統並沒有義務切換到 EL1,但是在 EL1 讓我們有特權執行一些 OS 的任務
先關掉 MMU, MMU 會在 page table 設定好後,再次打開,現階段先關掉, 而 sctlr_el1
是可以被大於等於 EL1 層級的 exception 拿到, 而暫存器 0 的位置是控制 MMU 參考 ARMv8 手冊 p.2654,所以才會用 #define SCTLR_MMU_DISABLED (0 << 0)
和#define SCTLR_MMU_ENABLED (1 << 0)
來定義開關
我們其實不會用到 hypervisor. hcr_el2
(Hypervisor Configuration Register EL2) 雖然沒有用到但還是要給予基本的設定, 因為他牽動到 EL1 的例外狀態,執行的狀態應該要是 AArch64
而非AArch32
,而這邊就是在做 configure 的設定.
scr_el3
是負責控制安全設定。例如:他控制更低階層執行安全或不安全狀態。 他也可以控制 EL2 執行狀態, 他讓 EL2 可以執行 AArch64 的狀態,而且所有比 EL2 低的 exception levels 將會不安全,可參考 ARMv8 手冊 p.2648, NS-bit(non-security) 值給 0 的話,有些 TLB 指令是不能用的。
spsr_el3
暫存器的設定就如同前面所說,能設定處理器的狀態,然後再利用 eret
指令重新儲存處理器的狀態。
#define SPSR_MASK_ALL (7 << 6)
來關閉中斷,再利用 #define SPSR_EL1h (5 << 0)
來決定後面 3-bits 的 exception level, 哪個例外的 stack pointer, 如下:
elr_el3
裏面放置的地址是在 eret 指令執行後要跳轉回去的地址, 這邊我們先把 el1_entry
lebal 的地址載回去, 告訴他重新設完處理器後要回到 el1_entry
,也就是原本程式初始化的那段。
現在在回頭將 CurrentEL
暫存器的值印出來,結果為exception level
: 1
http://jasonyychiu.blogspot.com/2017/11/interrupt.html
程式碼中用任意鍵切換兩個 timer 1 和 timer 3 的中斷, 在 ARMv8 架構中,中斷被視為是例外的一種,以下介紹 4 種類型的例外:
Synchronous exception Exceptions
: 這種例外通常是被當前執行的指令造成。例如: 你可以使用 str 指令去儲存一些 data 在不存在的記憶體空間。 在這個 case 裡,一個同步的例外會被產生,同步例外會被用來產生軟體中斷,是藉由 svc 指令來產生的軟體中斷(也稱作 synchronous exception),將在後面提及。
IRQ (Interrupt Request)
: 這個是正常中斷,他們是非同步的,意思是他們與當前指令無關,和 synchronous exceptions 比起來,他們並不是被處理器產生的,而是透過外部硬體去控制。
p109
FIQ (Fast Interrupt Request)
: 這個類型的中斷叫作快速中斷, 是為了例外優先順序而存在的。如此一來他可以分辨這個例外是正常還是快速。 快速中斷再發出第一個訊號後,將由一個 separate exception handler 來處理。Linux 裡沒有使用 Fast Interrupt 這個結構。
SError (System Error)
: 系統錯誤也像 IRQ 和 FIQ,是由外部硬體發出非同步中斷。但又不同於 IRQ 和 FIQ,SError 會指出一些錯誤條件。 例子
每個例外類型都需要有自己的 handler。 個別的 handlers 應該要被定義再不同的執行狀態,這裡有4個執行狀態,如果執行在 EL1 就必須了解它們:
EL1t Exception
是 EL1 的 stack pointer 是和 EL0 一起共享時。這個會發生在 SPSel 暫存器值是 0 時。EL1h Exception
是發生在 EL1 貢獻 stack pointer 是分配給 EL1,這個會發生在 SPSel 暫存器值是 1 時,也就是我們要使用的狀態。EL0_64 Exception
EL0 執行在 64-bit 模式EL0_32 Exception
EL0 執行在 32-bit 模式一般而言, 我們應該會有 16 種狀態,因為一種例外有 4 種執行狀態(4*4)。有一種特別的結構,他會掌握所有 handler,我們稱之為 exception vector table 或是 vector table。 這個表可以被想像成例外向量的矩陣,每個 exception vector (or handler) 都有一組連續的指令負責處理特定的例外, 因此在手冊裡提到每個例外最多都會佔據 0x80 bytes (手冊 p.1876)。 這些記憶體空間雖然不多,但開發者還是可以讓程式從例外向量跳轉去其他記憶體空間。
align 錯誤的例子,有些程式如果沒 align ,會造成錯誤。
首先先寫一個可以進入 exception vector 的 macro,至於這裡 .align 7
的原因, 是因為每個例外長度都為 0x80 bytes, 換算成 10 進位就是 128,而 2^7 也恰好等於 128
處理器並不知道例外向量表在哪,所以我們必須把 vector table 的地址存入暫存器 vbar_el1
(全名,Vector Base Address Register)
vectors
的 label 那樣:exception level = 1
, 所以 lower exception level = 0
,因此順序如下:PBASE
為 0x3F000000
,一開始覺得很奇怪手冊上明明提到 base address 為 0X7E00B000
, 為什麼會變成 0X3F00B000
,後來在規格書的前面找到一段話:
在文件中提到,BCM2835 系統中的 timer 0 和 timer 2 是留給 GPU 使用的,而我們真的可以用到的是 timer 1 和 timer 3,所以就用程式碼練習操控 timer 1 和 timer 3 的切換。
extern char choose
是在 kernel 裡給使用者使用的參數,而 ENABLE_IRQS_1
放置各個 timer
應該要設置的 bitvector table
會進入 el1_irq
的中斷嗎?這時候 jump 到 el1_irq
的 label 時就會執行 handle_irq
IRQ_PENDING_1
(0-31 bit)這個暫存器是掌握了中斷的狀態,我們可以利用這個暫存器去檢查 interrupt 是由哪個 timer 產生的。注意多個中斷是可以同時被等待的。這邊我讓各個中斷印出自己所屬的 timer。參考:洪文彬學長的論文–-嵌入式微核心系統之設計與實作
行程管理者是用來管理系統中的行程。 目前暫定此系統會處理 Process 及 Thread。
行程管理區塊(PCB)在系統中是很龐大的資料結構,也包括以下機制:
論文中的排程的, 行程就緒佇列(Ready Queue)共有 64 個,且系統中行程的優先權共有 64 種。一個行程的優先權以無號整數(Unsigned Integer)(0 ~ 63)來表示,且較小的值表示高優先權,較大的值表示低優先權。
Minix 使用多重佇列演算法,先說明 Minix 將任務分為四個層,而在佇列中要考慮的就是,第二層的I/O 任務行程,伺服器(服務者)行程在第三層,使用者行程在第四層
而排程程式為這個層次準備了三個可執行行程的佇列,Rdy_head 指向佇列第一個元素 Rdy_tail 指向佇列最後一個元素,每當行程從暫停被喚醒時,便將其置於末端(Rdy_tail 有助於此項操作),每當行程被暫停時,就將其從佇列中移除,此排程演算法簡單來說就是會選取最高優先權且非空的第一個行程,如果所有佇列皆空,則 idle
pick_proc 檢查每個佇列,先對 TASK_Q 做測試,如果準備好了,就設定 proc_ptr 並回傳,但如果 TASK_Q 和 SERVER_Q 都為空, USER_Q要做時不只需要將 proc_ptr 並回傳,還需回傳 bill_ptr,意思是向使用者索取 CPU 的費用,若無佇列準備則回到 idle,因為佇列在任何變動下皆會影響下一步,因此需要呼叫 pick_proc 來重新設定 proc_ptr
使用者任務在受限的時間中進行,因此為循環式排程,而其他如檔案系統或 I/O 任務則不用受時間限制,因為他相信作業系統是安全的,做完會停止下來
這是原作者程式碼,但我想將排程改成上述 Minix 之結構, 一開始的程式碼保留 4MB 放置 kernel image,將 stack pointer 放置 kernel image 所佔空間的最低處 0x00400000。
The X30 general-purpose register is used as the procedure call link register. <ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile>
中斷處理,每次重新 _schedule(),以下是 ARM v8的中斷處理
其 pState 暫存器 bit 所代表意義:
回到實作層面將資料結構從靜態矩陣改成動態的linked list 排程演算法選用多重佇列(Multiple Queue)。
我們將會將使用者的行程定為 EL0,這限制了他們獲得特權處理器的操作,沒有這個步驟,其他技術將沒辦法使用,因為使用者的程式將有可能將安全設定重寫, 但當我們限制使用者程序禁止使用 kernel 的程式,他要怎麼樣使用一些像 print 的簡單程式來使用 UART 呢? 我們會實作一些簡單的 API 來執行,而這些API 被執行時必須將 exception level 提升到 EL1 ,呼叫這些 API 就叫作系統呼叫
系統呼叫,最簡單的定義,就是希望每個系統呼叫都是一個同步例外,如果一個使用者決定要去執行一個系統呼叫,那第一步將是準備所需參數,然後再切換到 svc 指令。 這個指令產生同步的,這種例外是在 EL1 處理的,也就是作業系統處理的程式。 而 OS 會驗證參數,會應要求和執行正常例外 return,並確保在 svc 指令執行完時,恢復 EL0。
只要使用 svc 指令,就可以產生同步例外, 並觸發 vector table 裡的 el0_sync, 因為 system call 是由 user mode 去執行所以是 el_0,#ESR_ELx_EC_SHIFT
被定為 26(右移26), 是因為要去找 exception class,參考 ARMv8 手冊 p.2436
ESR_ELx_EC_SVC64
為 0x15kernel_entry
和 kernel_exit
是相對的兩個函式,在 interrupt 發生後,擔心中間產生對暫存器產生不可預期的結果,因此先將一班常用的暫存器結果存下來,做完中斷離開時再還原。kernel_entry
其中比較特別的是如果是 EL0 的話,要將 sp_el0
先存起來,後來再還回去,因為在樹莓派裡的 sp
是重複使用的,先將 stack point 向地址低的地方移,再將暫存器移入。el0_svc
執行系統呼叫前,會先打開 interrupt
uxtw
b.hs
ret_from_syscall
kernel_exit
kernel_exit
是搭配原作者所使用的 pt_regs 的結構。雖然 lesson5 原本的程式已經達到管理記憶體的,但是 user mode 和 kernel mode 用同一塊記憶體還是有危險性存在,這代表 user 可以任意改寫 kernel 的資料。 除此之外,就算我們能夠確保沒有惡意程式的存在,不停的確認空間是否被佔據,也增加了程式的 overhead,這也就是為什麼我們需要 Virtual memory management。
這個部份將會著重在虛擬記憶體,當進程 (process) 要求一塊新的記憶體空間, MMU 將會啟動,並且利用虛擬記憶體映射一塊實體記憶體給進程。
記憶體轉換的處理是始於 PGD Table, 而 PGD Table 的地址被存入 ttbr0_el1 暫存器。 每個進程都有一份 page table 的副本,包含 PGD 地址,當 context switch 發生時, 下一個進程的 PGD 地址將會被載入 ttbr0_el1 暫存器。
原作者 s-matyukevich github 上的精美圖示
接下來,MMU 會使用 PGD 指標和虛擬記憶體去計算相對應的實體記憶體,所有的虛擬記憶體地址都只使用 48 bits,在做轉換時, MMU 被分為4個部份。
可是如果當要映射的記憶體空間不是 4KB, 而是 2MB 的話, 會少一個 level, 且把原本給 PTE 的空間給 offset, 讓 offset 從 12 bits 變成 21 bits, 用 21 bits 解碼 2MB的空間。
但我們可能都很好奇, MMU 是如何預先知道最後是要指向 PTE 還是還是 PMD? 其實是這是 page table 裡的一個重要東西,叫作描述檔(descriptor)
這裡的關鍵是,描述檔裡的地址,會指向下一個對齊的 page, 也就是說前面 12 bits 都可以留給 MMU 做使用,先定為 0。
bit-0: 是 valid bit, 簡單來說就是 MMU 會去看這份描述檔的 valid bit,來判斷這份 descriptor 是否有效, 因此必須填入1, 但如果是 0 就會發出同步例外,當作例外錯誤來處理(後面會講到如何分配新的 page 和新的描述檔來應對)
bit-1: 這個 bit 指出是否現在這個 descriptor 指出下一個在這個層級中的 page table (也被稱作 "table descriptor")或是實體 page(這種 descriptors 稱作 "block descriptors")。
bits[11:2]: 這些 bits 是被 table descriptors 忽略的。 因為 block descriptors 包含一些特徵,例如 mapped page 是否可被可被快取或可被執行。
bits[47:12]: 這是存地址用的,只有[47:12]需要被儲存,其他則是先為 0。
bits[63:48]: 其他特性.
每個 block descriptor 都包含了一些特性去控制虛擬記憶體, 然而這些特性有重要的一部份不是在 descriptor 中配置的。取而代之, ARM 處理器允許他們儲存一些重要的資訊在一個特殊的暫存器 mair_el1
,這個暫存器包含 8 個部份,每個部份 8-bit 長,而 descriptor 也不會全部都拿,只拿對自己有幫助的部份,只提供 2 bits 當作 mair 部份的參考,詳細的 mair_el1
暫存器資訊在 ARMv8 手冊 p.2609
這個代表
所以 AttrIndex[2:0]
決定了選擇哪一區段的 Attr,像程式碼 n = 001,就是選擇 Attr1
而 8-bit 代表的意思,則由上下兩表組成如果是01000100就是 normal memory,inner non-cacheable 和 normal memory,outer non-cacheable
在 mmu 打開後,每個程式都只能拿到虛擬記憶體空間,而不能拿到實體記憶體, kernel 和 user 如果不要拿到同一塊記憶體空間的話,有一種方法是每次載入 kernel 都重新載入 pgd 暫存器,但這個方法的成本很高, 會讓 cache 失效, 另一個方法是把地址分為兩部份, 3G 給 user,1G 給 kernel,另外 Armv8 有一個特殊設計, 讓 user/kernel 可以達到 address split。
有兩個暫存器可以儲存 pgd 的地址: ttbr0_el1
和 ttbr1_el1
,前面提到,我們其實只用到 64 bits 裡的 48 bits,所以前面 16 bits 可以用來區分 ttbr0 及 ttbr1 轉換的進程,如果高位 16 bits 都為 0 則 pdg 的地址就存入 ttbr0_el1, 但如果地址起始是 0xffff 就把 pdg 的地址就存入 ttbr1_el1, 這個架構還能確保進程在 el0 上執行,決對不會動到虛擬記憶體 0xffff 開頭的內容。
https://developer.arm.com/docs/100941/latest/memory-attributes
指定排程優先權大小
stack 大小
stack 位置
__detachstate
: 此參數有兩個選擇,一為PTHREAD_CREATE_DETACHED
分離線程(結束後,線程資源直接回收,且不能同步),二為 PTHREAD_CREATE_JOINABLE
非分離線程(可同步,資源的回收,由線程來做), 一旦設為分離線程,則不可改為非分離線程
__schedpolicy
: 標示新線程的排程方法,表示新线程的调度策略,SCHED_OTHER(正常、非即時)、SCHED_RR(即時,輪詢)和SCHED_FIFO(即時)三种,預設為SCHED_OTHER,即時的調度只有在 Kernel mode 有效
__schedparam
: 目前只有一個參數,sched_priority
預設為 0,為排程時的優先權,有定義 sched_get_priority_max 和 sched_get_priority_min 能得到系統最大和最小優先權。
__contentionscope: 目前有兩個選項PTHREAD_SCOPE_SYSTEM (所有線程一起競爭 CPU 時間)和 PTHREAD_SCOPE_PROCESS(只與同個進程競爭裡的 CPU 時間)。
value_ptr 會回傳值給 pthread_join 的第二個參數, 但大部份例子是 NULL,用途為?
A: 其實應該先解析以下 join 的參數二
pthread_join
pthread_self
pthread_equal: 不需用到 system call
thread_yield ( void );
呼叫此函式的執行緒暫時放棄 CPU 執行權。
int pthread_detach ( pthread_t thread , **value_ptr );
此函式將執行緒狀態設為無法被等待執行結束 (Non-joinable) 。之後若此執行緒
執行結束, value_ptr 可用來存放執行緒的回傳值。
int pthread_attr_init ( pthread_attr_t *attr );
初始化執行緒屬性資料為預設值。
int pthread_attr_destroy ( pthread_attr_t *attr );
刪除執行緒屬性資料。
int pthread_attr_setdetachstate ( pthread_attr_t *attr, int detachstate );
依照 detachstate 的值來設定執行緒屬性 attr 是否被設定為可被等待執行結束
(Joinable) 或不可被等待執行結束 (Non-Joinable) 。
int pthread_attr_getdetachstate ( const pthread_attr_t *attr, int *detachstate );
得到執行緒屬性資料中是否可被等待執行結束 (Joinable) 的值。
int pthread_attr_getstackaddr ( const pthread_attr_t *attr, void **stackaddr );
得到執行緒堆疊的位址。
int pthread_attr_getstacksize ( const pthread_attr_t *attr, size_t *stacksize );
得到執行緒堆疊大小。
thread_mutex_lock
int pthread_cond_init ( pthread_cond_t *condition, pthread_condattr_t *attr );
使用條件變數屬性 attr 來初始化條件變數 (condition variable) 。
int pthread_cond_destroy ( pthread_cond_t *condition );
刪除修件變數。
int pthread_cond_signal ( pthread_cond_t *condition );
叫醒停頓 (Block) 在該條件變數的執行緒中的一個。
int pthread_cond_broadcast ( pthread_cond_t *condition );
叫醒所有停頓 (Block) 在該條件變數的執行緒。
int pthread_cond_wait ( pthread_cond_t *cond, pthread_mutex_t * mutex);
呼叫此函式的執行緒會停頓 (Block) 在該條件變數中,如果 mutex 已經被該執行緒上鎖,則 mutex 會自動解除。
int pthread_cond_timewait ( pthread_cond_t *cond, pthread_mutex_t *mutex, const
struct timespec *abstime );
呼叫此函式的執行緒會停頓 (Block) 在該條件變數中,且己經上鎖的 mutex 會被解除。但此執行緒停頓 (Block) 有時間限制,當經過了由 abstime 所設定的時間之後,該執行緒會繼續執行,並且會重新上鎖 mutex 。
int pthread_condattr_init ( pthread_condattr_t *attr );
初始化關連到條件變數的屬性 attr 。
int pthread_condattr_destroy ( pthread_condattr_t *attr );
刪除關連到條件變數的屬性 attr 。
ksym.o
如果掛載已存在 symbol 會拒絕此註冊。incom
/ 卸載 rmcom
msr
: Load value from a system register to one of the general purpose registers (x0–x30)and
: Perform the logical AND operation. We use this command to strip the last byte from the value we obtain from the mpidr_el1
register.cbz
: Compare the result of the previously executed operation to 0 and jump (or branch in ARM terminology) to the provided label if the comparison yields true.b
: Perform an unconditional branch to some label.adr
: Load a label's relative address into the target register. In this case, we want pointers to the start and end of the .bss region.sub
: Subtract values from two registers.bl
: (Branch with a link) perform an unconditional branch and store the return address in x30 (the link register). When the subroutine is finished, use the ret instruction to jump back to the return address.mov
: Move a value between registers or from a constant to a register.blr
: Branch with link to register, calls a subroutine at an address in a register, setting register 30 to pc + 4lsl
: Logical shift left (register).[x10, #0x10]
: signed offset 從 x10 + 0x10的地址取值[sp, #-16]!
: pre-index 從 sp-16 地址取值,取值完後在把 sp-16 寫回 sp[sp], #16
: post-index 從 sp 地址取值,取值完後在把 sp+16 寫回 spaffinity
MPIDR
image 大小
CACHE LINE
PAGE size 4096 的根據?
Learning operating system development using Linux kernel and Raspberry Pi
https://www.datadoctor.biz/data_recovery_programming_book_chapter3-page19.html
http://lexra.pixnet.net/blog/post/303910876-■-master-boot-record-(mbr)-以及-fat32-解析
libary