xv6-zynq
? ? ? ? 這個(gè)5.1出不了門,正好重溫一下嵌入式操作系統(tǒng)知識(shí),手頭有一塊Xilinx的開發(fā)版,想著試一試將xv6移植到開發(fā)板上。
? ? ? ? 開發(fā)版使用Zynq UltraScale+ MPSoCs EV系列的系列的芯片,型號(hào)為 XCZU4EV-1SFVC784I。ZU4EV 芯片的PS 系統(tǒng)集成了 4 個(gè)ARM Cortex?-A53處理器,2個(gè)Cortex-R5 處理器。其中R5基于 ARMv7R ,是32位的指令集,A53基于ARMv8,是64為指令集,一般R5用于實(shí)時(shí)任務(wù),A53用作高速任務(wù)。xv6 是MIT 開發(fā)的一個(gè)教學(xué)用的完整的類 Unix 操作系統(tǒng),是? Unix Version 6(v6)操作系統(tǒng)的重新實(shí)現(xiàn)。xv6 在一定程度上遵守 v6 的結(jié)構(gòu)和風(fēng)格,用ANSI C實(shí)現(xiàn),并且是基于 x86多核處理器的。包含了進(jìn)程管理、內(nèi)存管理、文件系統(tǒng)等組件,非常適合操作系統(tǒng)的學(xué)習(xí)。這回就嘗試將xv6移植到ZYNQ的A53上運(yùn)行。
? ? ? ? 網(wǎng)上有將xv6移植到qemu虛擬virt上的開源項(xiàng)目xv6-OS-for-arm-v8,就以這個(gè)為基礎(chǔ),主要解決如下幾個(gè)問(wèn)題。(1)開發(fā)環(huán)境搭建。 Zynq使用xilix的vitis開發(fā)與調(diào)試,首先要將xv6引入到vitis中才能進(jìn)一步使用相關(guān)的調(diào)試手段。(2)系統(tǒng)引導(dǎo)程序。主要完成A53處理器異常等級(jí)配置,建立c語(yǔ)言的棧調(diào)用環(huán)境,然后跳轉(zhuǎn)到c語(yǔ)言主程序。(3)串口驅(qū)動(dòng)。在系統(tǒng)運(yùn)行初期提供輔助調(diào)試手段和在系統(tǒng)運(yùn)行時(shí)為控制臺(tái)程序提供輸入輸出。(4)任務(wù)調(diào)度進(jìn)程。(5)SD卡運(yùn)行程序。
一、開發(fā)環(huán)境搭建
?1.vitis
????????嵌入式 Linux 開發(fā),一般需要一臺(tái) Linux 操作系統(tǒng)主機(jī),用來(lái)編譯 u-boot 或者 Linux-kernel。一般習(xí)慣先在windows平臺(tái)安裝虛擬機(jī),然后在虛擬集中安裝Linux主機(jī),鑒于ubuntu Linux 桌面操作 系統(tǒng)的安裝以及配置較為簡(jiǎn)單,選擇了ubuntu 桌面操作系統(tǒng)。這里使用Ubuntu 18.04.2 LTS 64 位操作系統(tǒng)。在操作系統(tǒng)安裝完成后安裝Vivado 軟件(2020.1,集成vitis)。
這樣xv6在zynq下的調(diào)試環(huán)境就搭建完成。其中xv6的entry、start和內(nèi)核頁(yè)表從0X40100000的地址開始,xv6其他部分從0X40300000的地址開始。
二、系統(tǒng)引導(dǎo)程序
? ? ? ? zynq復(fù)位后首先運(yùn)行存儲(chǔ)在rom中的boot程序,由該程序加載fsbl,全稱為first stage boot loader,再由fsbl進(jìn)一步引導(dǎo)uboot或目標(biāo)系統(tǒng)。這里直接使用fsbl加載xv6。
? ? ? ? A53復(fù)位后處于EL3異常等級(jí),操作系統(tǒng)內(nèi)核一般運(yùn)行在EL1,用戶程序運(yùn)行在EL0。在復(fù)位后bootrom,完成ram、flash 的初始化后加載fsbl,fsbl分別加載PL的配置文件(bit),然后加載并跳轉(zhuǎn)到PS部分即xv6。因此xv6首先要返回到EL1,然后初始化C語(yǔ)言的調(diào)用棧環(huán)境。
? ? ? ? armv8使用eret從高一級(jí)的異常狀態(tài)返回,這里需要配置SCR_EL3的NS=0,使EL1可以訪問(wèn)Secure memory,特別是gic控制器。
.text .align 16 .global _start#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED)#define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW#define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (0 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS)#define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) _start: Loop_el:mov x0,0mrs x0, currentELcmp x0, #0xCbeq InitEL3cmp x0, #0x8beq InitEL2cmp x0, #0x4beq InitEL1 InitEL3:adr x0, Loop_elmsr elr_el3,x0ldr x1, =SCTLR_VALUE_MMU_DISABLEDmsr sctlr_el1, x1ldr x1, =HCR_VALUEmsr hcr_el2, x1ldr x1, =SCR_VALUEmsr scr_el3, x1ldr x1, =SPSR_VALUEmsr spsr_el3, x1msr elr_el3, x0eret? ? ? ?初始化C語(yǔ)言的棧幀環(huán)境及跳轉(zhuǎn)。
Init_el0:adr x0, Loop_elmsr elr_el1,x0mov x0,0msr spsr_el1,x0eret entry_0:adrp x0, init_stktopmov sp, x0# clear the entry bss section, the svc stack, and kernel page tableLDR x1, =edata_entryLDR x2, =end_entryMOV x3, #0x00 1:CMP x1, x2B.GT 2fSTR x3, [x1]ADD x1, x1, #0x08BLT 1b 2:BL start? ? ? ? 其中init_stktop、edata_entry、end_entry 均定義在鏈接文件lscript.ld?中,sp=init_stktop為棧頂,棧空間大小為0x1000?,將從edata_entry到end_entry的空間清零,包含了xv6自身引導(dǎo)程序的bss段,棧和內(nèi)核頁(yè)表空間。
? ? ? ? 最后跳轉(zhuǎn)到C語(yǔ)言入口start函數(shù)地址。
三、串口驅(qū)動(dòng)
? ? ? 在xv6啟動(dòng)初始化階段,僅用到了UART0輸出,用于打印內(nèi)核信息,該階段工作在實(shí)地址模式。UART0的基址為0XFF000000.
? ? ? ? UART0輸出相關(guān)寄存器的偏移。
? ? ? ? 當(dāng)判斷TXFULL為空時(shí),就可以向TXFIFO寫發(fā)送數(shù)據(jù)。使用volatile 關(guān)鍵字定義寄存器變量,防止緩存。重寫_uart_putc(int c)函數(shù)如下:
#define XUARTPS_SR_OFFSET 0x002CU /**< Channel Status [14:0] */ #define STDIN_BASEADDRESS 0xFF000000 #define XUARTPS_FIFO_OFFSET 0x0030U /**< FIFO [7:0] */ #define XUARTPS_SR_TXFULL 0x00000010U /**< TX FIFO full */ void _uart_putc(int c) {while(1){uint v_sr=0;volatile uint8 * uartsr = (uint8*)(STDIN_BASEADDRESS+XUARTPS_SR_OFFSET);v_sr =*uartsr;if((v_sr & XUARTPS_SR_TXFULL) == XUARTPS_SR_TXFULL){continue;}else{break;}}/* Write the byte into the TX FIFO */volatile uint8 * uart0 = (uint8*)(STDIN_BASEADDRESS+XUARTPS_FIFO_OFFSET);*uart0 = (c&0x00ff); }? ? ? ?? ?當(dāng)xv6完成內(nèi)核頁(yè)表初始化,開啟mmu后,需要使用虛地址訪問(wèn)UART0,基址經(jīng)TTBR1_EL1中的頁(yè)表映射到地址0xFFFFFFFFFF000000,在EL1虛地址模式armv8對(duì)大于0x0000ffff_ffffffff的地址使用TTBR1_EL1作為頁(yè)表基地址寄存器,不大于的使用TTBR0_EL1,一般情況下TTBR0用于存放用戶空間的一級(jí)頁(yè)表基址,TTBR1存放內(nèi)核空間的一級(jí)頁(yè)表基址。
? ? ? ??UART0接受數(shù)據(jù)使用中斷,通過(guò)GIC的IRQ中斷,中斷號(hào)53。首先配置GIC中斷及對(duì)應(yīng)中斷號(hào)的處理函數(shù),然后開啟UART0接收數(shù)據(jù)中斷。詳細(xì)可參考UG1085.。
#define V_STDIN_BASEADDRESS 0xFFFFFFFFFF000000 void uart_enable_rx () {u32 TempMask ;volatile u32 *uart_RXWM = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_RXWM_OFFSET);*uart_RXWM = 1;volatile u32 *uart_TXWM = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_TXWM_OFFSET);*uart_TXWM = 1;TempMask = XUARTPS_IXR_RXOVR|XUARTPS_IXR_RXEMPTY;volatile u32 *uart_IER=(u32 *)(V_STDIN_BASEADDRESS+XUARTPS_IER_OFFSET);*uart_IER =TempMask;pic_enable(PIC_UART0, isr_uart); }? ? ? ? 中斷號(hào)處理函數(shù)isr_uart:
int uartgetc (void) {volatile u32 *uartsr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_SR_OFFSET);int CsrRegister = *uartsr;if(((CsrRegister & XUARTPS_SR_RXEMPTY) == (u32)0)){volatile u32 *uartfifo = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_FIFO_OFFSET);return *uartfifo;}else{volatile u32 *uartisr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_ISR_OFFSET);*uartisr = XUARTPS_IXR_RXEMPTY;return -1;} } void isr_uart (struct trapframe *tf, int idx) {volatile u32 *uartsr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_SR_OFFSET);int CsrRegister = *uartsr;if((CsrRegister & XUARTPS_SR_RXOVR) && ((CsrRegister & XUARTPS_SR_RXEMPTY) == (u32)0)){consoleintr(uartgetc);}volatile u32 *uartisr = (u32 *)(V_STDIN_BASEADDRESS+XUARTPS_ISR_OFFSET);*uartisr = XUARTPS_IXR_RXOVR; }四、任務(wù)調(diào)度進(jìn)程
?? ? ? 在進(jìn)程調(diào)度時(shí)需要保存調(diào)度出的進(jìn)程的上下文的環(huán)境(一組寄存器),并將要取得cpu的進(jìn)程的上下文更新到cpu。xv6聲明的上下文數(shù)據(jù)結(jié)構(gòu)如下:
struct context {// svc mode registersuint64 r3;uint64 r4;uint64 r5;uint64 r6;uint64 r7;uint64 r8;uint64 r9;uint64 r10;uint64 r11;uint64 r12;uint64 r13;uint64 r14;uint64 r15;uint64 r16;uint64 r17;uint64 r18;uint64 r19;uint64 r20;uint64 r21;uint64 r22;uint64 r23;uint64 r24;uint64 r25;uint64 r26;uint64 r27;uint64 r28;uint64 r29;uint64 lr; // x30 };? ? ? ? 在AARCH64 C語(yǔ)言棧回溯結(jié)構(gòu)中,一般使用x30作為返回地址,x29保存sp。x0用作第一個(gè)參數(shù),x1用作第二個(gè)參數(shù)。xv6的調(diào)度程序如下。
void scheduler(void) {struct proc *p;for(;;){// Enable interrupts on this processor.sti();// Loop over process table looking for process to run.acquire(&ptable.lock);for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){if(p->state != RUNNABLE) {//_puts("scheduler3\n");continue;}// Switch to chosen process. It is the process's job// to release ptable.lock and then reacquire it// before jumping back to us.proc = p;switchuvm(p);p->state = RUNNING;swtch(&cpu->scheduler, proc->context);// Process is done running for now.// It should have changed its p->state before coming back.proc = 0;}release(&ptable.lock);} }? ? ? ?這里x0=&cpu->scheduler,x1=proc->context。swtch在swtch.S中實(shí)現(xiàn)。
.global swtchswtch:stp x29, x30, [sp, #-16]!stp x27, x28, [sp, #-16]!stp x25, x26, [sp, #-16]!stp x23, x24, [sp, #-16]!stp x21, x22, [sp, #-16]!stp x19, x20, [sp, #-16]!stp x17, x18, [sp, #-16]!stp x15, x16, [sp, #-16]!stp x13, x14, [sp, #-16]!stp x11, x12, [sp, #-16]!stp x9, x10, [sp, #-16]!stp x7, x8, [sp, #-16]!stp x5, x6, [sp, #-16]!stp x4, x3, [sp, #-16]! _sw_1:# switch the stackmov x21, spstr x21, [x0]mov sp, x1 _sw_2:ldp x3, x4, [sp], #16ldp x5, x6, [sp], #16ldp x7, x8, [sp], #16ldp x9, x10, [sp], #16ldp x11, x12, [sp], #16ldp x13, x14, [sp], #16ldp x15, x16, [sp], #16ldp x17, x18, [sp], #16ldp x19, x20, [sp], #16ldp x21, x22, [sp], #16ldp x23, x24, [sp], #16ldp x25, x26, [sp], #16ldp x27, x28, [sp], #16ldp x29, x30, [sp], #16 _sw_3:# return to the callerbr x30? ? ? ? 將所有入棧棧幀sp(x29)進(jìn)行16字節(jié)對(duì)齊。
sp &= (~15);? ? ? ? 第一個(gè)進(jìn)程的棧結(jié)構(gòu)如下。
? ? ? ??第一個(gè)進(jìn)程的啟動(dòng)過(guò)程參考xv6中文文檔。
五、SD卡運(yùn)行程序
?? ? ? ? 在vitis中通過(guò)jtag調(diào)試好程序后,可以通過(guò)create boot image生成BOOT.bin文件拷貝到U盤根目錄下,同時(shí)通過(guò)撥碼開關(guān)調(diào)整啟動(dòng)方式一般就可以了,但是卻遇到了意外,xv6在運(yùn)行到cprinf函數(shù)時(shí)產(chǎn)生了同步異常,在jtag調(diào)試時(shí)沒(méi)有任何問(wèn)題,懷疑是系統(tǒng)配置導(dǎo)致。
? ? ? ? 通過(guò)gcc -S 生成void cprintf (char *fmt, ...)函數(shù)的匯編代碼:
cprintf:stp x29, x30, [sp, -256]!add x29, sp, 0str x0, [x29, 24]str x1, [x29, 200]str x2, [x29, 208]str x3, [x29, 216]str x4, [x29, 224]str x5, [x29, 232]str x6, [x29, 240]str x7, [x29, 248]str q0, [x29, 64]str q1, [x29, 80]str q2, [x29, 96]str q3, [x29, 112]str q4, [x29, 128]str q5, [x29, 144]str q6, [x29, 160]str q7, [x29, 176]adrp x0, cons? ? ? ? 系統(tǒng)初始化階段已經(jīng)配置了CPACR_EL1寄存器。? ?
// no trapping on FP/SIMD instructionsval32 = 0x03 << 20;asm("MSR CPACR_EL1, %[v]": :[v]"r" (val32):);? ? ? ? 暫時(shí)不去深究原因,暴力解決。手工去掉 str q0,~str q7相關(guān)代碼,重新編譯后,生成BOOT.bin,系統(tǒng)正常啟動(dòng):
================= In Stage 4 ============ PMU-FW is not running, certain applications may not be supported. Protection configuration applied Exit from FSBL starting-xv6-for-ARMv8.. Implementer: ARM Limited Current EL: EL1 Flushing TLB and Instr Cache clearing BSS section for the main kernel************************************************************************** ** ** ** ** ** xv6 on ARMv8-A (64-bit) Architecture ** ** ** ** ** ************************************************************************** * init: Starting Shell $ ls. 1 1 512 .. 1 1 512 cat 2 2 13592 echo 2 3 13088 grep 2 4 15176 info 2 5 13032 init 2 6 13808 kill 2 7 13064 ln 2 8 13064 ls 2 9 14832 mkdir 2 10 13144 rm 2 11 13136 sh 2 12 22048 stressfs 2 13 13552 usertests 2 14 47064 wc 2 15 14024 zombie 2 16 12776 UNIX 2 17 7828 console 3 18 0? ? ? ? 最后的vitis項(xiàng)目文件,?提取碼:64pb 。
總結(jié)
- 上一篇: 精通java益处_你真的精通Java吗?
- 下一篇: 智能云改-docker云迁移实战