Panda+ is a UNIX-like kernel developed in C for the Operating Systems course, running on the uMPS3 MIPS architecture emulator. It follows Dijkstra's THE model with 6 abstraction layers, of which the project implements Level 2 (queue management and data structures) and Level 3 (the OS kernel).
The kernel features preemptive round-robin scheduling, exception and interrupt handling, 10 syscalls, binary semaphores (Passeren/Verhogen), device I/O, process namespaces, and a Pass Up or Die mechanism for exception escalation.
Panda+ follows Dijkstra's THE model with 6 abstraction layers. The project implements Levels 2 and 3: the data structures that the kernel relies on, and the kernel itself. The lower layers (0-1) are provided by the uMPS3 hardware emulator, while the upper layers (4-6) would extend the OS with support structures, a file system, and an interactive shell.
Level 2 provides the foundational data structures used by the kernel: Process Control Blocks (PCB) organized in free lists, process queues, and process trees with parent-child-sibling relationships. Semaphore descriptors (SEMD) are managed via a hash table for O(1) lookup. Namespaces provide process isolation by grouping related processes.
typedef struct pcb_t {
struct list_head p_list; /* process queue */
struct pcb_t *p_parent; /* ptr to parent */
struct list_head p_child; /* children list */
struct list_head p_sib; /* sibling list */
state_t p_s; /* processor state */
cpu_t p_time; /* cpu time used */
int *p_semAdd; /* blocked on sem */
support_t *p_supportStruct;
nsd_t *namespaces[NS_TYPE_MAX];
pid_t p_pid;
} pcb_t;The kernel boots from main(): it sets up the Pass Up Vector for exception/TLB-Refill handling, initializes the Phase 1 data structures (PCBs, semaphores, namespaces), configures the Interval Timer to 100ms, allocates the first process in kernel mode with interrupts enabled, and finally invokes the scheduler.
void main() {
passupvector->tlb_refill_handler = uTLB_RefillHandler;
passupvector->exception_handler = exceptionHandler;
initPcbs(); initASH(); initNamespaces();
mkEmptyProcQ(&readyQueue);
LDIT(PSECOND); /* interval timer = 100ms */
pcb_PTR init = allocPcb();
init->p_s.status = ALLOFF | IMON | IEPON | TEBITON;
init->p_s.pc_epc = (unsigned int)test;
RAMTOP(init->p_s.reg_sp);
insertProcQ(&readyQueue, init);
scheduler();
}The scheduler implements a preemptive round-robin policy with time slices. It dequeues the next ready process, arms the Processor Local Timer, and loads the process state into the CPU. If no processes exist, the system halts. If processes are alive but all blocked with none on device semaphores, it's a deadlock — PANIC. Otherwise, interrupts are enabled and the CPU waits for a device to unblock a process.
void scheduler() {
currentProcess = removeProcQ(&readyQueue);
if (currentProcess != NULL) {
setTIMER(TIMESLICE * TIMESCALE);
LDST(¤tProcess->p_s);
}
else if (processCount == 0) HALT();
else if (softBlockedCount == 0) PANIC();
else {
setSTATUS(getSTATUS() | IECON | IMON);
WAIT();
}
}When an exception occurs, uMPS3 saves the processor state and jumps to the exception handler. The handler reads the Cause register to dispatch to the appropriate routine: interrupt handler, TLB exception (pass-up or die), syscall handler, or general exception. Syscalls are only allowed from kernel mode — a user-mode syscall triggers a Reserved Instruction exception.
void exceptionHandler() {
switch (CAUSE_GET_EXCCODE(getCAUSE())) {
case EXC_INT: /* Interrupt */
interruptHandler(); break;
case EXC_MOD: case EXC_TLBL: case EXC_TLBS:
passUpOrDieHandler(PGFAULTEXCEPT); break;
case EXC_SYS: /* Syscall */
systemcallHandler(); break;
default:
passUpOrDieHandler(GENERALEXCEPT);
}
}The kernel exposes 10 syscalls: process creation/termination, Passeren (P) and Verhogen (V) on binary semaphores, device I/O, CPU time accounting, clock wait, support struct access, PID retrieval, and child enumeration. Semaphores coordinate access to shared resources — a process calling P on a zero semaphore is blocked and rescheduled; V unblocks a waiting process or sets the semaphore to 1.
/* Syscall dispatch (reg_a0) */
case CREATEPROCESS: createProcess(...);
case TERMPROCESS: termProcess(pid);
case PASSEREN: passeren(sem); /* P() */
case VERHOGEN: verhogen(sem); /* V() */
case DOIO: doIO(cmdAddr, cmdVal);
case GETTIME: getTime();
case CLOCKWAIT: clockWait();
case GETSUPPORTPTR: getSupportPtr();
case GETPROCESSID: getProcessId(parent);
case GETCHILDREN: getChildren(buf, size);