Update post of cs350's lab.

This commit is contained in:
2022-03-26 16:14:09 -04:00
parent 1e3e7ec54f
commit f5d33d25f3
2 changed files with 323 additions and 2 deletions
+157
View File
@@ -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
+166 -2
View File
File diff suppressed because one or more lines are too long