diff --git a/kernel/include/process.hpp b/kernel/include/process.hpp index 5504f953..2bc70b35 100644 --- a/kernel/include/process.hpp +++ b/kernel/include/process.hpp @@ -33,7 +33,8 @@ enum class process_state : char { BLOCKED = 4, SLEEPING= 5, WAITING = 6, - KILLED = 7 + KILLED = 7, + BLOCKED_TIMEOUT = 7 }; struct segment_t { diff --git a/kernel/include/scheduler.hpp b/kernel/include/scheduler.hpp index 75845f21..ec2fe465 100644 --- a/kernel/include/scheduler.hpp +++ b/kernel/include/scheduler.hpp @@ -19,6 +19,8 @@ namespace scheduler { constexpr const size_t MAX_PROCESS = 128; +constexpr const pid_t INVALID_PID = 1024 * 1024 * 1024; //I'm pretty sure we won't violate this limit + pid_t get_pid(); scheduler::process_t& get_process(pid_t pid); scheduler::process_state get_process_state(pid_t pid); @@ -56,7 +58,7 @@ const path& get_working_directory(); void set_working_directory(const path& directory); void block_process_light(pid_t pid); -//TODO Maybe do that for unblock as well! +void block_process_timeout_light(pid_t pid, size_t ms); process_t& create_kernel_task(const char* name, char* user_stack, char* kernel_stack, void (*fun)()); process_t& create_kernel_task_args(const char* name, char* user_stack, char* kernel_stack, void (*fun)(void*), void* data); diff --git a/kernel/include/sleep_queue.hpp b/kernel/include/sleep_queue.hpp index 67276411..f28cf2a6 100644 --- a/kernel/include/sleep_queue.hpp +++ b/kernel/include/sleep_queue.hpp @@ -15,24 +15,40 @@ #include "scheduler.hpp" #include "logging.hpp" +/*! + * \brief A simple sleep queue + */ struct sleep_queue { private: mutable spinlock lock; circular_buffer queue; public: + /*! + * \brief Test if the sleep queue is empty + */ bool empty() const { std::lock_guard l(lock); return queue.empty(); } + /*! + * \brief Returns the top process in the queue. + * + * If the queue is empty, the behaviour is undefined. + */ scheduler::pid_t top_process() const { std::lock_guard l(lock); return queue.top(); } + /*! + * \brief Wake up the first process from the queue. + * + * If the queue is empty, the behaviour is undefined. + */ scheduler::pid_t wake_up(){ std::lock_guard l(lock); @@ -50,6 +66,9 @@ public: return pid; } + /*! + * \brief Wait inside the queue until woken up. + */ void sleep(){ lock.acquire(); @@ -68,6 +87,52 @@ public: scheduler::reschedule(); } + + /*! + * \brief Wait inside the queue until woken up or until the + * timeout is passed. + * + * \return true if the thread was woken up, false if the timeout is passed + */ + bool sleep(size_t ms){ + if(!ms){ + return false; + } + + lock.acquire(); + + //Get the current process information + auto pid = scheduler::get_pid(); + + logging::logf(logging::log_level::TRACE, "sleep_queue: %u wait with timeout %u\n", pid, ms); + + //Enqueue the process in the sleep queue + queue.push(pid); + + //This process will sleep + scheduler::block_process_timeout_light(pid, ms); + + lock.release(); + + scheduler::reschedule(); + + // At this point we need the lock again to check the queue + lock.acquire(); + + bool obtained = true; + + // If the queue still contains our pid, it means a wake up + // from timeout + if(queue.contains(pid)){ + obtained = false; + queue.replace(pid, scheduler::INVALID_PID); + } + + // Final release of the lock + lock.release(); + + return obtained; + } }; #endif diff --git a/kernel/src/scheduler.cpp b/kernel/src/scheduler.cpp index 3c516223..f5c0c8db 100644 --- a/kernel/src/scheduler.cpp +++ b/kernel/src/scheduler.cpp @@ -762,7 +762,7 @@ void scheduler::tick(){ // Update sleep timeouts for(auto& process : pcb){ - if(process.state == process_state::SLEEPING){ + if(process.state == process_state::SLEEPING || process.state == process_state::BLOCKED_TIMEOUT){ --process.sleep_timeout; if(process.sleep_timeout == 0){ @@ -836,6 +836,21 @@ void scheduler::block_process_light(pid_t pid){ pcb[pid].state = process_state::BLOCKED; } +void scheduler::block_process_timeout_light(pid_t pid, size_t ms){ + thor_assert(pid < scheduler::MAX_PROCESS, "pid out of bounds"); + + logging::logf(logging::log_level::DEBUG, "scheduler: Block process (light) %u with timeout %u\n", pid, ms); + + pcb[pid].state = process_state::BLOCKED_TIMEOUT; + + // Compute the amount of ticks to sleep + auto sleep_ticks = ms * (timer::timer_frequency() / 1000); + sleep_ticks = !sleep_ticks ? 1 : sleep_ticks; + + // Put the process to sleep + pcb[pid].sleep_timeout = sleep_ticks; +} + void scheduler::block_process(pid_t pid){ thor_assert(is_started(), "The scheduler is not started"); thor_assert(pid < scheduler::MAX_PROCESS, "pid out of bounds"); @@ -854,7 +869,7 @@ void scheduler::unblock_process(pid_t pid){ thor_assert(pid < scheduler::MAX_PROCESS, "pid out of bounds"); thor_assert(pid != idle_pid, "No reason to unblock the idle task"); thor_assert(is_started(), "The scheduler is not started"); - thor_assert(pcb[pid].state == process_state::BLOCKED || pcb[pid].state == process_state::WAITING, "Can only unblock BLOCKED/WAITING processes"); + thor_assert(pcb[pid].state == process_state::BLOCKED || pcb[pid].state == process_state::BLOCKED_TIMEOUT || pcb[pid].state == process_state::WAITING, "Can only unblock BLOCKED/WAITING processes"); pcb[pid].state = process_state::READY; }