基於ARM的自制最小操作系統Open-OS:實現

通過上一篇文章的介紹,我想讀者應該對多任務操作系統有了一個粗略整體的認識,雖然是一個簡陋的認識,但是對後面的學習卻至關重要。有了這個整體認識,剩下的就是實現了。

在構建這個OS的過程中,會涉及到一些硬件處理器的知識和基本的軟件知識:

內存管理單元(mmu)

緩存(Cache)

編譯環境(Makefile)

程序的結構(ELF)

鏈接和加載

ABI/EABI的一些規範

C和彙編的接口APCS

GCC工具集的使用 等等

當然會逐步的展開去講解,比如涉及到cache時,不會一上來就說cache的原理,講cache的寄存器每個bit的含義,然後進行源碼分析,這樣也許不僅不會幫助到初學者,反而可能讓想學習的人打了退堂鼓。因此在介紹的過程中我都會幫助讀者建立一個整體的認識,在整體的認識下,讀者就可以自己去深入細節了(硬件、軟件是如何具體的實現),在深入細節的過程中來加強整體的認識。

例如:有的讀者在學習了第一章的知識,就可以以自己的代碼方式去實現多任務切換(細節)。

說明

構建這個ARM OS的不是為了供商業使用(沒有必要),市場上有很多成熟的系統可供使用,真正的目的是為了學習,通過漸進式的方式講解操作系統的概念和實現原理,為後面的嵌入式boot/linux驅動開發的學習打下堅實的基礎。

函數調用堆棧

在上一篇文章提到了ARM處理器的基本模型(存儲程序計算機):存儲器取指令-執行指令-再取指令-再執行無限循環往復,同時還有一個重要的計算機基礎內容:堆棧。

在計算器的石器時代,人們都是使用匯編語言進行程序開發,堆棧的概念並不是很重要。在有了高級語言之後,像C語言的函數調用必須藉助堆棧機制。

引出問題

這裡先要引出一個問題:什麼是ABI?什麼是EABI?嵌入式開發過程中常常會遇到很多奇怪的問題,也許答案就在這裡。

ABI是“Application Binary Interface”的縮寫,即應用程序二進制接口。它是編譯器和我們編寫彙編代碼時需遵循的規範。

ABI既然是一個規範,對於ARM來說其實就是針對ARM 體系結構的應用程序二進制接口 (ABI) 的文檔集,這些文檔包括 ARM 過程調用標準 (APCS)、ARM ELF、ARM DWARF、基礎平臺 ABI (BPABI)、C++ ABI、異常處理 ABI、運行時 ABI 和 C 庫 ABI。

這裡我們舉個簡單的例子來給大家說明,例如ARM 32bit架構的ABI規定了基本的數據類型,這些數據類型所佔用的內存字節數。

基於ARM的自制最小操作系統Open-OS:實現

那麼各個廠商針對ARM 32架構的編譯器的就必須符合ARM的ABI規範,例如,C語言的char類型一般就分配單個字節內存(字節對齊同樣也有ABI規範)。

那我們能不能定義char類型佔用10個byte。答案是可以,自己造處理器,然後自己定義ABI規範。

既然編譯器都遵守相同的規範,那麼符合ABI接口的對象文件(OBJ)鏈接就可以在一起,而不需要考慮它們的源文件,同時符合ABI接口的可執行文件運行在任何支持ABI接口的系統中。

延伸

Linux內核關於ABI的配置選項

<code>make menuconfig
        Kernel Features --->
                [ ] Use the ARM EABI to compile the kernel
                [ ] Allow old ABI binaries to run with this kernel (EXPERIMENTAL)/<code>

ABI規範裡面我們重點關注下AAPCS這篇文檔。下面主要來說下AAPCS主要的幾個關鍵點

定義基本的數據類型

C語言中存在各種基本數據類型,其所佔用的字節數是由處理器對應的ABI規範定義的。下圖列出了ARM 32處理器ABI規範

基於ARM的自制最小操作系統Open-OS:實現

規範字節對齊處理

分配寄存器的功能

規定棧幀結構

函數參數的傳遞

函數返回值

關於ARM更多的ABI規範,這裡不再一一贅述了,可參考ARM官方網站的相關ARM手冊資料。

· ABI Introduction

· ABI Advisory Note 1

· Procedure Call Standard for the ARM Architecture Documentation

· ELF for the ARM Architecture Documentation

· DWARF for the ARM Architecture

· Base Platform ABI for the ARM Architecture

· C++ ABI for the ARM architecture Documentation

· Exception handling ABI for the ARM architecture Documentation

· Run-time ABI for the ARM Architecture

· C Library ABI for the ARM architecture Documentation

· Support for Debugging Overlaid Programs

· Addenda to, and Errata in, the ABI for the ARM Architecture Documentation

· Differences between v1 and v2 of the ABI for the ARM Architecture

實現

在實現之前,我們先來看下運行效果,程序中設計了四個任務,然後每個任務輪流執行,在linux環境下面輸入(qemu的使用可參考網絡資料):

qemu-arm a.out

基於ARM的自制最小操作系統Open-OS:實現

OS代碼構成

基於ARM的自制最小操作系統Open-OS:實現

OS編譯

基於ARM的自制最小操作系統Open-OS:實現

代碼

pch.h

<code>
#define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*2*2

struct _thread {
\tunsigned long ip;
\tunsigned long sp;
};


typedef struct _pcb {
\tint pid;
\tvolatile long state; // -1:unrunnable,0 run >0: stoped
\tunsigned long stack[KERNEL_STACK_SIZE];
\tstruct _thread thread;
\tunsigned long task_entry;
\tstruct _pcb *next;
}pcb_t;/<code>

os.c

<code>#include <stdio.h>
#include <string.h>
#include "pcb.h"

static void my_process(void);
static void my_schedule(void);
static pcb_t task[MAX_TASK_NUM];

static pcb_t * my_current_task = NULL;

//static volatile int my_need_sched = 0;

int main(void)
{
int i = 0;
int pid = 0;
unsigned int cpsr = 0;
printf("==>this is my kernel\\n");\t
//init process 0
task[0].pid = 0;
task[0].state = 0;
task[0].task_entry = (unsigned long)my_process;
task[0].thread.ip = (unsigned long)my_process;
task[0].thread.sp = (unsigned long)&task[0].stack[KERNEL_STACK_SIZE-1];
task[0].next = &task[0];
printf("==>sp=%x,ip=%x\\n",task[0].thread.sp,task[0].thread.ip);
for(i = 1; i < MAX_TASK_NUM; i ++){
\tmemcpy(&task[i],&task[0],sizeof(pcb_t));
task[i].pid = i;
\ttask[i].state = -1;
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
\ttask[i].thread.ip = (unsigned long)my_process;
\ttask[i].next = task[i-1].next;
task[i-1].next = &task[i];
\tprintf("==>sp=%x,ip=%x\\n",task[i].thread.sp,task[i].thread.ip);
}
my_current_task = &task[0];

\tasm volatile(

\t\t"mov sp,%1\\n\\t"
\t\t"mov pc,%0\\n\\t"
\t\t:
\t\t:"r" (task[0].thread.ip),"r" (task[0].thread.sp)\t
\t);

\treturn 0;
}

static void my_schedule(void){
\tpcb_t *next,*prev;
\tif(my_current_task == NULL || my_current_task->next == NULL){
\t\treturn;
\t}
\tprintf(">>>>my_schedule<<<<\\n");
\t//schedule
\tnext = my_current_task->next;
\tprev = my_current_task;
\tif(next->state == 0){
\t\tmy_current_task = next;
\t\tprintf( ">>>>switch %d to %d <<<<\\n",prev->pid,next->pid);
#if 1
\t\tasm volatile(
\t\t\t"stmfd sp!,{r0-r12,lr}\\n\\t"
\t\t\t"str sp,%0\\n\\t"
\t\t\t"adrl r8,rettt\\n\\t"
\t\t\t"str r8,%1\\n\\t"
\t\t\t"ldr pc,%3\\n\\t"
\t\t\t"rettt:\\n\\t"
\t\t\t"ldr sp,%2\\n\\t"
\t\t\t"ldmfd sp!,{r0-r12,lr}\\n\\t"
\t\t\t:"=m" (prev->thread.sp),"=m" (prev->thread.ip)
\t\t\t:"m" (next->thread.sp), "m" (next->thread.ip)
\t\t\t:"r8"
\t\t);
#endif\t\t\t
\t}else{
\t\tnext->state = 0;
\t\tmy_current_task = next;
\t\tprintf(">>>>new switch %d to %d <<<<\\n",prev->pid,next->pid);
\t\t//switch to new process
#if 1
\t\tasm volatile(
\t\t\t"stmfd sp!,{r0-r12,lr}\\n\\t"
\t\t\t"str sp,%0\\n\\t"
\t\t\t"adrl r8,rettt\\n\\t"
\t\t\t"str r8,%1\\n\\t"
\t\t\t"ldr r8,%2\\n\\t"
\t\t\t"ldr r9,%3\\n\\t"
\t\t\t"mov sp,r8\\n\\t"

\t\t\t"mov pc,r9\\n\\t"
\t\t\t: "=m" (prev->thread.sp), "=m" (prev->thread.ip)
\t\t\t: "m" (next->thread.sp), "m" (next->thread.ip)
\t\t\t: "r8","r9"\t\t\t
\t\t);
#endif\t\t

\t}\t
}
static void my_process(){
int i = 0;
while(1){
i ++;
if(i%100000000 == 0){
printf("this is process %d -\\n",my_current_task->pid);
my_schedule();
printf("this is process %d +\\n",my_current_task->pid);

}
}
}/<code>

讀者可以嘗試在自己的虛擬機上編譯運行,看下效果,關於代碼具體細節這裡就不在解釋了。

關於OS的說明:

這個OS是基於孟寧老師linux3.6 X86的教學使用的。這裡修改為基於ARM的,並且後面會在這個OS基礎之上進行擴展,添加一些新的功能特性。

孟寧老師的項目地址:https://github.com/mengning/mykernel

預告

在做這個項目的時候,每次調試我都是手動輸入編譯命令,編譯是非常頻繁、動作週而復始,然而隨著項目的擴張,源碼文件越來越多,是否還要繼續這樣?有沒有什麼工具能夠幫助我們來構建一個專業和高效的開發環境?答案是有。

後面將會講到如何使用make來搭建一個開發環境。除了開發環境是不是還有別的需要做?

上面os實現的是各個任務按照一定的時間輪流執行,其實這個就稱之為調度器。

任務調度算法:

根據一個標準,找到下一個將要運行的任務,然後將正在運行的任務和將要運行的任務做一次上下文切換。那麼這個標準就是我們要說的調度算法。

後面會將現在的OS調度算法進行擴充,能夠支持基於優先級的調度算法。

延伸

基於優先級和時間片、以及這兩種算法的不同組合衍生出不同的調度算法?linux的調度算法?有興趣的讀者可以深入。



分享到:


相關文章: