From a4d15a1d33dc5eb6fead80060087cb232d62d91d Mon Sep 17 00:00:00 2001 From: Fran Lorda Date: Fri, 2 Oct 2026 05:43:15 -0300 Subject: [PATCH] feat: add sched v2 skeleton --- sched/.clang-files | 4 ++ sched/inc/env.h | 8 +++ sched/inc/lib.h | 10 +++ sched/inc/syscall.h | 7 +++ sched/kern/Makefrag | 5 +- sched/kern/env.c | 10 ++- sched/kern/init.c | 9 ++- sched/kern/sched.c | 49 ++++++++++----- sched/kern/sched.h | 4 ++ sched/kern/syscall.c | 72 +++++++++++++++++++++- sched/kern/trap.c | 14 ++++- sched/kern/trap.h | 5 ++ sched/lib/Makefrag | 3 +- sched/lib/syscall.c | 33 ++++++++++ sched/lib/thread.c | 30 +++++++++ sched/user/priotest.c | 131 ++++++++++++++++++++++++++++++++++++++++ sched/user/sleeptest.c | 32 ++++++++++ sched/user/threadtest.c | 42 +++++++++++++ 18 files changed, 441 insertions(+), 27 deletions(-) create mode 100644 sched/lib/thread.c create mode 100644 sched/user/priotest.c create mode 100644 sched/user/sleeptest.c create mode 100644 sched/user/threadtest.c diff --git a/sched/.clang-files b/sched/.clang-files index 9167412..d4dbbed 100644 --- a/sched/.clang-files +++ b/sched/.clang-files @@ -28,6 +28,9 @@ user/faultreadkernel.c user/faultnostack.c user/breakpoint.c user/fairness.c +user/sleeptest.c +user/priotest.c +user/threadtest.c user/divzero.c user/faultbadhandler.c user/buggyhello2.c @@ -36,6 +39,7 @@ lib/panic.c lib/readline.c lib/syscall.c lib/fork.c +lib/thread.c lib/printfmt.c lib/exit.c lib/string.c diff --git a/sched/inc/env.h b/sched/inc/env.h index d9a6322..a2556ca 100644 --- a/sched/inc/env.h +++ b/sched/inc/env.h @@ -35,6 +35,7 @@ enum { ENV_FREE = 0, ENV_DYING, ENV_RUNNABLE, ENV_RUNNING, ENV_NOT_RUNNABLE }; // Special environment types enum EnvType { ENV_TYPE_USER = 0, + ENV_TYPE_THREAD, // Part 4: thread that shares its address space with its parent }; struct Env { @@ -59,6 +60,13 @@ struct Env { uint32_t env_ipc_value; // Data value sent to us envid_t env_ipc_from; // envid of the sender int env_ipc_perm; // Perm of page mapping received + + // Part 2: sleep based on timer ticks + uint32_t env_sleep_until; // global tick at which the env should wake up (0 = not sleeping) + + // Part 3: priorities + uint32_t env_priority; // env's priority (higher value = higher priority) + uint32_t env_wait_ticks; // ticks accumulated waiting in the ready queue (for aging) }; #endif // !JOS_INC_ENV_H diff --git a/sched/inc/lib.h b/sched/inc/lib.h index e7e83dc..6a9856e 100644 --- a/sched/inc/lib.h +++ b/sched/inc/lib.h @@ -53,6 +53,13 @@ int sys_page_map( int sys_page_unmap(envid_t env, void *pg); int sys_ipc_try_send(envid_t to_env, uint32_t value, void *pg, int perm); int sys_ipc_recv(void *rcv_pg); +// Part 2 +int sys_sleep(uint32_t n); +// Part 3 +int sys_getpriority(envid_t envid); +int sys_setpriority(envid_t envid, uint32_t priority); +// Part 4 +envid_t sys_thread_create(void *entry, void *ustack_top); // This must be inlined. Exercise for reader: why? static inline envid_t __attribute__((always_inline)) sys_exofork(void) @@ -72,6 +79,9 @@ envid_t ipc_find_env(enum EnvType type); envid_t fork(void); envid_t sfork(void); // Challenge! +// thread.c +envid_t thread_create(void (*func)(void *), void *arg); + /* File open modes */ #define O_RDONLY 0x0000 /* open for reading only */ diff --git a/sched/inc/syscall.h b/sched/inc/syscall.h index 71b3512..a459e0a 100644 --- a/sched/inc/syscall.h +++ b/sched/inc/syscall.h @@ -16,6 +16,13 @@ enum { SYS_yield, SYS_ipc_try_send, SYS_ipc_recv, + // Part 2: sleep based on timer ticks + SYS_sleep, + // Part 3: priority handling + SYS_getpriority, + SYS_setpriority, + // Part 4: thread creation + SYS_thread_create, NSYSCALLS }; diff --git a/sched/kern/Makefrag b/sched/kern/Makefrag index 41d14a2..ecd7921 100644 --- a/sched/kern/Makefrag +++ b/sched/kern/Makefrag @@ -78,7 +78,10 @@ KERN_BINFILES += user/idle \ user/fairness \ user/pingpong \ user/pingpongs \ - user/primes + user/primes \ + user/sleeptest \ + user/priotest \ + user/threadtest KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES)) KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES)) KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES)) diff --git a/sched/kern/env.c b/sched/kern/env.c index 926fc66..78cdf68 100644 --- a/sched/kern/env.c +++ b/sched/kern/env.c @@ -290,7 +290,7 @@ region_alloc(struct Env *e, void *va, size_t len) if (!(p = page_alloc(!ALLOC_ZERO))) panic("region_alloc: could not alloc page\n"); if (page_insert(e->env_pgdir, p, (uint32_t *) va_act, PTE_U | PTE_W)) - panic("region_alloc: page_insert falied\n"); + panic("region_alloc: page_insert failed\n"); } } @@ -402,6 +402,12 @@ env_create(uint8_t *binary, enum EnvType type) // // Frees env e and all memory it uses. // +// Part 4: the code below unmaps all of 'e's user-space memory and frees its +// page directory. A thread (ENV_TYPE_THREAD) shares its env_pgdir with its +// parent process: freeing it here would destroy the parent's memory and that +// of the other threads. Consider both directions of the parent/thread +// relationship when implementing this part. +// void env_free(struct Env *e) { @@ -509,7 +515,7 @@ env_run(struct Env *e) // Your code here curenv = e; - // Needed if we run with multiple procesors + // Needed if we run with multiple processors // Record the CPU we are running on for user-space debugging unlock_kernel(); curenv->env_cpunum = cpunum(); diff --git a/sched/kern/init.c b/sched/kern/init.c index b546772..6e88189 100644 --- a/sched/kern/init.c +++ b/sched/kern/init.c @@ -68,13 +68,18 @@ i386_init(void) ENV_CREATE(TEST, ENV_TYPE_USER); #else // Touch all you want. + // To run the tests for each part, replace these lines with, e.g.: + // ENV_CREATE(user_sleeptest, ENV_TYPE_USER); // Part 2 + // ENV_CREATE(user_priotest, ENV_TYPE_USER); // Part 3 + // ENV_CREATE(user_threadtest, ENV_TYPE_USER); // Part 4 + // The binaries are already declared in kern/Makefrag (KERN_BINFILES). ENV_CREATE(user_hello, ENV_TYPE_USER); ENV_CREATE(user_hello, ENV_TYPE_USER); ENV_CREATE(user_hello, ENV_TYPE_USER); #endif // TEST* - // Eliminar esta llamada una vez completada la parte 1 - // e implementado sched_yield(). + // Remove this call once part 1 is complete + // and sched_yield() is implemented. env_run(&envs[0]); // Schedule and run the first user environment! diff --git a/sched/kern/sched.c b/sched/kern/sched.c index 75cee8b..7976216 100644 --- a/sched/kern/sched.c +++ b/sched/kern/sched.c @@ -4,40 +4,50 @@ #include #include #include +#include void sched_halt(void); +// Wake up environments that were sleeping and whose env_sleep_until <= ticks. +// The global counter 'ticks' is declared in kern/trap.h. +// Call from trap_dispatch after incrementing ticks and before sched_yield. +void +sched_wakeup_sleeping(void) +{ + // Part 2: Your code here - wake up sleeping envs +} + // Choose a user environment to run and run it. void sched_yield(void) { #ifdef SCHED_ROUND_ROBIN - // Implement simple round-robin scheduling. + // Implement round-robin scheduling with timer preemption. // - // Search through 'envs' for an ENV_RUNNABLE environment in - // circular fashion starting just after the env this CPU was - // last running. Switch to the first such environment found. + // Walk 'envs' circularly looking for an ENV_RUNNABLE environment, + // starting right after the last environment run on this CPU. Switch + // to the first one found. // - // If no envs are runnable, but the environment previously - // running on this CPU is still ENV_RUNNING, it's okay to - // choose that environment. + // If no env is runnable but the environment that was running on this + // CPU is still ENV_RUNNING, it's fine to pick it again. // - // Never choose an environment that's currently running on - // another CPU (env_status == ENV_RUNNING). If there are - // no runnable environments, simply drop through to the code - // below to halt the cpu. + // Never pick an environment that's currently running on another CPU + // (env_status == ENV_RUNNING). If none is available, fall through + // to sched_halt. // Your code here - Round robin #endif #ifdef SCHED_PRIORITIES - // Implement simple priorities scheduling. + // Implement priority scheduling with anti-starvation aging. // - // Environments now have a "priority" so it must be consider - // when the selection is performed. + // Select the ENV_RUNNABLE environment with the highest env_priority. + // Increment env_wait_ticks for the environments that are not selected. + // Apply aging: if an environment has been waiting too long, temporarily + // increase its effective priority to avoid starvation. // - // Be careful to not fall in "starvation" such that only one - // environment is selected and run every time. + // If the selected environment is the same as curenv and it's still + // ENV_RUNNING, it's fine to run it again. // Your code here - Priorities #endif @@ -62,6 +72,13 @@ sched_halt(void) // For debugging and testing purposes, if there are no runnable // environments in the system, then drop into the kernel monitor. + // + // Part 2: careful, a sleeping process (sys_sleep) is ENV_NOT_RUNNABLE, + // so it doesn't count as "runnable" in this check. What happens, then, + // if every process is sleeping at the same time? Review this condition + // to distinguish "nothing to do right now" (should wait for the timer) + // from "no process is left alive" (only then should statistics be + // printed and the kernel halted for good). for (i = 0; i < NENV; i++) { if ((envs[i].env_status == ENV_RUNNABLE || envs[i].env_status == ENV_RUNNING || diff --git a/sched/kern/sched.h b/sched/kern/sched.h index 9e86ff8..9ffb8a4 100644 --- a/sched/kern/sched.h +++ b/sched/kern/sched.h @@ -9,4 +9,8 @@ // This function does not return. void sched_yield(void) __attribute__((noreturn)); +// Wake up sleeping environments whose env_sleep_until has already elapsed. +// Part 2: implement in kern/sched.c, call from trap_dispatch. +void sched_wakeup_sleeping(void); + #endif // !JOS_KERN_SCHED_H diff --git a/sched/kern/syscall.c b/sched/kern/syscall.c index 00fb797..cb10bf8 100644 --- a/sched/kern/syscall.c +++ b/sched/kern/syscall.c @@ -339,8 +339,7 @@ sys_page_unmap(envid_t envid, void *va) // then no page mapping is transferred, but no error occurs. // The ipc only happens when no errors occur. // -// Returns 0 on success, < 0 on error. -// Errors are: +// Returns 0 on success, < 0 on error. Errors are: // -E_BAD_ENV if environment envid doesn't currently exist. // (No need to check permissions.) // -E_IPC_NOT_RECV if envid is not currently blocked in sys_ipc_recv, @@ -429,6 +428,67 @@ sys_ipc_recv(void *dstva) return 0; } +// Put the current environment to sleep for 'n' timer interrupts. +// The environment becomes ENV_NOT_RUNNABLE and env_sleep_until is set to +// (global_ticks + n). The scheduler will wake it up once global_ticks >= +// env_sleep_until. +// +// This function only returns on error, but the system call will eventually +// return 0 on success. +// Return < 0 on error. Errors are: +// -E_INVAL if n == 0. +static int +sys_sleep(uint32_t n) +{ + // Part 2: Your code here + // + // Hint: See how sys_ipc_recv and sys_ipc_try_send solve the problem + // related with not retuning but anyhow setting a "return value". + return -E_INVAL; +} + +// Return the priority of the environment envid. +// Returns < 0 on error. Errors are: +// -E_BAD_ENV if envid doesn't exist or the caller lacks permission. +static int +sys_getpriority(envid_t envid) +{ + // Part 3: Your code here + return -E_INVAL; +} + +// Set the priority of the environment envid to 'priority'. +// A process CANNOT increase its own priority (only reduce it). +// A process CAN modify the priority of its direct children. +// +// Returns 0 on success, or < 0 on error. Errors are: +// -E_BAD_ENV if envid doesn't exist or without permission. +// -E_INVAL if trying to increase the current process's priority. +static int +sys_setpriority(envid_t envid, uint32_t priority) +{ + // Part 3: Your code here + return -E_INVAL; +} + +// Create a new thread that shares the current process's address +// space. The thread starts executing at 'entry' with the stack pointing to +// 'ustack_top'. +// +// Unlike fork, the thread does NOT have its own page directory; it uses the +// same env_pgdir as the parent process. When the parent process terminates, +// all of its threads are destroyed too. +// +// Returns the envid of the new thread on success, or < 0 on error. Errors are: +// -E_NO_FREE_ENV if there are no free entries in the PCB. +// -E_INVAL if entry or ustack_top are invalid. +static envid_t +sys_thread_create(void *entry, void *ustack_top) +{ + // Part 4: Your code here + return -E_INVAL; +} + // Dispatches to the correct kernel function, passing the arguments. int32_t syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5) @@ -464,6 +524,14 @@ syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, return sys_env_set_pgfault_upcall(a1, (void *) a2); case SYS_yield: sys_yield(); // No return + case SYS_sleep: + return sys_sleep(a1); + case SYS_getpriority: + return sys_getpriority(a1); + case SYS_setpriority: + return sys_setpriority(a1, a2); + case SYS_thread_create: + return sys_thread_create((void *) a1, (void *) a2); default: return -E_INVAL; } diff --git a/sched/kern/trap.c b/sched/kern/trap.c index 66e4090..3dfa804 100644 --- a/sched/kern/trap.c +++ b/sched/kern/trap.c @@ -22,6 +22,10 @@ static struct Taskstate ts; */ static struct Trapframe *last_tf; +// Global counter of timer ticks. +// Part 2: initialize to 0 and increment on every timer interrupt. +uint32_t ticks = 0; + /* Interrupt descriptor table. (Must be built at run time because * shifted function addresses can't be represented in relocation records.) */ @@ -222,11 +226,15 @@ trap_dispatch(struct Trapframe *tf) return; } - // Handle clock interrupts. Don't forget to acknowledge the - // interrupt using lapic_eoi() before calling the scheduler! + // Handle clock interrupts. + // - Acknowledge the interrupt using lapic_eoi() + // - Increment ticks + // - Wake up sleeping environments + // - Call the scheduler switch (tf->tf_trapno - IRQ_OFFSET) { case IRQ_TIMER: lapic_eoi(); + // Part 2: Your code here sched_yield(); return; } @@ -269,7 +277,7 @@ trap(struct Trapframe *tf) lock_kernel(); assert(curenv); - // Garbage collect if current enviroment is a zombie + // Garbage collect if current environment is a zombie if (curenv->env_status == ENV_DYING) { env_free(curenv); curenv = NULL; diff --git a/sched/kern/trap.h b/sched/kern/trap.h index 4a03e60..ffd5421 100644 --- a/sched/kern/trap.h +++ b/sched/kern/trap.h @@ -6,6 +6,7 @@ #error "This is a JOS kernel header; user programs should not #include it" #endif +#include #include #include @@ -13,6 +14,10 @@ extern struct Gatedesc idt[]; extern struct Pseudodesc idt_pd; +/* Global counter of timer ticks (defined in kern/trap.c). + * Part 2: must be incremented on every IRQ_TIMER interrupt. */ +extern uint32_t ticks; + void trap_init(void); void trap_init_percpu(void); void print_regs(struct PushRegs *regs); diff --git a/sched/lib/Makefrag b/sched/lib/Makefrag index 7710df3..b284ba5 100644 --- a/sched/lib/Makefrag +++ b/sched/lib/Makefrag @@ -14,7 +14,8 @@ LIB_SRCFILES := $(LIB_SRCFILES) \ lib/pgfault.c \ lib/pfentry.S \ lib/fork.c \ - lib/ipc.c + lib/ipc.c \ + lib/thread.c diff --git a/sched/lib/syscall.c b/sched/lib/syscall.c index 256ba98..5282aea 100644 --- a/sched/lib/syscall.c +++ b/sched/lib/syscall.c @@ -112,3 +112,36 @@ sys_ipc_recv(void *dstva) { return syscall(SYS_ipc_recv, 1, (uint32_t) dstva, 0, 0, 0, 0); } + +// Part 2 +int +sys_sleep(uint32_t n) +{ + return syscall(SYS_sleep, 1, n, 0, 0, 0, 0); +} + +// Part 3 +int +sys_getpriority(envid_t envid) +{ + return syscall(SYS_getpriority, 0, envid, 0, 0, 0, 0); +} + +int +sys_setpriority(envid_t envid, uint32_t priority) +{ + return syscall(SYS_setpriority, 1, envid, priority, 0, 0, 0); +} + +// Part 4 +envid_t +sys_thread_create(void *entry, void *ustack_top) +{ + return syscall(SYS_thread_create, + 0, + (uint32_t) entry, + (uint32_t) ustack_top, + 0, + 0, + 0); +} diff --git a/sched/lib/thread.c b/sched/lib/thread.c new file mode 100644 index 0000000..f2f1114 --- /dev/null +++ b/sched/lib/thread.c @@ -0,0 +1,30 @@ +// User-space thread library +// +// Provide thread_create() as an abstraction over sys_thread_create(). +// The caller is responsible for allocating and freeing the thread's stack. + +#include + +// Size of each thread's stack (in bytes). Can be changed if justified. +#define THREAD_STACK_SIZE PGSIZE + +// Create a new thread that runs func(arg). +// +// Returns the thread's envid on success, or < 0 on error. +// +// Hints: +// 1. Allocate the thread's stack with sys_page_alloc, in the current +// process's address space (envid 0). +// 2. Choose where to place it and document the criteria. Careful: the page +// [USTACKTOP - PGSIZE, USTACKTOP) is already occupied by the main +// thread's stack (see region_alloc in load_icode), and each thread +// needs its own range without overlapping the others. +// 3. Set up the stack so that returning from func calls exit(), and so +// that func receives arg as its argument. +// 4. Call sys_thread_create(func, stack_top) to create the thread. +envid_t +thread_create(void (*func)(void *), void *arg) +{ + // Part 4: Your code here + panic("thread_create not implemented"); +} diff --git a/sched/user/priotest.c b/sched/user/priotest.c new file mode 100644 index 0000000..052792d --- /dev/null +++ b/sched/user/priotest.c @@ -0,0 +1,131 @@ +// Part 3: test for the priority scheduler. +// +// The parent creates several children and assigns each one a different +// priority. This is allowed by sys_setpriority's rules: a process can modify +// the priority of its direct children to any value. +// +// The children deliberately get priorities *below* the parent's. fork() leaves +// the child ENV_RUNNABLE with the priority it inherited, and the parent only +// assigns the real one once fork() has already returned. If the children ended +// up above the parent, the first one would preempt it and run to completion +// before the parent got to create the next one, so there would never be more +// than one child competing at a time and the test would compare nothing. +// Staying above all of them lets the parent create the whole set, and then it +// releases them all at once by lowering its own priority — which is exactly +// the operation the security rules do allow. +// +// Each child prints its progress while yielding the CPU on every iteration, +// so the order and frequency of the lines show who the scheduler favors. +// +// Priorities are expressed relative to the default returned by +// sys_getpriority(), since the valid priority range is up to each +// implementation (see Part 3). +// The test needs a default of at least NCHILD + 1 to have room for one +// level per child plus one below all of them for the parent. + +#include + +#define NCHILD 4 +#define NITER 5 + +// An environment is still alive as long as its slot in envs[] (mapped +// read-only at UENVS) is still its own and hasn't gone back to the free list. +static bool +alive(envid_t id) +{ + const volatile struct Env *e = &envs[ENVX(id)]; + + return e->env_id == id && e->env_status != ENV_FREE; +} + +static void +worker(int inherited) +{ + int prio, i; + + // The parent assigns the priority right after fork() returns, but by + // then this child is already runnable and tied with it, so it may get + // the CPU first. Wait for the assignment instead of running with the + // inherited priority. + while ((prio = sys_getpriority(0)) == inherited) + sys_yield(); + + for (i = 0; i < NITER; i++) { + cprintf("[%08x] prio=%d iteracion %d\n", thisenv->env_id, prio, i); + sys_yield(); + } +} + +void +umain(int argc, char **argv) +{ + envid_t children[NCHILD]; + int base = sys_getpriority(0); + int own, i, r; + + cprintf("priotest: prioridad por defecto = %d\n", base); + + if (base < NCHILD + 1) { + cprintf("priotest: la prioridad por defecto (%d) no deja lugar " + "para %d hijos por debajo\n", + base, + NCHILD); + return; + } + + // One level per child, all of them below the parent, from highest to + // lowest. + for (i = 0; i < NCHILD; i++) { + int prio = base - 1 - i; + envid_t child = fork(); + + if (child < 0) + panic("priotest: fork: %e", child); + + if (child == 0) { + worker(base); + exit(); + } + + children[i] = child; + r = sys_setpriority(child, prio); + cprintf("priotest: hijo [%08x] -> prioridad %d (r=%d)\n", + child, + prio, + r); + } + + // Security rule: a process cannot raise its own priority. + r = sys_setpriority(0, base + 1); + if (r < 0) + cprintf("priotest: OK, no se pudo aumentar la propia prioridad " + "(r=%d)\n", + r); + else + cprintf("priotest: ERROR, se pudo aumentar la propia " + "prioridad\n"); + + // Security rule: it can lower it. Lowering it below every child is + // also what releases them: until this point the parent outranked all + // of them and none had made progress. + own = base - NCHILD - 1; + r = sys_setpriority(0, own); + if (r == 0) + cprintf("priotest: OK, se pudo reducir la propia prioridad a " + "%d; arrancan los hijos\n", + own); + else + cprintf("priotest: ERROR, no se pudo reducir la propia " + "prioridad (r=%d)\n", + r); + + // Wait for the children in the order they were created, which is also + // the order of decreasing priority. The parent is now the lowest + // priority in the system, so the only reason it gets the CPU back at + // all is aging. + for (i = 0; i < NCHILD; i++) + while (alive(children[i])) + sys_yield(); + + cprintf("priotest: fin del proceso padre\n"); +} diff --git a/sched/user/sleeptest.c b/sched/user/sleeptest.c new file mode 100644 index 0000000..64206b3 --- /dev/null +++ b/sched/user/sleeptest.c @@ -0,0 +1,32 @@ +// Part 2: test for sys_sleep. +// Checks that processes go to sleep and wake up correctly. + +#include + +void +umain(int argc, char **argv) +{ + int i; + envid_t child; + + cprintf("[%08x] sleeptest starting\n", thisenv->env_id); + + for (i = 0; i < 3; i++) { + child = fork(); + if (child == 0) { + uint32_t sleep_ticks = (i + 1) * 5; + cprintf("[%08x] child %d sleeping for %d ticks\n", + thisenv->env_id, + i, + sleep_ticks); + sys_sleep(sleep_ticks); + cprintf("[%08x] child %d woke up!\n", thisenv->env_id, i); + exit(); + } + } + + // The parent also sleeps for a bit + cprintf("[%08x] parent sleeping for 3 ticks\n", thisenv->env_id); + sys_sleep(3); + cprintf("[%08x] parent awake\n", thisenv->env_id); +} diff --git a/sched/user/threadtest.c b/sched/user/threadtest.c new file mode 100644 index 0000000..159646d --- /dev/null +++ b/sched/user/threadtest.c @@ -0,0 +1,42 @@ +// Test for user-space threads. +// +// Goal: create at least NTHREADS threads with thread_create() that share the +// shared_counter global variable and print their progress, showing that they +// all see the same memory (unlike what would happen with fork). + +#include + +#define NTHREADS 3 +#define NITER 5 + +// Variable shared by every thread in the process (lives in the BSS, which is +// part of the shared address space). +static volatile int shared_counter = 0; + +// Function each thread runs. +// +// Hints: +// - Print the value of shared_counter before and after modifying it, to +// show that one thread's changes are visible to the others. +// - Yield the CPU with sys_yield() inside the loop to interleave the +// threads' execution. +// - To identify itself, use sys_getenvid(): the global variable thisenv is +// shared among the threads and points to the main process's env. +static void +thread_func(void *arg) +{ + // Part 4: Your code here +} + +void +umain(int argc, char **argv) +{ + // Part 4: Your code here + // Hints: + // 1. Create NTHREADS threads with thread_create(thread_func, arg), + // passing a different identifier for each one in arg. + // 2. Wait for them to finish before printing the final result + // (with sys_yield(), or with your own waiting scheme). + // 3. Print shared_counter at the end: if the threads share memory it + // should be NTHREADS * NITER, not 0. +}