Preemptive multi-tasking

I changed the previous cooperative threads into preemptive threads.
It changes contexts during timer interrupts every 16ms with non-inteligent round robin scheduling.
I noticed that ARM swaps R13/R14 during IRQ, and needed to go back and force between IRQ and SVC mode to get the original (banked) registers as below. When IRQ_hander C function returns non null, it’ll switch contexts.

_IRQ_iterrupt:
  //-- irq mode
  sub lr, lr, #4 // in IRQ mode, r14_irq(lr_irq) points to PC+#4 in user mode
  // save context
  push {r0-r12, lr} // save user mode registers

  mrs r0, spsr // spsr -> r0
  cps #0x13
  //-- svc mode
  mov r1, sp
  mov r2, lr

  cps #0x12
  //-- irq mode
  push {r0-r2} // save spsr, user mode sp, lr

  // call IRQ_hander(user-mode-sp)
  mov r0, r2
	bl	IRQ_handler
  cmp r0, #0
  bne _IRQ_interrupt_context_switch

  pop {r0-r2} // restore spsr, user mode sp, lr
  msr spsr, r0 // r0 -> spsr
  cps #0x13
  //-- svc mode
  mov sp, r1 // restore sp
  mov lr, r2 // restore lr

  cps #0x12
  //-- irq mode
  pop  {r0-r12,lr}
  movs pc, lr

_IRQ_interrupt_context_switch:
  //-- irq mode
  // r0 is next thread's SP

  pop {r1-r3} // restore spsr, user mode sp, lr

  // save registers in user mode stack
  // r1: user mode cpsr
  // r2: user mode sp
  // r3: user mode lr
  sub r2, r2, #4
  str r1, [r2] // spsr

  ldr r4, [r13, #4*13]
  sub r2, r2, #4
  str r4, [r2] // user mode pc (r14_irq)

  sub r2, r2, #4
  str r3, [r2] // user mode lr

  ldr r4, [r13, #4*12]
  sub r2, r2, #4
  str r4, [r2] // user mode r12

  ldr r4, [r13, #4*11]
  sub r2, r2, #4
  str r4, [r2] // user mode r11

  ldr r4, [r13, #4*10]
  sub r2, r2, #4
  str r4, [r2] // user mode r10

  ldr r4, [r13, #4*9]
  sub r2, r2, #4
  str r4, [r2] // user mode r9

  ldr r4, [r13, #4*8]
  sub r2, r2, #4
  str r4, [r2] // user mode r8

  ldr r4, [r13, #4*7]
  sub r2, r2, #4
  str r4, [r2] // user mode r7

  ldr r4, [r13, #4*6]
  sub r2, r2, #4
  str r4, [r2] // user mode r6

  ldr r4, [r13, #4*5]
  sub r2, r2, #4
  str r4, [r2] // user mode r5

  ldr r4, [r13, #4*4]
  sub r2, r2, #4
  str r4, [r2] // user mode r4

  ldr r4, [r13, #4*3]
  sub r2, r2, #4
  str r4, [r2] // user mode r3

  ldr r4, [r13, #4*2]
  sub r2, r2, #4
  str r4, [r2] // user mode r2

  ldr r4, [r13, #4*1]
  sub r2, r2, #4
  str r4, [r2] // user mode r1

  ldr r4, [r13]
  sub r2, r2, #4
  str r4, [r2] // user mode r0

  mov r4, sp // r4 <- r13_irq

  msr spsr, r1 // r1 -> spsr
  cps #0x13          //@ svc mode
  //-- svc mode
  // change user mode stack to next thread's stack
  mov sp, r0
  // push into r13_irq (r4)
  ldr r2, [sp, #4*14] // user mode pc
  sub r4, r4, #4
  str r2, [r4]
  ldr r2, [sp, #4*15] // spsr
  sub r4, r4, #4
  str r2, [r4]
  // restore registers
  pop {r0-r12,lr}
  add sp, sp, #4*2 // pop pc, spsr

  cps #0x12          //@ irq mode
  //-- irq mode
  sub sp, sp, #4*2
  pop {lr}
  // enable irq and restore spsr
  bic lr, lr, #0x80
  msr spsr, lr // lr -> spsr
  // restore lr (user mode pc)
  pop {lr}
  // discard pushed registers
  add r13, r13, #4*14
  // movs pc, * ... mov's' pc restores status register
  movs pc, lr

I also tried to implement and add critical section but the one in ARM reference’s hanged probably because I haven’t setup MMU.

// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s03s02.html
.equ  locked,   1
.equ  unlocked, 0

// BUG: lock_mutex hangs
// LDREX doesn't work when MMU is disabled. Don't use this.
// Declare for use from C as extern void lock_mutex(void * mutex);
.global _lock_mutex_mmu
_lock_mutex_mmu:
  LDR     r1, =locked
1:
  LDREX   r2, [r0]
  CMP     r2, r1        // Test if mutex is locked or unlocked
  BEQ     2f
  STREXNE r2, r1, [r0]  // Not locked, attempt to lock it
  CMPNE   r2, #1        // Check if Store-Exclusive failed
  BEQ     1b           // Failed - retry from 1
  // Lock acquired
  DMB                   // Required before accessing protected resource
  BX      lr
2:
// Take appropriate action while waiting for mutex to become unlocked
  //wfi
  nop
  B       1b           // Retry from 1


// BUG: unlock_mutex
// Declare for use from C as extern void unlock_mutex(void * mutex);
.global _unlock_mutex_mmu
_unlock_mutex_mmu:
    LDR     r1, =unlocked
    DMB                   // Required before releasing protected resource
    STR     r1, [r0]      // Unlock mutex
    // SIGNAL_UPDATE: none
    BX      lr


// BUG: SWP version
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/CJHBGBBJ.html
// still hangs
.global _lock_mutex_swp
_lock_mutex_swp:
    LDR r2, =locked
    SWP r1, r2, [r0]       // Swap R2 with location [R0], [R0] value placed in R1
    CMP r1, r2             // Check if memory value was ‘locked’
    BEQ _lock_mutex_swp     // If so, retry immediately
    BX  lr                 // If not, lock successful, return

// BUG: not really excusive when context swithes after ldr befor str
.global _lock_mutex_simple
_lock_mutex_simple:
  ldr r1, =unlocked
  ldr r3, =locked
  ldr r2, [r0]
  cmp r2, r3
  beq _lock_mutex_simple
  str r1, [r0]
  bx lr

.global _unlock_mutex_simple
_unlock_mutex_simple:
    LDR r1, =unlocked
    STR r1, [r0]           // Write value ‘unlocked’ to location [R0]
    BX  lr

Today’s code -> https://github.com/sokoide/rpi-baremetal -> 009_context_switch2

Thread and context switch

I implemented a simple context switch for ARM. First, I implemented a THREAD and THREADCTL as below.

*** rpi.h
#define MAX_THREADS 1024
void InitThread();
void CreateThread(void *thread_entry);
void ContextSwitch();

typedef enum {
  THREAD_NONE,
  THREAD_CREATED,
  THREAD_RUNNING,
  THREAD_WAITING
} THREAD_STATE;

typedef struct {
  THREAD_STATE state;
  int *stack;
  unsigned int stackSize;
} THREAD;

typedef struct {
  unsigned int currentId;
  unsigned int length;
  THREAD thread[MAX_THREADS];
} THREADCTL;

extern THREADCTL threadctl;

*** rpi-thread.c
THREADCTL threadctl;

void InitThread() {
  threadctl.length = 1;
  threadctl.thread[0].state = THREAD_RUNNING;
  threadctl.currentId = 0;

  threadctl.thread[0].stack = NULL;
  /* threadctl.thrad[0].stackSize = TBD; */
}

void CreateThread(void *thread_entry) {
  unsigned int id = threadctl.length;
  const unsigned int stackSize = 4096;  // 4096 * sizeof(int) allocated
  int *stackBase;

  threadctl.length++;
  threadctl.thread[id].state = THREAD_CREATED;
  stackBase = (int *)malloc(stackSize * sizeof(int));
  stackBase += stackSize - 1;
  threadctl.thread[id].stack = stackBase-13;
  threadctl.thread[id].stackSize = stackSize;

  // push default registers into the stack which will be poped in
  // _context_switch
  *threadctl.thread[id].stack =
      (int)thread_entry;  // r14: lr (to be stored in pc)
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r12
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r11
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r10
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r9
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r8
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r7
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r6
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r5
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r4
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r3
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r2
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r1
  threadctl.thread[id].stack--;
  *threadctl.thread[id].stack = 0;  // r0
  // don't do stack--!
}

void ContextSwitch() {
  if (threadctl.length <= 1) {
    return;
  }

  unsigned int currentId = threadctl.currentId;
  unsigned int nextId = currentId + 1;

  if (nextId >= threadctl.length) {
    nextId = 0;
  }

  char message[512];
  if (NULL != threadctl.thread[currentId].stack &&
      NULL != threadctl.thread[nextId].stack) {
    sprintf(message, "%d jumping from th:%d@%p to th:%d@%p", timerctl.counter,
            currentId, *threadctl.thread[currentId].stack, nextId,
            *threadctl.thread[nextId].stack);
  } else {
    sprintf(message, "%d jumping from th:%d to th:%d", timerctl.counter,
            currentId, nextId);
  }
  FillRect(0, 16, kWidth, 16, 0);
  PrintStr(0, 16, message, 7);

  // get current sp
  threadctl.thread[currentId].stack = (int *)_get_stack_pointer();

  // do context switch
  threadctl.thread[nextId].state = THREAD_RUNNING;
  threadctl.thread[currentId].state = THREAD_WAITING;
  threadctl.currentId = nextId;
  _context_switch(&threadctl.thread[currentId].stack,
                  &threadctl.thread[nextId].stack);
}

The _context_switch is written in assembly which pushes r0-14 in per-thread stack and save the stack pointer in the per-thread variable ‘stack’.

.global _get_stack_pointer
_get_stack_pointer:
  mov r0, r13
  bx lr
  
.global _context_switch
_context_switch:
  // same as stmfd/stmdb !r13, {...}
  push {r0-r12,r14}
  str sp, [r0]
  ldr sp, [r1]
  // same as ldmfd/ldmia !r13, {...}
  pop {r0-r12}
  pop {pc} // pc points to the previous lr

I use it in main function.

InitThread(); // add the current thread into THREADCTL
CreateThread(task_a); // create a new thread for task_a and start it
CreateThread(task_b); // create a new thread for task_b and start it
...
// switch contexts using timer interrupt
while (true) {
  _disable_IRQ();
  _wfi();
  if (StatusFifo8(&fifoTimer) == 0) {
    _enable_IRQ();
  } else {
    unsigned char data = GetFifo8(&fifoTimer);
    _enable_IRQ();
      
     switch (data) {
      case (const int)timerData1:
        counter1++;
        SetTimer(timer1, timerInterval1, timerData1);
        draw_counter(0, counter1);
        ContextSwitch();
        break;
    }
  }
}
  
void task_a() {
  unsigned int counter = 0;
  while (true) {
    draw_counter(1, counter++);
    ContextSwitch(); // for now, it's changing contexts by the function itself
  }
}

void task_b() {
  unsigned int counter = 0;
  while (true) {
    draw_counter(2, counter++);
    ContextSwitch(); // for now, it's changing contexts by the function itself
  }
}

Tested it and worked!
Today’s task_a/b have ContextSwitch() explicitly. I’ll make the context switch full automatic in the next post.

https://github.com/sokoide/rpi-baremetal -> 008_context_switch.

context_switch

Unittest and refactoring for RPi baremetal

I wanted to change the previous array based timer to a list based one, and wanted to write a unittest for the list before implementing.
So, added googletest git repo from Chromium repo as a submodule and created Test.mak for unittest on the build machine for the build machine architecture (OSX, x64, mach-o), then refactored the previous 007-wfi with it for the target architecture (RPi2, cortex-a7, elf).

https://github.com/sokoide/rpi-baremetal -> 007_wfi

make -f Test.mak test
[==========] Running 9 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 5 tests from Fifo8Case
[ RUN      ] Fifo8Case.Init
[       OK ] Fifo8Case.Init (0 ms)
[ RUN      ] Fifo8Case.Put
[       OK ] Fifo8Case.Put (0 ms)
[ RUN      ] Fifo8Case.PutGet
[       OK ] Fifo8Case.PutGet (0 ms)
[ RUN      ] Fifo8Case.Put4Get2
[       OK ] Fifo8Case.Put4Get2 (0 ms)
[ RUN      ] Fifo8Case.PutOverflow
[       OK ] Fifo8Case.PutOverflow (0 ms)
[----------] 5 tests from Fifo8Case (0 ms total)

[----------] 4 tests from TimerCase
[ RUN      ] TimerCase.InitTimerCtl
[       OK ] TimerCase.InitTimerCtl (0 ms)
[ RUN      ] TimerCase.InsertTimer1
[       OK ] TimerCase.InsertTimer1 (0 ms)
[ RUN      ] TimerCase.InsertTimer5
[       OK ] TimerCase.InsertTimer5 (0 ms)
[ RUN      ] TimerCase.RemoveTimer
[       OK ] TimerCase.RemoveTimer (0 ms)
[----------] 4 tests from TimerCase (0 ms total)

[----------] Global test environment tear-down
[==========] 9 tests from 2 test cases ran. (0 ms total)
[  PASSED  ] 9 tests.

WFI and timer change

Before implementing context switch, I wanted to improve interrupt handling and better timer.
I was using busy loop to check messages from interrupt. First, I used WFI ARM instruction to wait (sleep) until interrupted.

startup.s:
.global _wfi
_wfi:
  wfi
  bx lr

hoge.c:
while (true) {
  _disable_IRQ();
  _wfi();
  if (StatusFifo8(&fifoTimer) == 0) {
    _enable_IRQ();
  } else {
    unsigned char data = GetFifo8(&fifoTimer);
    _enable_IRQ();
...

And I used FIFO8 used in Haribote OS (32bit tiny OS for x86).

Then quickly wrote dirty timer handling which supports up to MAX_TIMER (currently 512). It’s not using a list but an array and not efficient -> TODO.

#define MAX_TIMER 512
typedef struct _TIMER {
  unsigned int timeout;
  unsigned char data;
} TIMER;

typedef struct _TIMERCTL {
  unsigned int counter, next;
  unsigned int length;  // number of used timers
  FIFO8 *fifo;
  TIMER timer[MAX_TIMER];
} TIMERCTL;

extern TIMERCTL timerctl;

void InitTimer(FIFO8 *fifo);
TIMER *SetTimer(TIMER *timer, unsigned int timeout, unsigned char data);

One more improvement. I bought a USB power cable with a switch! I don’t need to plug-in/out to reboot my code anymore 🙂

Today’s code -> https://github.com/sokoide/rpi-baremetal -> 007_wfi.

USB power cable with switch.
usbcable

3 timers whose intervals are 100ms, 500ms and 1s each.
timer2

Frames per second

Changed to display FPS using yesterday’s timer interrupt.
When I tested, it was 120 FPS @ 640×480, 16-17 FPS @ 1920 x 1080 just to fill whole display with a solid color.
Then I added FillRect() and it decreased to 95 FPS @ 640×480 with 3 rectangles.

It’ll be a bit better if I add 8×8 between 32×32 and 1×1, but I didn’t do that today. Even with that, it looks this pixel by pixel draw is too slow if I add more objects. Perhaps I should do block-copy of tiles and sprites.

void FillRect(int x, int y, int width, int height, char color) {
  // left ... draw pixel by pixel
  int x1 = x;
  int x2 = ((x + 31) / 32) * 32;
  if (x1 != x2) {
    char *p8 = (char *)fbRequest.fbBaseAddress;
    p8 += y * kWidth + x1;
    for (int yy = y; yy < y + height; yy++) {
      for (int xx = x1; xx < x2; xx++) {
        *p8++ = color;
      }
      p8 += kWidth - (x2 - x1);
    }
  }

  // center draw 32 pixels at once
  int x3 = ((x + width) / 32) * 32;
  if (x2 != x3) {
    uint32_t *p = (uint32_t *)fbRequest.fbBaseAddress;
    p += (y * kWidth + x2) / 4;
    for (int yy = y; yy < y + height; yy++) {
      for (int xx = x2; xx < x3; xx += 4 * 8) {
        int32_t c = color << 24 | color << 16 | color << 8 | color;
        *p++ = c;
        *p++ = c;
        *p++ = c;
        *p++ = c;
        *p++ = c;
        *p++ = c;
        *p++ = c;
        *p++ = c;
      }
      p += (kWidth - (x3 - x2)) / 4;
    }
  }

  // right ... draw pixel by pixel
  int x4 = x + width;
  if (x3 != x4) {
    char *p8 = (char *)fbRequest.fbBaseAddress;
    p8 += y * kWidth + x3;
    for (int yy = y; yy < y + height; yy++) {
      for (int xx = x3; xx < x4; xx++) {
        *p8++ = color;
      }
      p8 += kWidth - (x4 - x3);
    }
  }
}

Today's code: https://github.com/sokoide/rpi-baremetal -> 006_fps.

fps

Print string

I drew a string using hankaku.bin 8×16 font provided by the X86 OS book.
First, I converted the 4096 byte bin file into elf format.

hankaku.o: hankaku.bin                                                                                     
  $(OBJCOPY) -I binary -O elf32-littlearm -B arm $< $@                                                     

when you convert that way, you can refer to the address in .data section by _hankaku_bin_obj_start as below.

void printstr(int x, int y, char *str, char color) {
  size_t len = strlen(str);
  for (int i = 0; i < len; i++) {
    char c = str[i];
    myputchar(x + i * 8, y, c, color);
  }
}

void myputchar(int x, int y, char c, char color) {
  char *hankaku = (char *)&_binary_hankaku_bin_start;
  char *base = (char *)fbRequest.fbBaseAddress;
  char *p;
  char d;

  for (int i = 0; i < 16; i++) {
    p = base + (y + i) * kWidth + x;
    d = hankaku[c * 16 + i];
...                                                

And it's displayed by this!

  printstr(10, 0, "HOG", 7);
  printstr(10 + 8 * 3, 0, "E", 1);

Today's code: https://github.com/sokoide/rpi-baremetal -> 005_character.

hoge

Draw pixels

Got VRAM frame buffer address from Video Core via MailBox protocol and drew 1920×1080 pixels. I found it’s very slow ~3-5 fps.
Then I changed it to 640×480 which made it faster but not 60fps.
I’ll need to initialize interrupts & use timer and add fonts to show FPS tomorrrow.

Buffer address specified by MailBox’s property-tag protocol should be 16 byte aligned since the least 4 bits are used to specify tag (7 for ARM to VC, 8 for VC to ARM) like this.

struct FramebufferRequest {
  uint32_t size;
  uint32_t bufferRequestResponseCode;

  // Set_Physical_Display
  uint32_t tag_setPd;
  uint32_t size_setPd;
  uint32_t rr_setPd;
  uint32_t width_setPd;
  uint32_t height_setPd;
...
} fbRequest __attribute__((aligned(16)));

Today’s code: https://github.com/sokoide/rpi-baremetal -> 003_screen.

vga

Make your own OS in 30 days – day17/18: Idle task and console

Day16’s task_a was sleeping until interrupted, and other task_b0,b1,b2 were running. If we don’t have bx tasks, it needs a different logic.
It introduces an idle task to keep the design simple by always having a task than the main task_a.

void task_idle(void)
{
	for (;;) {
		io_hlt();
	}
}

To allow all tasks to get keyboard input, the task manager defines FIFO queue per task. Then added a console which accepts ‘mem’, ‘cls’ and ‘dir’ commands.
‘dir’ checks 8.3 file name and size.

struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);

if (strcmp(cmdline, "mem") == 0) {
	sprintf(s, "total   %dMB", memtotal / (1024 * 1024));
	putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
	cursor_y = cons_newline(cursor_y, sheet);
	sprintf(s, "free %dKB", memman_total(memman) / 1024);
	putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
	cursor_y = cons_newline(cursor_y, sheet);
	cursor_y = cons_newline(cursor_y, sheet);
} else if (strcmp(cmdline, "cls") == 0) {
	for (y = 28; y < 28 + 128; y++) {
		for (x = 8; x < 8 + 240; x++) {
			sheet->buf[x + y * sheet->bxsize] = COL8_000000;
		}
	}
	sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
	cursor_y = 28;
} else if (strcmp(cmdline, "dir") == 0) {
	for (x = 0; x < 224; x++) {
		if (finfo[x].name[0] == 0x00) {
			break;
		}
		if (finfo[x].name[0] != 0xe5) {
			if ((finfo[x].type & 0x18) == 0) {
				sprintf(s, "filename.ext   %7d", finfo[x].size);
				for (y = 0; y < 8; y++) {
					s[y] = finfo[x].name[y];
				}
				s[ 9] = finfo[x].ext[0];
				s[10] = finfo[x].ext[1];
				s[11] = finfo[x].ext[2];
				putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
				cursor_y = cons_newline(cursor_y, sheet);
			}
		}
	}
	cursor_y = cons_newline(cursor_y, sheet);
}

day17_18

Make your own OS in 30 days – day15/16: Multitasking

First, changed from Q ver 0.9 to qemu 2.2.1 to resolve VESA issue before day15/16. I needed to make qemu foreground after running it.

Makefie)
default :
	#./Q.app/Contents/MacOS/i386-softmmu.app/Contents/MacOS/i386-softmmu -L . -m 32 -localtime -std-vga -fda fdimage0.bin
	/usr/local/Cellar/qemu/2.2.1/bin/qemu-system-i386 -fda fdimage0.bin -L . -m 32 -localtime -vga std -smp cpus=2 &
	sleep 1
	osascript -e "tell application \"/usr/local/Cellar/qemu/2.2.1/bin/qemu-system-i386\" to activate"

Day15/16 is about multitasking. You can do far-jump to TSS; Task Status Segment, CPU doesn’t make a regular jump but change contexts. If you want to create and jump to a new task, the procedure is as follows.
1. Register TSS in GDT
2. Allocate a stack for the new task
3. Far jump to the TSS

#define TASK_GDT0▸▸ 3 // first task segment number
for (i = 0; i < MAX_TASKS; i++) {
	taskctl->tasks0[i].flags = 0;
	taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
	set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
}

task_a = task_init(memman);
fifo.task = task_a;
task_b = task_alloc();
task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
task_b->tss.eip = (int) &task_b_main;
task_b->tss.es = 1 * 8;
task_b->tss.cs = 2 * 8;
task_b->tss.ss = 1 * 8;
task_b->tss.ds = 1 * 8;
task_b->tss.fs = 1 * 8;
task_b->tss.gs = 1 * 8;
*((int *) (task_b->tss.esp + 4)) = (int) sht_back;

task_run(task_b);

Then it switches tasks using yesterday’s timer interrupt handler every 20ms.
Task_a (main task) is basically sleeping (not getting CPU time at all) until interrupted to use more CPU time for task_b. Task_a should not be sleeping when it’s interrupted. The OS resolves it by awaken task_a when it gets interrupting in FIFO handler via key/mouse/other interrupts.

It only uses a single core through the book. I found how to enable other CPU cores and message each other at University of San Francisco’s class “Advanced Microcomputer Programming”-> lesson 5 -> pmhello.s via StackOverflow.

day15_16

1 2