···11#+title: xv6 - Introduction
22#+author: Akshit Gaur
33-#+description: Documenting the xv6 kernel
33+#+description: Introducing the xv6 kernel
44#+TAGS: xv6, os
55+#+SERIES: xv6
56#+OPTIONS: toc:nil
6778* Why??
+274
src/content/posts/xv6-mem-management/index.md
···11+---
22+title: "xv6 - Memory Management"
33+published: 2026-01-02
44+draft: false
55+description: 'Documenting the xv6 kernel'
66+series: 'xv6'
77+tags: ["xv6","os"]
88+---
99+1010+We know that xv6 uses a simple freelist, keeping that in mind, let's take a look at [kalloc.c](https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/kalloc.c)!
1111+1212+- ```c
1313+ extern char end[];
1414+ ```
1515+1616+ First memory address after kernel.
1717+ Defined by `kernel.ld`
1818+- ```c
1919+ struct run {
2020+ struct run *next;
2121+ };
2222+ ```
2323+2424+ This is the freelist.
2525+- ```c
2626+ struct {
2727+ struct spinlock lock;
2828+ struct run *freelist;
2929+ } kmem;
3030+ ```
3131+3232+ Protects the freelist with a lock.
3333+- ```c
3434+ void
3535+ kinit()
3636+ {
3737+ initlock(&kmem.lock, "kmem");
3838+ freerange(end, (void*)PHYSTOP);
3939+ }
4040+ ```
4141+4242+ Initialises the lock and frees the entire memory space above the kernel.
4343+- ```c
4444+ void
4545+ freerange(void *pa_start, void *pa_end)
4646+ {
4747+ char *p;
4848+ p = (char*)PGROUNDUP((uint64)pa_start);
4949+ for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
5050+ kfree(p);
5151+ }
5252+ ```
5353+5454+ We go from `pa_start` to `pa_end` and free each page!
5555+5656+The next two functions do require a closer look-
5757+5858+5959+# kfree
6060+6161+```c
6262+void
6363+kfree(void *pa)
6464+{
6565+ struct run *r;
6666+6767+ if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
6868+ panic("kfree");
6969+7070+ // Fill with junk to catch dangling refs.
7171+ memset(pa, 1, PGSIZE);
7272+7373+ r = (struct run*)pa;
7474+7575+ acquire(&kmem.lock);
7676+ r->next = kmem.freelist;
7777+ kmem.freelist = r;
7878+ release(&kmem.lock);
7979+}
8080+```
8181+8282+Let's go line by line here.
8383+8484+1. ```c
8585+ void
8686+ kfree(void *pa)
8787+ {
8888+ struct run *r;
8989+9090+ ...
9191+ }
9292+ ```
9393+9494+ - We take in physical address (`pa`) as input, which is a pointer that points to the page we want to free.
9595+ - We also create `struct run` to actually manipulate memory.
9696+2. ```c
9797+ if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
9898+ panic("kfree");
9999+ ```
100100+101101+ Here, we define the conditions in which `kfree` is invalid to run and should panic-
102102+103103+ - `(uint64)pa % PGSIZE != 0` -> The pointer `pa` is not page-aligned!
104104+105105+ - `(char*)pa < end` -> `pa` points to a kernel page!
106106+107107+ - `(uint64)pa >= PHYSTOP` -> `pa` points to a memory address larger than the physical memory!
108108+109109+3. ```c
110110+ memset(pa, 1, PGSIZE);
111111+ ```
112112+113113+ As is quite obvious here, we fill the entire page with junk value so a snooping program cannot see the residual data. It also helps in debugging and catching dangling refs.
114114+115115+4. ```c
116116+ r = (struct run*)pa;
117117+118118+ acquire(&kmem.lock);
119119+ r->next = kmem.freelist;
120120+ kmem.freelist = r;
121121+ release(&kmem.lock);
122122+ ```
123123+124124+ Step-by-step execution of this block-
125125+126126+ - We assign the page to `r`.
127127+128128+ - We acquire the lock to memory.
129129+130130+ - We prepend this page to the start of the freelist.
131131+132132+ - We point the freelist pointer to `r`.
133133+134134+ - We release the lock on memory we are currently holding.
135135+136136+And that concludes the `kfree` function!
137137+138138+139139+# kalloc
140140+141141+```c
142142+void *
143143+kalloc(void)
144144+{
145145+ struct run *r;
146146+147147+ acquire(&kmem.lock);
148148+ r = kmem.freelist;
149149+ if(r)
150150+ kmem.freelist = r->next;
151151+ release(&kmem.lock);
152152+153153+ if(r)
154154+ memset((char*)r, 5, PGSIZE); // fill with junk
155155+ return (void*)r;
156156+}
157157+```
158158+159159+Let's again analyse each line (only unique ones)-
160160+161161+1. ```c
162162+ struct run *r;
163163+164164+ acquire(&kmem.lock);
165165+ r = kmem.freelist;
166166+ ```
167167+168168+ We acquire the memory lock and point a variable `r` to it.
169169+170170+2. ```c
171171+ if(r)
172172+ kmem.freelist = r->next;
173173+ release(&kmem.lock);
174174+ ```
175175+176176+ If r exists, we move freelist pointer to the 2nd node and release the lock.
177177+178178+3. ```c
179179+ if(r)
180180+ memset((char*)r, 5, PGSIZE); // fill with junk
181181+ return (void*)r;
182182+ ```
183183+184184+ Again, if r exists, we fill it with junk to foil any would be snooper's plan!
185185+186186+And that concludes the `kalloc.c` file in xv6 kernel! You are finally ready to conquer your memory!!
187187+188188+Our final `kalloc.c`
189189+190190+```c
191191+// Physical memory allocator, for user processes,
192192+// kernel stacks, page-table pages,
193193+// and pipe buffers. Allocates whole 4096-byte pages.
194194+195195+#include "types.h"
196196+#include "param.h"
197197+#include "memlayout.h"
198198+#include "spinlock.h"
199199+#include "riscv.h"
200200+#include "defs.h"
201201+202202+void freerange(void *pa_start, void *pa_end);
203203+204204+extern char end[]; // first address after kernel.
205205+ // defined by kernel.ld.
206206+207207+struct run {
208208+ struct run *next;
209209+};
210210+211211+struct {
212212+ struct spinlock lock;
213213+ struct run *freelist;
214214+} kmem;
215215+216216+void
217217+kinit()
218218+{
219219+ initlock(&kmem.lock, "kmem");
220220+ freerange(end, (void*)PHYSTOP);
221221+}
222222+223223+void
224224+freerange(void *pa_start, void *pa_end)
225225+{
226226+ char *p;
227227+ p = (char*)PGROUNDUP((uint64)pa_start);
228228+ for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
229229+ kfree(p);
230230+}
231231+232232+// Free the page of physical memory pointed at by pa,
233233+// which normally should have been returned by a
234234+// call to kalloc(). (The exception is when
235235+// initializing the allocator; see kinit above.)
236236+void
237237+kfree(void *pa)
238238+{
239239+ struct run *r;
240240+241241+ if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
242242+ panic("kfree");
243243+244244+ // Fill with junk to catch dangling refs.
245245+ memset(pa, 1, PGSIZE);
246246+247247+ r = (struct run*)pa;
248248+249249+ acquire(&kmem.lock);
250250+ r->next = kmem.freelist;
251251+ kmem.freelist = r;
252252+ release(&kmem.lock);
253253+}
254254+255255+// Allocate one 4096-byte page of physical memory.
256256+// Returns a pointer that the kernel can use.
257257+// Returns 0 if the memory cannot be allocated.
258258+void *
259259+kalloc(void)
260260+{
261261+ struct run *r;
262262+263263+ acquire(&kmem.lock);
264264+ r = kmem.freelist;
265265+ if(r)
266266+ kmem.freelist = r->next;
267267+ release(&kmem.lock);
268268+269269+ if(r)
270270+ memset((char*)r, 5, PGSIZE); // fill with junk
271271+ return (void*)r;
272272+}
273273+```
274274+
+271
src/content/posts/xv6-mem-management/index.org
···11+# Created 2026-01-02 Fri 19:58
22+#+options: toc:nil
33+#+title: xv6 - Memory Management
44+#+author: Akshit Gaur
55+#+description: How does xv6 manage memory?
66+#+tags: xv6, os
77+#+series: xv6
88+#+property: header-args:C :noweb no-export
99+1010+We know that xv6 uses a simple freelist, keeping that in mind, let's take a look at [[https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/kalloc.c][kalloc.c]]!
1111+1212+-
1313+ #+begin_src C
1414+ extern char end[];
1515+ #+end_src
1616+ First memory address after kernel.
1717+ Defined by =kernel.ld=
1818+-
1919+ #+begin_src C
2020+ struct run {
2121+ struct run *next;
2222+ };
2323+ #+end_src
2424+2525+ This is the freelist.
2626+-
2727+ #+begin_src C
2828+ struct {
2929+ struct spinlock lock;
3030+ struct run *freelist;
3131+ } kmem;
3232+ #+end_src
3333+ Protects the freelist with a lock.
3434+-
3535+ #+begin_src C
3636+ void
3737+ kinit()
3838+ {
3939+ initlock(&kmem.lock, "kmem");
4040+ freerange(end, (void*)PHYSTOP);
4141+ }
4242+ #+end_src
4343+4444+ Initialises the lock and frees the entire memory space above the kernel.
4545+-
4646+ #+begin_src C
4747+ void
4848+ freerange(void *pa_start, void *pa_end)
4949+ {
5050+ char *p;
5151+ p = (char*)PGROUNDUP((uint64)pa_start);
5252+ for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
5353+ kfree(p);
5454+ }
5555+ #+end_src
5656+ We go from =pa_start= to =pa_end= and free each page!
5757+5858+5959+The next two functions do require a closer look-
6060+* kfree
6161+#+begin_src C
6262+ void
6363+ kfree(void *pa)
6464+ {
6565+ struct run *r;
6666+6767+ if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
6868+ panic("kfree");
6969+7070+ // Fill with junk to catch dangling refs.
7171+ memset(pa, 1, PGSIZE);
7272+7373+ r = (struct run*)pa;
7474+7575+ acquire(&kmem.lock);
7676+ r->next = kmem.freelist;
7777+ kmem.freelist = r;
7878+ release(&kmem.lock);
7979+ }
8080+#+end_src
8181+8282+Let's go line by line here.
8383+8484+1.
8585+ #+begin_src C
8686+ void
8787+ kfree(void *pa)
8888+ {
8989+ struct run *r;
9090+9191+ ...
9292+ }
9393+ #+end_src
9494+ - We take in physical address (=pa=) as input, which is a pointer that points to the page we want to free.
9595+ - We also create =struct run= to actually manipulate memory.
9696+2.
9797+ #+begin_src C
9898+ if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
9999+ panic("kfree");
100100+ #+end_src
101101+ Here, we define the conditions in which =kfree= is invalid to run and should panic-
102102+ - ~(uint64)pa % PGSIZE != 0~ -> The pointer =pa= is not page-aligned!
103103+104104+ - ~(char*)pa < end~ -> =pa= points to a kernel page!
105105+106106+ - ~(uint64)pa >= PHYSTOP~ -> =pa= points to a memory address larger than the physical memory!
107107+108108+3.
109109+ #+begin_src C
110110+ memset(pa, 1, PGSIZE);
111111+ #+end_src
112112+ As is quite obvious here, we fill the entire page with junk value so a snooping program cannot see the residual data. It also helps in debugging and catching dangling refs.
113113+114114+4.
115115+ #+begin_src C
116116+ r = (struct run*)pa;
117117+118118+ acquire(&kmem.lock);
119119+ r->next = kmem.freelist;
120120+ kmem.freelist = r;
121121+ release(&kmem.lock);
122122+ #+end_src
123123+ Step-by-step execution of this block-
124124+ - We assign the page to =r=.
125125+126126+ - We acquire the lock to memory.
127127+128128+ - We prepend this page to the start of the freelist.
129129+130130+ - We point the freelist pointer to =r=.
131131+132132+ - We release the lock on memory we are currently holding.
133133+134134+135135+136136+And that concludes the =kfree= function!
137137+* [#C] kalloc
138138+#+begin_src C
139139+ void *
140140+ kalloc(void)
141141+ {
142142+ struct run *r;
143143+144144+ acquire(&kmem.lock);
145145+ r = kmem.freelist;
146146+ if(r)
147147+ kmem.freelist = r->next;
148148+ release(&kmem.lock);
149149+150150+ if(r)
151151+ memset((char*)r, 5, PGSIZE); // fill with junk
152152+ return (void*)r;
153153+ }
154154+#+end_src
155155+156156+Let's again analyse each line (only unique ones)-
157157+158158+1.
159159+ #+begin_src C
160160+ struct run *r;
161161+162162+ acquire(&kmem.lock);
163163+ r = kmem.freelist;
164164+ #+end_src
165165+ We acquire the memory lock and point a variable =r= to it.
166166+167167+2.
168168+ #+begin_src C
169169+ if(r)
170170+ kmem.freelist = r->next;
171171+ release(&kmem.lock);
172172+ #+end_src
173173+ If r exists, we move freelist pointer to the 2nd node and release the lock.
174174+175175+3.
176176+ #+begin_src C
177177+ if(r)
178178+ memset((char*)r, 5, PGSIZE); // fill with junk
179179+ return (void*)r;
180180+ #+end_src
181181+ Again, if r exists, we fill it with junk to foil any would be snooper's plan!
182182+183183+184184+And that concludes the =kalloc.c= file in xv6 kernel! You are finally ready to conquer your memory!!
185185+186186+187187+Our final =kalloc.c=
188188+#+begin_src C
189189+ // Physical memory allocator, for user processes,
190190+ // kernel stacks, page-table pages,
191191+ // and pipe buffers. Allocates whole 4096-byte pages.
192192+193193+ #include "types.h"
194194+ #include "param.h"
195195+ #include "memlayout.h"
196196+ #include "spinlock.h"
197197+ #include "riscv.h"
198198+ #include "defs.h"
199199+200200+ void freerange(void *pa_start, void *pa_end);
201201+202202+ extern char end[]; // first address after kernel.
203203+ // defined by kernel.ld.
204204+205205+ struct run {
206206+ struct run *next;
207207+ };
208208+209209+ struct {
210210+ struct spinlock lock;
211211+ struct run *freelist;
212212+ } kmem;
213213+214214+ void
215215+ kinit()
216216+ {
217217+ initlock(&kmem.lock, "kmem");
218218+ freerange(end, (void*)PHYSTOP);
219219+ }
220220+221221+ void
222222+ freerange(void *pa_start, void *pa_end)
223223+ {
224224+ char *p;
225225+ p = (char*)PGROUNDUP((uint64)pa_start);
226226+ for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
227227+ kfree(p);
228228+ }
229229+230230+ // Free the page of physical memory pointed at by pa,
231231+ // which normally should have been returned by a
232232+ // call to kalloc(). (The exception is when
233233+ // initializing the allocator; see kinit above.)
234234+ void
235235+ kfree(void *pa)
236236+ {
237237+ struct run *r;
238238+239239+ if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
240240+ panic("kfree");
241241+242242+ // Fill with junk to catch dangling refs.
243243+ memset(pa, 1, PGSIZE);
244244+245245+ r = (struct run*)pa;
246246+247247+ acquire(&kmem.lock);
248248+ r->next = kmem.freelist;
249249+ kmem.freelist = r;
250250+ release(&kmem.lock);
251251+ }
252252+253253+ // Allocate one 4096-byte page of physical memory.
254254+ // Returns a pointer that the kernel can use.
255255+ // Returns 0 if the memory cannot be allocated.
256256+ void *
257257+ kalloc(void)
258258+ {
259259+ struct run *r;
260260+261261+ acquire(&kmem.lock);
262262+ r = kmem.freelist;
263263+ if(r)
264264+ kmem.freelist = r->next;
265265+ release(&kmem.lock);
266266+267267+ if(r)
268268+ memset((char*)r, 5, PGSIZE); // fill with junk
269269+ return (void*)r;
270270+ }
271271+#+end_src
+3-2
src/content/posts/xv6-spinlocking/index.md
···11---
22title: "xv6 - SpinLocks"
33-published: 2025-12-17
33+published: 2026-01-02
44draft: false
55-description: 'Documenting the xv6 kernel'
55+description: 'Should I spin or should I lock?'
66+series: 'xv6'
67tags: ["xv6","os"]
78---
89
+2-1
src/content/posts/xv6-spinlocking/index.org
···11#+title: xv6 - SpinLocks
22#+author: Akshit Gaur
33-#+description: Documenting the xv6 kernel
33+#+description: Should I spin or should I lock?
44#+TAGS: xv6, os
55+#+SERIES: xv6
56#+OPTIONS: toc:nil
67#+PROPERTY: header-args:c :noweb no-export
78