#include "process.h"
#include "common.h"
#include "alloc.h"
#include "vmm.h"
#include "descriptortables.h"
#include "elf.h"
#include "screen.h"
#include "debugprint.h"
#include "isr.h"
#include "timer.h"
#include "message.h"
#define MESSAGE_QUEUE_SIZE 64
Process* gKernelProcess = NULL;
Thread* gFirstThread = NULL;
Thread* gCurrentThread = NULL;
Thread* gDestroyedThread = NULL;
uint32 gProcessIdGenerator = 0;
uint32 gThreadIdGenerator = 0;
uint32 gSystemContextSwitchCount = 0;
uint32 gLastUptimeSeconds = 0;
extern Tss gTss;
uint32 generateProcessId() {
return gProcessIdGenerator++;
}
uint32 generateThreadId() {
return gThreadIdGenerator++;
}
uint32 getSystemContextSwitchCount() {
return gSystemContextSwitchCount;
}
void initializeTasking() {
Process* process = (Process*)kmalloc(sizeof(Process));
memset((uint8*)process, 0, sizeof(Process));
strcpy(process->name, "[kernel]");
process->pid = generateProcessId();
process->pd = (uint32*) KERN_PAGE_DIRECTORY;
process->workingDirectory = getFileSystemRootNode();
gKernelProcess = process;
Thread* thread = (Thread*)kmalloc(sizeof(Thread));
memset((uint8*)thread, 0, sizeof(Thread));
thread->owner = gKernelProcess;
thread->threadId = generateThreadId();
thread->userMode = 0;
thread->state = TS_RUN;
thread->messageQueue = FifoBuffer_create(sizeof(SosoMessage) * MESSAGE_QUEUE_SIZE);
Spinlock_Init(&(thread->messageQueueLock));
thread->regs.cr3 = (uint32) process->pd;
uint32 selector = 0x10;
thread->regs.ss = selector;
thread->regs.eflags = 0x0;
thread->regs.cs = 0x08;
thread->regs.eip = NULL;
thread->regs.ds = selector;
thread->regs.es = selector;
thread->regs.fs = selector;
thread->regs.gs = selector;
thread->regs.esp = 0; //no need because this is already main kernel thread. ESP will written to this in first schedule.
thread->kstack.ss0 = 0x10;
thread->kstack.esp0 = 0;//For kernel threads, this is not required
gFirstThread = thread;
gCurrentThread = thread;
}
static int getStringArrayItemCount(char *const array[]) {
if (NULL == array) {
return 0;
}
int i = 0;
const char* a = array[0];
while (NULL != a) {
a = array[++i];
}
return i;
}
static char** cloneStringArray(char *const array[]) {
int itemCount = getStringArrayItemCount(array);
char** newArray = kmalloc(sizeof(char*) * (itemCount + 1));
for (int i = 0; i < itemCount; ++i) {
const char* str = array[i];
int len = strlen(str);
char* newStr = kmalloc(len + 1);
strcpy(newStr, str);
newArray[i] = newStr;
}
newArray[itemCount] = NULL;
return newArray;
}
static void destroyStringArray(char** array) {
char* a = array[0];
int i = 0;
while (NULL != a) {
kfree(a);
a = array[++i];
}
kfree(array);
}
//This function must be called within the correct page directory for target process
static void copyArgvEnvToProcess(char *const argv[], char *const envp[]) {
char** destination = (char**)USER_ARGV_ENV_LOC;
int destinationIndex = 0;
//printkf("ARGVENV: destination:%x\n", destination);
int argvCount = getStringArrayItemCount(argv);
int envpCount = getStringArrayItemCount(envp);
//printkf("ARGVENV: argvCount:%d envpCount:%d\n", argvCount, envpCount);
char* stringTable = (char*)USER_ARGV_ENV_LOC + sizeof(char*) * (argvCount + envpCount + 2);
//printkf("ARGVENV: stringTable:%x\n", stringTable);
for (int i = 0; i < argvCount; ++i) {
strcpy(stringTable, argv[i]);
destination[destinationIndex] = stringTable;
stringTable += strlen(argv[i]) + 2;
destinationIndex++;
}
destination[destinationIndex++] = NULL;
for (int i = 0; i < envpCount; ++i) {
strcpy(stringTable, envp[i]);
destination[destinationIndex] = stringTable;
stringTable += strlen(envp[i]) + 2;
destinationIndex++;
}
destination[destinationIndex++] = NULL;
}
Process* createUserProcessFromElfData(const char* name, uint8* elfData, char *const argv[], char *const envp[], Process* parent, FileSystemNode* tty) {
return createUserProcessEx(name, generateProcessId(), generateThreadId(), NULL, elfData, argv, envp, parent, tty);
}
Process* createUserProcessEx(const char* name, uint32 processId, uint32 threadId, Function0 func, uint8* elfData, char *const argv[], char *const envp[], Process* parent, FileSystemNode* tty) {
printkf("createUserProcessEx: %s %d %d\n", name, processId, threadId);
if (0 == processId) {
processId = generateProcessId();
}
if (0 == threadId) {
threadId = generateThreadId();
}
Process* process = (Process*)kmalloc(sizeof(Process));
memset((uint8*)process, 0, sizeof(Process));
strcpy(process->name, name);
process->pid = processId;
process->pd = createPd();//our page directories are identity mapped so this is also a physical address.
process->workingDirectory = getFileSystemRootNode();
Thread* thread = (Thread*)kmalloc(sizeof(Thread));
memset((uint8*)thread, 0, sizeof(Thread));
thread->owner = process;
thread->threadId = threadId;
thread->userMode = 1;
thread->state = TS_RUN;
thread->messageQueue = FifoBuffer_create(sizeof(SosoMessage) * MESSAGE_QUEUE_SIZE);
Spinlock_Init(&(thread->messageQueueLock));
thread->regs.cr3 = (uint32) process->pd;
//Since stack grows backwards, we must allocate previous page. So lets substract a small amount.
uint32 stackp = USER_STACK-4;
if (parent) {
process->parent = parent;
process->workingDirectory = parent->workingDirectory;
process->tty = parent->tty;
}
if (tty) {
process->tty = tty;
}
char** newArgv = cloneStringArray(argv);
char** newEnvp = cloneStringArray(envp);
//Change memory view (page directory)
asm("mov %0, %%eax; mov %%eax, %%cr3"::"m"(process->pd));
initializeProcessHeap(process);
initializeProcessMmap(process);
copyArgvEnvToProcess(newArgv, newEnvp);
destroyStringArray(newArgv);
destroyStringArray(newEnvp);
uint32 selector = 0x23;
thread->regs.ss = selector;
thread->regs.eflags = 0x0;
thread->regs.cs = 0x1B;
thread->regs.eip = (uint32)func;
thread->regs.ds = selector;
thread->regs.es = selector;
thread->regs.fs = selector;
thread->regs.gs = selector;
thread->regs.esp = stackp;
char* p_addr = getPageFrame4M();
char* v_addr = (char *) (USER_STACK - PAGESIZE_4M);
addPageToPd(process->pd, v_addr, p_addr, PG_USER);
thread->kstack.ss0 = 0x10;
uint8* stack = (uint8*)kmalloc(KERN_STACK_SIZE);
thread->kstack.esp0 = (uint32)(stack + KERN_STACK_SIZE - 4);
thread->kstack.stackStart = (uint32)stack;
Thread* p = gCurrentThread;
while (p->next != NULL) {
p = p->next;
}
p->next = thread;
if (elfData) {
printkf("about to load ELF data\n");
uint32 startLocation = loadElf((char*)elfData);
if (startLocation > 0) {
thread->regs.eip = startLocation;
}
}
//Restore memory view (page directory)
asm("mov %0, %%eax ;mov %%eax, %%cr3":: "m"(gCurrentThread->regs.cr3));
open_fs_forProcess(thread, process->tty, 0);//0: standard input
open_fs_forProcess(thread, process->tty, 0);//1: standard output
open_fs_forProcess(thread, process->tty, 0);//2: standard error
printkf("running process %d\n", process->pid);
return process;
}
//This function should be called in interrupts disabled state
void destroyThread(Thread* thread) {
Spinlock_Lock(&(thread->messageQueueLock));
//TODO: signal the process somehow
Thread* previousThread = getPreviousThread(thread);
if (NULL != previousThread) {
previousThread->next = thread->next;
kfree((void*)thread->kstack.stackStart);
FifoBuffer_destroy(thread->messageQueue);
Debug_PrintF("destroying thread %d\n", thread->threadId);
kfree(thread);
if (thread == gCurrentThread) {
gCurrentThread = NULL;
}
}
else {
printkf("Could not find previous thread for thread %d\n", thread->threadId);
PANIC("This should not be happened!\n");
}
}
//This function should be called in interrupts disabled state
void destroyProcess(Process* process) {
Thread* thread = gFirstThread;
Thread* previous = NULL;
while (thread) {
if (process == thread->owner) {
if (NULL != previous) {
previous->next = thread->next;
kfree((void*)thread->kstack.stackStart);
Spinlock_Lock(&(thread->messageQueueLock));
FifoBuffer_destroy(thread->messageQueue);
Debug_PrintF("destroying thread id:%d (owner process %d)\n", thread->threadId, process->pid);
kfree(thread);
if (thread == gCurrentThread) {
gCurrentThread = NULL;
}
thread = previous->next;
continue;
}
}
previous = thread;
thread = thread->next;
}
//Cleanup opened files
for (int i = 0; i < MAX_OPENED_FILES; ++i) {
if (process->fd[i] != NULL) {
close_fs(process->fd[i]);
}
}
if (process->parent) {
thread = gFirstThread;
while (thread) {
if (process->parent == thread->owner) {
if (thread->state == TS_WAITCHILD) {
thread->state = TS_RUN;
}
}
thread = thread->next;
}
}
Debug_PrintF("destroying process %d\n", process->pid);
destroyPd(process->pd);
kfree(process);
}
void threadStateToString(ThreadState state, uint8* buffer, uint32 bufferSize) {
if (bufferSize < 1) {
return;
}
buffer[0] = '\0';
if (bufferSize < 10) {
return;
}
switch (state) {
case TS_RUN:
strcpy((char*)buffer, "run");
break;
case TS_SLEEP:
strcpy((char*)buffer, "sleep");
break;
case TS_SUSPEND:
strcpy((char*)buffer, "suspend");
break;
case TS_WAITCHILD:
strcpy((char*)buffer, "waitchild");
break;
case TS_WAITIO:
strcpy((char*)buffer, "waitio");
break;
case TS_YIELD:
strcpy((char*)buffer, "yield");
break;
default:
break;
}
}
void waitForSchedule() {
//printkf("Waiting for a schedule()\n");
enableInterrupts();
while (TRUE) {
halt();
}
disableInterrupts();
PANIC("waitForSchedule(): Should not be reached here!!!\n");
}
void yield(uint32 count) {
gCurrentThread->yield = count;
gCurrentThread->state = TS_YIELD;
enableInterrupts();
while (gCurrentThread->yield > 0) {
halt();
}
disableInterrupts();
}
int32 getEmptyFd(Process* process) {
int32 result = -1;
beginCriticalSection();
for (int i = 0; i < MAX_OPENED_FILES; ++i) {
if (process->fd[i] == NULL) {
result = i;
break;
}
}
endCriticalSection();
return result;
}
int32 addFileToProcess(Process* process, File* file) {
int32 result = -1;
beginCriticalSection();
//printkf("addFileToProcess: pid:%d\n", process->pid);
for (int i = 0; i < MAX_OPENED_FILES; ++i) {
//printkf("addFileToProcess: i:%d fd[%d]:%x\n", i, i, process->fd[i]);
if (process->fd[i] == NULL) {
result = i;
file->fd = i;
process->fd[i] = file;
break;
}
}
endCriticalSection();
return result;
}
int32 removeFileFromProcess(Process* process, File* file) {
int32 result = -1;
beginCriticalSection();
for (int i = 0; i < MAX_OPENED_FILES; ++i) {
if (process->fd[i] == file) {
result = i;
process->fd[i] = NULL;
break;
}
}
endCriticalSection();
return result;
}
Thread* getThreadById(uint32 threadId) {
Thread* p = gFirstThread;
while (p != NULL) {
if (p->threadId == threadId) {
return p;
}
p = p->next;
}
return NULL;
}
Thread* getPreviousThread(Thread* thread) {
Thread* t = gFirstThread;
while (t->next != NULL) {
if (t->next == thread) {
return t;
}
t = t->next;
}
return NULL;
}
Thread* getMainKernelThread() {
return gFirstThread;
}
Thread* getCurrentThread() {
return gCurrentThread;
}
BOOL isThreadValid(Thread* thread) {
Thread* p = gFirstThread;
while (p != NULL) {
if (p == thread) {
return TRUE;
}
p = p->next;
}
return FALSE;
}
BOOL isProcessValid(Process* process) {
Thread* p = gFirstThread;
while (p != NULL) {
if (p->owner == process) {
return TRUE;
}
p = p->next;
}
return FALSE;
}
static void switchToTask(Thread* current);
static void updateMetrics(Thread* thread) {
uint32 seconds = getUptimeSeconds();
if (seconds > gLastUptimeSeconds) {
gLastUptimeSeconds = seconds;
Thread* t = gFirstThread;
while (t != NULL) {
t->contextSwitchCount = t->totalContextSwitchCount - t->totalContextSwitchCountPrevious;
t->totalContextSwitchCountPrevious = t->totalContextSwitchCount;
t = t->next;
}
}
++gSystemContextSwitchCount;
++thread->totalContextSwitchCount;
}
void schedule(TimerInt_Registers* registers) {
Thread* current = gCurrentThread;
if (NULL != current) {
if (current->next == NULL && current == gFirstThread) {
//We are the only process, no need to schedule
return;
}
current->regs.eflags = registers->eflags;
current->regs.cs = registers->cs;
current->regs.eip = registers->eip;
current->regs.eax = registers->eax;
current->regs.ecx = registers->ecx;
current->regs.edx = registers->edx;
current->regs.ebx = registers->ebx;
current->regs.ebp = registers->ebp;
current->regs.esi = registers->esi;
current->regs.edi = registers->edi;
current->regs.ds = registers->ds;
current->regs.es = registers->es;
current->regs.fs = registers->fs;
current->regs.gs = registers->gs;
if (current->regs.cs != 0x08) {
//Debug_PrintF("schedule() - 2.1\n");
current->regs.esp = registers->esp_if_privilege_change;
current->regs.ss = registers->ss_if_privilege_change;
}
else {
//Debug_PrintF("schedule() - 2.2\n");
current->regs.esp = registers->esp + 12;
current->regs.ss = gTss.ss0;
}
//Save the TSS from the old process
current->kstack.ss0 = gTss.ss0;
current->kstack.esp0 = gTss.esp0;
current = current->next;
while (NULL != current) {
if (current->state == TS_YIELD) {
if (current->yield > 0) {
--current->yield;
}
if (current->yield == 0) {
current->state = TS_RUN;
}
}
if (current->state == TS_SLEEP) {
uint32 uptime = getUptimeMilliseconds();
uint32 target = (uint32)current->state_privateData;
if (uptime >= target) {
current->state = TS_RUN;
current->state_privateData = NULL;
}
}
if (current->state == TS_RUN) {
break;
}
current = current->next;
}
if (current == NULL) {
//reached last process returning to first
current = gFirstThread;
}
}
else {
//current is NULL. This means thread is destroyed, so start from the begining
current = gFirstThread;
}
gCurrentThread = current;//Now gCurrentThread is the thread we are about to schedule to
/*
if (gCurrentThread->threadId == 5) {
printkf("I am scheduling to %d and its EIP is %x\n", gCurrentThread->threadId, gCurrentThread->regs.eip);
}
*/
updateMetrics(current);
switchToTask(current);
}
static void switchToTask(Thread* current) {
uint32 kesp, eflags;
uint16 kss, ss, cs;
//Set TSS values
gTss.ss0 = current->kstack.ss0;
gTss.esp0 = current->kstack.esp0;
ss = current->regs.ss;
cs = current->regs.cs;
eflags = (current->regs.eflags | 0x200) & 0xFFFFBFFF;
int oldMode;
if (cs != 0x08) {
oldMode = USERMODE;
kss = current->kstack.ss0;
kesp = current->kstack.esp0;
}
else {
oldMode = KERNELMODE;
kss = current->regs.ss;
kesp = current->regs.esp;
}
//switchTask is in task.asm
asm(" mov %0, %%ss; \
mov %1, %%esp; \
cmpl %[KMODE], %[mode]; \
je nextt; \
push %2; \
push %3; \
nextt: \
push %4; \
push %5; \
push %6; \
push %7; \
ljmp $0x08, $switchTask"
:: \
"m"(kss), \
"m"(kesp), \
"m"(ss), \
"m"(current->regs.esp), \
"m"(eflags), \
"m"(cs), \
"m"(current->regs.eip), \
"m"(current), \
[KMODE] "i"(KERNELMODE), \
[mode] "g"(oldMode)
);
}