QEMUのRaspbery Pi 3モデルを使って、タスクの切り替えを確認してみました。
今度はAArch64でタスクの切り替えをやってみました。
ソースコードです。
- https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/task01
- https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/task02
タスクAからタスクBを起動するだけ
一番シンプルにタスクBを起動してみます。
タスクBを起動するは、「PCとスタックポインタとレジスタ」をタスクBに切り替えれば良いです。
タスクB
void task_b(void)
{
uart_puts("task B ");
uart_puthex(get_sp()); uart_putc('\n');
switch_flg = 0;
for (;;) {
io_halt();
}
}
タスクBのスタック領域を作る
タスクBのスタックは0x40000の固定値としています
aarch64ではx30が関数から戻りアドレスです、ここに、task_b()のアドレスを設定します。
戻り値として使用済みスタックの先頭を返す。
レジスタ番号と対応している数値を代入しているのは、デバッグのためです。
uint64_t *setup_task_b_stack(void)
{
uint64_t *p = (uint64_t *) 0x40000;
uart_puthex((uint64_t) p);
uart_putc('\n');
/* prepare data in stack */
p--;
*p = 0x0101010101010101ULL; /* x1 */
p--;
*p = 0x00000000000000000LL; /* x0 */
p--;
*p = 0x0303030303030303ULL; /* x3 */
p--;
*p = 0x0202020202020202ULL; /* x2 */
p--;
*p = 0x0505050505050505ULL; /* x5 */
p--;
*p = 0x0404040404040404ULL; /* x4 */
p--;
*p = 0x0707070707070707ULL; /* x7 */
p--;
*p = 0x0606060606060606ULL; /* x6 */
p--;
*p = 0x0909090909090909ULL; /* x9 */
p--;
*p = 0x0808080808080808ULL; /* x8 */
p--;
*p = 0x1111111111111111ULL; /* x11 */
p--;
*p = 0x1010101010101010ULL; /* x10 */
p--;
*p = 0x1313131313131313ULL; /* x13 */
p--;
*p = 0x1212121212121212ULL; /* x12 */
p--;
*p = 0x1515151515151515ULL; /* x15 */
p--;
*p = 0x1414141414141414ULL; /* x14 */
p--;
*p = 0x1717171717171717ULL; /* x17 */
p--;
*p = 0x1616161616161616ULL; /* x16 */
p--;
*p = 0x1919191919191919ULL; /* x19 */
p--;
*p = 0x1818181818181818ULL; /* x18 */
p--;
*p = 0x2121212121212121ULL; /* x21 */
p--;
*p = 0x2020202020202020ULL; /* x20 */
p--;
*p = 0x2323232323232323ULL; /* x23 */
p--;
*p = 0x2222222222222222ULL; /* x22 */
p--;
*p = 0x2525252525252525ULL; /* x25 */
p--;
*p = 0x2424242424242424ULL; /* x24 */
p--;
*p = 0x2727272727272727ULL; /* x27 */
p--;
*p = 0x2626262626262626ULL; /* x26 */
p--;
*p = 0x2929292929292929ULL; /* x29 */
p--;
*p = 0x2828282828282828ULL; /* x28 */
p--;
*p = ( uint64_t ) 0x00; /* xzr - has no effect, used so there are an even number of registers. */
p--;
*p = ( uint64_t ) task_b; /* x30 - procedure call link register. */
uart_dump(p);
uart_puthex((uint64_t) p);
uart_putc('\n');
return p;
}
タスクを切り替える関数をアセンブラで記述します。
引数で起動するスタックの先頭を渡す。
スタックポインタを切り替える。
スタックからレジスタをリストアする。
x30がtaskb()になっているので、ret命令でtask_b()が起動する
void switch_task(uint64_t *next_task_top_of_stack);
.globl switch_task
switch_task:
/* switch sp */
mov sp, x0
/* restore context */
ldp x30, xzr, [sp], #16
ldp x28, x29, [sp], #16
ldp x26, x27, [sp], #16
ldp x24, x25, [sp], #16
ldp x22, x23, [sp], #16
ldp x20, x21, [sp], #16
ldp x18, x19, [sp], #16
ldp x16, x17, [sp], #16
ldp x14, x15, [sp], #16
ldp x12, x13, [sp], #16
ldp x10, x11, [sp], #16
ldp x8, x9, [sp], #16
ldp x6, x7, [sp], #16
ldp x4, x5, [sp], #16
ldp x2, x3, [sp], #16
ldp x0, x1, [sp], #16
ret
kernel_main()関数
setup_task_b_stack()でスタックを生成する。
UART入力の割り込みハンドラでswitch_flgを1になったら、switch_task()でタスクAを起動する。
void kernel_main(void)
{
uint64_t *task_b_top_of_stack;
uart_puts("int01\n");
// enable UART RX interrupt.
*UART0_IMSC = 1 << 4;
// UART interrupt routing.
*IRQ_ENABLE2 = 1 << 25;
// IRQ routeing to CORE0.
*GPU_INTERRUPTS_ROUTING = 0x00;
enable_irq();
task_b_top_of_stack = setup_task_b_stack();
uart_puts("task A ");
uart_puthex(get_sp()); uart_putc('\n');
while (1) {
if(switch_flg) {
switch_task(task_b_top_of_stack);
}
io_halt();
}
}
動作例
qemu-system-aarch64 -M raspi3 -m 128 -serial mon:stdio -nographic -kernel kernel.elf
int01
0000000000040000
000000000003FF00: 80 19 08 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000000003FF10: 28 28 28 28 28 28 28 28 29 29 29 29 29 29 29 29 (((((((())))))))
000000000003FF20: 26 26 26 26 26 26 26 26 27 27 27 27 27 27 27 27 &&&&&&&&''''''''
000000000003FF30: 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 $$$$$$$$%%%%%%%%
000000000003FF40: 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 """"""""########
000000000003FF50: 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 !!!!!!!!
000000000003FF60: 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 ................
000000000003FF70: 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 ................
000000000003FF80: 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 ................
000000000003FF90: 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 ................
000000000003FFA0: 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 ................
000000000003FFB0: 08 08 08 08 08 08 08 08 09 09 09 09 09 09 09 09 ................
000000000003FFS0: 06 06 06 06 06 06 06 06 07 07 07 07 07 07 07 07 ................
000000000003FFD0: 04 04 04 04 04 04 04 04 05 05 05 05 05 05 05 05 ................
000000000003FFE0: 02 02 02 02 02 02 02 02 03 03 03 03 03 03 03 03 ................
000000000003FFF0: 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 ................
0000000000040000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000000003FF00
task A 000000000007FFE0
c_irq_handler
000000000007FF20
task B 000000000003FFF0
割り込みを契機に、タスクBが起動して、スタックポインタが0x3FFF0になっていることが確認できます。
タスクAとタスクBを行ったり来たりする。
次にタスクAとタスクBを行ったり来たりしてみます。
タスクAとタスクBを行ったり来たりするには、コンテキストの保持する処理を追加すれば良いです。
switch_task()でSPを切り替える前に、コンテキストを保持するためにレジスタを保存する処理を入れます。
コンテキストを保持したスタックポインタの値をポインタ変数に保存するために、引数を追加し、引数の型をポインタのポインタにします。
void switch_task(uint64_t **prev_task_top_of_stack, uint64_t **next_task_top_of_stack);
.globl switch_task
switch_task:
/* save context */
stp x0, x1, [sp, #-16]!
stp x2, x3, [sp, #-16]!
stp x4, x5, [sp, #-16]!
stp x6, x7, [sp, #-16]!
stp x8, x9, [sp, #-16]!
stp x10, x11, [sp, #-16]!
stp x12, x13, [sp, #-16]!
stp x14, x15, [sp, #-16]!
stp x16, x17, [sp, #-16]!
stp x18, x19, [sp, #-16]!
stp x20, x21, [sp, #-16]!
stp x22, x23, [sp, #-16]!
stp x24, x25, [sp, #-16]!
stp x26, x27, [sp, #-16]!
stp x28, x29, [sp, #-16]!
stp x30, xzr, [sp, #-16]!
/* store sp */
mov x19, sp
str x19, [x0]
/* switch sp */
ldr x19, [x1]
mov sp, x19
/* restore context */
ldp x30, xzr, [sp], #16
ldp x28, x29, [sp], #16
ldp x26, x27, [sp], #16
ldp x24, x25, [sp], #16
ldp x22, x23, [sp], #16
ldp x20, x21, [sp], #16
ldp x18, x19, [sp], #16
ldp x16, x17, [sp], #16
ldp x14, x15, [sp], #16
ldp x12, x13, [sp], #16
ldp x10, x11, [sp], #16
ldp x8, x9, [sp], #16
ldp x6, x7, [sp], #16
ldp x4, x5, [sp], #16
ldp x2, x3, [sp], #16
ldp x0, x1, [sp], #16
ret
これで、タスクを行ったり来たりできます。
task_b()に、タスクAに戻るswitch_task()を追加します。
switch_flgの状態を追加します。
void task_b(void)
{
for (;;) {
if (switch_flg == 1 ) {
uart_puts("task B ");
uart_puthex(get_sp()); uart_putc('\n');
switch_flg = 2;
} else if(switch_flg == 3) {
switch_task(&task_b_top_of_stack, &task_a_top_of_stack);
} else {
io_halt();
}
}
}
void c_irq_handler(void)
{
char c;
// check inteerupt source
if (*CORE0_INTERRUPT_SOURCE & (1 << 8)) {
if (*IRQ_PEND2 & (1 << 25)) {
if (*UART0_MIS & (1 << 4)) {
c = (unsigned char) *UART0_DR; // read for clear tx interrupt.
uart_putc(c);
uart_puts(" c_irq_handler ");
uart_puthex(get_sp()); uart_putc('\n');
if (switch_flg == 0) {
switch_flg = 1;
} else if (switch_flg == 2 ) {
switch_flg = 3;
}
return;
}
}
}
return;
}
void kernel_main(void)
{
uart_puts("task01\n");
// enable UART RX interrupt.
*UART0_IMSC = 1 << 4;
// UART interrupt routing.
*IRQ_ENABLE2 = 1 << 25;
// IRQ routeing to CORE0.
*GPU_INTERRUPTS_ROUTING = 0x00;
enable_irq();
task_b_top_of_stack = setup_task_b_stack();
uart_puts("task A ");
uart_puthex(get_sp()); uart_putc('\n');
while (1) {
if(switch_flg == 1) {
switch_task(&task_a_top_of_stack, &task_b_top_of_stack);
} else if (switch_flg == 3 ) {
uart_puts("task A ");
uart_puthex(get_sp()); uart_putc('\n');
switch_flg = 0;
} else {
io_halt();
}
}
}
実行例
UARTに一文字入力するごとにタスクAとタスクBを行ったり来たりしています。
qemu-system-aarch64 -M raspi3 -m 128 -serial mon:stdio -nographic -kernel kernel.elf
task01
0000000000040000
000000000003FF00: 80 19 08 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000000003FF10: 28 28 28 28 28 28 28 28 29 29 29 29 29 29 29 29 (((((((())))))))
000000000003FF20: 26 26 26 26 26 26 26 26 27 27 27 27 27 27 27 27 &&&&&&&&''''''''
000000000003FF30: 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 $$$$$$$$%%%%%%%%
000000000003FF40: 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 """"""""########
000000000003FF50: 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 !!!!!!!!
000000000003FF60: 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 ................
000000000003FF70: 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 ................
000000000003FF80: 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 ................
000000000003FF90: 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 ................
000000000003FFA0: 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 ................
000000000003FFB0: 08 08 08 08 08 08 08 08 09 09 09 09 09 09 09 09 ................
000000000003FFS0: 06 06 06 06 06 06 06 06 07 07 07 07 07 07 07 07 ................
000000000003FFD0: 04 04 04 04 04 04 04 04 05 05 05 05 05 05 05 05 ................
000000000003FFE0: 02 02 02 02 02 02 02 02 03 03 03 03 03 03 03 03 ................
000000000003FFF0: 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 ................
0000000000040000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000000003FF00
task A 000000000007FFD0
c_irq_handler 000000000006FF50
task B 000000000003FFD0
c_irq_handler 000000000006FF50
task A 000000000007FFD0
c_irq_handler 000000000006FF50
task B 000000000003FFD0
c_irq_handler 000000000006FF50
task A 000000000007FFD0
参考情報
-
OSのタスク切り替え処理とAArch64(ARM64)の関数呼び出し規約
今回の記事は、この記事のコードを単体で動作するようにしただけです。