From f5d33d25f3e5ae88c9e715d6db89ecd7bbbfd0c2 Mon Sep 17 00:00:00 2001 From: CoderSherlock Date: Sat, 26 Mar 2022 16:14:09 -0400 Subject: [PATCH] Update post of cs350's lab. --- _posts/2022-02-22-cs350-labs.md | 157 +++++++++++++++++++++++++++++ _site/feed.xml | 168 +++++++++++++++++++++++++++++++- 2 files changed, 323 insertions(+), 2 deletions(-) diff --git a/_posts/2022-02-22-cs350-labs.md b/_posts/2022-02-22-cs350-labs.md index a78dd84..9edbf7f 100644 --- a/_posts/2022-02-22-cs350-labs.md +++ b/_posts/2022-02-22-cs350-labs.md @@ -30,6 +30,7 @@ Remember, it's for helping in learning. DON'T COPY & PASTE CODE! ## Lab6-7-Scheduling ### First user process in xv6 +#### Kernel works In xv6, as the same as conventional linux OS, the very first user level process is **init**. Before **init**'s running, all the OS bootstraps are happened in a high privileged mode(kernel level). @@ -112,10 +113,166 @@ userinit(void) p->state = RUNNABLE; } ~~~ +#### Where the user-level code was integrated? If you search the keyword "_binary_initcode_start" in the source code, you can't find any references. The clue comes from the *Makefile*. +In the makefile, **initcode** is a prerequisites to compile the kernel image. +**Step 1**: Before kernel was compiled, *initcode.S* was first compiled to a runnable binary *initcode*. +This binary was very odd because it was not supposed to let any other OS to run it. +*Initcode.s* was first compiled without any standard including, and generating the intermediate file *initcode.o*. +**Step 2**: *Initcode.o* then linked to *Initcode.out* with two uncommon settings. +First it specify the entry of this binary file as when "start" symbol points to. +This "start" symbol was declared in the assembly code. +Second it specify a absolute address(0) for the text segments. +By doing this, text segments will be placed at the start of the binary file (except the header of the ELF)[^ldman]. + +**Step 3**: *Initcode.out* is already a minimized binary but it's not enough. +That's why when using **objcopy** to copy it to the file *initcode*, it further strip all headers and debug information[^objcopyman]. +At this point, we have a minimal binary file *initcode*. +From the first byte of this file, it's only includes runnable instructions. +And the size of the file is only 44 bytes. +~~~bash +initcode: initcode.S + $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S # Step 1 + $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o # Step 2 + $(OBJCOPY) -S -O binary initcode.out initcode # Step 3 + $(OBJDUMP) -S initcode.o > initcode.asm +~~~ + +This binary later were appended to the kernel using following commands. +And during this appending, 3 symbols were generated and added to the symbol table of the *kernel*[^ldman]. +**"_binary_initcode_start"** contains the address of where the initcode segment was appended to. +**"_binary_initcode_end"** contains the address of where the initcode segment was ended at. +**"_binary_initcode_size"** is a \*ABS\* type symbol with value 0x2C(45) that specify the size of the initcode segment is 45 bytes. +~~~bash +kernel: $(OBJS) entry.o entryother initcode kernel.ld + $(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) -b binary initcode entryother # <- This Line + $(OBJDUMP) -S kernel > kernel.asm + $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym +~~~ +**In short summary**, +using objdump, we can verify that source code *initcode.S* has been compiled and loaded into the kernel. +Also the segment of initcode's instructions was located by the pointer "_binary_initcode_start". +That's explain when calling ***inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);***, +functionalities implemented in initcode.S will be loaded into the runtime of the first process within xv6. +~~~bash +# Header of the file kernel +kernel: file format elf32-i386 +kernel +architecture: i386, flags 0x00000112: +EXEC_P, HAS_SYMS, D_PAGED +start address 0x0010000c + +Program Header: + LOAD off 0x00001000 vaddr 0x80100000 paddr 0x00100000 align 2**12 + filesz 0x00008c6a memsz 0x00008c6a flags r-x +... +Sections: +Idx Name Size VMA LMA File off Algn + 0 .text 00008586 80100000 00100000 00001000 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE +... +SYMBOL TABLE: +... +8010b50c g .data 00000000 _binary_initcode_end +... +8010b4e0 g .data 00000000 _binary_initcode_start +... +0000002c g *ABS* 00000000 _binary_initcode_size +... +~~~ + +#### User-level code + +Take a look of content in the *initcode.S*, you will find the code can explain itself well. +There are no other jobs but just calling system call **exec** to run a user-level binary **"init"**. + +*Initcode.S*: +~~~bash +# Initial process execs /init. + +#include "syscall.h" +#include "traps.h" +# exec(init, argv) +.globl start +start: + pushl $argv + pushl $init + pushl $0 // where caller pc would be + movl $SYS_exec, %eax + int $T_SYSCALL +# for(;;) exit(); +exit: + movl $SYS_exit, %eax + int $T_SYSCALL + jmp exit + +# char init[] = "/init\0"; +init: + .string "/init\0" + +# char *argv[] = { init, 0 }; +.p2align 2 +argv: + .long init + .long 0 +~~~ + +The **"init"** mentioned above is not a pure user-level binary executable that compiled from the source code *init.c*. +Within *init.c*, a file named *console* will be created at the runtime for saving standard outputs and errors. +Then it will forked a child process(the second user process), and let it run program **"sh"**. + +**"sh"** is the xv6's default shell, a user-level program that generated from source *sh.c*. +After the shell boots up, you can interactive with the xv6. +This's how first process (and second process) was started in the xv6. + +*init.c*: +~~~c +// init: The initial user-level program + +#include "types.h" +#include "stat.h" +#include "user.h" +#include "fcntl.h" + +char *argv[] = { "sh", 0 }; + +int +main(void) +{ + int pid, wpid; + + if(open("console", O_RDWR) < 0){ + mknod("console", 1, 1); + open("console", O_RDWR); + } + dup(0); // stdout + dup(0); // stderr + + for(;;){ + printf(1, "init: starting sh\n"); + pid = fork(); + if(pid < 0){ + printf(1, "init: fork failed\n"); + exit(); + } + if(pid == 0){ + exec("sh", argv); + printf(1, "init: exec sh failed\n"); + exit(); + } + while((wpid=wait()) >= 0 && wpid != pid) + printf(1, "zombie!\n"); + } +} +~~~ + +[^ldman]: [ld\(1\) - Linux man page](https://linux.die.net/man/1/ld) +[^objcopyman]: [3 objcopy - binutils mannual](https://sourceware.org/binutils/docs/binutils/objcopy.html) + +### Xv6's round robin schduler \ No newline at end of file diff --git a/_site/feed.xml b/_site/feed.xml index e64609d..885bf59 100644 --- a/_site/feed.xml +++ b/_site/feed.xml @@ -1,4 +1,4 @@ -Jekyll2022-03-24T22:47:03-04:00http://localhost:4000/feed.xmlStop Talking, Start DoingMy personal blog, with some boring research staff and some tricks I was fancy to. I'll try my best to make this blog fun and useful. Not just a place I complain about all happens in my Lab. +Jekyll2022-03-26T16:13:07-04:00http://localhost:4000/feed.xmlStop Talking, Start DoingMy personal blog, with some boring research staff and some tricks I was fancy to. I'll try my best to make this blog fun and useful. Not just a place I complain about all happens in my Lab. Pengzhan Haohaopengzhan@gmail.comLabs of CS3502022-02-22T16:08:17-05:002022-02-22T16:08:17-05:00http://localhost:4000/posts/cs350-labs<p>This will be a series regarding lab I gave during the spring 2022 semester.</p> <p>The reason why I am writing this down is because it has been a week and no students ask for the solution of the last Lab. @@ -23,6 +23,7 @@ Remember, it’s for helping in learning. DON’T COPY &amp; PASTE CODE!< <h2 id="lab6-7-scheduling">Lab6-7-Scheduling</h2> <h3 id="first-user-process-in-xv6">First user process in xv6</h3> +<h4 id="kernel-works">Kernel works</h4> <p>In xv6, as the same as conventional linux OS, the very first user level process is <strong>init</strong>. Before <strong>init</strong>’s running, all the OS bootstraps are happened in a high privileged mode(kernel level).</p> @@ -103,8 +104,171 @@ Simply put, it will load instructions of <strong>init</strong> into <span class="n">p</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">RUNNABLE</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> +<h4 id="where-the-user-level-code-was-integrated">Where the user-level code was integrated?</h4> <p>If you search the keyword “_binary_initcode_start” in the source code, you can’t find any references. -The clue comes from the <em>Makefile</em>.</p>Pengzhan HaoThis will be a series regarding lab I gave during the spring 2022 semester. The reason why I am writing this down is because it has been a week and no students ask for the solution of the last Lab. I realise that learning gap between students are huge, especially when a non-profit university is admitting more and more students. To help all students in understanding concepts of modern OS, I decided to write this post. It starts with the past lab content I have (as the skelton), and will be amended with extra materials I think it helps. Remember, it’s for helping in learning. DON’T COPY &amp; PASTE CODE! Index Lab1: Introduction of Makefile and Xv6. Lab3: System calls for process management. Lab4: Inter-processes communication. Lab6/7: CPU scheduling. Lab1-Introduction Lab3-Process Lab4-IPC Lab6-7-Scheduling First user process in xv6 In xv6, as the same as conventional linux OS, the very first user level process is init. Before init’s running, all the OS bootstraps are happened in a high privileged mode(kernel level). Xv6’s kernel has the entry point as the main function located in the file main.c. The main function invokes 17 functions to set up kernel page tables, interrupt handlers, I/O devices and etc. When all kernel preparations are done, by calling the function userinit(), kernel will boot up process init. int main(void) { kinit1(end, P2V(4*1024*1024)); // phys page allocator kvmalloc(); // kernel page table mpinit(); // collect info about this machine lapicinit(); seginit(); // set up segments cprintf("\ncpu%d: starting xv6\n\n", cpu-&gt;id); picinit(); // interrupt controller ioapicinit(); // another interrupt controller consoleinit(); // I/O devices &amp; their interrupts uartinit(); // serial port pinit(); // process table tvinit(); // trap vectors binit(); // buffer cache fileinit(); // file table ideinit(); // disk if(!ismp) timerinit(); // uniprocessor timer startothers(); // start other processors kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers() userinit(); // first user process // Finish setting up this processor in mpmain. mpmain(); } It’s tricky since that init is a user process, but kernel can’t call any user level system calls to create it. Why? 1. Kernel has all privileges to create a user process. So it doesn’t need to call system calls such as fork(). And 2. All other user processes can be created by forking from its parent. Forking including clone the whole user virtual memory layout. However, first process have no parent to fork from. That’s why its makes the creation of the first user process becomes so unique. In proc.c, userinit() define there gives us the whole procedure of creating init. Similar to the fork(), but more simple. Process control block(structures for storing the process status) was created at the very first by calling allocproc(). After then, by invoking setupkvm()(defined in vm.c), kernel memory map was setup for the process. During setting up kernel memory map, a page size virtual memory will assigned to the process as ready. And later, this page size memory will be used to store instructions of init. Followed by setup kernel stack for the init process, calling inituvm() will load init’s text into the page that just being allocated. inituvm() takes 3 arguments: a pointer to the process’s page directory (p-&gt;pgdir), a char-type pointer declared from external which point to init’s text segment(_binary_initcode_start), and a char-type pointer which point to an external integer as the size of the init’s text segment(_binary_initcode_size). Simply put, it will load instructions of init into the memory. So now, the problem becomes when and where did instructions for init has compiled into the kernel? void userinit(void) { struct proc *p; extern char _binary_initcode_start[], _binary_initcode_size[]; p = allocproc(); initproc = p; if((p-&gt;pgdir = setupkvm()) == 0) panic("userinit: out of memory?"); inituvm(p-&gt;pgdir, _binary_initcode_start, (int)_binary_initcode_size); p-&gt;sz = PGSIZE; memset(p-&gt;tf, 0, sizeof(*p-&gt;tf)); p-&gt;tf-&gt;cs = (SEG_UCODE &lt;&lt; 3) | DPL_USER; p-&gt;tf-&gt;ds = (SEG_UDATA &lt;&lt; 3) | DPL_USER; p-&gt;tf-&gt;es = p-&gt;tf-&gt;ds; p-&gt;tf-&gt;ss = p-&gt;tf-&gt;ds; p-&gt;tf-&gt;eflags = FL_IF; p-&gt;tf-&gt;esp = PGSIZE; p-&gt;tf-&gt;eip = 0; // beginning of initcode.S safestrcpy(p-&gt;name, "initcode", sizeof(p-&gt;name)); p-&gt;cwd = namei("/"); p-&gt;state = RUNNABLE; } If you search the keyword “_binary_initcode_start” in the source code, you can’t find any references. The clue comes from the Makefile.EDDL: How do we train neural networks on limited edge devices - PART 22021-10-31T13:01:14-04:002021-10-31T13:01:14-04:00http://localhost:4000/posts/eddl-how-do-we-train-on-limited-edge-devices-part2<p>In the last post, part1, our idea of distributed learning on edge environment was generally addressed. +The clue comes from the <em>Makefile</em>.</p> + +<p>In the makefile, <strong>initcode</strong> is a prerequisites to compile the kernel image. +<strong>Step 1</strong>: Before kernel was compiled, <em>initcode.S</em> was first compiled to a runnable binary <em>initcode</em>. +This binary was very odd because it was not supposed to let any other OS to run it. +<em>Initcode.s</em> was first compiled without any standard including, and generating the intermediate file <em>initcode.o</em>.</p> + +<p><strong>Step 2</strong>: <em>Initcode.o</em> then linked to <em>Initcode.out</em> with two uncommon settings. +First it specify the entry of this binary file as when “start” symbol points to. +This “start” symbol was declared in the assembly code. +Second it specify a absolute address(0) for the text segments. +By doing this, text segments will be placed at the start of the binary file (except the header of the ELF)<sup id="fnref:ldman" role="doc-noteref"><a href="#fn:ldman" class="footnote" rel="footnote">1</a></sup>.</p> + +<p><strong>Step 3</strong>: <em>Initcode.out</em> is already a minimized binary but it’s not enough. +That’s why when using <strong>objcopy</strong> to copy it to the file <em>initcode</em>, it further strip all headers and debug information<sup id="fnref:objcopyman" role="doc-noteref"><a href="#fn:objcopyman" class="footnote" rel="footnote">2</a></sup>. +At this point, we have a minimal binary file <em>initcode</em>. +From the first byte of this file, it’s only includes runnable instructions. +And the size of the file is only 44 bytes.</p> +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>initcode: initcode.S + <span class="si">$(</span>CC<span class="si">)</span> <span class="si">$(</span>CFLAGS<span class="si">)</span> <span class="nt">-nostdinc</span> <span class="nt">-I</span><span class="nb">.</span> <span class="nt">-c</span> initcode.S <span class="c"># Step 1</span> + <span class="si">$(</span>LD<span class="si">)</span> <span class="si">$(</span>LDFLAGS<span class="si">)</span> <span class="nt">-N</span> <span class="nt">-e</span> start <span class="nt">-Ttext</span> 0 <span class="nt">-o</span> initcode.out initcode.o <span class="c"># Step 2</span> + <span class="si">$(</span>OBJCOPY<span class="si">)</span> <span class="nt">-S</span> <span class="nt">-O</span> binary initcode.out initcode <span class="c"># Step 3</span> + <span class="si">$(</span>OBJDUMP<span class="si">)</span> <span class="nt">-S</span> initcode.o <span class="o">&gt;</span> initcode.asm +</code></pre></div></div> + +<p>This binary later were appended to the kernel using following commands. +And during this appending, 3 symbols were generated and added to the symbol table of the <em>kernel</em><sup id="fnref:ldman:1" role="doc-noteref"><a href="#fn:ldman" class="footnote" rel="footnote">1</a></sup>. +<strong>“_binary_initcode_start”</strong> contains the address of where the initcode segment was appended to. +<strong>“_binary_initcode_end”</strong> contains the address of where the initcode segment was ended at. +<strong>“_binary_initcode_size”</strong> is a *ABS* type symbol with value 0x2C(45) that specify the size of the initcode segment is 45 bytes.</p> +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kernel: <span class="si">$(</span>OBJS<span class="si">)</span> entry.o entryother initcode kernel.ld + <span class="si">$(</span>LD<span class="si">)</span> <span class="si">$(</span>LDFLAGS<span class="si">)</span> <span class="nt">-T</span> kernel.ld <span class="nt">-o</span> kernel entry.o <span class="si">$(</span>OBJS<span class="si">)</span> <span class="nt">-b</span> binary initcode entryother <span class="c"># &lt;- This Line</span> + <span class="si">$(</span>OBJDUMP<span class="si">)</span> <span class="nt">-S</span> kernel <span class="o">&gt;</span> kernel.asm + <span class="si">$(</span>OBJDUMP<span class="si">)</span> <span class="nt">-t</span> kernel | <span class="nb">sed</span> <span class="s1">'1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d'</span> <span class="o">&gt;</span> kernel.sym +</code></pre></div></div> +<p><strong>In short summary</strong>, +using objdump, we can verify that source code <em>initcode.S</em> has been compiled and loaded into the kernel. +Also the segment of initcode’s instructions was located by the pointer “_binary_initcode_start”. +That’s explain when calling <strong><em>inituvm(p-&gt;pgdir, _binary_initcode_start, (int)_binary_initcode_size);</em></strong>, +functionalities implemented in initcode.S will be loaded into the runtime of the first process within xv6.</p> +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Header of the file kernel</span> +kernel: file format elf32-i386 +kernel +architecture: i386, flags 0x00000112: +EXEC_P, HAS_SYMS, D_PAGED +start address 0x0010000c + +Program Header: + LOAD off 0x00001000 vaddr 0x80100000 paddr 0x00100000 align 2<span class="k">**</span>12 + filesz 0x00008c6a memsz 0x00008c6a flags r-x +... +Sections: +Idx Name Size VMA LMA File off Algn + 0 .text 00008586 80100000 00100000 00001000 2<span class="k">**</span>2 + CONTENTS, ALLOC, LOAD, READONLY, CODE +... +SYMBOL TABLE: +... +8010b50c g .data 00000000 _binary_initcode_end +... +8010b4e0 g .data 00000000 _binary_initcode_start +... +0000002c g <span class="k">*</span>ABS<span class="k">*</span> 00000000 _binary_initcode_size +... +</code></pre></div></div> + +<h4 id="user-level-code">User-level code</h4> + +<p>Take a look of content in the <em>initcode.S</em>, you will find the code can explain itself well. +There are no other jobs but just calling system call <strong>exec</strong> to run a user-level binary <strong>“init”</strong>.</p> + +<p><em>Initcode.S</em>:</p> +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Initial process execs /init.</span> + +<span class="c">#include "syscall.h"</span> +<span class="c">#include "traps.h"</span> + + +<span class="c"># exec(init, argv)</span> +.globl start +start: + pushl <span class="nv">$argv</span> + pushl <span class="nv">$init</span> + pushl <span class="nv">$0</span> // where <span class="nb">caller </span>pc would be + movl <span class="nv">$SYS_exec</span>, %eax + int <span class="nv">$T_SYSCALL</span> + +<span class="c"># for(;;) exit();</span> +<span class="nb">exit</span>: + movl <span class="nv">$SYS_exit</span>, %eax + int <span class="nv">$T_SYSCALL</span> + jmp <span class="nb">exit</span> + +<span class="c"># char init[] = "/init\0";</span> +init: + .string <span class="s2">"/init</span><span class="se">\0</span><span class="s2">"</span> + +<span class="c"># char *argv[] = { init, 0 };</span> +.p2align 2 +argv: + .long init + .long 0 +</code></pre></div></div> + +<p>The <strong>“init”</strong> mentioned above is not a pure user-level binary executable that compiled from the source code <em>init.c</em>. +Within <em>init.c</em>, a file named <em>console</em> will be created at the runtime for saving standard outputs and errors. +Then it will forked a child process(the second user process), and let it run program <strong>“sh”</strong>.</p> + +<p><strong>“sh”</strong> is the xv6’s default shell, a user-level program that generated from source <em>sh.c</em>. +After the shell boots up, you can interactive with the xv6. +This’s how first process (and second process) was started in the xv6.</p> + +<p><em>init.c</em>:</p> +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// init: The initial user-level program</span> + +<span class="cp">#include "types.h" +#include "stat.h" +#include "user.h" +#include "fcntl.h" +</span> +<span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> <span class="s">"sh"</span><span class="p">,</span> <span class="mi">0</span> <span class="p">};</span> + +<span class="kt">int</span> +<span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="n">wpid</span><span class="p">;</span> + + <span class="k">if</span><span class="p">(</span><span class="n">open</span><span class="p">(</span><span class="s">"console"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">){</span> + <span class="n">mknod</span><span class="p">(</span><span class="s">"console"</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> + <span class="n">open</span><span class="p">(</span><span class="s">"console"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">);</span> + <span class="p">}</span> + <span class="n">dup</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// stdout</span> + <span class="n">dup</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// stderr</span> + + <span class="k">for</span><span class="p">(;;){</span> + <span class="n">printf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"init: starting sh</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> + <span class="n">pid</span> <span class="o">=</span> <span class="n">fork</span><span class="p">();</span> + <span class="k">if</span><span class="p">(</span><span class="n">pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">){</span> + <span class="n">printf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"init: fork failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> + <span class="n">exit</span><span class="p">();</span> + <span class="p">}</span> + <span class="k">if</span><span class="p">(</span><span class="n">pid</span> <span class="o">==</span> <span class="mi">0</span><span class="p">){</span> + <span class="n">exec</span><span class="p">(</span><span class="s">"sh"</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span> + <span class="n">printf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"init: exec sh failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> + <span class="n">exit</span><span class="p">();</span> + <span class="p">}</span> + <span class="k">while</span><span class="p">((</span><span class="n">wpid</span><span class="o">=</span><span class="n">wait</span><span class="p">())</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">wpid</span> <span class="o">!=</span> <span class="n">pid</span><span class="p">)</span> + <span class="n">printf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"zombie!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<h3 id="xv6s-round-robin-schduler">Xv6’s round robin schduler</h3> +<div class="footnotes" role="doc-endnotes"> + <ol> + <li id="fn:ldman" role="doc-endnote"> + <p><a href="https://linux.die.net/man/1/ld">ld(1) - Linux man page</a> <a href="#fnref:ldman" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:ldman:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p> + </li> + <li id="fn:objcopyman" role="doc-endnote"> + <p><a href="https://sourceware.org/binutils/docs/binutils/objcopy.html">3 objcopy - binutils mannual</a> <a href="#fnref:objcopyman" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + </ol> +</div>Pengzhan HaoThis will be a series regarding lab I gave during the spring 2022 semester. The reason why I am writing this down is because it has been a week and no students ask for the solution of the last Lab. I realise that learning gap between students are huge, especially when a non-profit university is admitting more and more students. To help all students in understanding concepts of modern OS, I decided to write this post. It starts with the past lab content I have (as the skelton), and will be amended with extra materials I think it helps. Remember, it’s for helping in learning. DON’T COPY &amp; PASTE CODE! Index Lab1: Introduction of Makefile and Xv6. Lab3: System calls for process management. Lab4: Inter-processes communication. Lab6/7: CPU scheduling. Lab1-Introduction Lab3-Process Lab4-IPC Lab6-7-Scheduling First user process in xv6 Kernel works In xv6, as the same as conventional linux OS, the very first user level process is init. Before init’s running, all the OS bootstraps are happened in a high privileged mode(kernel level). Xv6’s kernel has the entry point as the main function located in the file main.c. The main function invokes 17 functions to set up kernel page tables, interrupt handlers, I/O devices and etc. When all kernel preparations are done, by calling the function userinit(), kernel will boot up process init. int main(void) { kinit1(end, P2V(4*1024*1024)); // phys page allocator kvmalloc(); // kernel page table mpinit(); // collect info about this machine lapicinit(); seginit(); // set up segments cprintf("\ncpu%d: starting xv6\n\n", cpu-&gt;id); picinit(); // interrupt controller ioapicinit(); // another interrupt controller consoleinit(); // I/O devices &amp; their interrupts uartinit(); // serial port pinit(); // process table tvinit(); // trap vectors binit(); // buffer cache fileinit(); // file table ideinit(); // disk if(!ismp) timerinit(); // uniprocessor timer startothers(); // start other processors kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers() userinit(); // first user process // Finish setting up this processor in mpmain. mpmain(); } It’s tricky since that init is a user process, but kernel can’t call any user level system calls to create it. Why? 1. Kernel has all privileges to create a user process. So it doesn’t need to call system calls such as fork(). And 2. All other user processes can be created by forking from its parent. Forking including clone the whole user virtual memory layout. However, first process have no parent to fork from. That’s why its makes the creation of the first user process becomes so unique. In proc.c, userinit() define there gives us the whole procedure of creating init. Similar to the fork(), but more simple. Process control block(structures for storing the process status) was created at the very first by calling allocproc(). After then, by invoking setupkvm()(defined in vm.c), kernel memory map was setup for the process. During setting up kernel memory map, a page size virtual memory will assigned to the process as ready. And later, this page size memory will be used to store instructions of init. Followed by setup kernel stack for the init process, calling inituvm() will load init’s text into the page that just being allocated. inituvm() takes 3 arguments: a pointer to the process’s page directory (p-&gt;pgdir), a char-type pointer declared from external which point to init’s text segment(_binary_initcode_start), and a char-type pointer which point to an external integer as the size of the init’s text segment(_binary_initcode_size). Simply put, it will load instructions of init into the memory. So now, the problem becomes when and where did instructions for init has compiled into the kernel? void userinit(void) { struct proc *p; extern char _binary_initcode_start[], _binary_initcode_size[]; p = allocproc(); initproc = p; if((p-&gt;pgdir = setupkvm()) == 0) panic("userinit: out of memory?"); inituvm(p-&gt;pgdir, _binary_initcode_start, (int)_binary_initcode_size); p-&gt;sz = PGSIZE; memset(p-&gt;tf, 0, sizeof(*p-&gt;tf)); p-&gt;tf-&gt;cs = (SEG_UCODE &lt;&lt; 3) | DPL_USER; p-&gt;tf-&gt;ds = (SEG_UDATA &lt;&lt; 3) | DPL_USER; p-&gt;tf-&gt;es = p-&gt;tf-&gt;ds; p-&gt;tf-&gt;ss = p-&gt;tf-&gt;ds; p-&gt;tf-&gt;eflags = FL_IF; p-&gt;tf-&gt;esp = PGSIZE; p-&gt;tf-&gt;eip = 0; // beginning of initcode.S safestrcpy(p-&gt;name, "initcode", sizeof(p-&gt;name)); p-&gt;cwd = namei("/"); p-&gt;state = RUNNABLE; } Where the user-level code was integrated? If you search the keyword “_binary_initcode_start” in the source code, you can’t find any references. The clue comes from the Makefile. In the makefile, initcode is a prerequisites to compile the kernel image. Step 1: Before kernel was compiled, initcode.S was first compiled to a runnable binary initcode. This binary was very odd because it was not supposed to let any other OS to run it. Initcode.s was first compiled without any standard including, and generating the intermediate file initcode.o. Step 2: Initcode.o then linked to Initcode.out with two uncommon settings. First it specify the entry of this binary file as when “start” symbol points to. This “start” symbol was declared in the assembly code. Second it specify a absolute address(0) for the text segments. By doing this, text segments will be placed at the start of the binary file (except the header of the ELF)1. Step 3: Initcode.out is already a minimized binary but it’s not enough. That’s why when using objcopy to copy it to the file initcode, it further strip all headers and debug information2. At this point, we have a minimal binary file initcode. From the first byte of this file, it’s only includes runnable instructions. And the size of the file is only 44 bytes. initcode: initcode.S $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S # Step 1 $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o # Step 2 $(OBJCOPY) -S -O binary initcode.out initcode # Step 3 $(OBJDUMP) -S initcode.o &gt; initcode.asm This binary later were appended to the kernel using following commands. And during this appending, 3 symbols were generated and added to the symbol table of the kernel1. “_binary_initcode_start” contains the address of where the initcode segment was appended to. “_binary_initcode_end” contains the address of where the initcode segment was ended at. “_binary_initcode_size” is a *ABS* type symbol with value 0x2C(45) that specify the size of the initcode segment is 45 bytes. kernel: $(OBJS) entry.o entryother initcode kernel.ld $(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) -b binary initcode entryother # &lt;- This Line $(OBJDUMP) -S kernel &gt; kernel.asm $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' &gt; kernel.sym In short summary, using objdump, we can verify that source code initcode.S has been compiled and loaded into the kernel. Also the segment of initcode’s instructions was located by the pointer “_binary_initcode_start”. That’s explain when calling inituvm(p-&gt;pgdir, _binary_initcode_start, (int)_binary_initcode_size);, functionalities implemented in initcode.S will be loaded into the runtime of the first process within xv6. # Header of the file kernel kernel: file format elf32-i386 kernel architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x0010000c Program Header: LOAD off 0x00001000 vaddr 0x80100000 paddr 0x00100000 align 2**12 filesz 0x00008c6a memsz 0x00008c6a flags r-x ... Sections: Idx Name Size VMA LMA File off Algn 0 .text 00008586 80100000 00100000 00001000 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE ... SYMBOL TABLE: ... 8010b50c g .data 00000000 _binary_initcode_end ... 8010b4e0 g .data 00000000 _binary_initcode_start ... 0000002c g *ABS* 00000000 _binary_initcode_size ... User-level code Take a look of content in the initcode.S, you will find the code can explain itself well. There are no other jobs but just calling system call exec to run a user-level binary “init”. Initcode.S: # Initial process execs /init. #include "syscall.h" #include "traps.h" # exec(init, argv) .globl start start: pushl $argv pushl $init pushl $0 // where caller pc would be movl $SYS_exec, %eax int $T_SYSCALL # for(;;) exit(); exit: movl $SYS_exit, %eax int $T_SYSCALL jmp exit # char init[] = "/init\0"; init: .string "/init\0" # char *argv[] = { init, 0 }; .p2align 2 argv: .long init .long 0 The “init” mentioned above is not a pure user-level binary executable that compiled from the source code init.c. Within init.c, a file named console will be created at the runtime for saving standard outputs and errors. Then it will forked a child process(the second user process), and let it run program “sh”. “sh” is the xv6’s default shell, a user-level program that generated from source sh.c. After the shell boots up, you can interactive with the xv6. This’s how first process (and second process) was started in the xv6. init.c: // init: The initial user-level program #include "types.h" #include "stat.h" #include "user.h" #include "fcntl.h" char *argv[] = { "sh", 0 }; int main(void) { int pid, wpid; if(open("console", O_RDWR) &lt; 0){ mknod("console", 1, 1); open("console", O_RDWR); } dup(0); // stdout dup(0); // stderr for(;;){ printf(1, "init: starting sh\n"); pid = fork(); if(pid &lt; 0){ printf(1, "init: fork failed\n"); exit(); } if(pid == 0){ exec("sh", argv); printf(1, "init: exec sh failed\n"); exit(); } while((wpid=wait()) &gt;= 0 &amp;&amp; wpid != pid) printf(1, "zombie!\n"); } } Xv6’s round robin schduler ld(1) - Linux man page &#8617; &#8617;2 3 objcopy - binutils mannual &#8617;EDDL: How do we train neural networks on limited edge devices - PART 22021-10-31T13:01:14-04:002021-10-31T13:01:14-04:00http://localhost:4000/posts/eddl-how-do-we-train-on-limited-edge-devices-part2<p>In the last post, part1, our idea of distributed learning on edge environment was generally addressed. I introduced the reason why edge distributed learning is needed and what improvements it can achieve. In this post, I will talk about our motivation study and how our framework works.</p>