···11+---
22+title: "xv6 - SpinLocks"
33+published: 2025-12-17
44+draft: false
55+description: 'Documenting the xv6 kernel'
66+tags: ["xv6","os"]
77+---
88+99+1010+1111+# Spin Locking
1212+1313+We know that xv6 uses SpinLocks, sleep and wakeup for its locking. In this post, let us discuss SpinLocks.
1414+1515+SpinLocks are defined in [spinlock.c](https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.c).
1616+1717+1818+## Basics
1919+2020+There are primarily **two** functions related to locks, acquire and release
2121+2222+:::warning
2323+Spin Locks should not be held for a long time as spinning wastes cycles!
2424+:::
2525+2626+:::tip
2727+In such cases, consider using `sleep()` and `wakeup()`!
2828+:::
2929+3030+3131+### acquire
3232+3333+Acquiring the lock seems simple enough on the surface.
3434+3535+```c
3636+while (locked == 1);
3737+locked = 1;
3838+cpu = mycpu();
3939+```
4040+4141+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.
4242+4343+Another thing to note is that we need to disable preemption or interrupts while we are in the critical section!
4444+4545+Therefore, we have-
4646+4747+```
4848+ 1 -> [ Locked ] -> y
4949+ if (y != 0) try again
5050+```
5151+5252+5353+### release
5454+5555+release is quite simple, just put value 0 in the locked field!
5656+5757+5858+## Theory
5959+6060+Now, `acquire()` disables the interrupts and `release()` re-enables them. But now, situations with multiple locks becomes problematic.
6161+6262+```c
6363+acquire(&lk1);
6464+acquire(&lk2);
6565+6666+// Critical Section
6767+6868+release(&lk2); // Interrupts re-enabled prematurely!!
6969+7070+// Some more operations
7171+7272+release(&lk1);
7373+```
7474+7575+So, we store a counter `noff` and a status int `intena` (interrupts enabled) for each hart.
7676+7777+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.
7878+7979+During release, we decrement the counter and if it becomes 0, we restore the interrupts to their saved state in `intena`!
8080+8181+8282+## Code
8383+8484+8585+### Structure
8686+8787+The struct spinlock itself, is defined in [spinlock.h](https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.h).
8888+8989+```c title="spinlock.h"
9090+struct spinlock {
9191+ uint locked; // Is the lock held?
9292+9393+ // For debugging:
9494+ char *name; // Name of lock.
9595+ struct cpu *cpu; // The cpu holding the lock.
9696+};
9797+```
9898+9999+It contains an int `locked`, which indicates that the lock is free/unacquired when `locked = 0` and otherwise for `locked = 1`
100100+101101+102102+### Imports
103103+104104+We first import the necessary files. Nothing too exciting here.
105105+106106+```c title="imports"
107107+#include "types.h"
108108+#include "param.h"
109109+#include "memlayout.h"
110110+#include "spinlock.h"
111111+#include "riscv.h"
112112+#include "proc.h"
113113+#include "defs.h"
114114+```
115115+116116+117117+### Initalising the lock
118118+119119+Before we can even acquire the lock, we need to actually initialise it! Its steps are easy enough to be obvious.
120120+121121+```c title="initlock"
122122+void
123123+initlock(struct spinlock *lk, char *name)
124124+{
125125+ lk->name = name;
126126+ lk->locked = 0;
127127+ lk->cpu = 0;
128128+}
129129+```
130130+131131+132132+### Acquiring the Lock
133133+134134+Let' start writing the acquire function!
135135+136136+```c
137137+void acquire(struct spinlock *lk) {
138138+ <<aq_disable_interrupt>>
139139+ <<aq_atomic_swap>>
140140+ <<aq_misc>>
141141+}
142142+```
143143+144144+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.
145145+146146+ ```c title="Disable Interrupts"
147147+ push_off();
148148+ if(holding(lk))
149149+ panic("acquire");
150150+ ```
151151+152152+2. ```c title="Atomic Swap and Loop"
153153+ while(__sync_lock_test_and_set(&lk->locked, 1) != 0);
154154+ ```
155155+156156+3. We need to ensure that compiler optimisations do not mess with the order, also store the cpu's information in the struct.
157157+158158+ ```c title="Misc"
159159+ __sync_synchronize();
160160+ lk->cpu = mycpu();
161161+ ```
162162+163163+Our final acquire function-
164164+165165+```c title="acquire"
166166+void acquire(struct spinlock *lk) {
167167+ push_off();
168168+ if(holding(lk))
169169+ panic("acquire");
170170+ while(__sync_lock_test_and_set(&lk->locked, 1) != 0);
171171+ __sync_synchronize();
172172+ lk->cpu = mycpu();
173173+}
174174+```
175175+176176+177177+### Releasing the lock
178178+179179+Releasing is a lot simpler.
180180+181181+```c
182182+void release(struct spinlock *lk) {
183183+ <<rl_misc>>
184184+ <<rl_en_int>>
185185+}
186186+```
187187+188188+1. Ensure we are holding the lock, scrub cpu information, synchronise, and release the lock
189189+190190+ ```c title="Misc"
191191+ if(!holding(lk))
192192+ panic("release");
193193+194194+ lk->cpu = 0;
195195+196196+ __sync_synchronize();
197197+198198+ __sync_lock_release(&lk->locked);
199199+ ```
200200+201201+2. Re-enable Interrupts
202202+203203+ ```c title="Interrupts"
204204+ pop_off();
205205+ ```
206206+207207+Release function-
208208+209209+```c title="release"
210210+void release(struct spinlock *lk) {
211211+ if(!holding(lk))
212212+ panic("release");
213213+214214+ lk->cpu = 0;
215215+216216+ __sync_synchronize();
217217+218218+ __sync_lock_release(&lk->locked);
219219+ pop_off();
220220+}
221221+```
222222+
+178
src/content/posts/xv6-spinlocking/index.org
···11+#+title: xv6 - SpinLocks
22+#+author: Akshit Gaur
33+#+description: Documenting the xv6 kernel
44+#+TAGS: xv6, os
55+#+OPTIONS: toc:nil
66+#+PROPERTY: header-args:c :noweb no-export
77+88+* Spin Locking
99+We know that xv6 uses SpinLocks, sleep and wakeup for its locking. In this post, let us discuss SpinLocks.
1010+1111+SpinLocks are defined in [[https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.c][spinlock.c]].
1212+1313+** Basics
1414+There are primarily *two* functions related to locks, acquire and release
1515+1616+:::warning
1717+Spin Locks should not be held for a long time as spinning wastes cycles!
1818+:::
1919+2020+:::tip
2121+In such cases, consider using =sleep()= and =wakeup()=!
2222+:::
2323+2424+*** acquire
2525+Acquiring the lock seems simple enough on the surface.
2626+#+begin_src c
2727+ while (locked == 1);
2828+ locked = 1;
2929+ cpu = mycpu();
3030+#+end_src
3131+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.
3232+3333+Another thing to note is that we need to disable preemption or interrupts while we are in the critical section!
3434+3535+Therefore, we have-
3636+#+begin_example
3737+ 1 -> [ Locked ] -> y
3838+ if (y != 0) try again
3939+#+end_example
4040+4141+*** release
4242+release is quite simple, just put value 0 in the locked field!
4343+4444+** Theory
4545+Now, =acquire()= disables the interrupts and =release()= re-enables them. But now, situations with multiple locks becomes problematic.
4646+#+begin_src c
4747+acquire(&lk1);
4848+acquire(&lk2);
4949+5050+// Critical Section
5151+5252+release(&lk2); // Interrupts re-enabled prematurely!!
5353+5454+// Some more operations
5555+5656+release(&lk1);
5757+#+end_src
5858+5959+So, we store a counter =noff= and a status int =intena= (interrupts enabled) for each hart.
6060+6161+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.
6262+6363+During release, we decrement the counter and if it becomes 0, we restore the interrupts to their saved state in =intena=!
6464+6565+** Code
6666+*** Structure
6767+The struct spinlock itself, is defined in [[https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/spinlock.h][spinlock.h]].
6868+#+begin_src c :title "spinlock.h"
6969+ struct spinlock {
7070+ uint locked; // Is the lock held?
7171+7272+ // For debugging:
7373+ char *name; // Name of lock.
7474+ struct cpu *cpu; // The cpu holding the lock.
7575+ };
7676+#+end_src
7777+It contains an int =locked=, which indicates that the lock is free/unacquired when =locked = 0= and otherwise for =locked = 1=
7878+7979+*** Imports
8080+We first import the necessary files. Nothing too exciting here.
8181+8282+#+name: imports
8383+#+begin_src c :title "imports"
8484+ #include "types.h"
8585+ #include "param.h"
8686+ #include "memlayout.h"
8787+ #include "spinlock.h"
8888+ #include "riscv.h"
8989+ #include "proc.h"
9090+ #include "defs.h"
9191+#+end_src
9292+9393+*** Initalising the lock
9494+Before we can even acquire the lock, we need to actually initialise it! Its steps are easy enough to be obvious.
9595+#+name: initlock
9696+#+begin_src c :title "initlock"
9797+ void
9898+ initlock(struct spinlock *lk, char *name)
9999+ {
100100+ lk->name = name;
101101+ lk->locked = 0;
102102+ lk->cpu = 0;
103103+ }
104104+#+end_src
105105+106106+*** Acquiring the Lock
107107+Let' start writing the acquire function!
108108+#+name: acquire
109109+#+begin_src c
110110+ void acquire(struct spinlock *lk) {
111111+ <<aq_disable_interrupt>>
112112+ <<aq_atomic_swap>>
113113+ <<aq_misc>>
114114+ }
115115+#+end_src
116116+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.
117117+ #+name: aq_disable_interrupt
118118+ #+begin_src c :title "Disable Interrupts"
119119+ push_off();
120120+ if(holding(lk))
121121+ panic("acquire");
122122+ #+end_src
123123+124124+2.
125125+ #+name: aq_atomic_swap
126126+ #+begin_src c :title "Atomic Swap and Loop"
127127+ while(__sync_lock_test_and_set(&lk->locked, 1) != 0);
128128+ #+end_src
129129+130130+3. We need to ensure that compiler optimisations do not mess with the order, also store the cpu's information in the struct.
131131+ #+name: aq_misc
132132+ #+begin_src c :title "Misc"
133133+ __sync_synchronize();
134134+ lk->cpu = mycpu();
135135+ #+end_src
136136+137137+138138+Our final acquire function-
139139+140140+#+begin_src c :noweb yes :title "acquire"
141141+ <<acquire>>
142142+#+end_src
143143+144144+*** Releasing the lock
145145+Releasing is a lot simpler.
146146+147147+#+name: release
148148+#+begin_src c
149149+ void release(struct spinlock *lk) {
150150+ <<rl_misc>>
151151+ <<rl_en_int>>
152152+ }
153153+#+end_src
154154+155155+1. Ensure we are holding the lock, scrub cpu information, synchronise, and release the lock
156156+ #+name: rl_misc
157157+ #+begin_src c :title "Misc"
158158+ if(!holding(lk))
159159+ panic("release");
160160+161161+ lk->cpu = 0;
162162+163163+ __sync_synchronize();
164164+165165+ __sync_lock_release(&lk->locked);
166166+ #+end_src
167167+168168+2. Re-enable Interrupts
169169+ #+name: rl_en_int
170170+ #+begin_src c :title "Interrupts"
171171+ pop_off();
172172+ #+end_src
173173+174174+175175+Release function-
176176+#+begin_src c :title "release" :noweb yes
177177+<<release>>
178178+#+end_src