309 lines
10 KiB
C
309 lines
10 KiB
C
// This is NEW CODE written by Zak, separated from the old proc.c code
|
|
|
|
#include "types.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "riscv.h"
|
|
#include "sched.h"
|
|
#include "defs.h"
|
|
#include "sched.h"
|
|
#include "proc.h"
|
|
#include "bitarray.h"
|
|
#include "fpu.h"
|
|
#include "kprintf.h"
|
|
|
|
struct bitarray *runnablearrays[NPRIO];
|
|
struct bitarray *exhaustedarrays[NPRIO];
|
|
struct bitarray *sleeping;
|
|
sched_core_t sched_cores[SCHED_CORE_MAX];
|
|
extern struct proc proc[NPROC];
|
|
int timeslice_max;
|
|
int timeslice_min;
|
|
|
|
int sched_timesliceforpriority(int priority) {
|
|
if (priority < 0) priority = 0;
|
|
if (priority >= NPRIO) priority = NPRIO - 1;
|
|
return timeslice_min + ((timeslice_max - timeslice_min) / (priority + 1));
|
|
}
|
|
|
|
/* Attempts to restate a process. This should be avoided in situations where
|
|
* you're already locking other processes, you may need to use more specific code in
|
|
* some cases! This function assumes that the caller has locked the process.
|
|
*/
|
|
void sched_restate_alreadylocked(struct proc* p, sched_state_t s) {
|
|
if (p->state == s) {
|
|
return;
|
|
}
|
|
if (p->state == SCHED_STATE_RUNNABLE) {
|
|
bitarray_set(runnablearrays[p->prio], (int) (p - proc), 0);
|
|
bitarray_set(exhaustedarrays[p->prio], (int) (p - proc), 0);
|
|
} else if (p->state == SCHED_STATE_SLEEPING) {
|
|
bitarray_set(sleeping, (int) (p - proc), 0);
|
|
}
|
|
if (s == SCHED_STATE_RUNNABLE) {
|
|
//if (p->timeslice > 0) {
|
|
bitarray_set(runnablearrays[p->prio], (int) (p - proc), 1);
|
|
//} else {
|
|
// bitarray_set(exhaustedarrays[p->prio], (int) (p - proc), 1);
|
|
//}
|
|
for (int i = 0; i < 64; i++) {
|
|
unsigned long foo = 1 << i;
|
|
if (p->affinitymask & foo) {
|
|
sched_cores[i].preempted = 1;
|
|
}
|
|
}
|
|
} else if (s == SCHED_STATE_SLEEPING) {
|
|
bitarray_set(sleeping, (int) (p - proc), 1);
|
|
}
|
|
p->state = s;
|
|
}
|
|
|
|
// My simplified function to set processor affinity of the caller (TODO: Finish this...)
|
|
int affin(uint64 mask) {
|
|
myproc()->affinitymask = mask;
|
|
yield();
|
|
return 0;
|
|
}
|
|
|
|
/* Wake any processes sleeping on the given pointer, ideally preempting
|
|
* any lower priority processes that might be running.
|
|
*
|
|
* NOTE: This can't be called while locking any processes.
|
|
*/
|
|
void sched_wake(void* pointer) {
|
|
struct proc* me = myproc();
|
|
for (int i = 0; i >= 0 && i < NPROC; i = bitarray_findlowest(sleeping, i+1)) {
|
|
struct proc* pr = &proc[i];
|
|
if (pr == me) {
|
|
// Skip the calling process!
|
|
} else {
|
|
acquire(&pr->lock);
|
|
if (pr->chan == pointer) {
|
|
// If the pointer matches it will almost certainly be a candidate,
|
|
// but make sure it's actually sleeping first!
|
|
if (pr->state == SCHED_STATE_SLEEPING) {
|
|
// Then set the state to SCHED_STATE_RUNNABLE and preempt any lower priority
|
|
sched_restate_alreadylocked(pr, SCHED_STATE_RUNNABLE);
|
|
}
|
|
}
|
|
release(&pr->lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Scans for the next SCHED_STATE_RUNNABLE-looking candidate, ideally WITHOUT locking each proc. */
|
|
struct proc* sched_nextcandidate(int prio, struct proc* p) {
|
|
p++;
|
|
if (p < &proc[NPROC]) {
|
|
int idx = bitarray_findlowest(runnablearrays[prio], p->prio == prio ? (int) (p - proc) : 0);
|
|
if (idx >= 0) return &proc[idx];
|
|
}
|
|
/*for(; p < &proc[NPROC]; p++) {
|
|
if (p->state == SCHED_STATE_RUNNABLE) {
|
|
return p;
|
|
}
|
|
}*/
|
|
return 0ULL;
|
|
}
|
|
|
|
// Called when the scheduler is idle to wait for interrupts
|
|
void sched_waitforinterrupts() {
|
|
intr_on(); // Ensure interrupts are actually on
|
|
#ifdef _ZCC
|
|
// Use straightforward inline assembler syntax
|
|
__asm {
|
|
wfi
|
|
};
|
|
#else
|
|
asm volatile("wfi"); // GNU syntax
|
|
#endif
|
|
}
|
|
|
|
int sched_reviveexhausted(int prioidx) {
|
|
int exhausted = 0;
|
|
int count = 0;
|
|
while ((exhausted = bitarray_poplowest(exhaustedarrays[prioidx], exhausted)) >= 0) {
|
|
//printf("Reviving process #%d (index #%d)\n", proc[exhausted].pid, exhausted);
|
|
proc[exhausted].timeslice = sched_timesliceforpriority(prioidx);
|
|
bitarray_set(runnablearrays[proc[exhausted].prio], exhausted, 1);
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// The scheduler is entered on each CPU core after initialisation, it loops
|
|
// selecting processes to switch to and (ideally) powering down the CPU when
|
|
// none are found. The new scheduler handles CPU affinity as well as priority,
|
|
// but is not yet an optimal algorithm.
|
|
void scheduler() {
|
|
sched_core_t* thiscpu = SCHED_CORE_THIS_NOINTERRUPTS();
|
|
uint64 affinitymask = 1ULL << SCHED_CORE_THISNUMBER_NOINTERRUPTS();
|
|
thiscpu->allowstarvation = 1; //(SCHED_CORE_THISNUMBER_NOINTERRUPTS() > 0 ? 1 : 0); // Default policy, run low-priority processes only on the main core
|
|
thiscpu->process = (void*)0ULL;
|
|
|
|
while (1) {
|
|
int found_any = 0;
|
|
int found_here = 0;
|
|
int prioidx;
|
|
intr_on(); // Enable interrupts in case they are disabled
|
|
for (prioidx = 0; prioidx < NPRIO; prioidx++) {
|
|
do {
|
|
found_here = 0;
|
|
struct proc* thisproc;
|
|
//printf("PRIO%d\n", prioidx);
|
|
for (thisproc = proc; thisproc != 0ULL; thisproc = sched_nextcandidate(prioidx, thisproc)) {
|
|
acquire(&thisproc->lock);
|
|
|
|
if ((thisproc->affinitymask & affinitymask) && thisproc->state == SCHED_STATE_RUNNABLE) {
|
|
bitarray_set(runnablearrays[prioidx] /* check this is == runnablearrays[thisproc->prio] ? */, (int) (thisproc - proc), 0);
|
|
bitarray_set(runnablearrays[thisproc->prio], (int) (thisproc - proc), 0);
|
|
//enterprocess:
|
|
thisproc->state = SCHED_STATE_RUNNING; // Set the processes state to running, then unset it in the runnable arrays:
|
|
thiscpu->process = thisproc; // Set the CPU's process to this process
|
|
sched_switchcontext(&thiscpu->registers, &thisproc->context); // Perform the actual context switch
|
|
thisproc->timeslice -= 10000;
|
|
//printf("Timeslice=%d\n", thisproc->timeslice);
|
|
|
|
if (thisproc->state == SCHED_STATE_RUNNING || thisproc->state == SCHED_STATE_RUNNABLE) {
|
|
if (thisproc->timeslice > 0) {
|
|
/*
|
|
thisproc->state = SCHED_STATE_RUNNABLE;
|
|
bitarray_set(runnablearrays[thisproc->prio], (int) (thisproc - proc), 1);
|
|
//goto enterprocess;
|
|
found_any = found_here = 1;
|
|
*/
|
|
} else {
|
|
//thisproc->state = SCHED_STATE_RUNNABLE;
|
|
//bitarray_set(runnablearrays[thisproc->prio], (int) (thisproc - proc), 0);
|
|
//bitarray_set(exhaustedarrays[thisproc->prio], (int) (thisproc - proc), 1);
|
|
}
|
|
}
|
|
|
|
// Check if the FPU status is dirty
|
|
int fpustat = fpu_status_read();
|
|
if (fpustat != 0) {
|
|
printf("FPU Status %d, saving state\n", fpustat);
|
|
fpu_save(&thiscpu->process->fpu_context);
|
|
thiscpu->process->fpu_active = 0;
|
|
thiscpu->process->fpu_saved = 1;
|
|
fpu_status_write(0);
|
|
printf("FPU Status %d after saving state\n", fpu_status_read());
|
|
}
|
|
thiscpu->process = (void*)0ULL; // Process is finished, immediately exit it's context
|
|
}
|
|
|
|
release(&thisproc->lock);
|
|
|
|
if (found_here) {
|
|
int check = prioidx;
|
|
for (prioidx = 0; prioidx < check-1 && (bitarray_findlowest(runnablearrays[prioidx], 0) < 0 || (thiscpu->allowstarvation && (bitarray_findlowest(exhaustedarrays[prioidx], 0) < 0))); prioidx++) {
|
|
found_here = 0; // Reset the outer loop if more important processes may be runnable
|
|
//break; // End the inner loop
|
|
}
|
|
}
|
|
}
|
|
} while (found_here);
|
|
if (thiscpu->allowstarvation) {
|
|
// sched_reviveexhausted(prioidx);
|
|
}
|
|
}
|
|
if (!thiscpu->allowstarvation) {
|
|
for (int i = 0; i < NPRIO; i++) {
|
|
if (sched_reviveexhausted(i) > 0) {
|
|
found_any = 1;
|
|
}
|
|
}
|
|
}
|
|
if (!found_any) {
|
|
//printf("Not found any processes\n");
|
|
// This function will enable interrupts and put the CPU in wait mode
|
|
sched_waitforinterrupts();
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* sched_statename(sched_state_t s, int padded) {
|
|
switch (s) {
|
|
case SCHED_STATE_UNUSED:
|
|
return padded ? "SCHED_STATE_UNUSED " : "SCHED_STATE_UNUSED";
|
|
case SCHED_STATE_USED:
|
|
return padded ? "SCHED_STATE_USED " : "SCHED_STATE_USED";
|
|
case SCHED_STATE_SLEEPING:
|
|
return padded ? "SCHED_STATE_SLEEPING" : "SCHED_STATE_SLEEPING";
|
|
case SCHED_STATE_RUNNABLE:
|
|
return padded ? "SCHED_STATE_RUNNABLE" : "SCHED_STATE_RUNNABLE";
|
|
case SCHED_STATE_RUNNING:
|
|
return padded ? "SCHED_STATE_RUNNING " : "SCHED_STATE_RUNNING";
|
|
case SCHED_STATE_ZOMBIE:
|
|
return padded ? "SCHED_STATE_ZOMBIE " : "SCHED_STATE_ZOMBIE";
|
|
default:
|
|
return padded ? "BADSTATE" : "BADSTATE";
|
|
}
|
|
}
|
|
|
|
// Called when CTRL+p is pressed
|
|
void sched_dumpstatus() {
|
|
int i; // Iterator variable is reused
|
|
|
|
printf("\n"); // Print a newline to improve alignment
|
|
|
|
for (i = 0; i < NPROC; i++) {
|
|
if (proc[i].state != SCHED_STATE_UNUSED) {
|
|
printf("proc[%d] pid=%d state=%s name=\"%s\" prio=%d maxprio=%d\n", i, proc[i].pid, sched_statename(proc[i].state, 1), proc[i].name, proc[i].prio, proc[i].maxprio);
|
|
}
|
|
}
|
|
|
|
printf("Sleeping: ");
|
|
for (i = 0; i < NPROC; i++) {
|
|
if (bitarray_getnolock(sleeping, i)) {
|
|
printf("%d ", i);
|
|
}
|
|
}
|
|
printf("\n");
|
|
int prio;
|
|
for (prio = 0; prio < NPRIO; prio++) {
|
|
printf("Runnables priority #%d (main set): ", prio);
|
|
for (i = 0; i < NPROC; i++) {
|
|
if (bitarray_getnolock(runnablearrays[prio], i)) {
|
|
printf("%d ", i);
|
|
}
|
|
}
|
|
printf("\n");
|
|
printf("Runnables priority #%d (exhausted): ", prio);
|
|
for (i = 0; i < NPROC; i++) {
|
|
if (bitarray_getnolock(exhaustedarrays[prio], i)) {
|
|
printf("%d ", i);
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
printf("RAM: %d MB total %d MB free\n", (int) (physpg_totalram()/MB), (int)(physpg_freeram()/MB));
|
|
}
|
|
|
|
/*
|
|
* hypothetical thread-sync code I never finished
|
|
int
|
|
thrsync() {
|
|
struct proc* p = myproc();
|
|
struct proc* mainthread = p->mainthread;
|
|
if (!mainthread) {
|
|
return 0;
|
|
}
|
|
int unsynced;
|
|
do {
|
|
unsynced = 0;
|
|
for (int i = 0; i < NPROC; i++) {
|
|
struct proc* thr = &proc[i];
|
|
if (thr != p) {
|
|
acquire(&thr->lock);
|
|
if (thr->mainthread == mainthread) {
|
|
// TODO ...
|
|
}
|
|
release(&thr->lock);
|
|
}
|
|
}
|
|
} while (unsynced);
|
|
return unsynced;
|
|
}
|
|
*/
|