當前位置:編程學習大全網 - 編程語言 - 實時操作系統怎麽創建任務代碼分析

實時操作系統怎麽創建任務代碼分析

最簡單的任務調度

以現代觀點而言,壹個標準個人電腦的OS應該提供以下的功能:

進程管理(Processing management)

內存管理(Memory management)

文件系統(File system)

網絡通訊(Networking)

安全機制(Security)

用戶界面(User interface)

驅動程序(Device drivers)

但壹個最簡易的嵌入式操作系統,所包含的可以少很多。最簡單的操作系統,通常都是圍繞著進程管理展開的。所以,現在可以嘗試下壹個最簡單的“操作系統”,只能做簡單地進行人工任務調度。為了簡單起見,使用最簡單的AT89S52運行程序:內存小的數的清字節數,外設只有幾個IO,結構簡單,很方便操作系統的編寫。

1.裸跑的任務和操作系統中的任務

相信大家都很熟悉,用單片機裸跑,程序壹般都寫成如下壹個大的while死循環:

void main (void)

{

while (1) /* repeat forever */

{

do_something();

}

}

或者又像:

void main (void)

{

while (1) /* repeat forever */

{

do_something1();

do_something2(); //Catch data input

do_something3();

.

.

.

}

}

這裏每壹個函數完成壹個獨立的操作或者任務,這些函數(也可以叫任務)以壹定的順序執行,壹個接著壹個。這裏的任務切換,單純就是執行完壹個,再執行另壹個。不斷循環。

但是,壹旦增加更多的任務,那麽執行的順序就變成了壹個問題。在以上的例子中,壹旦函數do_something1()運行了太長的時間,那麽主循環就需要很長的時間才可以執行到do_something2()。如果do_something2()是接收輸入數據的函數,數據就很有可能丟失。當然,我們也可以在循環中插入更多的do_something2()函數調用,或者把do_something1()拆分成幾個比較小的部分。但這就比較考驗編程者功力了,如果任務太多,編寫程序將成為壹個相當復雜的問題。

這時,壹個幫助妳分配各個任務運行時間的操作系統就很有必要了。在操作系統中,任務壹般形如:

void check_serial_io_task (void) _task_ 1

{

/* This task checks for serial I/O */

}

void process_serial_cmds_task (void) _task_ 2

{

/* This task processes serial commands */

}

void check_kbd_io_task (void) _task_ 3

{

/* This task checks for keyboard I/O */

}

任務之間的切換已經交給操作系統完成了,熟悉的main函數和while(1)壹般已經隱去不見了。

2.如何做任務切換

還是說單片機裸跑,裸跑時,把C語言文件編譯成匯編,可以看到,是用CALL指令去調壹個任務函數,執行完畢後,用RET退出。但是這樣的方法用在切換頻繁的操作系統中,就無疑不適合了,因為我們無法做到預知什麽時候退出,即調用RET。

任務切換,看起來很玄,實際上說白了,就是改變程序指針PC的值。前邊寫的_task_ 1,_task_ 2,編譯以後,都存儲在ROM中。把PC指向這段ROM,他就執行了,想切換另壹個任務,就用PC指向那個任務。就這麽簡單。這樣說,是不是就是PC=壹個地址就可以了?不行,因為絕大多數單片機,是不允許給PC寄存器直接賦值的。那樣寫,編譯器會報錯的。壹般操作系統,都用以下方法改變PC的值:

unsigned char Task_Stack1[3];

Task_Stack1[1] = (uint16) Task_1;

Task_Stack1[2] = (uint16) Task_1 >> 8;

SP = Task_Stack1+2;

}//編譯成RET

PC的值不能直接改變,但是可以變通,通過其他方式改變PC的值。壹個函數執行完畢,總是要改變PC的。這是,PC是如何改變的呢?函數執行前,PC被壓入了堆棧中。函數結束,要調用的是RET指令,也就是PC出棧。壓在堆棧中的原始PC值,這時從堆棧中彈出,程序又回到了原來的位置。這裏就是模仿這壹過程:模擬壹個堆棧的結構,把要執行的函數入口地址(C語言中的函數名)裝入其中,把SP指向這個自己創建的堆棧棧頂。壹個RET指令,就將[SP]和[SP-1]彈到PC中了。就這樣,PC改變到了要執行的函數入口地址,開始執行目標函數。(AT89s52的PC為16位,壓到堆棧中是兩個字節)

3.壹個最簡單的人工調度系統

應用上面的思想,寫壹個最簡單的3任務人工調度系統。代碼如下:

typedef unsigned char uint8;

typedef unsigned int uint16;

#include

sbit led0 = P0^0;

sbit led1 = P0^1;

sbit led2 = P0^2;

uint8 Cur_TaskID; //當前運行的任務號

uint8 Task_Stack0[10]; //0號任務的堆棧

uint8 Task_Stack1[10];

uint8 Task_Stack2[10];

uint8 Task_StackSP[3]; //3個堆棧的棧頂指針

//Task_StackSP[0] -> Task_Stack0

//Task_StackSP[1] -> Task_Stack1

//Task_StackSP[2] -> Task_Stack2

void Task_0(); //任務0

void Task_1(); //任務1

void Task_2(); //任務2

void Task_Scheduling(uint8 Task_ID); //任務調度

void main (void)

{

Task_Stack0[1] = (uint16) Task_0; //按照小端模式,任務函數入口地址裝入任務堆棧

Task_Stack0[2] = (uint16) Task_0 >> 8;

Task_Stack1[1] = (uint16) Task_1;

Task_Stack1[2] = (uint16) Task_1 >> 8;

Task_Stack2[1] = (uint16) Task_2;

Task_Stack2[2] = (uint16) Task_2 >> 8;

Task_StackSP[0] = Task_Stack0;

Task_StackSP[0] += 2; //剛入棧兩個元素。這裏取得棧頂地址,即Task_Stack0[2]

Task_StackSP[1] = Task_Stack1;

Task_StackSP[1] += 2;

Task_StackSP[2] = Task_Stack2;

Task_StackSP[2] += 2;

Cur_TaskID = 0;

SP = Task_StackSP[0]; //SP取得0號任務的棧頂地址

}//利用main的返回指令RET,使PC取得0號任務入口地址

//任務調度函數

void Task_Scheduling(uint8 Task_ID)

{

Task_StackSP[Cur_TaskID] = SP;

Cur_TaskID = Task_ID;

SP = Task_StackSP[Cur_TaskID];

}

//0號任務函數

void Task_0()

{

while(1)

{

led0 = 0;

Task_Scheduling(1);

}

}

//1號任務函數

void Task_1()

{

while(1)

{

led1 = 0;

Task_Scheduling(2);

}

}

//2號任務函數

void Task_2()

{

while(1)

{

led2 = 0;

Task_Scheduling(0);

}

}

代碼要做的,就是3個任務的順序執行。任務調度函數Task_Scheduling的思想也即如前面所述。在Keil中可以運行代碼,可以看到,程序在3個任務中順序執行了。

  • 上一篇:為什麽我的IIC從設備無法發送應答信號
  • 下一篇:《女神訣》第壹年隱藏事件攻略(時間線版)
  • copyright 2024編程學習大全網