X-Git-Url: https://pd.if.org/git/?p=zos;a=blobdiff_plain;f=process.c;fp=process.c;h=046247e54fe9eb58768d5ef9253d030d1b221a64;hp=0000000000000000000000000000000000000000;hb=7c1b1bcd0cd0b3e3738afe96aa9b94a275a7951b;hpb=7b91cb3f96d7b8ba0874a1b24b19f624e43c99d5 diff --git a/process.c b/process.c new file mode 100644 index 0000000..046247e --- /dev/null +++ b/process.c @@ -0,0 +1,472 @@ +#include +#include + +#include "kernel.h" +#include "mem.h" +#include "process.h" + +#include "timer.h" +#include "interrupt.h" +#include + +#include +#include +#include + +/* 8 MB stack */ +#define RLIMIT_STACK_VAL 0x800000 + +/* + * memory map for processes + * 0xFFFF FFFF F800 0000 and up, kernel, can copy kernel pml4 entries + * program load at 4 MB + * kernel stack at 128 TB 7F... + * stack at 127 TB = 8000 0000 0000 (and down)/ thread stacks go somewhere + * stacks could be limited to physmem. perhaps it's ok for each address space + * to have it's own total stacks limit + * mmaps at 126 TB = 7E00 0000 0000 (and down) + * heap at 96 TB = 6000 0000 0000 (and up) + * heap at 2 TB? + * heap after program load... + * program at 4 MB = 40 0000 + */ + +/* so, to create a process, + * allocate 2 MB for stack, + * allocate whatever is needed for the code + * load in the code to the code base + * set up the registers + * set up the stack for the iretq or sysret + * iretq or sysret to start the task + */ + +/* current process is per processor? */ +struct process *current_task = 0; +static struct process *available = 0; /* a stack of unused task structs */ +static struct process mainTask; +static struct process *idle_task; +static void idle_task_main() { + while (1) { + halt(); + schedule(); + } +} + +struct pqueue { + struct process *head; + struct process *tail; + struct spinlock_t lock; + unsigned int size; +}; + +static struct pqueue sleepqueue; +static struct pqueue runqueue; +static struct pqueue terminating; + +void initqueue(struct pqueue *q) { + q->lock = (struct spinlock_t){0}; + q->head = 0; + q->tail = 0; + q->size = 0; +} + +void dumpqueue(struct pqueue *q, char *name) { + struct process *task; + + if (name) { + printk("%u %s:", timer_ticks, name); + } else { + printk("%u queue %llx:", timer_ticks, q); + } + + for (task = q->head; task; task = task->next) { + printk(" %u", task->pid); + if (task->sleep) { + printk(":%u", task->sleep); + } + } + printk("\n"); +} + +/* dequeue sleeper is no different than a regular dequeue */ +void enqueue_sleeper(struct pqueue *q, struct process *task) { + struct process *sleeper; + + spinlock_acquire(&q->lock); + + task->next = 0; + task->prev = 0; + + if (!q->head) { + q->head = task; + task->prev = task; + } else { + for (sleeper = q->head; sleeper; sleeper = sleeper->next) { + if (task->sleep < sleeper->sleep) { + /* insert before this one */ + task->next = sleeper; + task->prev = sleeper->prev; + if (sleeper != q->head) { + sleeper->prev->next = task; + } else { + q->head = task; + } + sleeper->prev = task; + break; + } + } + + if (!sleeper) { + /* if we got here, we're the last task */ + task->next = 0; + task->prev = q->head->prev; + task->prev->next = task; + q->head->prev = task; + } + } + + q->size++; + + spinlock_release(&q->lock); +} + +void enqueue(struct pqueue *q, struct process *task) { + spinlock_acquire(&q->lock); + + task->next = 0; + if (q->head) { + task->prev = q->head->prev; + task->prev->next = task; + q->head->prev = task; + } else { + q->head = task; + task->prev = task; + } + q->size++; + + spinlock_release(&q->lock); +} + +struct process *dequeue(struct pqueue *q) { + struct process *task; + + spinlock_acquire(&q->lock); + task = q->head; + if (task) { + q->head = task->next; + if (q->head) { + q->head->prev = task->prev; + } + task->next = 0; + task->prev = 0; + } + q->size--; + spinlock_release(&q->lock); + return task; +} + +void enqueue_runnable(struct process *task) { + enqueue(&runqueue, task); +} + +struct process *dequeue_runnable(void) { + return dequeue(&runqueue); +} + +pid_t getpid() { + return current_task->pid; +} + +void exit(int status) { + current_task->status = status; + + /* put in parents process "exited" queue for wait() */ + schedule(); +} + +void terminate(struct process *p) { + + /* remove it from whatever queue it's in */ + /* enqueue it into terminating queue */ + + /* push it on the top of the runqueue */ + enqueue(&terminating, p); + /* free all memory, except maybe kernel stack */ + + /* remove from task queues */ + + /* add to available */ + schedule(); +} + +void yield(void); + +void sleep(uint32_t ticks) { + if (current_task->sleep) { + panic("tried to sleep pid %u which was already sleeping until %u\n", current_task->pid, current_task->sleep); + } + current_task->sleep = timer_ticks + ticks; + enqueue_sleeper(&sleepqueue, current_task); + current_task->flags &= ~TM_RUNNABLE; + yield(); +} + +static void third_main() { + sleep(500); + while (1) { + printk("%llu: Hello from A pid %lu, cpl = %x\n", timer_ticks, getpid(), current_task->pl); + sleep(300+300 * getpid()); + } +} + +static void other_main() { + while (1) { + printk("%llu: Hello from B pid %lu\n", timer_ticks, getpid()); + sleep(10000); + } +} + +#define HOG +#ifdef HOG +static void hog_main() { + static int x = 0; + static volatile uint64_t donetil = 0; + printk("first scheduled hog\n"); + while (1) { + x++; + if (timer_ticks >= donetil) { + printk("%llu hog %u running\n", timer_ticks,current_task->pid); + donetil += 1000; + } + } +} +#endif + +struct interrupt_handler pih; + +static void preemption_interrupt(struct interrupt_context *c, void *n) { + struct process *sleeper; + + /* check for sleepers */ + while (sleepqueue.head && sleepqueue.head->sleep <= timer_ticks) { + sleeper = dequeue(&sleepqueue); + sleeper->sleep = 0; + sleeper->flags |= TM_RUNNABLE; + enqueue_runnable(sleeper); + } + + if (current_task != idle_task) { + current_task->quantum--; + } else { + schedule(); + } + + if (current_task->quantum == 0) { + if (current_task != idle_task) { + if (current_task->flags & TM_RUNNABLE) { + enqueue_runnable(current_task); + } + } + schedule(); + } + return; +} + +void usermain(void); + +void init_tasking() { + struct process *task; + + initqueue(&runqueue); + initqueue(&sleepqueue); + initqueue(&terminating); + + mainTask.reg.cr3 = getcr3(); + mainTask.reg.rflags = getrflags(); + mainTask.reg.cs = 0x10; + mainTask.reg.ss = 0x18; + mainTask.pl = 0x0; + mainTask.pid = 0; + mainTask.flags = 0; + + idle_task = new_task(idle_task_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL|TASK_NOSCHED); + + /* TODO move these into a dummy/test function */ + task = new_task(other_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL); + task = new_task(third_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL); + task = new_task(third_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL); + task = new_task(third_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL); +#ifdef HOG + task = new_task(hog_main, mainTask.reg.rflags, mainTask.reg.cr3, TASK_KERNEL); +#endif + + task = new_task(usermain, mainTask.reg.rflags, create_addrspace(), 0); + current_task = &mainTask; + pih.handler = preemption_interrupt; + pih.context = 0; + interrupt_add_handler(IRQ0, &pih); + printk("set up tasking\n"); +} + +struct process *new_task(void (*main)(), uint64_t rflags, uint64_t pagedir, uint64_t flags) { + struct process *p; + + p = koalloc(sizeof *p); + if (!p) { + panic("can't allocate memory for new task\n"); + return 0; + } + create_task(p, main, rflags, pagedir, flags); + return p; +} + +void setup_usertask(void); + +static pid_t next_pid = 2; + +pid_t create_task(struct process *task, void (*main)(), uint64_t rflags, uint64_t addrspace, uint64_t flags) { + + task->reg = (struct registers){ 0 }; /* clear out all registers for new task */ + + /* roll over? re-use? */ + /* could possible use bts on max nums. possibly wasteful, and still O(n) */ + if (!available) { + task->pid = next_pid++; + } + + task->reg.rflags = rflags; + task->reg.cr3 = addrspace; + task->kstack = (uint64_t)PHY2VIRTP(palloc()); /* new kernel stack */ + task->reg.rsp = task->kstack + 0x1000; + task->reg.rip = (uint64_t)main; + task->reg.cs = 0x10; /* kernel code segment */ + task->reg.ss = 0x18; /* kernel data segment */ + task->pl = 0; /* not a user task */ + task->quantum = 0; /* will get a quantum when it's scheduled */ + task->flags = TM_RUNNABLE; /* new tasks are runnable */ + + if (! (flags & TASK_KERNEL)) { + task->main = (int (*)(int,char**))main; + task->reg.rdi = (uintptr_t)task->main; + task->reg.rip = (uintptr_t)setup_usertask; + task->flags |= TM_USER; + } + + /* stacks will need thinking */ + /* stack can't just go at fixed 128 TB, threads need their own */ + if (! (flags & TASK_KERNEL)) { + printk("created user task %u %llx, space = %llx, entry = %llx\n", task->pid, task, task->reg.cr3, task->reg.rip); + } + + if (!(flags & TASK_NOSCHED)) { + enqueue_runnable(task); + } + + return task->pid; +} + + +void setup_usertask(void) { + struct process *task; + task = current_task; + + /* need a sysret trampoline for user tasks */ + //task->reg.rip = (uint64_t)usermodetrampoline; + //task->reg.rcx = (uint64_t)main; + // hmm. if this is in kernel space, it's going to fail after the sysret + // we need to copy the code into the user space, and hope it's PIC... + + /* create a 1 meg map for the user code at the 8 GB mark */ + printk("setting up usertask %u, space = %llx\n", task->pid, getcr3()); + vmapz(task->reg.cr3, GB(8), MB(1), MEM_USERSPACE|MEM_RW); + printk("mapped 1 MB for user\n"); + memmove((void *)GB(8), task->main, KB(4)); + printk("copied main func from %llx to %llx\n", task->main, GB(8)); + /* copy the user main function to the GB 8 mark */ + /* hmm, not mapped here..., so have to map it in the trampoline + * so it has the user mappings + */ + /* map it into kernel space? will need a mutex on that */ + /* then just copy the tables? */ + + task->reg.rip = GB(8); + task->pl = 3; + +#if 0 + task->reg.cs = 0x20; + task->reg.ss = 0x28; +#endif + + /* create the user mode stack */ + task->stacks = TB(127); + vmapz(task->reg.cr3, task->stacks - MB(2), MB(2), MEM_USERSPACE); + task->usp = task->stacks; /* user stack pointer */ + + /* What if this fails? */ + /* need a kernel mode stack for syscalls, and possibly interrupts */ +#if 0 + task->kstack = TB(128); + printk("creating 4 KB kernel stack at virtual address %llx\n", task->kstack-KB(4)); + vmapz(addrspace, task->kstack - KB(4), KB(4), MEM_RW|MAP_TRACE); + task->reg.rsp = task->kstack; +#endif + printk("user task %u, kstack rsp = %llx\n", task->pid, task->kstack); + //print_decode(0, task->reg.cr3, task->reg.rsp-8); +#if 0 + test_address((void *)(task->reg.rsp - 8)); + allstop(); +#endif + /* this won't return */ + printk("user tramp\n"); + /* we loaded to the 8 GB mark... */ + usermodetrampoline( (void (*)()) GB(8), task->reg.rflags); +} + +void schedule(void) { + current_task->flags |= TM_SCHEDULE; +} + +/* set the current task for scheduling, and if we're not in an interrupt, + * immediately re-schedule. could just halt for now, which will wait for the + * next interrupt. + */ +void yield(void) { + schedule(); + /* TODO check if in interrupt. if not, then call one? */ + halt(); +} + +int need_schedule(void) { + return current_task ? current_task->flags & TM_SCHEDULE : 0; +} + +/* probably want to use lock free queue with compare and swap + * see http://preshing.com/20120612/an-introduction-to-lock-free-programming/ + */ + +void do_schedule(void) { + struct process *prev, *next; + + prev = current_task; + + next = dequeue_runnable(); + if (!next) { + /* no runnable task */ + next = idle_task; + } + + next->flags &= ~TM_SCHEDULE; /* clear the schedule flag */ + next->quantum = 10; /* smaller slice for same task */ + + /* don't run though the trouble if we're not changing tasks */ + if (next != prev) { + next->quantum = 20; + dumpqueue(&runqueue, "runqueue"); + current_task = next; + switch_task(&prev->reg, &next->reg); + } +} + +void preempt() { + schedule(); +}