1import{_ as n,c as a,e,o as l}from"./app-CjUkjRdW.js";const i={};function t(p,s){return l(),a("div",null,s[0]||(s[0]=[e(`<h1 id="cooperative-multitasking" tabindex="-1"><a class="header-anchor" href="#cooperative-multitasking"><span>Cooperative Multitasking</span></a></h1><p>The idea of cooperative multitasking is that, at certain points in the task's execution, it voluntarily yields control back to the kernel. This is done by invoking a system call, typically called <code>yield</code>. In other cases, if the task invokes a system call that blocks, this is also considered a yield. The kernel then decides which task to run next, and returns control to that task.</p><p>The advantage of cooperative multitasking is that it is very simple to implement. The disadvantage is that if a task does not yield, it will never be preempted. This means that a single task can monopolize the CPU, and the system will become unresponsive. In <strong>preemptive multitasking</strong>, the kernel uses a timer to interrupt the currently running task, and preempt it if its time slice has expired, or if a higher priority task is ready to run. This ensures that no task can monopolize the CPU. We'll see how to implement preemptive multitasking later.</p><h2 id="scheduling" tabindex="-1"><a class="header-anchor" href="#scheduling"><span>Scheduling</span></a></h2><p>We'll add a new kernel component called the <strong>scheduler</strong>. The scheduler is responsible for keeping track of all the tasks in the system, and deciding which task to run next. It is invoked by the kernel at certain points, such as when a task yields or blocks. This means it needs to keep track of the currently running task, and the list of ready tasks. Upon invocation, it will decide which task to run next based on some strategy, and switch to that task. The simplest strategy is <strong>round-robin</strong> scheduling, where the scheduler simply runs each task in turn. There are many other strategies, but we'll start with round-robin.</p><p>To make it easy to manage the ready tasks, we'll use a <strong>task queue</strong>. The current task will be stored in a global variable, outside the queue. Here's how we're going to make scheduling decisions:</p><ul><li>When the scheduler is invoked (e.g. when a task yields), it will add the current task to the end of the queue, and then remove the first task from the queue, assign it to the current task, and switch to it.</li><li>If the queue is empty, the scheduler will simply return, and the current task will continue running.</li><li>When a task exits, we simply won't add it back to the queue, and the next task in the queue will be run.</li><li>If a task exits and there are no more tasks in the queue, we'll simply halt the CPU.</li></ul><p>Before we start implementing the scheduler, we need to make some changes to the <code>Task</code> type. We need to track the task <strong>state</strong>, which can be <code>New</code>, <code>Ready</code>, <code>Running</code>, or <code>Terminated</code> (for now). I'm also going to change the layout of the <code>Task</code> type by making the <code>rsp</code> field the first field, so that we can easily access it from inline assembly later. Here's the updated <code>tasks.nim</code> module:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/tasks.nim</span></span> 2<span class="line"><span class="token keyword">type</span></span> 3<span class="line"> TaskState <span class="token operator">=</span> <span class="token keyword">enum</span></span> 4<span class="line"> New<span class="token punctuation">,</span> Ready<span class="token punctuation">,</span> Running<span class="token punctuation">,</span> Terminated</span> 5<span class="line"></span> 6<span class="line"> Task<span class="token operator">*</span> <span class="token operator">=</span> <span class="token keyword">ref</span> <span class="token keyword">object</span></span> 7<span class="line"> rsp<span class="token operator">*:</span> uint64</span> 8<span class="line">
8 state<span class="token operator">*:</span> TaskState</span> 9<span class="line"> <span class="token comment"># other fields...</span></span> 10<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Let's start by creating a new module called <code>sched.nim</code> in the <code>kernel</code> directory. We'll define a task queue, a global variable to store the current task, and a proc to add a task to the queue:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/sched.nim</span></span> 11<span class="line"><span class="token keyword">import</span> std<span class="token operator">/</span>deques</span> 12<span class="line"></span> 13<span class="line"><span class="token keyword">import</span> tasks</span> 14<span class="line"></span> 15<span class="line"><span class="token keyword">var</span></span> 16<span class="line"> readyTasks <span class="token operator">=</span> <span class="token function">initDeque[Task]</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 17<span class="line"> currentTask<span class="token operator">*</span> <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span><span class="token operator">:</span> Task</span> 18<span class="line"></span> 19<span class="line"><span class="token keyword">proc</span> <span class="token function">addTask<span class="token operator">*</span></span><span class="token punctuation">(</span>t<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=</span></span> 20<span class="line"> readyTasks<span class="token operator">.</span><span class="token function">addLast</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span></span> 21<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>If you remember, we already had defined a <code>currentTask</code> in the <code>tasks.nim</code> module. We'll make some changes to that module later when we get to context switching. I'm annotating <code>currentTask</code> with the <code>exportc</code> pragma since we'll need to access it from inline assembly later. Let's now add the main scheduler proc:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/sched.nim</span></span> 22<span class="line"><span class="token operator">...</span></span> 23<span class="line"></span> 24<span class="line"><span class="token keyword">proc</span> <span class="token function">schedule<span class="token operator">*</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span></span> 25<span class="line"> <span class="token keyword">if</span> readyTasks<span class="token operator">.</span>len <span class="token operator">==</span> <span class="token number">0</span><span class="token operator">:</span></span> 26<span class="line"> <span class="token keyword">if</span> currentTask<span class="token operator">.</span>isNil <span class="token operator">or</span> currentTask<span class="token operator">.</span>
26state <span class="token operator">==</span> Terminated<span class="token operator">:</span></span> 27<span class="line"> debugln <span class="token operator">&</span><span class="token string">"sched: no tasks to run, halting"</span></span> 28<span class="line"> <span class="token function">halt</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 29<span class="line"> <span class="token keyword">else</span><span class="token operator">:</span></span> 30<span class="line"> <span class="token comment"># no ready tasks, keep running the current task</span></span> 31<span class="line"> <span class="token keyword">return</span></span> 32<span class="line"></span> 33<span class="line"> <span class="token keyword">if</span> <span class="token function">not</span> <span class="token punctuation">(</span>currentTask<span class="token operator">.</span>isNil <span class="token operator">or</span> currentTask<span class="token operator">.</span>state <span class="token operator">==</span> Terminated<span class="token punctuation">)</span><span class="token operator">:</span></span> 34<span class="line"> <span class="token comment"># put the current task back into the queue</span></span> 35<span class="line"> currentTask<span class="token operator">.</span>
35state <span class="token operator">=</span> TaskState<span class="token operator">.</span>Ready</span> 36<span class="line"> readyTasks<span class="token operator">.</span><span class="token function">addLast</span><span class="token punctuation">(</span>currentTask<span class="token punctuation">)</span></span> 37<span class="line"></span> 38<span class="line"> <span class="token comment"># switch to the first task in the queue</span></span> 39<span class="line"> <span class="token keyword">var</span> nextTask <span class="token operator">=</span> readyTasks<span class="token operator">.</span><span class="token function">popFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 40<span class="line"> </span> 41<span class="line"> <span class="token function">switchTo</span><span class="token punctuation">(</span>nextTask<span class="token punctuation">)</span></span> 42<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The current implementation of <code>switchTo</code> (in <code>tasks.nim</code>) only knows how to switch to a new task. We'll need to change it to perform an actual context switch.</p><h2 id="context-switching" tabindex="-1"><a class="header-anchor" href="#context-switching"><span>Context Switching</span></a></h2><p>When switching between tasks, we need to save the state of the currently running task, and restore the state of the task that is about to run. The task state typically includes the CPU registers and the stack pointer. We don't need to save the instruction pointer, because once we swap the stack pointers, the old task resumes execution at the same point where its stack pointer was swapped out previously, and will continue as if nothing had happened. It's this swapping of stack pointers that causes the context switch.</p><p>Let's create a new module <code>ctxswitch.nim</code> to handle context switching. We'll move the <code>switchTo</code> proc from <code>tasks.nim</code> to <code>ctxswitch.nim</code>, and modify it to handle switching between tasks.</p><p>When the current task is not <code>nil</code> or terminated, we'll save its stack pointer and register state, regardless of whether we're switching to a new task or not. When we're switching to a new task, we'll simply load the new task's stack pointer and <code>iretq</code> to return to user mode. When we're switching to an existing task, we'll restore its stack pointer and register state and return normally.</p><p>Here's the modified <code>switchTo</code> proc:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/ctxswitch.nim</span></span> 43<span class="line"><span class="token keyword">import</span> cpu</span> 44<span class="line"><span class="token keyword">import</span> gdt</span> 45<span class="line"><span class="token keyword">import</span> tasks</span> 46<span class="line"><span class="token keyword">import</span> vmm</span> 47<span class="line"></span> 48<span class="line"><span class="token keyword">var</span></span> 49<span class="line"> currentTask <span class="token punctuation">{.</span>importc<span class="token punctuation">.}</span><span class="token operator">:</span> Task</span> 50<span class="line"></span> 51<span class="line"><span class="token keyword">proc</span> <span class="token function">switchTo<span class="token operator">*</span></span><span class="token punctuation">(</span>next<span class="token operator">:</span> <span class="token keyword">var</span> Task<span class="token punctuation">)</span> <span class="token operator">=</span></span> 52<span class="line"> tss<span class="token operator">.</span>rsp0 <span class="token operator">=</span> next<span class="token operator">.</span>kstack<span class="token operator">.</span>bottom</span> 53<span class="line"> <span class="token function">setActivePML4</span><span class="token punctuation">(</span>next<span class="token operator">.</span>space<span class="token operator">.</span>pml4<span class="token punctuation">)</span></span> 54<span class="line"></span> 55<span class="line"> <span class="token keyword">if</span> <span class="token function">not</span> <span class="token punctuation">(</span>currentTask<span class="token operator">.</span>isNil <span class="token operator">or</span> currentTask<span class="token operator">.</span>
55state <span class="token operator">==</span> TaskState<span class="token operator">.</span>Terminated<span class="token punctuation">)</span><span class="token operator">:</span></span> 56<span class="line"> <span class="token function">pushRegs</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 57<span class="line"> <span class="token keyword">asm</span> <span class="token string">"""</span> 58<span class="line"> mov %0, rsp</span> 59<span class="line"> : "=m" (\`currentTask\`->rsp)</span> 60<span class="line"> """</span></span> 61<span class="line"></span> 62<span class="line"> currentTask <span class="token operator">=</span> next</span> 63<span class="line"></span> 64<span class="line"> <span class="token keyword">case</span> next<span class="token operator">.</span>state</span> 65<span class="line"> <span class="token operator">of</span> TaskState<span class="token operator">.</span>New<span class="token operator">:</span></span> 66<span class="line"> next<span class="token operator">.</span>
66state <span class="token operator">=</span> TaskState<span class="token operator">.</span>Running</span> 67<span class="line"> <span class="token keyword">asm</span> <span class="token string">"""</span> 68<span class="line"> mov rsp, %0</span> 69<span class="line"> iretq</span> 70<span class="line"> :</span> 71<span class="line"> : "m" (\`currentTask\`->rsp)</span> 72<span class="line"> """</span></span> 73<span class="line"> <span class="token keyword">else</span><span class="token operator">:</span></span> 74<span class="line"> next<span class="token operator">.</span>state <span class="token operator">=</span> TaskState<span class="token operator">.</span>Running</span> 75<span class="line"> <span class="token keyword">asm</span> <span class="token string">"""</span> 76<span class="line"> mov rsp, %0</span> 77<span class="line"> :</span> 78<span class="line"> : "m" (\`currentTask\`->rsp)</span> 79<span class="line"> """</span></span> 80<span class="line"> <span class="token function">popRegs</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 81<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Let's define <code>pushRegs</code> and <code>popRegs</code>, but instead of defining them in this module, we'll put them in the <code>cpu.nim</code> module, where they belong. Here, I'll be using Nim templates instead of procs to avoid the overhead of calling a proc.</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/cpu.nim</span></span> 82<span class="line"></span> 83<span class="line"><span class="token keyword">template</span> <span class="token function">pushRegs<span class="token operator">*</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span></span> 84<span class="line"> <span class="token keyword">asm</span> <span class="token string">"""</span> 85<span class="line"> push rax</span> 86<span class="line"> push rbx</span> 87<span class="line"> push rcx</span> 88<span class="line"> push rdx</span> 89<span class="line"> push rsi</span> 90<span class="line"> push rdi</span> 91<span class="line"> push rbp</span> 92<span class="line"> push r8</span> 93<span class="line"> push r9</span> 94<span class="line"> push r10</span> 95<span class="line"> push r11</span> 96<span class="line"> push r12</span> 97<span class="line"> push r13</span> 98<span class="line"> push r14</span> 99<span class="line"> push r15</span> 100<span class="line"> """</span></span> 101<span class="line"></span> 102<span class="line"><span class="token keyword">template</span> <span class="token function">popRegs<span class="token operator">*</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span></span> 103<span class="line"> <span class="token keyword">asm</span> <span class="token string">"""</span> 104<span class="line"> pop r15</span> 105<span class="line"> pop r14</span> 106<span class="line"> pop r13</span> 107<span class="line"> pop r12</span> 108<span class="line"> pop r11</span> 109<span class="line"> pop r10</span> 110<span class="line"> pop r9</span> 111<span class="line"> pop r8</span> 112<span class="line"> pop rbp</span> 113<span class="line"> pop rdi</span> 114<span class="line"> pop rsi</span> 115<span class="line"> pop rdx</span> 116<span class="line"> pop rcx</span> 117<span class="line"> pop rbx</span> 118<span class="line"> pop rax</span> 119<span class="line"> """</span></span> 120<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="yield-system-call" tabindex="-1"><a class="header-anchor" href="#yield-system-call"><span><code>yield</code> System Call</span></a></h2><p>To allow a task to yield control to the kernel, we'll add a new system call called <code>yield</code>
120. When a task invokes this system call, the kernel simply calls the scheduler to switch to the next task. Let's add it to the <code>syscall.nim</code> module:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/syscalls.nim</span></span> 121<span class="line"><span class="token operator">...</span></span> 122<span class="line"></span> 123<span class="line highlighted"><span class="token keyword">proc</span> <span class="token function">\`yield\`<span class="token operator">*</span></span><span class="token punctuation">(</span>args<span class="token operator">:</span> <span class="token keyword">ptr</span> SyscallArgs<span class="token punctuation">)</span><span class="token operator">:</span> uint64 <span class="token punctuation">{.</span>cdecl<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 124<span class="line highlighted"> debugln <span class="token operator">&</span><span class="token string">"syscall: yield"</span></span> 125<span class="line highlighted"> <span class="token function">schedule</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 126<span class="line"></span> 127<span class="line"><span class="token keyword">proc</span> <span class="token function">syscallInit<span class="token operator">*</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span></span> 128<span class="line"> <span class="token comment"># set up syscall table</span></span> 129<span class="line"> syscallTable<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> exit</span> 130<span class="line"> syscallTable<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> print</span> 131<span class="line highlighted"> syscallTable<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token identifier"><span class="token punctuation">\`</span>yield<span class="token punctuation">\`</span></span></span> 132<span class="line"> <span class="token operator">...</span></span> 133<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Notice that we have to quote the <code>yield</code> proc name because it's a reserved keyword in Nim. Now, tasks can invoke syscall 3 (with no arguments) to yield control to the kernel. Let's add this syscall to our user task:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/user/utask.nim</span></span> 134<span class="line"><span class="token operator">...</span></span> 135<span class="line"></span> 136<span class="line"><span class="token keyword">proc</span> <span class="token function">UserMain<span class="token operator">*</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 137<span class="line"> <span class="token function">NimMain</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 138<span class="line"></span> 139<span class="line"> <span class="token keyword">asm</span> <span class="token string">"""</span> 140<span class="line"> # call print</span> 141<span class="line"> ...</span> 142<span class="line"></span> 143<span class="line highlighted"> # call yield</span> 144<span class="line highlighted"> mov rdi, 3</span> 145<span class="line highlighted"> syscall</span> 146<span class="line"></span> 147<span class="line"> # call exit</span> 148<span class="line"> ...</span> 149<span class="line"> """</span></span> 150<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The task now will print something, yield control to the kernel, and then exit.</p><h2 id="handling-task-exits" tabindex="-1"><a class="header-anchor" href="#handling-task-exits"><span>Handling Task Exits</span></a></h2><p>When the current task calls the <code>exit</code> system call, we should also invoke the scheduler, but the task shouldn't be put back into the queue. We can do this by setting the task's state to <code>Terminated</code> before invoking the scheduler. Terminating a task may involve other steps later (e.g. freeing its memory), so let's add a <code>terminateTask</code> proc to the <code>tasks.nim</code> module:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/tasks.nim</span></span> 151<span class="line"></span> 152<span class="line"><span class="token keyword">proc</span> <span class="token function">terminateTask<span class="token operator">*</span></span><span class="token punctuation">(</span>t<span class="token operator">:</span> <span class="token keyword">var</span> Task<span class="token punctuation">)</span> <span class="token operator">=</span></span> 153<span class="line"> t<span class="token operator">.</span>
153state <span class="token operator">=</span> TaskState<span class="token operator">.</span>Terminated</span> 154<span class="line"> <span class="token comment"># other cleanup...</span></span> 155<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Now, let's modify the <code>exit</code> syscall to call <code>terminateTask</code> before invoking the scheduler:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/syscalls.nim</span></span> 156<span class="line"></span> 157<span class="line"><span class="token keyword">proc</span> <span class="token function">exit<span class="token operator">*</span></span><span class="token punctuation">(</span>args<span class="token operator">:</span> <span class="token keyword">ptr</span> SyscallArgs<span class="token punctuation">)</span><span class="token operator">:</span> uint64 <span class="token punctuation">{.</span>cdecl<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 158<span class="line"> debugln <span class="token operator">&</span><span class="token string">"syscall: exit: code={args.arg1}"</span></span> 159<span class="line"> <span class="token function">terminateTask</span><span class="token punctuation">(</span>currentTask<span class="token punctuation">)</span></span> 160<span class="line"> <span class="token function">schedule</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 161<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="running-multiple-tasks" tabindex="-1"><a class="header-anchor" href="#running-multiple-tasks"><span>Running multiple tasks</span></a></h2><p>Let's now create two user tasks, add them to the task queue, and invoke the scheduler.</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># src/kernel/main.nim</span></span> 162<span class="line"><span class="token operator">...</span></span> 163<span class="line"></span> 164<span class="line"><span class="token keyword">proc</span> <span class="token function">KernelMainInner</span><span class="token punctuation">(</span>bootInfo<span class="token operator">:</span> <span class="token keyword">ptr</span> BootInfo<span class="token punctuation">)</span> <span class="token operator">=</span></span> 165<span class="line"> <span class="token operator">...</span></span> 166<span class="line"></span> 167<span class="line"> debugln <span class="token string">"kernel: Creating user tasks"</span></span> 168<span class="line"> <span class="token keyword">var</span> task1 <span class="token operator">=</span> <span class="token function">createTask</span><span class="token punctuation">(</span></span> 169<span class="line"> imagePhysAddr <span class="token operator">=</span> bootInfo<span class="token operator">.</span>userImagePhysicalBase<span class="token operator">.</span>PhysAddr<span class="token punctuation">,</span></span> 170<span class="line"> imagePageCount <span class="token operator">=</span> bootInfo<span class="token operator">.</span>userImagePages<span class="token punctuation">,</span></span> 171<span class="line"> <span class="token punctuation">)</span></span> 172<span class="line"> <span class="token keyword">var</span> task2 <span class="token operator">=</span> <span class="token function">createTask</span><span class="token punctuation">(</span></span> 173<span class="line"> imagePhysAddr <span class="token operator">=</span> bootInfo<span class="token operator">.</span>userImagePhysicalBase<span class="token operator">.</span>PhysAddr<span class="token punctuation">,</span></span> 174<span class="line"> imagePageCount <span class="token operator">=</span> bootInfo<span class="token operator">.</span>userImagePages<span class="token punctuation">,</span></span> 175<span class="line"> <span class="token punctuation">)</span></span> 176<span class="line"></span> 177<span class="line"> debugln <span class="token string">"kernel: Adding tasks to scheduler"</span></span> 178<span class="line"> sched<span class="token operator">.</span><span class="token function">addTask</span><span class="token punctuation">(</span>task1<span class="token punctuation">)</span></span> 179<span class="line"> sched<span class="token operator">.</span><span class="token function">addTask</span><span class="token punctuation">(</span>task2<span class="token punctuation">)</span></span> 180<span class="line"></span> 181<span class="line"> debugln <span class="token string">"kernel: Starting scheduler"</span></span> 182<span class="line"> sched<span class="token operator">.</span><span class="token function">schedule</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 183<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Let's run it and see what happens.</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token output">kernel: Creating user tasks</span> 184<span class="line">kernel: Applying relocations to user image</span> 185<span class="line">kernel: Applying relocations to user image</span> 186<span class="line">kernel: Adding tasks to scheduler</span> 187<span class="line">kernel: Starting scheduler</span> 188<span class="line">sched: switching -> 0</span> 189<span class="line">syscall: print</span> 190<span class="line">Hello from user mode!</span> 191<span class="line">syscall: yield</span> 192<span class="line">sched: switching 0 -> 1</span> 193<span class="line">syscall: print</span> 194<span class="line">Hello from user mode!</span> 195<span class="line">syscall: yield</span> 196<span class="line">sched: switching 1 -> 0</span> 197<span class="line">syscall: exit: code=0</span> 198<span class="line">sched: switching 0 -> 1</span> 199<span class="line">syscall: exit: code=0</span> 200<span class="line">sched: no tasks to run, halting</span> 201<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>It works! The scheduler first runs task 0, which prints a message, then yields control to the kernel. The scheduler then switches to task 1, which prints another message, and then yields control back to the kernel. The scheduler then switches back to task 0, which calls <code>exit</code>, which terminates the task. The scheduler then switches to task 1, which also calls <code>exit</code> and terminates the task. Since there are no more tasks in the queue, the scheduler halts the CPU.</p><p>Let's add a third task for fun and see what happens.</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token output">kernel: Adding tasks to scheduler</span> 202<span class="line">kernel: Starting scheduler</span> 203<span class="line">sched: switching -> 0</span> 204<span class="line">syscall: print</span> 205<span class="line">Hello from user mode!</span> 206<span class="line">syscall: yield</span> 207<span class="line">sched: switching 0 -> 1</span> 208<span class="line">syscall: print</span> 209<span class="line">Hello from user mode!</span> 210<span class="line">syscall: yield</span> 211<span class="line">sched: switching 1 ->
211 2</span> 212<span class="line">syscall: print</span> 213<span class="line">Hello from user mode!</span> 214<span class="line">syscall: yield</span> 215<span class="line">sched: switching 2 -> 0</span> 216<span class="line">syscall: exit: code=0</span> 217<span class="line">sched: switching 0 -> 1</span> 218<span class="line">syscall: exit: code=0</span> 219<span class="line">sched: switching 1 -> 2</span> 220<span class="line">syscall: exit: code=0</span> 221<span class="line">sched: no tasks to run, halting</span> 222<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>It works! The scheduler runs all three tasks in a round-robin fashion, and then halts the CPU when there are no more tasks to run. This is exciting! We now have a simple cooperative multitasking system.</p><p>Now that we have a few system calls, it's time to add a system library that can be used by user tasks to invoke these system calls, instead of using inline assembly. We'll do that in the next chapter.</p>`,43)]))}const o=n(i,[["render",t],["__file","23-coop-multitasking.html.vue"]]),r=JSON.parse(`{"path":"/osdev/23-coop-multitasking.html","title":"Cooperative Multitasking","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Scheduling","slug":"scheduling","link":"#scheduling","children":[]},{"level":2,"title":"Context Switching","slug":"context-switching","link":"#context-switching","children":[]},{"level":2,"title":"yield System Call","slug":"yield-system-call","link":"#yield-system-call","children":[]},{"level":2,"title":"Handling Task Exits","slug":"handling-task-exits","link":"#handling-task-exits","children":[]},{"level":2,"title":"Running multiple tasks","slug":"running-multiple-tasks","link":"#running-multiple-tasks","children":[]}],"git":{"updatedTime":1744638230000},"filePathRelative":"osdev/23-coop-multitasking.md","excerpt":"\\n<p>The idea of cooperative multitasking is that, at certain points in the task's execution,\\nit voluntarily yields control back to the kernel. This is done by invoking a system call,\\ntypically called <code>yield</code>. In other cases, if the task invokes a system call that blocks,\\nthis is also considered a yield. The kernel then decides which task to run next, and\\nreturns control to that task.</p>"}`);export{o as comp,r as data};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.