Musings from the mountains himwant.org

xv6 spin locks

+421 -3
+222
src/content/posts/xv6-spinlocking/index.md
··· 1 + --- 2 + title: "xv6 - SpinLocks" 3 + published: 2025-12-17 4 + draft: false 5 + description: 'Documenting the xv6 kernel' 6 + tags: ["xv6","os"] 7 + --- 8 + 9 + 10 + 11 + # Spin Locking 12 + 13 + We know that xv6 uses SpinLocks, sleep and wakeup for its locking. In this post, let us discuss SpinLocks. 14 + 15 + SpinLocks are defined in [spinlock.c](https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.c). 16 + 17 + 18 + ## Basics 19 + 20 + There are primarily **two** functions related to locks, acquire and release 21 + 22 + :::warning 23 + Spin Locks should not be held for a long time as spinning wastes cycles! 24 + ::: 25 + 26 + :::tip 27 + In such cases, consider using `sleep()` and `wakeup()`! 28 + ::: 29 + 30 + 31 + ### acquire 32 + 33 + Acquiring the lock seems simple enough on the surface. 34 + 35 + ```c 36 + while (locked == 1); 37 + locked = 1; 38 + cpu = mycpu(); 39 + ``` 40 + 41 + But two processors can simultaniously execute this! Therefore we need an atomic operation to read and write to `locked` in one instruction. Fortunately RISC-V has one such instrcution - `AMOSWAP`, which we will use. 42 + 43 + Another thing to note is that we need to disable preemption or interrupts while we are in the critical section! 44 + 45 + Therefore, we have- 46 + 47 + ``` 48 + 1 -> [ Locked ] -> y 49 + if (y != 0) try again 50 + ``` 51 + 52 + 53 + ### release 54 + 55 + release is quite simple, just put value 0 in the locked field! 56 + 57 + 58 + ## Theory 59 + 60 + Now, `acquire()` disables the interrupts and `release()` re-enables them. But now, situations with multiple locks becomes problematic. 61 + 62 + ```c 63 + acquire(&lk1); 64 + acquire(&lk2); 65 + 66 + // Critical Section 67 + 68 + release(&lk2); // Interrupts re-enabled prematurely!! 69 + 70 + // Some more operations 71 + 72 + release(&lk1); 73 + ``` 74 + 75 + So, we store a counter `noff` and a status int `intena` (interrupts enabled) for each hart. 76 + 77 + If this is the first acquire (`noff =​= 0`), we set `intena` to current interrupt status (0 if disables, 1 otherwise). We then increment the counter regardless. 78 + 79 + During release, we decrement the counter and if it becomes 0, we restore the interrupts to their saved state in `intena`! 80 + 81 + 82 + ## Code 83 + 84 + 85 + ### Structure 86 + 87 + The struct spinlock itself, is defined in [spinlock.h](https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.h). 88 + 89 + ```c title="spinlock.h" 90 + struct spinlock { 91 + uint locked; // Is the lock held? 92 + 93 + // For debugging: 94 + char *name; // Name of lock. 95 + struct cpu *cpu; // The cpu holding the lock. 96 + }; 97 + ``` 98 + 99 + It contains an int `locked`, which indicates that the lock is free/unacquired when `locked = 0` and otherwise for `locked = 1` 100 + 101 + 102 + ### Imports 103 + 104 + We first import the necessary files. Nothing too exciting here. 105 + 106 + ```c title="imports" 107 + #include "types.h" 108 + #include "param.h" 109 + #include "memlayout.h" 110 + #include "spinlock.h" 111 + #include "riscv.h" 112 + #include "proc.h" 113 + #include "defs.h" 114 + ``` 115 + 116 + 117 + ### Initalising the lock 118 + 119 + Before we can even acquire the lock, we need to actually initialise it! Its steps are easy enough to be obvious. 120 + 121 + ```c title="initlock" 122 + void 123 + initlock(struct spinlock *lk, char *name) 124 + { 125 + lk->name = name; 126 + lk->locked = 0; 127 + lk->cpu = 0; 128 + } 129 + ``` 130 + 131 + 132 + ### Acquiring the Lock 133 + 134 + Let' start writing the acquire function! 135 + 136 + ```c 137 + void acquire(struct spinlock *lk) { 138 + <<aq_disable_interrupt>> 139 + <<aq_atomic_swap>> 140 + <<aq_misc>> 141 + } 142 + ``` 143 + 144 + 1. As discussed earlier we first disable the interrupts. `push_off()` takes care of that. We also ensure we already aren't holding the lock. 145 + 146 + ```c title="Disable Interrupts" 147 + push_off(); 148 + if(holding(lk)) 149 + panic("acquire"); 150 + ``` 151 + 152 + 2. ```c title="Atomic Swap and Loop" 153 + while(__sync_lock_test_and_set(&lk->locked, 1) != 0); 154 + ``` 155 + 156 + 3. We need to ensure that compiler optimisations do not mess with the order, also store the cpu's information in the struct. 157 + 158 + ```c title="Misc" 159 + __sync_synchronize(); 160 + lk->cpu = mycpu(); 161 + ``` 162 + 163 + Our final acquire function- 164 + 165 + ```c title="acquire" 166 + void acquire(struct spinlock *lk) { 167 + push_off(); 168 + if(holding(lk)) 169 + panic("acquire"); 170 + while(__sync_lock_test_and_set(&lk->locked, 1) != 0); 171 + __sync_synchronize(); 172 + lk->cpu = mycpu(); 173 + } 174 + ``` 175 + 176 + 177 + ### Releasing the lock 178 + 179 + Releasing is a lot simpler. 180 + 181 + ```c 182 + void release(struct spinlock *lk) { 183 + <<rl_misc>> 184 + <<rl_en_int>> 185 + } 186 + ``` 187 + 188 + 1. Ensure we are holding the lock, scrub cpu information, synchronise, and release the lock 189 + 190 + ```c title="Misc" 191 + if(!holding(lk)) 192 + panic("release"); 193 + 194 + lk->cpu = 0; 195 + 196 + __sync_synchronize(); 197 + 198 + __sync_lock_release(&lk->locked); 199 + ``` 200 + 201 + 2. Re-enable Interrupts 202 + 203 + ```c title="Interrupts" 204 + pop_off(); 205 + ``` 206 + 207 + Release function- 208 + 209 + ```c title="release" 210 + void release(struct spinlock *lk) { 211 + if(!holding(lk)) 212 + panic("release"); 213 + 214 + lk->cpu = 0; 215 + 216 + __sync_synchronize(); 217 + 218 + __sync_lock_release(&lk->locked); 219 + pop_off(); 220 + } 221 + ``` 222 +
+178
src/content/posts/xv6-spinlocking/index.org
··· 1 + #+title: xv6 - SpinLocks 2 + #+author: Akshit Gaur 3 + #+description: Documenting the xv6 kernel 4 + #+TAGS: xv6, os 5 + #+OPTIONS: toc:nil 6 + #+PROPERTY: header-args:c :noweb no-export 7 + 8 + * Spin Locking 9 + We know that xv6 uses SpinLocks, sleep and wakeup for its locking. In this post, let us discuss SpinLocks. 10 + 11 + SpinLocks are defined in [[https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.c][spinlock.c]]. 12 + 13 + ** Basics 14 + There are primarily *two* functions related to locks, acquire and release 15 + 16 + :::warning 17 + Spin Locks should not be held for a long time as spinning wastes cycles! 18 + ::: 19 + 20 + :::tip 21 + In such cases, consider using =sleep()= and =wakeup()=! 22 + ::: 23 + 24 + *** acquire 25 + Acquiring the lock seems simple enough on the surface. 26 + #+begin_src c 27 + while (locked == 1); 28 + locked = 1; 29 + cpu = mycpu(); 30 + #+end_src 31 + But two processors can simultaniously execute this! Therefore we need an atomic operation to read and write to =locked= in one instruction. Fortunately RISC-V has one such instrcution - =AMOSWAP=, which we will use. 32 + 33 + Another thing to note is that we need to disable preemption or interrupts while we are in the critical section! 34 + 35 + Therefore, we have- 36 + #+begin_example 37 + 1 -> [ Locked ] -> y 38 + if (y != 0) try again 39 + #+end_example 40 + 41 + *** release 42 + release is quite simple, just put value 0 in the locked field! 43 + 44 + ** Theory 45 + Now, =acquire()= disables the interrupts and =release()= re-enables them. But now, situations with multiple locks becomes problematic. 46 + #+begin_src c 47 + acquire(&lk1); 48 + acquire(&lk2); 49 + 50 + // Critical Section 51 + 52 + release(&lk2); // Interrupts re-enabled prematurely!! 53 + 54 + // Some more operations 55 + 56 + release(&lk1); 57 + #+end_src 58 + 59 + So, we store a counter =noff= and a status int =intena= (interrupts enabled) for each hart. 60 + 61 + If this is the first acquire (=noff =​= 0=), we set =intena= to current interrupt status (0 if disables, 1 otherwise). We then increment the counter regardless. 62 + 63 + During release, we decrement the counter and if it becomes 0, we restore the interrupts to their saved state in =intena=! 64 + 65 + ** Code 66 + *** Structure 67 + The struct spinlock itself, is defined in [[https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.h][spinlock.h]]. 68 + #+begin_src c :title "spinlock.h" 69 + struct spinlock { 70 + uint locked; // Is the lock held? 71 + 72 + // For debugging: 73 + char *name; // Name of lock. 74 + struct cpu *cpu; // The cpu holding the lock. 75 + }; 76 + #+end_src 77 + It contains an int =locked=, which indicates that the lock is free/unacquired when =locked = 0= and otherwise for =locked = 1= 78 + 79 + *** Imports 80 + We first import the necessary files. Nothing too exciting here. 81 + 82 + #+name: imports 83 + #+begin_src c :title "imports" 84 + #include "types.h" 85 + #include "param.h" 86 + #include "memlayout.h" 87 + #include "spinlock.h" 88 + #include "riscv.h" 89 + #include "proc.h" 90 + #include "defs.h" 91 + #+end_src 92 + 93 + *** Initalising the lock 94 + Before we can even acquire the lock, we need to actually initialise it! Its steps are easy enough to be obvious. 95 + #+name: initlock 96 + #+begin_src c :title "initlock" 97 + void 98 + initlock(struct spinlock *lk, char *name) 99 + { 100 + lk->name = name; 101 + lk->locked = 0; 102 + lk->cpu = 0; 103 + } 104 + #+end_src 105 + 106 + *** Acquiring the Lock 107 + Let' start writing the acquire function! 108 + #+name: acquire 109 + #+begin_src c 110 + void acquire(struct spinlock *lk) { 111 + <<aq_disable_interrupt>> 112 + <<aq_atomic_swap>> 113 + <<aq_misc>> 114 + } 115 + #+end_src 116 + 1. As discussed earlier we first disable the interrupts. =push_off()= takes care of that. We also ensure we already aren't holding the lock. 117 + #+name: aq_disable_interrupt 118 + #+begin_src c :title "Disable Interrupts" 119 + push_off(); 120 + if(holding(lk)) 121 + panic("acquire"); 122 + #+end_src 123 + 124 + 2. 125 + #+name: aq_atomic_swap 126 + #+begin_src c :title "Atomic Swap and Loop" 127 + while(__sync_lock_test_and_set(&lk->locked, 1) != 0); 128 + #+end_src 129 + 130 + 3. We need to ensure that compiler optimisations do not mess with the order, also store the cpu's information in the struct. 131 + #+name: aq_misc 132 + #+begin_src c :title "Misc" 133 + __sync_synchronize(); 134 + lk->cpu = mycpu(); 135 + #+end_src 136 + 137 + 138 + Our final acquire function- 139 + 140 + #+begin_src c :noweb yes :title "acquire" 141 + <<acquire>> 142 + #+end_src 143 + 144 + *** Releasing the lock 145 + Releasing is a lot simpler. 146 + 147 + #+name: release 148 + #+begin_src c 149 + void release(struct spinlock *lk) { 150 + <<rl_misc>> 151 + <<rl_en_int>> 152 + } 153 + #+end_src 154 + 155 + 1. Ensure we are holding the lock, scrub cpu information, synchronise, and release the lock 156 + #+name: rl_misc 157 + #+begin_src c :title "Misc" 158 + if(!holding(lk)) 159 + panic("release"); 160 + 161 + lk->cpu = 0; 162 + 163 + __sync_synchronize(); 164 + 165 + __sync_lock_release(&lk->locked); 166 + #+end_src 167 + 168 + 2. Re-enable Interrupts 169 + #+name: rl_en_int 170 + #+begin_src c :title "Interrupts" 171 + pop_off(); 172 + #+end_src 173 + 174 + 175 + Release function- 176 + #+begin_src c :title "release" :noweb yes 177 + <<release>> 178 + #+end_src
+12 -2
src/content/posts/xv6/index.md src/content/posts/xv6-intro/index.md
··· 1 1 --- 2 - title: "xv6" 3 - published: 2025-12-16 2 + title: "xv6 - Introduction" 3 + published: 2025-12-17 4 4 draft: false 5 5 description: 'Documenting the xv6 kernel' 6 6 tags: ["xv6","os"] ··· 139 139 ``` 140 140 - Therefore the allowed range is - 0&#x2026;0x3F-FFFF-FFFF 141 141 142 + 143 + ## Startup 144 + 145 + We go from 146 + 147 + ``` 148 + entry.S -> start.c -> main.c 149 + (Setup Stack) (Machine Mode) (Supervisor Mode) 150 + ``` 151 +
+9 -1
src/content/posts/xv6/index.org src/content/posts/xv6-intro/index.org
··· 1 - #+title: xv6 1 + #+title: xv6 - Introduction 2 2 #+author: Akshit Gaur 3 3 #+description: Documenting the xv6 kernel 4 4 #+TAGS: xv6, os ··· 106 106 = 0x40-0000-0000 107 107 #+end_example 108 108 - Therefore the allowed range is - 0...0x3F-FFFF-FFFF 109 + 110 + ** Startup 111 + We go from 112 + 113 + #+begin_example 114 + entry.S -> start.c -> main.c 115 + (Setup Stack) (Machine Mode) (Supervisor Mode) 116 + #+end_example
src/content/posts/xv6/ua-space.jpeg src/content/posts/xv6-intro/ua-space.jpeg