CS444 hw2 due Sun., Oct. 15 We start implementing tiny-UNIX on the SAPC, with 3 syscalls read, write, and exit. However, where UNIX would use a file descriptor we will use hw1's device numbers, e.g. "write(TTY0,buf,10)". For Sun UNIX, the syscall numbers are defined in and the sysent struct to dispatch from its trap handler is defined in . You will need to provide equivalent headers for SAPCs. Note: Since there is a Linux "syscall.h" in the search path of our cross-compiler's cpp, we'll call ours tsyscall.h. Let's follow x86 Linux syscall linkage: --int 0x80 is the syscall instruction --the syscall # is in eax --the syscall args are in ebx (first), ecx (second), and edx (third). We continue to run everything in kernel mode, which means that the user and kernel share the same stack--the user uses some, then a syscall is encountered and the kernel uses some more, pops it off on return, and the user goes on in the same stack-state as before. Similar story for an interrupt. This assignment builds on your standalone i/o package from hw1. If you prefer, you can use the hw1 solution which is included in the provided hw2 directory. In any case, fix yours, if necessary, up to spec first. Here are the files you need: Shared between user and kernel: tty_public.h: device numbers tsyscall.h: syscall numbers (like UNIX sys/syscall.h) Kernel files: ioconf.h, io.h, tty.h: i/o headers used only by kernel tsystm.h: syscall dispatch, kernel fn protos (like UNIX sys/systm.h) startup0.s: same as $pclibsrc/startup0.s. Sets up stack, calls into startup1.c startup1.c: Same as $pclibsrc/startup.c, but calls your kernel initialization routine instead of main. tunix.c: has kernel init routine. It needs to call ioinit, call set_trap_gate(0x80,&syscallhand), and possibly other inits. tunix.c also has the code for syscallc, sys call exit, and set_trap_gate. The code for set_trap_gate is just that of set_intr_gate with the line: desc->flags = GATE_P|GATE_DPL_KERNEL|GATE_INTGATE; replaced by: desc->flags = GATE_P|GATE_DPL_KERNEL|GATE_TRAPGATE; Syscallc: first write it with a big switch statement over the various different syscalls. If you have time, upgrade it to use a sysent dispatch table. sysentry.s: Trap handler's assembler envelope routine _syscallhand for the trap resulting from a system call-- needs to push eax, ebx, ecx, edx on stack, where they can be accessed from C, call _syscallc, then pops, iret. Just modify $pclibsrc/irq4.s, for example. io.c: rename "read" to "sysread", etc. to avoid linking problems, since "read" is a now user-level call. ioconf.c, ioconf.h: from hw1. tty.c, tty.h, tty_public.h: tty driver from hw1, unchanged User-level files: tunistd.h: syscall prototypes (like UNIX unistd.h) uprog.c: has main(). Easily extended to multiple user files, or user assembler sources, as long as they follow the syscall rules and have a _main entry point. First example is main() { write(TTY1,"hi!\n",4);}. Work back to testio.c from hw1. ulib.s: library set-ups for syscalls: _read, _write, _exit Provided for write, you add read and exit. crt0.s: user-level "C startup module" sets up stack, calls _main, does exit syscall. Entry point _ustart. Edit $pclibsrc/startup0.s hw1 solution files not directly used in hw2: io_public.h: like tunistd.h above, but also lists ioinit(), and not exit(). testio.c: remove ioinit() call here to turn into proper user program. (and note that the kprintf's are only for debugging) How the finished system should run: The idea here is that each user program to be run on the SAPC has to be separately built with tunix, downloaded and run. Startup0 executes first, and transfers control to the kernel initialization in tunix.c, which sets up the system and starts the user code at ustart (calling ustart will do the trick*). The C user startup module reinitializes the stack and calls main. The syscalls in the user code (in ulib.s, called from test1.c) cause execution of the system call handler in tunix.c (and functions called from there), returning to the user code in ulib.s at the rti. Finally the user does a syscall exit. The kernel gets control, and finishes up. *since we are staying in kernel mode even in the user code, to simplify the project. A real OS has to arrange a mode change here. The provided files makefile: The provided makefile can make a hw1 solution by "make testio.lnx" and a hw2 system by "make U=test1" to use test1.c for a user program. The empty files that are provided are there to test the makefile. (Empty files are valid .c and .s programs, a handy fact.) If you try "make U=test1" with the provided files, you should see compiles followed by a load with an error as follows: /groups/ulab/bin/i386-ld -N -Ttext 1000e0 -o uprog.lnx \ crt0.opc uprog.opc ulib.opc startup0.opc startup1.opc tunix.opc sysentry.opc io.opc tty.opc ioconf.opc queue.opc /groups/ulab/pcdev/lib/libc.a io.opc: In function `write': /home/eoneil/444/hw2/io.c:50: multiple definition of `write' ulib.opc(.text+0x0): first defined here make: *** [uprog.lnx] Error 1 This happens because both ulib.s and io.c define global symbols named "write". You need to change write to syswrite in io.c to fix this. We want to use "write" for the user-level system call, so the kernel needs another name for its function. Syswrite is the Linux kernel name for its write-implementing function, so let's adopt that name. Suggested Steps There are lots of little pieces to this system. Here is a suggested sequence to follow: 1. Get a system built. Change io.c's write to syswrite, read to sysread, fill out startup0 and startup1, write a tiny tunix.c init fn that calls ioinit, then calls main (cheating for now--later it should call ustart), and then returns to startup, shutting down. At this point, test1.c just has a main that kprintf's a message. Now it should build and run, but does no syscalls. 2. ulib.s is set up for write already, so do the write syscall first. Set the trap vector up in kernel init in tunix.c, and write sysentry.s--have it push registers on the stack and then call into tunix.c. In tunix.c, access the pushed registers (themselves syscall arguments from the user) via args to the C function, and call syswrite. Make test1.c do a simple write. We'll go over the trick about the syscall args in class. 3. Next implement syscall exit and add an exit to test1.c. Note that exit has one argument, the exit code, so a program can put exit(1) for the first kind of error, exit(2) for the second, etc., and exit(0) for successful exit. 4. Write the proper user startup module crt0.s to reinitialize the stack and call into main, then when that returns, it does an exit syscall. Note: make this execute a real exit syscall, not a call to the kernel or int $3 to Tutor. crt0.s has entry point _ustart. Change the call to main in kernel initialization to call ustart now. Try a user program without its own exit syscall.