From a03618133f7df0954802a470a4bee7674f7aed45 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 11 Nov 2008 22:19:01 -0800 Subject: [PATCH] Implement a proper block layer with partition support. This is in preparation for introducing new block devices, in particular USB storage-based block devices so that Pintos can boot from USB memory sticks on notebook and desktop PCs. This block layer was inspired by one from Anthony Romano but it has been extensively (perhaps entirely) rewritten. Thus, bugs must certainly be blamed on the committer. --- doc/bibliography.texi | 8 + doc/filesys.texi | 22 +- doc/installation.texi | 5 +- doc/intro.texi | 21 +- doc/reference.texi | 185 +++++---- doc/threads.texi | 42 +- doc/userprog.texi | 54 +-- doc/vm.texi | 39 +- src/Makefile.build | 27 +- src/devices/block.c | 223 ++++++++++ src/devices/block.h | 74 ++++ src/devices/disk.h | 26 -- src/devices/{disk.c => ide.c} | 310 ++++++-------- src/devices/ide.h | 6 + src/devices/partition.c | 324 +++++++++++++++ src/devices/partition.h | 8 + src/devices/shutdown.c | 4 +- src/filesys/Make.vars | 4 +- src/filesys/directory.c | 8 +- src/filesys/directory.h | 6 +- src/filesys/filesys.c | 13 +- src/filesys/filesys.h | 4 +- src/filesys/free-map.c | 12 +- src/filesys/free-map.h | 6 +- src/filesys/fsutil.c | 82 ++-- src/filesys/inode.c | 68 ++-- src/filesys/inode.h | 8 +- src/lib/kernel/list.h | 13 + src/lib/stdio.c | 18 + src/lib/stdio.h | 1 + src/tests/Make.tests | 8 +- src/tests/filesys/extended/Make.tests | 10 +- src/tests/userprog/Make.tests | 4 +- src/threads/Make.vars | 2 +- src/threads/init.c | 122 ++++-- src/threads/init.h | 3 - src/threads/kernel.lds.S | 12 +- src/threads/loader.S | 560 +++++++++++--------------- src/threads/loader.h | 22 +- src/threads/palloc.c | 9 +- src/threads/start.S | 213 +++++++++- src/userprog/Make.vars | 2 +- src/utils/Pintos.pm | 491 ++++++++++++++++++++++ src/utils/pintos | 459 ++++++++++----------- src/utils/pintos-mkdisk | 137 ++++++- src/utils/pintos-set-cmdline | 42 ++ src/vm/Make.vars | 2 +- 47 files changed, 2576 insertions(+), 1143 deletions(-) create mode 100644 src/devices/block.c create mode 100644 src/devices/block.h delete mode 100644 src/devices/disk.h rename src/devices/{disk.c => ide.c} (70%) create mode 100644 src/devices/ide.h create mode 100644 src/devices/partition.c create mode 100644 src/devices/partition.h create mode 100644 src/utils/Pintos.pm create mode 100644 src/utils/pintos-set-cmdline diff --git a/doc/bibliography.texi b/doc/bibliography.texi index e998a07..efdb05f 100644 --- a/doc/bibliography.texi +++ b/doc/bibliography.texi @@ -110,6 +110,14 @@ Interface---DRAFT---24 April 2001}. A draft of a revised version of The Open Group, @uref{http://www.unix.org/single_unix_specification/, , Single UNIX Specification V3}, 2001. +@bibdfn{Partitions} +A.@: E.@: Brouwer, @uref{specs/partitions/partition_tables.html, , +Minimal partition table specification}, 1999. + +@bibdfn{IntrList} +R.@: Brown, @uref{http://www.ctyme.com/rbrown.htm, , Ralf Brown's +Interrupt List}, 2000. + @node Operating System Design References @section Operating System Design References diff --git a/doc/filesys.texi b/doc/filesys.texi index eb2ba76..4c77d30 100644 --- a/doc/filesys.texi +++ b/doc/filesys.texi @@ -174,8 +174,9 @@ rationale for it in your design documentation, and as long as it does not suffer from external fragmentation (as does the extent-based file system we provide). -You can assume that the disk will not be larger than 8 MB. You must -support files as large as the disk (minus metadata). Each inode is +You can assume that the file system partition will not be larger than +8 MB. You must +support files as large as the partition (minus metadata). Each inode is stored in one disk sector, limiting the number of block pointers that it can contain. Supporting 8 MB files will require you to implement doubly-indirect blocks. @@ -189,7 +190,7 @@ every time a write is made off the end of the file. Your file system must allow this. There should be no predetermined limit on the size of a file, except -that a file cannot exceed the size of the disk (minus metadata). This +that a file cannot exceed the size of the file system (minus metadata). This also applies to the root directory file, which should now be allowed to expand beyond its initial limit of 16 files. @@ -456,10 +457,12 @@ modified by the reference solution. 30 files changed, 2721 insertions(+), 286 deletions(-) @end verbatim -@item Can @code{DISK_SECTOR_SIZE} change? +@item Can @code{BLOCk_SECTOR_SIZE} change? -No, @code{DISK_SECTOR_SIZE} is fixed at 512. This is a fixed property -of IDE disk hardware. +No, @code{BLOCK_SECTOR_SIZE} is fixed at 512. For IDE disks, this +value is a fixed property of the hardware. Other disks do not +necessarily have a 512-byte sector, but for simplicity Pintos only +supports those that do. @end table @menu @@ -474,9 +477,10 @@ of IDE disk hardware. @table @b @item What is the largest file size that we are supposed to support? -The disk we create will be 8 MB or smaller. However, individual files -will have to be smaller than the disk to accommodate the metadata. -You'll need to consider this when deciding your inode organization. +The file system partition we create will be 8 MB or smaller. However, +individual files will have to be smaller than the partition to +accommodate the metadata. You'll need to consider this when deciding +your inode organization. @end table @node Subdirectories FAQ diff --git a/doc/installation.texi b/doc/installation.texi index 0fc7b06..f6dfe44 100644 --- a/doc/installation.texi +++ b/doc/installation.texi @@ -79,8 +79,9 @@ described below (@pxref{Building Bochs for Pintos}). @item Install scripts from @file{src/utils}. Copy @file{backtrace}, -@file{pintos}, @file{pintos-gdb}, @file{pintos-mkdisk} into the -default @env{PATH}. +@file{pintos}, @file{pintos-gdb}, @file{pintos-mkdisk}, +@file{pintos-set-cmdline}, and @file{Pintos.pm} into the default +@env{PATH}. @item Install @file{src/misc/gdb-macros} in a public location. Then use a diff --git a/doc/intro.texi b/doc/intro.texi index 6601adc..ad5e286 100644 --- a/doc/intro.texi +++ b/doc/intro.texi @@ -161,21 +161,17 @@ single object file. It contains debug information, so you can run GDB (@pxref{GDB}) or @command{backtrace} (@pxref{Backtraces}) on it. @item kernel.bin -Memory image of the kernel. These are the exact bytes loaded into -memory to run the Pintos kernel. To simplify loading, it is always -padded out with zero bytes up to an exact multiple of 4 kB in -size. +Memory image of the kernel, that is, the exact bytes loaded into +memory to run the Pintos kernel. This is just @file{kernel.o} with +debug information stripped out, which saves a lot of space, which in +turn keeps the kernel from bumping up against a @w{512 kB} size limit +imposed by the kernel loader's design. @item loader.bin Memory image for the kernel loader, a small chunk of code written in assembly language that reads the kernel from disk into memory and starts it up. It is exactly 512 bytes long, a size fixed by the PC BIOS. - -@item os.dsk -Disk image for the kernel, which is just @file{loader.bin} followed by -@file{kernel.bin}. This file is used as a ``virtual disk'' by the -simulator. @end table Subdirectories of @file{build} contain object files (@file{.o}) and @@ -207,8 +203,7 @@ right corner, or rerun the whole process by clicking on the ``Reset'' button just to its left. The other buttons are not very useful for our purposes. -(If no window appeared at all, and you just got a terminal full of -corrupt-looking text, then you're probably logged in remotely and X +(If no window appeared at all, then you're probably logged in remotely and X forwarding is not set up correctly. In this case, you can fix your X setup, or you can use the @option{-v} option to disable X output: @code{pintos -v -- run alarm-multiple}.) @@ -513,10 +508,6 @@ The original structure and form of Pintos was inspired by the Nachos instructional operating system from the University of California, Berkeley (@bibref{Christopher}). -A few of the Pintos source files are derived from code used in the -Massachusetts Institute of Technology's 6.828 advanced operating systems -course. These files bear the original MIT license notice. - The Pintos projects and documentation originated with those designed for Nachos by current and former CS 140 teaching assistants at Stanford University, including at least Yu Ping, Greg Hutchins, Kelly Shaw, Paul diff --git a/doc/reference.texi b/doc/reference.texi index d442610..432bc86 100644 --- a/doc/reference.texi +++ b/doc/reference.texi @@ -1,13 +1,12 @@ @node Reference Guide @appendix Reference Guide -This chapter is a reference for the Pintos code. It covers the -entire code base, but you'll only be using Pintos one part at a time, -so you may find that you want to read each part as you work on the +This chapter is a reference for the Pintos code. The reference guide +does not cover all of the code in Pintos, but it does cover those +pieces that students most often find troublesome. You may find that +you want to read each part of the reference guide as you work on the project where it becomes important. -(Actually, the reference guide is currently incomplete.) - We recommend using ``tags'' to follow along with references to function and variable names (@pxref{Tags}). @@ -30,7 +29,9 @@ initialization. @menu * Pintos Loader:: -* Kernel Initialization:: +* Low-Level Kernel Initialization:: +* High-Level Kernel Initialization:: +* Physical Memory Map:: @end menu @node Pintos Loader @@ -38,78 +39,94 @@ initialization. The first part of Pintos that runs is the loader, in @file{threads/loader.S}. The PC BIOS loads the loader into memory. -The loader, in turn, is responsible for initializing the CPU, loading -the rest of Pintos into memory, and then jumping to its start. It's -not important to understand exactly what the loader does, but if +The loader, in turn, is responsible for finding the kernel on disk, +loading it into memory, and then jumping to its start. It's +not important to understand exactly how the loader works, but if you're interested, read on. You should probably read along with the loader's source. You should also understand the basics of the 80@var{x}86 architecture as described by chapter 3, ``Basic Execution Environment,'' of @bibref{IA32-v1}. -Because the PC BIOS loads the loader, the loader has to play by the -BIOS's rules. In particular, the BIOS only loads 512 bytes (one disk -sector) into memory. This is a severe restriction and it means that, -practically speaking, the loader has to be written in assembly -language. - -The Pintos loader first initializes the CPU. The first important part of -this is to enable the A20 line, that is, the CPU's address line -numbered 20. For historical reasons, PCs boot with this address -line fixed at 0, which means that attempts to access memory beyond the -first 1 MB (2 raised to the 20th power) will fail. Pintos wants to -access more memory than this, so we have to enable it. - -Next, the loader asks the BIOS for the PC's memory size. Again for -historical reasons, the function that we call in the BIOS to do this -can only detect up to 64 MB of RAM, so that's the practical limit that -Pintos can support. The memory size is stashed away in a location in -the loader that the kernel can read after it boots. - -Third, the loader creates a basic page table. This page table maps +The PC BIOS loads the loader from the first sector of the first hard +disk, called the @dfn{master boot record} (MBR). PC conventions +reserve 64 bytes of the MBR for the partition table, and Pintos uses +about 128 additional bytes for kernel command-line arguments. This +leaves a little over 300 bytes for the loader's own code. This is a +severe restriction that means, practically speaking, the loader must +be written in assembly language. + +The Pintos loader and kernel don't have to be on the same disk, nor +does is the kernel required to be in any particular location on a +given disk. The loader's first job, then, is to find the kernel by +reading the partition table on each hard disk, looking for a bootable +partition of the type used for a Pintos kernel. + +When the loader finds a bootable kernel partition, it reads the +partition's contents into memory at physical address @w{128 kB}. The +kernel is at the beginning of the partition, which might be larger +than necessary due to partition boundary alignment conventions, so the +loader reads no more than @w{512 kB} (and the Pintos build process +will refuse to produce kernels larger than that). Reading more data +than this would cross into the region from @w{640 kB} to @w{1 MB} that +the PC architecture reserves for hardware and the BIOS, and a standard +PC BIOS does not provide any means to load the kernel above @w{1 MB}. + +The loader's final job is to extract the entry point from the loaded +kernel image and transfer control to it. The entry point is not at a +predictable location, but the kernel's ELF header contains a pointer +to it. The loader extracts the pointer and jumps to the location it +points to. + +The Pintos kernel command line +is stored in the boot loader. The @command{pintos} program actually +modifies a copy of the boot loader on disk each time it runs the kernel, +inserting whatever command-line arguments the user supplies to the kernel, +and then the kernel at boot time reads those arguments out of the boot +loader in memory. This is not an elegant solution, but it is simple +and effective. + +@node Low-Level Kernel Initialization +@subsection Low-Level Kernel Initialization + +The loader's last action is to transfer control to the kernel's entry +point, which is @func{start} in @file{threads/start.S}. The job of +this code is to switch the CPU from legacy 16-bit ``real mode'' into +the 32-bit ``protected mode'' used by all modern 80@var{x}86 operating +systems. + +The startup code's first task is actually to obtain the machine's +memory size, by asking the BIOS for the PC's memory size. The +simplest BIOS function to do this can only detect up to 64 MB of RAM, +so that's the practical limit that Pintos can support. The function +stores the memory size, in pages, in global variable +@code{init_ram_pages}. + +The first part of CPU initialization is to enable the A20 line, that +is, the CPU's address line numbered 20. For historical reasons, PCs +boot with this address line fixed at 0, which means that attempts to +access memory beyond the first 1 MB (2 raised to the 20th power) will +fail. Pintos wants to access more memory than this, so we have to +enable it. + +Next, the loader creates a basic page table. This page table maps the 64 MB at the base of virtual memory (starting at virtual address 0) directly to the identical physical addresses. It also maps the same physical memory starting at virtual address @code{LOADER_PHYS_BASE}, which defaults to @t{0xc0000000} (3 GB). The Pintos kernel only wants the latter mapping, but there's a chicken-and-egg problem if we don't include the former: our current -virtual address is roughly @t{0x7c00}, the location where the BIOS -loaded us, and we can't jump to @t{0xc0007c00} until we turn on the +virtual address is roughly @t{0x20000}, the location where the loader +put us, and we can't jump to @t{0xc0020000} until we turn on the page table, but if we turn on the page table without jumping there, then we've just pulled the rug out from under ourselves. After the page table is initialized, we load the CPU's control -registers to turn on protected mode and paging, and then we set up the -segment registers. We aren't yet equipped to handle interrupts in -protected mode, so we disable interrupts. - -Finally it's time to load the kernel from disk. We use a simple but -inflexible method to do this: we program the IDE disk -controller directly. We assume that the kernel is stored starting -from the second sector of the first IDE disk (the first sector normally -contains the boot loader). We also assume that the BIOS has -already set up the IDE controller for us. We read -@code{KERNEL_LOAD_PAGES} pages of data (4 kB per page) from the disk directly -into virtual memory, starting @code{LOADER_KERN_BASE} bytes past -@code{LOADER_PHYS_BASE}, which by default means that we load the -kernel starting 1 MB into physical memory. - -Then we jump to the start of the compiled kernel image. Using the -``linker script'' in @file{threads/kernel.lds.S}, the kernel has -arranged to begin with the assembly module -@file{threads/start.S}. This assembly module just calls -@func{main}, which never returns. - -There's one more trick: the Pintos kernel command line -is stored in the boot loader. The @command{pintos} program actually -modifies a copy of the boot loader on disk each time it runs the kernel, -putting -in whatever command line arguments the user supplies to the kernel, -and then the kernel at boot time reads those arguments out of the boot -loader in memory. This is not an elegant solution, but it is simple -and effective. +registers to turn on protected mode and paging, and set up the segment +registers. We aren't yet equipped to handle interrupts in protected +mode, so we disable interrupts. The final step is to call @func{main}. -@node Kernel Initialization -@subsection Kernel Initialization +@node High-Level Kernel Initialization +@subsection High-Level Kernel Initialization The kernel proper starts with the @func{main} function. The @func{main} function is written in C, as will be most of the code we @@ -124,17 +141,14 @@ These are usually named @func{@var{module}_init}, where module's source code, and @file{@var{module}.h} is the module's header. -First we initialize kernel RAM in @func{ram_init}. The first step -is to clear out the kernel's so-called ``BSS'' segment. The BSS is a +The first step in @func{main} is to call @func{bss_init}, which clears +out the kernel's ``BSS'', which is the traditional name for a segment that should be initialized to all zeros. In most C implementations, whenever you declare a variable outside a function without providing an initializer, that variable goes into the BSS. Because it's all zeros, the BSS isn't stored in the image that the loader brought into memory. We -just use @func{memset} to zero it out. The other task of -@func{ram_init} is to read out the machine's memory size from where -the loader stored it and put it into the @code{init_ram_pages} variable for -later use. +just use @func{memset} to zero it out. Next, @func{main} calls @func{read_command_line} to break the kernel command line into arguments, then @func{parse_options} to read any options at @@ -179,7 +193,7 @@ possible, so we use @func{timer_calibrate} calibrates the timer for accurate short delays. If the file system is compiled in, as it will starting in project 2, we -initialize the disks with @func{disk_init}, then the +initialize the IDE disks with @func{ide_init}, then the file system with @func{filesys_init}. Boot is complete, so we print a message. @@ -193,6 +207,34 @@ call @func{power_off} to terminate the machine simulator. Otherwise, @func{main} calls @func{thread_exit}, which allows any other running threads to continue running. +@node Physical Memory Map +@subsection Physical Memory Map + +@multitable {@t{00000000}--@t{00000000}} {Hardware} {Some much longer explanatory text} +@headitem Memory Range +@tab Owner +@tab Contents + +@item @t{00000000}--@t{000003ff} @tab CPU @tab Real mode interrupt table. +@item @t{00000400}--@t{000005ff} @tab BIOS @tab Miscellaneous data area. +@item @t{00000600}--@t{00007bff} @tab --- @tab --- +@item @t{00007c00}--@t{00007dff} @tab Pintos @tab Loader. +@item @t{0000e000}--@t{0000efff} @tab Pintos +@tab Stack for loader; kernel stack and @struct{thread} for initial +kernel thread. +@item @t{0000f000}--@t{0000ffff} @tab Pintos +@tab Page directory for startup code. +@item @t{00010000}--@t{00020000} @tab Pintos +@tab Page tables for startup code. +@item @t{00020000}--@t{0009ffff} @tab Pintos +@tab Kernel code, data, and uninitialized data segments. +@item @t{000a0000}--@t{000bffff} @tab Video @tab VGA display memory. +@item @t{000c0000}--@t{000effff} @tab Hardware +@tab Reserved for expansion card RAM and ROM. +@item @t{000f0000}--@t{000fffff} @tab BIOS @tab ROM BIOS. +@item @t{00100000}--@t{03ffffff} @tab Pintos @tab Dynamic memory allocation. +@end multitable + @node Threads @section Threads @@ -556,7 +598,7 @@ synchronization primitives to help out. * Semaphores:: * Locks:: * Monitors:: -* Optimization Barriers:: +* Optimization Barriers:: @end menu @node Disabling Interrupts @@ -1236,7 +1278,8 @@ at once. The page allocator divides the memory it allocates into two pools, called the kernel and user pools. By default, each pool gets half of -system memory, but this can be changed with the @option{-ul} kernel +system memory above @w{1 MB}, but the division can be changed with the +@option{-ul} kernel command line option (@pxref{Why PAL_USER?}). An allocation request draws from one pool or the other. If one pool becomes empty, the other may still diff --git a/doc/threads.texi b/doc/threads.texi index d90b438..c1f3bfc 100644 --- a/doc/threads.texi +++ b/doc/threads.texi @@ -113,27 +113,30 @@ code to look at. @item loader.S @itemx loader.h The kernel loader. Assembles to 512 bytes of code and data that the -PC BIOS loads into memory and which in turn loads the kernel into -memory, does basic processor initialization, and jumps to the -beginning of the kernel. @xref{Pintos Loader}, for details. You should -not need to look at this code or modify it. +PC BIOS loads into memory and which in turn finds the kernel on disk, +loads it into memory, and jumps to @func{start} in @file{start.S}. +@xref{Pintos Loader}, for details. You should not need to look at +this code or modify it. + +@item start.S +Does basic setup needed for memory protection and 32-bit +operation on 80@var{x}86 CPUs. Unlike the loader, this code is +actually part of the kernel. @xref{Low-Level Kernel Initialization}, +for details. @item kernel.lds.S The linker script used to link the kernel. Sets the load address of -the kernel and arranges for @file{start.S} to be at the very beginning +the kernel and arranges for @file{start.S} to be near the beginning of the kernel image. @xref{Pintos Loader}, for details. Again, you should not need to look at this code or modify it, but it's here in case you're curious. -@item start.S -Jumps to @func{main}. - @item init.c @itemx init.h Kernel initialization, including @func{main}, the kernel's ``main program.'' You should look over @func{main} at least to see what gets initialized. You might want to add your own initialization code -here. @xref{Kernel Initialization}, for details. +here. @xref{High-Level Kernel Initialization}, for details. @item thread.c @itemx thread.h @@ -220,10 +223,23 @@ Serial port driver. Again, @func{printf} calls this code for you, so you don't need to do so yourself. It handles serial input by passing it to the input layer (see below). -@item disk.c -@itemx disk.h -Supports reading and writing sectors on up to 4 IDE disks. This won't -actually be used until project 2. +@item block.c +@itemx block.h +An abstraction layer for @dfn{block devices}, that is, random-access, +disk-like devices that are organized as arrays of fixed-size blocks. +Out of the box, Pintos supports two types of block devices: IDE disks +and partitions. Block devices, regardless of type, won't actually be +used until project 2. + +@item ide.c +@itemx ide.h +Supports reading and writing sectors on up to 4 IDE disks. + +@item partition.c +@itemx partition.h +Understands the structure of partitions on disks, allowing a single +disk to be carved up into multiple regions (partitions) for +independent use. @item kbd.c @itemx kbd.h diff --git a/doc/userprog.texi b/doc/userprog.texi index 9d80e37..cf01158 100644 --- a/doc/userprog.texi +++ b/doc/userprog.texi @@ -183,14 +183,16 @@ threads that have it open, until the last one closes it. @xref{Removing an Open File}, for more information. @end itemize -You need to be able to create simulated disks. The -@command{pintos-mkdisk} program provides this functionality. From the -@file{userprog/build} directory, execute @code{pintos-mkdisk fs.dsk@tie{}2}. -This command creates a 2 MB simulated disk named @file{fs.dsk}. Then -format the disk by passing @option{-f -q} on the kernel's command -line: @code{pintos -f -q}. The @option{-f} option causes the disk to be -formatted, and @option{-q} causes Pintos to exit as soon as the format -is done. +You need to be able to create a simulated disk with a file system +partition. The @command{pintos-mkdisk} program provides this +functionality. From the @file{userprog/build} directory, execute +@code{pintos-mkdisk filesys.dsk --filesys-size=2}. This command +creates a simulated disk named @file{filesys.dsk} that contains a @w{2 +MB} Pintos file system partition. Then format the file system +partition by passing @option{-f -q} on the kernel's command line: +@code{pintos -f -q}. The @option{-f} option causes the file system to +be formatted, and @option{-q} causes Pintos to exit as soon as the +format is done. You'll need a way to copy files in and out of the simulated file system. The @code{pintos} @option{-p} (``put'') and @option{-g} (``get'') @@ -205,20 +207,19 @@ commands for copying files out of a VM are similar, but substitute Incidentally, these commands work by passing special commands @command{extract} and @command{append} on the kernel's command line and copying -to and from a special simulated ``scratch'' disk. If you're very +to and from a special simulated ``scratch'' partition. If you're very curious, you can look at the @command{pintos} script as well as @file{filesys/fsutil.c} to learn the implementation details. -Here's a summary of how to create and format a disk, copy the -@command{echo} program into the new disk, and then run @command{echo}, -passing argument @code{x}. (Argument passing won't work until -you implemented it.) It assumes -that you've already built the -examples in @file{examples} and that the current directory is -@file{userprog/build}: +Here's a summary of how to create a disk with a file system partition, +format the file system, copy the @command{echo} program into the new +disk, and then run @command{echo}, passing argument @code{x}. +(Argument passing won't work until you implemented it.) It assumes +that you've already built the examples in @file{examples} and that the +current directory is @file{userprog/build}: @example -pintos-mkdisk fs.dsk 2 +pintos-mkdisk filesys.dsk --filesys-size=2 pintos -f -q pintos -p ../../examples/echo -a echo -- -q pintos -q run 'echo x' @@ -227,19 +228,20 @@ pintos -q run 'echo x' The three final steps can actually be combined into a single command: @example -pintos-mkdisk fs.dsk 2 +pintos-mkdisk filesys.dsk --filesys-size=2 pintos -p ../../examples/echo -a echo -- -f -q run 'echo x' @end example If you don't want to keep the file system disk around for later use or inspection, you can even combine all four steps into a single command. -The @code{--fs-disk=@var{n}} option creates a temporary disk +The @code{--filesys-size=@var{n}} option creates a temporary file +system partition approximately @var{n} megabytes in size just for the duration of the @command{pintos} run. The Pintos automatic test suite makes extensive use of this syntax: @example -pintos --fs-disk=2 -p ../../examples/echo -a echo -- -f -q run 'echo x' +pintos --filesys-size=2 -p ../../examples/echo -a echo -- -f -q run 'echo x' @end example You can delete a file from the Pintos file system using the @code{rm @@ -272,11 +274,11 @@ for Pintos. (We've provided compilers and linkers that should do just fine.) You should realize immediately that, until you copy a -test program to the simulated disk, Pintos will be unable to do +test program to the simulated file system, Pintos will be unable to do useful work. You won't be able to do -interesting things until you copy a variety of programs to the disk. -You might want to create a clean reference disk and copy that -over whenever you trash your @file{fs.dsk} beyond a useful state, +interesting things until you copy a variety of programs to the file system. +You might want to create a clean reference file system disk and copy that +over whenever you trash your @file{filesys.dsk} beyond a useful state, which may happen occasionally while debugging. @node Virtual Memory Layout @@ -852,7 +854,7 @@ modified by the reference solution. @item The kernel always panics when I run @code{pintos -p @var{file} -- -q}. -Did you format the disk (with @samp{pintos -f})? +Did you format the file system (with @samp{pintos -f})? Is your file name too long? The file system limits file names to 14 characters. A command like @samp{pintos -p ../../examples/echo -- -q} @@ -873,6 +875,8 @@ Files are written under the name you refer to them, by default, so in this case the file copied in would be named @file{../file}. You probably want to run @code{pintos -p ../file -a file --} instead. +You can list the files in your file system with @code{pintos -q ls}. + @item All my user programs die with page faults. This will happen if you haven't implemented argument passing diff --git a/doc/vm.texi b/doc/vm.texi index fdbc5c6..ab64a4d 100644 --- a/doc/vm.texi +++ b/doc/vm.texi @@ -49,10 +49,10 @@ files or in files introduced in earlier projects. You will probably be encountering just a few files for the first time: @table @file -@item devices/disk.h -@itemx devices/disk.c -Provides access to the physical disk, abstracting away the rather awful -IDE interface. You will use this interface to access the swap disk. +@item devices/block.h +@itemx devices/block.c +Provides sector-based read and write access to block device. You will +use this interface to access the swap partition as a block device. @end table @node Memory Terminology @@ -162,8 +162,8 @@ address, on the right. @node Swap Slots @subsubsection Swap Slots -A @dfn{swap slot} is a continuous, page-size region of disk space on the -swap disk. Although hardware limitations dictating the placement of +A @dfn{swap slot} is a continuous, page-size region of disk space in the +swap partition. Although hardware limitations dictating the placement of slots are looser than for pages and frames, swap slots should be page-aligned because there is no downside in doing so. @@ -377,17 +377,19 @@ to work with accessed and dirty bits. The swap table tracks in-use and free swap slots. It should allow picking an unused swap slot for evicting a page from its frame to the -swap disk. It should allow freeing a swap slot when its page is read +swap partition. It should allow freeing a swap slot when its page is read back or the process whose page was swapped is terminated. -You may use the disk on interface @code{hd1:1} as the swap disk, using -the disk interface prototyped in @code{devices/disk.h}. From the +You may use the @code{BLOCK_SWAP} block device for swapping, obtaining +the @struct{block} that represents it by calling @func{block_get_role}. +From the @file{vm/build} directory, use the command @code{pintos-mkdisk swap.dsk -@var{n}} to create an @var{n} MB swap disk named @file{swap.dsk}. -Afterward, @file{swap.dsk} will automatically be attached as -@code{hd1:1} when you run @command{pintos}. Alternatively, you can tell +--swap-size=@var{n}} to create an disk named @file{swap.dsk} that +contains a @var{n}-MB swap partition. +Afterward, @file{swap.dsk} will automatically be attached as an extra disk +when you run @command{pintos}. Alternatively, you can tell @command{pintos} to use a temporary @var{n}-MB swap disk for a single -run with @option{--swap-disk=@var{n}}. +run with @option{--swap-size=@var{n}}. Swap slots should be allocated lazily, that is, only when they are actually required by eviction. Reading data pages from the executable @@ -485,7 +487,7 @@ little as possible about how to do things. Instead we will focus on what functionality we require your OS to support. We will expect you to come up with a design that makes sense. You will have the freedom to choose how to handle page faults, how to organize the swap -disk, how to implement paging, etc. +partition, how to implement paging, etc. @menu * Project 3 Design Document:: @@ -534,7 +536,7 @@ variables' values: @itemize @bullet @item If @code{page_read_bytes} equals @code{PGSIZE}, the page should be demand -paged from disk on its first access. +paged from the underlying file on its first access. @item If @code{page_zero_bytes} equals @code{PGSIZE}, the page does not need to @@ -545,7 +547,7 @@ first page fault. @item Otherwise, neither @code{page_read_bytes} nor @code{page_zero_bytes} equals @code{PGSIZE}. In this case, an initial part of the page is to -be read from disk and the remainder zeroed. +be read from the underlying file and the remainder zeroed. @end itemize @node Stack Growth @@ -619,7 +621,8 @@ mapped from. If the file's length is not a multiple of @code{PGSIZE}, then some bytes in the final mapped page ``stick out'' beyond the end of the -file. Set these bytes to zero when the page is faulted in from disk, +file. Set these bytes to zero when the page is faulted in from the +file system, and discard them when the page is written back to disk. If successful, this function returns a ``mapping ID'' that @@ -739,7 +742,7 @@ kernel functions need to obtain memory. You can layer some other allocator on top of @func{palloc_get_page} if you like, but it should be the underlying mechanism. -Also, you can use the @option{-ul} option to @command{pintos} to limit +Also, you can use the @option{-ul} kernel command-line option to limit the size of the user pool, which makes it easy to test your VM implementation with various user memory sizes. @end table diff --git a/src/Makefile.build b/src/Makefile.build index b733205..e997d27 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -2,17 +2,18 @@ SRCDIR = ../.. -all: os.dsk +all: kernel.bin loader.bin include ../../Make.config include ../Make.vars include ../../tests/Make.tests # Compiler and assembler options. -os.dsk: CPPFLAGS += -I$(SRCDIR)/lib/kernel +kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel # Core kernel. -threads_SRC = threads/init.c # Main program. +threads_SRC = threads/start.S # Startup code. +threads_SRC += threads/init.c # Main program. threads_SRC += threads/thread.c # Thread management core. threads_SRC += threads/switch.S # Thread switch routine. threads_SRC += threads/interrupt.c # Interrupt core. @@ -20,7 +21,6 @@ threads_SRC += threads/intr-stubs.S # Interrupt stubs. threads_SRC += threads/synch.c # Synchronization. threads_SRC += threads/palloc.c # Page allocator. threads_SRC += threads/malloc.c # Subpage allocator. -threads_SRC += threads/start.S # Startup code. # Device driver code. devices_SRC = devices/pit.c # Programmable interrupt timer chip. @@ -28,7 +28,9 @@ devices_SRC += devices/timer.c # Periodic timer device. devices_SRC += devices/kbd.c # Keyboard device. devices_SRC += devices/vga.c # Video device. devices_SRC += devices/serial.c # Serial port device. -devices_SRC += devices/disk.c # IDE disk device. +devices_SRC += devices/block.c # Block device abstraction layer. +devices_SRC += devices/partition.c # Partition block device. +devices_SRC += devices/ide.c # IDE disk block device. devices_SRC += devices/input.c # Serial and keyboard input. devices_SRC += devices/intq.c # Interrupt queue. devices_SRC += devices/rtc.c # Real-time clock. @@ -81,24 +83,23 @@ kernel.o: threads/kernel.lds.s $(OBJECTS) $(LD) -T $< -o $@ $(OBJECTS) kernel.bin: kernel.o - $(OBJCOPY) -O binary -R .note -R .comment -S $< $@.tmp - dd if=$@.tmp of=$@ bs=4096 conv=sync - rm $@.tmp + $(OBJCOPY) -R .note -R .comment -S $< $@ -threads/loader.o: threads/loader.S kernel.bin - $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) -DKERNEL_LOAD_PAGES=`perl -e 'print +(-s "kernel.bin") / 4096;'` +threads/loader.o: threads/loader.S + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) loader.bin: threads/loader.o - $(LD) -N -e start -Ttext 0x7c00 --oformat binary -o $@ $< + $(LD) -N -e 0 -Ttext 0x7c00 --oformat binary -o $@ $< -os.dsk: loader.bin kernel.bin +os.dsk: kernel.bin cat $^ > $@ clean:: rm -f $(OBJECTS) $(DEPENDS) rm -f threads/loader.o threads/kernel.lds.s threads/loader.d + rm -f kernel.bin.tmp rm -f kernel.o kernel.lds.s - rm -f kernel.bin loader.bin os.dsk + rm -f kernel.bin loader.bin rm -f bochsout.txt bochsrc.txt rm -f results grade diff --git a/src/devices/block.c b/src/devices/block.c new file mode 100644 index 0000000..452c433 --- /dev/null +++ b/src/devices/block.c @@ -0,0 +1,223 @@ +#include "devices/block.h" +#include +#include +#include +#include "devices/ide.h" +#include "threads/malloc.h" + +/* A block device. */ +struct block + { + struct list_elem list_elem; /* Element in all_blocks. */ + + char name[16]; /* Block device name. */ + enum block_type type; /* Type of block device. */ + block_sector_t size; /* Size in sectors. */ + + const struct block_operations *ops; /* Driver operations. */ + void *aux; /* Extra data owned by driver. */ + + unsigned long long read_cnt; /* Number of sectors read. */ + unsigned long long write_cnt; /* Number of sectors written. */ + }; + +/* List of all block devices. */ +static struct list all_blocks = LIST_INITIALIZER (all_blocks); + +/* The block block assigned to each Pintos role. */ +static struct block *block_by_role[BLOCK_ROLE_CNT]; + +static struct block *list_elem_to_block (struct list_elem *); + +/* Returns a human-readable name for the given block device + TYPE. */ +const char * +block_type_name (enum block_type type) +{ + static const char *block_type_names[BLOCK_CNT] = + { + "kernel", + "filesys", + "scratch", + "swap", + "raw", + "foreign", + }; + + ASSERT (type < BLOCK_CNT); + return block_type_names[type]; +} + +/* Returns the block device fulfilling the given ROLE, or a null + pointer if no block device has been assigned that role. */ +struct block * +block_get_role (enum block_type role) +{ + ASSERT (role < BLOCK_ROLE_CNT); + return block_by_role[role]; +} + +/* Assigns BLOCK the given ROLE. */ +void +block_set_role (enum block_type role, struct block *block) +{ + ASSERT (role < BLOCK_ROLE_CNT); + block_by_role[role] = block; +} + +/* Returns the first block device in kernel probe order, or a + null pointer if no block devices are registered. */ +struct block * +block_first (void) +{ + return list_elem_to_block (list_begin (&all_blocks)); +} + +/* Returns the block device following BLOCK in kernel probe + order, or a null pointer if BLOCK is the last block device. */ +struct block * +block_next (struct block *block) +{ + return list_elem_to_block (list_next (&block->list_elem)); +} + +/* Returns the block device with the given NAME, or a null + pointer if no block device has that name. */ +struct block * +block_get_by_name (const char *name) +{ + struct list_elem *e; + + for (e = list_begin (&all_blocks); e != list_end (&all_blocks); + e = list_next (e)) + { + struct block *block = list_entry (e, struct block, list_elem); + if (!strcmp (name, block->name)) + return block; + } + + return NULL; +} + +/* Verifies that SECTOR is a valid offset within BLOCK. + Panics if not. */ +static void +check_sector (struct block *block, block_sector_t sector) +{ + if (sector >= block->size) + { + /* We do not use ASSERT because we want to panic here + regardless of whether NDEBUG is defined. */ + PANIC ("Access past end of device %s (sector=%"PRDSNu", " + "size=%"PRDSNu")\n", block_name (block), sector, block->size); + } +} + +/* Reads sector SECTOR from BLOCK into BUFFER, which must + have room for BLOCK_SECTOR_SIZE bytes. + Internally synchronizes accesses to block devices, so external + per-block device locking is unneeded. */ +void +block_read (struct block *block, block_sector_t sector, void *buffer) +{ + check_sector (block, sector); + block->ops->read (block->aux, sector, buffer); + block->read_cnt++; +} + +/* Write sector SECTOR to BLOCK from BUFFER, which must contain + BLOCK_SECTOR_SIZE bytes. Returns after the block device has + acknowledged receiving the data. + Internally synchronizes accesses to block devices, so external + per-block device locking is unneeded. */ +void +block_write (struct block *block, block_sector_t sector, const void *buffer) +{ + check_sector (block, sector); + ASSERT (block->type != BLOCK_FOREIGN); + block->ops->write (block->aux, sector, buffer); + block->write_cnt++; +} + +/* Returns the number of sectors in BLOCK. */ +block_sector_t +block_size (struct block *block) +{ + return block->size; +} + +/* Returns BLOCK's name (e.g. "hda"). */ +const char * +block_name (struct block *block) +{ + return block->name; +} + +/* Returns BLOCK's type. */ +enum block_type +block_type (struct block *block) +{ + return block->type; +} + +/* Prints statistics for each block device used for a Pintos role. */ +void +block_print_stats (void) +{ + int i; + + for (i = 0; i < BLOCK_CNT; i++) + { + struct block *block = block_by_role[i]; + if (block != NULL) + { + printf ("%s (%s): %llu reads, %llu writes\n", + block->name, block_type_name (block->type), + block->read_cnt, block->write_cnt); + } + } +} + +/* Registers a new block device with the given NAME. If + EXTRA_INFO is non-null, it is printed as part of a user + message. The block device's SIZE in sectors and its TYPE must + be provided, as well as the it operation functions OPS, which + will be passed AUX in each function call. */ +struct block * +block_register (const char *name, enum block_type type, + const char *extra_info, block_sector_t size, + const struct block_operations *ops, void *aux) +{ + struct block *block = malloc (sizeof *block); + if (block == NULL) + PANIC ("Failed to allocate memory for block device descriptor"); + + list_push_back (&all_blocks, &block->list_elem); + strlcpy (block->name, name, sizeof block->name); + block->type = type; + block->size = size; + block->ops = ops; + block->aux = aux; + block->read_cnt = 0; + block->write_cnt = 0; + + printf ("%s: %'"PRDSNu" sectors (", block->name, block->size); + print_human_readable_size ((uint64_t) block->size * BLOCK_SECTOR_SIZE); + printf (")"); + if (extra_info != NULL) + printf (", %s", extra_info); + printf ("\n"); + + return block; +} + +/* Returns the block device corresponding to LIST_ELEM, or a null + pointer if LIST_ELEM is the list end of all_blocks. */ +static struct block * +list_elem_to_block (struct list_elem *list_elem) +{ + return (list_elem != list_end (&all_blocks) + ? list_entry (list_elem, struct block, list_elem) + : NULL); +} + diff --git a/src/devices/block.h b/src/devices/block.h new file mode 100644 index 0000000..21732d6 --- /dev/null +++ b/src/devices/block.h @@ -0,0 +1,74 @@ +#ifndef DEVICES_BLOCK_H +#define DEVICES_BLOCK_H + +#include +#include + +/* Size of a block device sector in bytes. + All IDE disks use this sector size, as do most USB and SCSI + disks. It's not worth it to try to cater to other sector + sizes in Pintos (yet). */ +#define BLOCK_SECTOR_SIZE 512 + +/* Index of a block device sector. + Good enough for devices up to 2 TB. */ +typedef uint32_t block_sector_t; + +/* Format specifier for printf(), e.g.: + printf ("sector=%"PRDSNu"\n", sector); */ +#define PRDSNu PRIu32 + +/* Higher-level interface for file systems, etc. */ + +struct block; + +/* Type of a block device. */ +enum block_type + { + /* Block device types that play a role in Pintos. */ + BLOCK_KERNEL, /* Pintos OS kernel. */ + BLOCK_FILESYS, /* File system. */ + BLOCK_SCRATCH, /* Scratch. */ + BLOCK_SWAP, /* Swap. */ + BLOCK_ROLE_CNT, + + /* Other kinds of block devices that Pintos may see but does + not interact with. */ + BLOCK_RAW = BLOCK_ROLE_CNT, /* "Raw" device with unidentified contents. */ + BLOCK_FOREIGN, /* Owned by non-Pintos operating system. */ + BLOCK_CNT /* Number of Pintos block types. */ + }; + +const char *block_type_name (enum block_type); + +/* Finding block devices. */ +struct block *block_get_role (enum block_type); +void block_set_role (enum block_type, struct block *); +struct block *block_get_by_name (const char *name); + +struct block *block_first (void); +struct block *block_next (struct block *); + +/* Block device operations. */ +block_sector_t block_size (struct block *); +void block_read (struct block *, block_sector_t, void *); +void block_write (struct block *, block_sector_t, const void *); +const char *block_name (struct block *); +enum block_type block_type (struct block *); + +/* Statistics. */ +void block_print_stats (void); + +/* Lower-level interface to block device drivers. */ + +struct block_operations + { + void (*read) (void *aux, block_sector_t, void *buffer); + void (*write) (void *aux, block_sector_t, const void *buffer); + }; + +struct block *block_register (const char *name, enum block_type, + const char *extra_info, block_sector_t size, + const struct block_operations *, void *aux); + +#endif /* devices/block.h */ diff --git a/src/devices/disk.h b/src/devices/disk.h deleted file mode 100644 index 3bcbb9a..0000000 --- a/src/devices/disk.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef DEVICES_DISK_H -#define DEVICES_DISK_H - -#include -#include - -/* Size of a disk sector in bytes. */ -#define DISK_SECTOR_SIZE 512 - -/* Index of a disk sector within a disk. - Good enough for disks up to 2 TB. */ -typedef uint32_t disk_sector_t; - -/* Format specifier for printf(), e.g.: - printf ("sector=%"PRDSNu"\n", sector); */ -#define PRDSNu PRIu32 - -void disk_init (void); -void disk_print_stats (void); - -struct disk *disk_get (int chan_no, int dev_no); -disk_sector_t disk_size (struct disk *); -void disk_read (struct disk *, disk_sector_t, void *); -void disk_write (struct disk *, disk_sector_t, const void *); - -#endif /* devices/disk.h */ diff --git a/src/devices/disk.c b/src/devices/ide.c similarity index 70% rename from src/devices/disk.c rename to src/devices/ide.c index 14fc631..2cc0292 100644 --- a/src/devices/disk.c +++ b/src/devices/ide.c @@ -1,8 +1,10 @@ -#include "devices/disk.h" +#include "devices/ide.h" #include #include #include #include +#include "devices/block.h" +#include "devices/partition.h" #include "devices/timer.h" #include "threads/io.h" #include "threads/interrupt.h" @@ -49,24 +51,19 @@ #define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */ /* An ATA device. */ -struct disk +struct ata_disk { - char name[8]; /* Name, e.g. "hd0:1". */ - struct channel *channel; /* Channel disk is on. */ + char name[8]; /* Name, e.g. "hda". */ + struct channel *channel; /* Channel that disk is attached to. */ int dev_no; /* Device 0 or 1 for master or slave. */ - - bool is_ata; /* 1=This device is an ATA disk. */ - disk_sector_t capacity; /* Capacity in sectors (if is_ata). */ - - long long read_cnt; /* Number of sectors read. */ - long long write_cnt; /* Number of sectors written. */ + bool is_ata; /* Is device an ATA disk? */ }; /* An ATA channel (aka controller). Each channel can control up to two disks. */ -struct channel +struct channel { - char name[8]; /* Name, e.g. "hd0". */ + char name[8]; /* Name, e.g. "ide0". */ uint16_t reg_base; /* Base I/O port. */ uint8_t irq; /* Interrupt in use. */ @@ -75,32 +72,34 @@ struct channel any interrupt would be spurious. */ struct semaphore completion_wait; /* Up'd by interrupt handler. */ - struct disk devices[2]; /* The devices on this channel. */ + struct ata_disk devices[2]; /* The devices on this channel. */ }; /* We support the two "legacy" ATA channels found in a standard PC. */ #define CHANNEL_CNT 2 static struct channel channels[CHANNEL_CNT]; +static struct block_operations ide_operations; + static void reset_channel (struct channel *); -static bool check_device_type (struct disk *); -static void identify_ata_device (struct disk *); +static bool check_device_type (struct ata_disk *); +static void identify_ata_device (struct ata_disk *); -static void select_sector (struct disk *, disk_sector_t); +static void select_sector (struct ata_disk *, block_sector_t); static void issue_pio_command (struct channel *, uint8_t command); static void input_sector (struct channel *, void *); static void output_sector (struct channel *, const void *); -static void wait_until_idle (const struct disk *); -static bool wait_while_busy (const struct disk *); -static void select_device (const struct disk *); -static void select_device_wait (const struct disk *); +static void wait_until_idle (const struct ata_disk *); +static bool wait_while_busy (const struct ata_disk *); +static void select_device (const struct ata_disk *); +static void select_device_wait (const struct ata_disk *); static void interrupt_handler (struct intr_frame *); /* Initialize the disk subsystem and detect disks. */ void -disk_init (void) +ide_init (void) { size_t chan_no; @@ -110,7 +109,7 @@ disk_init (void) int dev_no; /* Initialize channel. */ - snprintf (c->name, sizeof c->name, "hd%zu", chan_no); + snprintf (c->name, sizeof c->name, "ide%zu", chan_no); switch (chan_no) { case 0: @@ -131,15 +130,12 @@ disk_init (void) /* Initialize devices. */ for (dev_no = 0; dev_no < 2; dev_no++) { - struct disk *d = &c->devices[dev_no]; - snprintf (d->name, sizeof d->name, "%s:%d", c->name, dev_no); + struct ata_disk *d = &c->devices[dev_no]; + snprintf (d->name, sizeof d->name, + "hd%c", 'a' + chan_no * 2 + dev_no); d->channel = c; d->dev_no = dev_no; - d->is_ata = false; - d->capacity = 0; - - d->read_cnt = d->write_cnt = 0; } /* Register interrupt handler. */ @@ -158,112 +154,10 @@ disk_init (void) identify_ata_device (&c->devices[dev_no]); } } - -/* Prints disk statistics. */ -void -disk_print_stats (void) -{ - int chan_no; - - for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) - { - int dev_no; - - for (dev_no = 0; dev_no < 2; dev_no++) - { - struct disk *d = disk_get (chan_no, dev_no); - if (d != NULL && d->is_ata) - printf ("%s: %lld reads, %lld writes\n", - d->name, d->read_cnt, d->write_cnt); - } - } -} - -/* Returns the disk numbered DEV_NO--either 0 or 1 for master or - slave, respectively--within the channel numbered CHAN_NO. - - Pintos uses disks this way: - 0:0 - boot loader, command line args, and operating system kernel - 0:1 - file system - 1:0 - scratch - 1:1 - swap -*/ -struct disk * -disk_get (int chan_no, int dev_no) -{ - ASSERT (dev_no == 0 || dev_no == 1); - - if (chan_no < (int) CHANNEL_CNT) - { - struct disk *d = &channels[chan_no].devices[dev_no]; - if (d->is_ata) - return d; - } - return NULL; -} - -/* Returns the size of disk D, measured in DISK_SECTOR_SIZE-byte - sectors. */ -disk_sector_t -disk_size (struct disk *d) -{ - ASSERT (d != NULL); - - return d->capacity; -} - -/* Reads sector SEC_NO from disk D into BUFFER, which must have - room for DISK_SECTOR_SIZE bytes. - Internally synchronizes accesses to disks, so external - per-disk locking is unneeded. */ -void -disk_read (struct disk *d, disk_sector_t sec_no, void *buffer) -{ - struct channel *c; - - ASSERT (d != NULL); - ASSERT (buffer != NULL); - - c = d->channel; - lock_acquire (&c->lock); - select_sector (d, sec_no); - issue_pio_command (c, CMD_READ_SECTOR_RETRY); - sema_down (&c->completion_wait); - if (!wait_while_busy (d)) - PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no); - input_sector (c, buffer); - d->read_cnt++; - lock_release (&c->lock); -} - -/* Write sector SEC_NO to disk D from BUFFER, which must contain - DISK_SECTOR_SIZE bytes. Returns after the disk has - acknowledged receiving the data. - Internally synchronizes accesses to disks, so external - per-disk locking is unneeded. */ -void -disk_write (struct disk *d, disk_sector_t sec_no, const void *buffer) -{ - struct channel *c; - - ASSERT (d != NULL); - ASSERT (buffer != NULL); - - c = d->channel; - lock_acquire (&c->lock); - select_sector (d, sec_no); - issue_pio_command (c, CMD_WRITE_SECTOR_RETRY); - if (!wait_while_busy (d)) - PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no); - output_sector (c, buffer); - sema_down (&c->completion_wait); - d->write_cnt++; - lock_release (&c->lock); -} /* Disk detection and identification. */ -static void print_ata_string (char *string, size_t size); +static char *descramble_ata_string (char *, int size); /* Resets an ATA channel and waits for any devices present on it to finish the reset. */ @@ -277,7 +171,7 @@ reset_channel (struct channel *c) so we start by detecting device presence. */ for (dev_no = 0; dev_no < 2; dev_no++) { - struct disk *d = &c->devices[dev_no]; + struct ata_disk *d = &c->devices[dev_no]; select_device (d); @@ -333,7 +227,7 @@ reset_channel (struct channel *c) channel. If D is device 1 (slave), the return value is not meaningful. */ static bool -check_device_type (struct disk *d) +check_device_type (struct ata_disk *d) { struct channel *c = d->channel; uint8_t error, lbam, lbah, status; @@ -360,13 +254,17 @@ check_device_type (struct disk *d) } /* Sends an IDENTIFY DEVICE command to disk D and reads the - response. Initializes D's capacity member based on the result - and prints a message describing the disk to the console. */ + response. Registers the disk with the block device + layer. */ static void -identify_ata_device (struct disk *d) +identify_ata_device (struct ata_disk *d) { struct channel *c = d->channel; - uint16_t id[DISK_SECTOR_SIZE / 2]; + char id[BLOCK_SECTOR_SIZE]; + block_sector_t capacity; + char *model, *serial; + char extra_info[128]; + struct block *block; ASSERT (d->is_ata); @@ -383,57 +281,115 @@ identify_ata_device (struct disk *d) } input_sector (c, id); - /* Calculate capacity. */ - d->capacity = id[60] | ((uint32_t) id[61] << 16); - - /* Print identification message. */ - printf ("%s: detected %'"PRDSNu" sector (", d->name, d->capacity); - if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024 * 1024) - printf ("%"PRDSNu" GB", - d->capacity / (1024 / DISK_SECTOR_SIZE * 1024 * 1024)); - else if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024) - printf ("%"PRDSNu" MB", d->capacity / (1024 / DISK_SECTOR_SIZE * 1024)); - else if (d->capacity > 1024 / DISK_SECTOR_SIZE) - printf ("%"PRDSNu" kB", d->capacity / (1024 / DISK_SECTOR_SIZE)); - else - printf ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE); - printf (") disk, model \""); - print_ata_string ((char *) &id[27], 40); - printf ("\", serial \""); - print_ata_string ((char *) &id[10], 20); - printf ("\"\n"); + /* Calculate capacity. + Read model name and serial number. */ + capacity = *(uint32_t *) &id[60 * 2]; + model = descramble_ata_string (&id[10 * 2], 20); + serial = descramble_ata_string (&id[27 * 2], 40); + snprintf (extra_info, sizeof extra_info, + "model \"%s\", serial \"%s\"", model, serial); + + /* Disable access to IDE disks over 1 GB, which are likely + physical IDE disks rather than virtual ones. If we don't + allow access to those, we're less likely to scribble on + someone's important data. You can disable this check by + hand if you really want to do so. */ + if (capacity >= 1024 * 1024 * 1024 / BLOCK_SECTOR_SIZE) + { + printf ("%s: ignoring ", d->name); + print_human_readable_size (capacity * 512); + printf ("disk for safety\n"); + d->is_ata = false; + return; + } + + /* Register. */ + block = block_register (d->name, BLOCK_RAW, extra_info, capacity, + &ide_operations, d); + partition_scan (block); } -/* Prints STRING, which consists of SIZE bytes in a funky format: - each pair of bytes is in reverse order. Does not print - trailing whitespace and/or nulls. */ -static void -print_ata_string (char *string, size_t size) +/* Translates STRING, which consists of SIZE bytes in a funky + format, into a null-terminated string in-place. Drops + trailing whitespace and null bytes. Returns STRING. */ +static char * +descramble_ata_string (char *string, int size) { - size_t i; + int i; + + /* Swap all pairs of bytes. */ + for (i = 0; i + 1 < size; i += 2) + { + char tmp = string[i]; + string[i] = string[i + 1]; + string[i + 1] = tmp; + } /* Find the last non-white, non-null character. */ - for (; size > 0; size--) + for (size--; size > 0; size--) { - int c = string[(size - 1) ^ 1]; + int c = string[size - 1]; if (c != '\0' && !isspace (c)) break; } + string[size] = '\0'; + + return string; +} + +/* Reads sector SEC_NO from disk D into BUFFER, which must have + room for BLOCK_SECTOR_SIZE bytes. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +static void +ide_read (void *d_, block_sector_t sec_no, void *buffer) +{ + struct ata_disk *d = d_; + struct channel *c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_READ_SECTOR_RETRY); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) + PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no); + input_sector (c, buffer); + lock_release (&c->lock); +} - /* Print. */ - for (i = 0; i < size; i++) - printf ("%c", string[i ^ 1]); +/* Write sector SEC_NO to disk D from BUFFER, which must contain + BLOCK_SECTOR_SIZE bytes. Returns after the disk has + acknowledged receiving the data. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +static void +ide_write (void *d_, block_sector_t sec_no, const void *buffer) +{ + struct ata_disk *d = d_; + struct channel *c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_WRITE_SECTOR_RETRY); + if (!wait_while_busy (d)) + PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no); + output_sector (c, buffer); + sema_down (&c->completion_wait); + lock_release (&c->lock); } + +static struct block_operations ide_operations = + { + ide_read, + ide_write + }; /* Selects device D, waiting for it to become ready, and then writes SEC_NO to the disk's sector selection registers. (We use LBA mode.) */ static void -select_sector (struct disk *d, disk_sector_t sec_no) +select_sector (struct ata_disk *d, block_sector_t sec_no) { struct channel *c = d->channel; - ASSERT (sec_no < d->capacity); ASSERT (sec_no < (1UL << 28)); select_device_wait (d); @@ -459,19 +415,19 @@ issue_pio_command (struct channel *c, uint8_t command) } /* Reads a sector from channel C's data register in PIO mode into - SECTOR, which must have room for DISK_SECTOR_SIZE bytes. */ + SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */ static void input_sector (struct channel *c, void *sector) { - insw (reg_data (c), sector, DISK_SECTOR_SIZE / 2); + insw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2); } /* Writes SECTOR to channel C's data register in PIO mode. - SECTOR must contain DISK_SECTOR_SIZE bytes. */ + SECTOR must contain BLOCK_SECTOR_SIZE bytes. */ static void output_sector (struct channel *c, const void *sector) { - outsw (reg_data (c), sector, DISK_SECTOR_SIZE / 2); + outsw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2); } /* Low-level ATA primitives. */ @@ -482,7 +438,7 @@ output_sector (struct channel *c, const void *sector) As a side effect, reading the status register clears any pending interrupt. */ static void -wait_until_idle (const struct disk *d) +wait_until_idle (const struct ata_disk *d) { int i; @@ -501,7 +457,7 @@ wait_until_idle (const struct disk *d) The ATA standards say that a disk may take as long as that to complete its reset. */ static bool -wait_while_busy (const struct disk *d) +wait_while_busy (const struct ata_disk *d) { struct channel *c = d->channel; int i; @@ -525,7 +481,7 @@ wait_while_busy (const struct disk *d) /* Program D's channel so that D is now the selected disk. */ static void -select_device (const struct disk *d) +select_device (const struct ata_disk *d) { struct channel *c = d->channel; uint8_t dev = DEV_MBS; @@ -539,7 +495,7 @@ select_device (const struct disk *d) /* Select disk D in its channel, as select_device(), but wait for the channel to become idle before and after. */ static void -select_device_wait (const struct disk *d) +select_device_wait (const struct ata_disk *d) { wait_until_idle (d); select_device (d); diff --git a/src/devices/ide.h b/src/devices/ide.h new file mode 100644 index 0000000..b35da5e --- /dev/null +++ b/src/devices/ide.h @@ -0,0 +1,6 @@ +#ifndef DEVICES_IDE_H +#define DEVICES_IDE_H + +void ide_init (void); + +#endif /* devices/ide.h */ diff --git a/src/devices/partition.c b/src/devices/partition.c new file mode 100644 index 0000000..7e97332 --- /dev/null +++ b/src/devices/partition.c @@ -0,0 +1,324 @@ +#include "devices/partition.h" +#include +#include +#include +#include +#include "devices/block.h" +#include "threads/malloc.h" + +/* A partition of a block device. */ +struct partition + { + struct block *block; /* Underlying block device. */ + block_sector_t start; /* First sector within device. */ + }; + +static struct block_operations partition_operations; + +static void read_partition_table (struct block *, block_sector_t sector, + block_sector_t primary_extended_sector, + int *part_nr); +static void found_partition (struct block *, uint8_t type, + block_sector_t start, block_sector_t size, + int part_nr); +static const char *partition_type_name (uint8_t); + +/* Scans BLOCK for partitions of interest to Pintos. */ +void +partition_scan (struct block *block) +{ + int part_nr = 0; + read_partition_table (block, 0, 0, &part_nr); + if (part_nr == 0) + printf ("%s: Device contains no partitions\n", block_name (block)); +} + +/* Reads the partition table in the given SECTOR of BLOCK and + scans it for partitions of interest to Pintos. + + If SECTOR is 0, so that this is the top-level partition table + on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful; + otherwise, it should designate the sector of the top-level + extended partition table that was traversed to arrive at + SECTOR, for use in finding logical partitions (see the large + comment below). + + PART_NR points to the number of non-empty primary or logical + partitions already encountered on BLOCK. It is incremented as + partitions are found. */ +static void +read_partition_table (struct block *block, block_sector_t sector, + block_sector_t primary_extended_sector, + int *part_nr) +{ + /* Format of a partition table entry. See [Partitions]. */ + struct partition_table_entry + { + uint8_t bootable; /* 0x00=not bootable, 0x80=bootable. */ + uint8_t start_chs[3]; /* Encoded starting cylinder, head, sector. */ + uint8_t type; /* Partition type (see partition_type_name). */ + uint8_t end_chs[3]; /* Encoded ending cylinder, head, sector. */ + uint32_t offset; /* Start sector offset from partition table. */ + uint32_t size; /* Number of sectors. */ + } + PACKED; + + /* Partition table sector. */ + struct partition_table + { + uint8_t loader[446]; /* Loader, in top-level partition table. */ + struct partition_table_entry partitions[4]; /* Table entries. */ + uint16_t signature; /* Should be 0xaa55. */ + } + PACKED; + + struct partition_table *pt; + size_t i; + + /* Check SECTOR validity. */ + if (sector >= block_size (block)) + { + printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n", + block_name (block), sector); + return; + } + + /* Read sector. */ + ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE); + pt = malloc (sizeof *pt); + if (pt == NULL) + PANIC ("Failed to allocate memory for partition table."); + block_read (block, 0, pt); + + /* Check signature. */ + if (pt->signature != 0xaa55) + { + if (primary_extended_sector == 0) + printf ("%s: Invalid partition table signature\n", block_name (block)); + else + printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n", + block_name (block), sector); + free (pt); + return; + } + + /* Parse partitions. */ + for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++) + { + struct partition_table_entry *e = &pt->partitions[i]; + + if (e->size == 0 || e->type == 0) + { + /* Ignore empty partition. */ + } + else if (e->type == 0x05 /* Extended partition. */ + || e->type == 0x0f /* Windows 98 extended partition. */ + || e->type == 0x85 /* Linux extended partition. */ + || e->type == 0xc5) /* DR-DOS extended partition. */ + { + printf ("%s: Extended partition in sector %"PRDSNu"\n", + block_name (block), sector); + + /* The interpretation of the offset field for extended + partitions is bizarre. When the extended partition + table entry is in the master boot record, that is, + the device's primary partition table in sector 0, then + the offset is an absolute sector number. Otherwise, + no matter how deep the partition table we're reading + is nested, the offset is relative to the start of + the extended partition that the MBR points to. */ + if (sector == 0) + read_partition_table (block, e->offset, e->offset, part_nr); + else + read_partition_table (block, e->offset + primary_extended_sector, + primary_extended_sector, part_nr); + } + else + { + ++*part_nr; + + found_partition (block, e->type, e->offset + sector, + e->size, *part_nr); + } + } + + free (pt); +} + +/* We have found a primary or logical partition of the given TYPE + on BLOCK, starting at sector START and continuing for SIZE + sectors, which we are giving the partition number PART_NR. + Check whether this is a partition of interest to Pintos, and + if so then add it to the proper element of partitions[]. */ +static void +found_partition (struct block *block, uint8_t part_type, + block_sector_t start, block_sector_t size, + int part_nr) +{ + if (start >= block_size (block)) + printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n", + block_name (block), part_nr, start); + else if (start + size < start || start + size > block_size (block)) + printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n", + block_name (block), part_nr, start + size, block_size (block)); + else + { + enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL + : part_type == 0x21 ? BLOCK_FILESYS + : part_type == 0x22 ? BLOCK_SCRATCH + : part_type == 0x23 ? BLOCK_SWAP + : BLOCK_FOREIGN); + struct partition *p; + char extra_info[128]; + char name[16]; + + p = malloc (sizeof *p); + if (p == NULL) + PANIC ("Failed to allocate memory for partition descriptor"); + p->block = block; + p->start = start; + + snprintf (name, sizeof name, "%s%d", block_name (block), part_nr); + snprintf (extra_info, sizeof extra_info, "%s (%02x)", + partition_type_name (part_type), part_type); + block_register (name, type, extra_info, size, &partition_operations, p); + } +} + +/* Returns a human-readable name for the given partition TYPE. */ +static const char * +partition_type_name (uint8_t type) +{ + /* Name of each known type of partition. + From util-linux-2.12r/fdisk/i386_sys_types.c. + This initializer makes use of a C99 feature that allows + array elements to be initialized by index. */ + static const char *type_names[256] = + { + [0x00] = "Empty", + [0x01] = "FAT12", + [0x02] = "XENIX root", + [0x03] = "XENIX usr", + [0x04] = "FAT16 <32M", + [0x05] = "Extended", + [0x06] = "FAT16", + [0x07] = "HPFS/NTFS", + [0x08] = "AIX", + [0x09] = "AIX bootable", + [0x0a] = "OS/2 Boot Manager", + [0x0b] = "W95 FAT32", + [0x0c] = "W95 FAT32 (LBA)", + [0x0e] = "W95 FAT16 (LBA)", + [0x0f] = "W95 Ext'd (LBA)", + [0x10] = "OPUS", + [0x11] = "Hidden FAT12", + [0x12] = "Compaq diagnostics", + [0x14] = "Hidden FAT16 <32M", + [0x16] = "Hidden FAT16", + [0x17] = "Hidden HPFS/NTFS", + [0x18] = "AST SmartSleep", + [0x1b] = "Hidden W95 FAT32", + [0x1c] = "Hidden W95 FAT32 (LBA)", + [0x1e] = "Hidden W95 FAT16 (LBA)", + [0x20] = "Pintos OS kernel", + [0x21] = "Pintos file system", + [0x22] = "Pintos scratch", + [0x23] = "Pintos swap", + [0x24] = "NEC DOS", + [0x39] = "Plan 9", + [0x3c] = "PartitionMagic recovery", + [0x40] = "Venix 80286", + [0x41] = "PPC PReP Boot", + [0x42] = "SFS", + [0x4d] = "QNX4.x", + [0x4e] = "QNX4.x 2nd part", + [0x4f] = "QNX4.x 3rd part", + [0x50] = "OnTrack DM", + [0x51] = "OnTrack DM6 Aux1", + [0x52] = "CP/M", + [0x53] = "OnTrack DM6 Aux3", + [0x54] = "OnTrackDM6", + [0x55] = "EZ-Drive", + [0x56] = "Golden Bow", + [0x5c] = "Priam Edisk", + [0x61] = "SpeedStor", + [0x63] = "GNU HURD or SysV", + [0x64] = "Novell Netware 286", + [0x65] = "Novell Netware 386", + [0x70] = "DiskSecure Multi-Boot", + [0x75] = "PC/IX", + [0x80] = "Old Minix", + [0x81] = "Minix / old Linux", + [0x82] = "Linux swap / Solaris", + [0x83] = "Linux", + [0x84] = "OS/2 hidden C: drive", + [0x85] = "Linux extended", + [0x86] = "NTFS volume set", + [0x87] = "NTFS volume set", + [0x88] = "Linux plaintext", + [0x8e] = "Linux LVM", + [0x93] = "Amoeba", + [0x94] = "Amoeba BBT", + [0x9f] = "BSD/OS", + [0xa0] = "IBM Thinkpad hibernation", + [0xa5] = "FreeBSD", + [0xa6] = "OpenBSD", + [0xa7] = "NeXTSTEP", + [0xa8] = "Darwin UFS", + [0xa9] = "NetBSD", + [0xab] = "Darwin boot", + [0xb7] = "BSDI fs", + [0xb8] = "BSDI swap", + [0xbb] = "Boot Wizard hidden", + [0xbe] = "Solaris boot", + [0xbf] = "Solaris", + [0xc1] = "DRDOS/sec (FAT-12)", + [0xc4] = "DRDOS/sec (FAT-16 < 32M)", + [0xc6] = "DRDOS/sec (FAT-16)", + [0xc7] = "Syrinx", + [0xda] = "Non-FS data", + [0xdb] = "CP/M / CTOS / ...", + [0xde] = "Dell Utility", + [0xdf] = "BootIt", + [0xe1] = "DOS access", + [0xe3] = "DOS R/O", + [0xe4] = "SpeedStor", + [0xeb] = "BeOS fs", + [0xee] = "EFI GPT", + [0xef] = "EFI (FAT-12/16/32)", + [0xf0] = "Linux/PA-RISC boot", + [0xf1] = "SpeedStor", + [0xf4] = "SpeedStor", + [0xf2] = "DOS secondary", + [0xfd] = "Linux raid autodetect", + [0xfe] = "LANstep", + [0xff] = "BBT", + }; + + return type_names[type] != NULL ? type_names[type] : "Unknown"; +} + +/* Reads sector SECTOR from partition P into BUFFER, which must + have room for BLOCK_SECTOR_SIZE bytes. */ +static void +partition_read (void *p_, block_sector_t sector, void *buffer) +{ + struct partition *p = p_; + block_read (p->block, p->start + sector, buffer); +} + +/* Write sector SECTOR to partition P from BUFFER, which must + contain BLOCK_SECTOR_SIZE bytes. Returns after the block has + acknowledged receiving the data. */ +static void +partition_write (void *p_, block_sector_t sector, const void *buffer) +{ + struct partition *p = p_; + block_write (p->block, p->start + sector, buffer); +} + +static struct block_operations partition_operations = + { + partition_read, + partition_write + }; diff --git a/src/devices/partition.h b/src/devices/partition.h new file mode 100644 index 0000000..47fea4d --- /dev/null +++ b/src/devices/partition.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PARTITION_H +#define DEVICES_PARTITION_H + +struct block; + +void partition_scan (struct block *); + +#endif /* devices/partition.h */ diff --git a/src/devices/shutdown.c b/src/devices/shutdown.c index 42b67df..7ff9a95 100644 --- a/src/devices/shutdown.c +++ b/src/devices/shutdown.c @@ -10,7 +10,7 @@ #include "userprog/exception.h" #endif #ifdef FILESYS -#include "devices/disk.h" +#include "devices/block.h" #include "filesys/filesys.h" #endif @@ -121,7 +121,7 @@ print_stats (void) timer_print_stats (); thread_print_stats (); #ifdef FILESYS - disk_print_stats (); + block_print_stats (); #endif console_print_stats (); kbd_print_stats (); diff --git a/src/filesys/Make.vars b/src/filesys/Make.vars index d8050cd..b3aa005 100644 --- a/src/filesys/Make.vars +++ b/src/filesys/Make.vars @@ -1,13 +1,13 @@ # -*- makefile -*- -os.dsk: DEFINES = -DUSERPROG -DFILESYS +kernel.bin: DEFINES = -DUSERPROG -DFILESYS KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.no-vm SIMULATOR = --qemu # Uncomment the lines below to enable VM. -#os.dsk: DEFINES += -DVM +#kernel.bin: DEFINES += -DVM #KERNEL_SUBDIRS += vm #TEST_SUBDIRS += tests/vm #GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.with-vm diff --git a/src/filesys/directory.c b/src/filesys/directory.c index 0d265d5..030c1c9 100644 --- a/src/filesys/directory.c +++ b/src/filesys/directory.c @@ -16,7 +16,7 @@ struct dir /* A single directory entry. */ struct dir_entry { - disk_sector_t inode_sector; /* Sector number of header. */ + block_sector_t inode_sector; /* Sector number of header. */ char name[NAME_MAX + 1]; /* Null terminated file name. */ bool in_use; /* In use or free? */ }; @@ -24,7 +24,7 @@ struct dir_entry /* Creates a directory with space for ENTRY_CNT entries in the given SECTOR. Returns true if successful, false on failure. */ bool -dir_create (disk_sector_t sector, size_t entry_cnt) +dir_create (block_sector_t sector, size_t entry_cnt) { return inode_create (sector, entry_cnt * sizeof (struct dir_entry)); } @@ -139,12 +139,12 @@ dir_lookup (const struct dir *dir, const char *name, Fails if NAME is invalid (i.e. too long) or a disk or memory error occurs. */ bool -dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) +dir_add (struct dir *dir, const char *name, block_sector_t inode_sector) { struct dir_entry e; off_t ofs; bool success = false; - + ASSERT (dir != NULL); ASSERT (name != NULL); diff --git a/src/filesys/directory.h b/src/filesys/directory.h index 7955937..930acf9 100644 --- a/src/filesys/directory.h +++ b/src/filesys/directory.h @@ -3,7 +3,7 @@ #include #include -#include "devices/disk.h" +#include "devices/block.h" /* Maximum length of a file name component. This is the traditional UNIX maximum length. @@ -14,7 +14,7 @@ struct inode; /* Opening and closing directories. */ -bool dir_create (disk_sector_t sector, size_t entry_cnt); +bool dir_create (block_sector_t sector, size_t entry_cnt); struct dir *dir_open (struct inode *); struct dir *dir_open_root (void); struct dir *dir_reopen (struct dir *); @@ -23,7 +23,7 @@ struct inode *dir_get_inode (struct dir *); /* Reading and writing. */ bool dir_lookup (const struct dir *, const char *name, struct inode **); -bool dir_add (struct dir *, const char *name, disk_sector_t); +bool dir_add (struct dir *, const char *name, block_sector_t); bool dir_remove (struct dir *, const char *name); bool dir_readdir (struct dir *, char name[NAME_MAX + 1]); diff --git a/src/filesys/filesys.c b/src/filesys/filesys.c index fedda08..7a53f5f 100644 --- a/src/filesys/filesys.c +++ b/src/filesys/filesys.c @@ -6,10 +6,9 @@ #include "filesys/free-map.h" #include "filesys/inode.h" #include "filesys/directory.h" -#include "devices/disk.h" -/* The disk that contains the file system. */ -struct disk *filesys_disk; +/* Partition that contains the file system. */ +struct block *fs_device; static void do_format (void); @@ -18,9 +17,9 @@ static void do_format (void); void filesys_init (bool format) { - filesys_disk = disk_get (0, 1); - if (filesys_disk == NULL) - PANIC ("hd0:1 (hdb) not present, file system initialization failed"); + fs_device = block_get_role (BLOCK_FILESYS); + if (fs_device == NULL) + PANIC ("No file system device found, can't initialize file system."); inode_init (); free_map_init (); @@ -46,7 +45,7 @@ filesys_done (void) bool filesys_create (const char *name, off_t initial_size) { - disk_sector_t inode_sector = 0; + block_sector_t inode_sector = 0; struct dir *dir = dir_open_root (); bool success = (dir != NULL && free_map_allocate (1, &inode_sector) diff --git a/src/filesys/filesys.h b/src/filesys/filesys.h index caef83c..c1cda84 100644 --- a/src/filesys/filesys.h +++ b/src/filesys/filesys.h @@ -8,8 +8,8 @@ #define FREE_MAP_SECTOR 0 /* Free map file inode sector. */ #define ROOT_DIR_SECTOR 1 /* Root directory file inode sector. */ -/* Disk used for file system. */ -extern struct disk *filesys_disk; +/* Block device that contains the file system. */ +struct block *fs_device; void filesys_init (bool format); void filesys_done (void); diff --git a/src/filesys/free-map.c b/src/filesys/free-map.c index 1cd9175..34f2060 100644 --- a/src/filesys/free-map.c +++ b/src/filesys/free-map.c @@ -6,15 +6,15 @@ #include "filesys/inode.h" static struct file *free_map_file; /* Free map file. */ -static struct bitmap *free_map; /* Free map, one bit per disk sector. */ +static struct bitmap *free_map; /* Free map, one bit per sector. */ /* Initializes the free map. */ void free_map_init (void) { - free_map = bitmap_create (disk_size (filesys_disk)); + free_map = bitmap_create (block_size (fs_device)); if (free_map == NULL) - PANIC ("bitmap creation failed--disk is too large"); + PANIC ("bitmap creation failed--file system device is too large"); bitmap_mark (free_map, FREE_MAP_SECTOR); bitmap_mark (free_map, ROOT_DIR_SECTOR); } @@ -24,9 +24,9 @@ free_map_init (void) Returns true if successful, false if all sectors were available. */ bool -free_map_allocate (size_t cnt, disk_sector_t *sectorp) +free_map_allocate (size_t cnt, block_sector_t *sectorp) { - disk_sector_t sector = bitmap_scan_and_flip (free_map, 0, cnt, false); + block_sector_t sector = bitmap_scan_and_flip (free_map, 0, cnt, false); if (sector != BITMAP_ERROR && free_map_file != NULL && !bitmap_write (free_map, free_map_file)) @@ -41,7 +41,7 @@ free_map_allocate (size_t cnt, disk_sector_t *sectorp) /* Makes CNT sectors starting at SECTOR available for use. */ void -free_map_release (disk_sector_t sector, size_t cnt) +free_map_release (block_sector_t sector, size_t cnt) { ASSERT (bitmap_all (free_map, sector, cnt)); bitmap_set_multiple (free_map, sector, cnt, false); diff --git a/src/filesys/free-map.h b/src/filesys/free-map.h index ce08f5c..316cd1c 100644 --- a/src/filesys/free-map.h +++ b/src/filesys/free-map.h @@ -3,7 +3,7 @@ #include #include -#include "devices/disk.h" +#include "devices/block.h" void free_map_init (void); void free_map_read (void); @@ -11,7 +11,7 @@ void free_map_create (void); void free_map_open (void); void free_map_close (void); -bool free_map_allocate (size_t, disk_sector_t *); -void free_map_release (disk_sector_t, size_t); +bool free_map_allocate (size_t, block_sector_t *); +void free_map_release (block_sector_t, size_t); #endif /* filesys/free-map.h */ diff --git a/src/filesys/fsutil.c b/src/filesys/fsutil.c index bf2c154..447f291 100644 --- a/src/filesys/fsutil.c +++ b/src/filesys/fsutil.c @@ -7,7 +7,6 @@ #include "filesys/directory.h" #include "filesys/file.h" #include "filesys/filesys.h" -#include "devices/disk.h" #include "threads/malloc.h" #include "threads/palloc.h" #include "threads/vaddr.h" @@ -67,28 +66,29 @@ fsutil_rm (char **argv) PANIC ("%s: delete failed\n", file_name); } -/* Extracts a ustar-format tar archive from the scratch disk, hdc - or hd1:0, into the Pintos file system. */ +/* Extracts a ustar-format tar archive from the scratch block + device into the Pintos file system. */ void fsutil_extract (char **argv UNUSED) { - static disk_sector_t sector = 0; + static block_sector_t sector = 0; - struct disk *src; + struct block *src; void *header, *data; /* Allocate buffers. */ - header = malloc (DISK_SECTOR_SIZE); - data = malloc (DISK_SECTOR_SIZE); + header = malloc (BLOCK_SECTOR_SIZE); + data = malloc (BLOCK_SECTOR_SIZE); if (header == NULL || data == NULL) PANIC ("couldn't allocate buffers"); - /* Open source disk. */ - src = disk_get (1, 0); + /* Open source block device. */ + src = block_get_role (BLOCK_SCRATCH); if (src == NULL) - PANIC ("couldn't open scratch disk (hdc or hd1:0)"); + PANIC ("couldn't open scratch device"); - printf ("Extracting ustar archive from scratch disk into file system...\n"); + printf ("Extracting ustar archive from scratch device " + "into file system...\n"); for (;;) { @@ -98,7 +98,7 @@ fsutil_extract (char **argv UNUSED) int size; /* Read and parse ustar header. */ - disk_read (src, sector++, header); + block_read (src, sector++, header); error = ustar_parse_header (header, &file_name, &type, &size); if (error != NULL) PANIC ("bad ustar header in sector %"PRDSNu" (%s)", sector - 1, error); @@ -126,10 +126,10 @@ fsutil_extract (char **argv UNUSED) /* Do copy. */ while (size > 0) { - int chunk_size = (size > DISK_SECTOR_SIZE - ? DISK_SECTOR_SIZE + int chunk_size = (size > BLOCK_SECTOR_SIZE + ? BLOCK_SECTOR_SIZE : size); - disk_read (src, sector++, data); + block_read (src, sector++, data); if (file_write (dst, data, chunk_size) != chunk_size) PANIC ("%s: write failed with %d bytes unwritten", file_name, size); @@ -141,42 +141,42 @@ fsutil_extract (char **argv UNUSED) } } - /* Erase the ustar header from the start of the disk, so that - the extraction operation is idempotent. We erase two blocks - because two blocks of zeros are the ustar end-of-archive - marker. */ + /* Erase the ustar header from the start of the block device, + so that the extraction operation is idempotent. We erase + two blocks because two blocks of zeros are the ustar + end-of-archive marker. */ printf ("Erasing ustar archive...\n"); - memset (header, 0, DISK_SECTOR_SIZE); - disk_write (src, 0, header); - disk_write (src, 1, header); + memset (header, 0, BLOCK_SECTOR_SIZE); + block_write (src, 0, header); + block_write (src, 1, header); free (data); free (header); } /* Copies file FILE_NAME from the file system to the scratch - disk, in ustar format. + device, in ustar format. The first call to this function will write starting at the - beginning of the scratch disk. Later calls advance across the - disk. This position is independent of that used for + beginning of the scratch device. Later calls advance across + the device. This position is independent of that used for fsutil_extract(), so `extract' should precede all `append's. */ void fsutil_append (char **argv) { - static disk_sector_t sector = 0; + static block_sector_t sector = 0; const char *file_name = argv[1]; void *buffer; struct file *src; - struct disk *dst; + struct block *dst; off_t size; - printf ("Appending '%s' to ustar archive on scratch disk...\n", file_name); + printf ("Appending '%s' to ustar archive on scratch device...\n", file_name); /* Allocate buffer. */ - buffer = malloc (DISK_SECTOR_SIZE); + buffer = malloc (BLOCK_SECTOR_SIZE); if (buffer == NULL) PANIC ("couldn't allocate buffer"); @@ -186,35 +186,35 @@ fsutil_append (char **argv) PANIC ("%s: open failed", file_name); size = file_length (src); - /* Open target disk. */ - dst = disk_get (1, 0); + /* Open target block device. */ + dst = block_get_role (BLOCK_SCRATCH); if (dst == NULL) - PANIC ("couldn't open target disk (hdc or hd1:0)"); + PANIC ("couldn't open scratch device"); /* Write ustar header to first sector. */ if (!ustar_make_header (file_name, USTAR_REGULAR, size, buffer)) PANIC ("%s: name too long for ustar format", file_name); - disk_write (dst, sector++, buffer); + block_write (dst, sector++, buffer); /* Do copy. */ while (size > 0) { - int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size; - if (sector >= disk_size (dst)) - PANIC ("%s: out of space on scratch disk", file_name); + int chunk_size = size > BLOCK_SECTOR_SIZE ? BLOCK_SECTOR_SIZE : size; + if (sector >= block_size (dst)) + PANIC ("%s: out of space on scratch device", file_name); if (file_read (src, buffer, chunk_size) != chunk_size) PANIC ("%s: read failed with %"PROTd" bytes unread", file_name, size); - memset (buffer + chunk_size, 0, DISK_SECTOR_SIZE - chunk_size); - disk_write (dst, sector++, buffer); + memset (buffer + chunk_size, 0, BLOCK_SECTOR_SIZE - chunk_size); + block_write (dst, sector++, buffer); size -= chunk_size; } /* Write ustar end-of-archive marker, which is two consecutive sectors full of zeros. Don't advance our position past them, though, in case we have more files to append. */ - memset (buffer, 0, DISK_SECTOR_SIZE); - disk_write (dst, sector, buffer); - disk_write (dst, sector, buffer + 1); + memset (buffer, 0, BLOCK_SECTOR_SIZE); + block_write (dst, sector, buffer); + block_write (dst, sector, buffer + 1); /* Finish up. */ file_close (src); diff --git a/src/filesys/inode.c b/src/filesys/inode.c index d890ed8..3463563 100644 --- a/src/filesys/inode.c +++ b/src/filesys/inode.c @@ -11,10 +11,10 @@ #define INODE_MAGIC 0x494e4f44 /* On-disk inode. - Must be exactly DISK_SECTOR_SIZE bytes long. */ + Must be exactly BLOCK_SECTOR_SIZE bytes long. */ struct inode_disk { - disk_sector_t start; /* First data sector. */ + block_sector_t start; /* First data sector. */ off_t length; /* File size in bytes. */ unsigned magic; /* Magic number. */ uint32_t unused[125]; /* Not used. */ @@ -25,30 +25,30 @@ struct inode_disk static inline size_t bytes_to_sectors (off_t size) { - return DIV_ROUND_UP (size, DISK_SECTOR_SIZE); + return DIV_ROUND_UP (size, BLOCK_SECTOR_SIZE); } /* In-memory inode. */ struct inode { struct list_elem elem; /* Element in inode list. */ - disk_sector_t sector; /* Sector number of disk location. */ + block_sector_t sector; /* Sector number of disk location. */ int open_cnt; /* Number of openers. */ bool removed; /* True if deleted, false otherwise. */ int deny_write_cnt; /* 0: writes ok, >0: deny writes. */ struct inode_disk data; /* Inode content. */ }; -/* Returns the disk sector that contains byte offset POS within - INODE. +/* Returns the block device sector that contains byte offset POS + within INODE. Returns -1 if INODE does not contain data for a byte at offset POS. */ -static disk_sector_t +static block_sector_t byte_to_sector (const struct inode *inode, off_t pos) { ASSERT (inode != NULL); if (pos < inode->data.length) - return inode->data.start + pos / DISK_SECTOR_SIZE; + return inode->data.start + pos / BLOCK_SECTOR_SIZE; else return -1; } @@ -66,11 +66,11 @@ inode_init (void) /* Initializes an inode with LENGTH bytes of data and writes the new inode to sector SECTOR on the file system - disk. + device. Returns true if successful. Returns false if memory or disk allocation fails. */ bool -inode_create (disk_sector_t sector, off_t length) +inode_create (block_sector_t sector, off_t length) { struct inode_disk *disk_inode = NULL; bool success = false; @@ -79,7 +79,7 @@ inode_create (disk_sector_t sector, off_t length) /* If this assertion fails, the inode structure is not exactly one sector in size, and you should fix that. */ - ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE); + ASSERT (sizeof *disk_inode == BLOCK_SECTOR_SIZE); disk_inode = calloc (1, sizeof *disk_inode); if (disk_inode != NULL) @@ -87,16 +87,16 @@ inode_create (disk_sector_t sector, off_t length) size_t sectors = bytes_to_sectors (length); disk_inode->length = length; disk_inode->magic = INODE_MAGIC; - if (free_map_allocate (sectors, &disk_inode->start)) + if (free_map_allocate (sectors, &disk_inode->start)) { - disk_write (filesys_disk, sector, disk_inode); + block_write (fs_device, sector, disk_inode); if (sectors > 0) { - static char zeros[DISK_SECTOR_SIZE]; + static char zeros[BLOCK_SECTOR_SIZE]; size_t i; for (i = 0; i < sectors; i++) - disk_write (filesys_disk, disk_inode->start + i, zeros); + block_write (fs_device, disk_inode->start + i, zeros); } success = true; } @@ -109,7 +109,7 @@ inode_create (disk_sector_t sector, off_t length) and returns a `struct inode' that contains it. Returns a null pointer if memory allocation fails. */ struct inode * -inode_open (disk_sector_t sector) +inode_open (block_sector_t sector) { struct list_elem *e; struct inode *inode; @@ -137,7 +137,7 @@ inode_open (disk_sector_t sector) inode->open_cnt = 1; inode->deny_write_cnt = 0; inode->removed = false; - disk_read (filesys_disk, inode->sector, &inode->data); + block_read (fs_device, inode->sector, &inode->data); return inode; } @@ -151,7 +151,7 @@ inode_reopen (struct inode *inode) } /* Returns INODE's inode number. */ -disk_sector_t +block_sector_t inode_get_inumber (const struct inode *inode) { return inode->sector; @@ -207,12 +207,12 @@ inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset) while (size > 0) { /* Disk sector to read, starting byte offset within sector. */ - disk_sector_t sector_idx = byte_to_sector (inode, offset); - int sector_ofs = offset % DISK_SECTOR_SIZE; + block_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % BLOCK_SECTOR_SIZE; /* Bytes left in inode, bytes left in sector, lesser of the two. */ off_t inode_left = inode_length (inode) - offset; - int sector_left = DISK_SECTOR_SIZE - sector_ofs; + int sector_left = BLOCK_SECTOR_SIZE - sector_ofs; int min_left = inode_left < sector_left ? inode_left : sector_left; /* Number of bytes to actually copy out of this sector. */ @@ -220,10 +220,10 @@ inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset) if (chunk_size <= 0) break; - if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) + if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) { /* Read full sector directly into caller's buffer. */ - disk_read (filesys_disk, sector_idx, buffer + bytes_read); + block_read (fs_device, sector_idx, buffer + bytes_read); } else { @@ -231,11 +231,11 @@ inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset) into caller's buffer. */ if (bounce == NULL) { - bounce = malloc (DISK_SECTOR_SIZE); + bounce = malloc (BLOCK_SECTOR_SIZE); if (bounce == NULL) break; } - disk_read (filesys_disk, sector_idx, bounce); + block_read (fs_device, sector_idx, bounce); memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size); } @@ -268,12 +268,12 @@ inode_write_at (struct inode *inode, const void *buffer_, off_t size, while (size > 0) { /* Sector to write, starting byte offset within sector. */ - disk_sector_t sector_idx = byte_to_sector (inode, offset); - int sector_ofs = offset % DISK_SECTOR_SIZE; + block_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % BLOCK_SECTOR_SIZE; /* Bytes left in inode, bytes left in sector, lesser of the two. */ off_t inode_left = inode_length (inode) - offset; - int sector_left = DISK_SECTOR_SIZE - sector_ofs; + int sector_left = BLOCK_SECTOR_SIZE - sector_ofs; int min_left = inode_left < sector_left ? inode_left : sector_left; /* Number of bytes to actually write into this sector. */ @@ -281,17 +281,17 @@ inode_write_at (struct inode *inode, const void *buffer_, off_t size, if (chunk_size <= 0) break; - if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) + if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) { /* Write full sector directly to disk. */ - disk_write (filesys_disk, sector_idx, buffer + bytes_written); + block_write (fs_device, sector_idx, buffer + bytes_written); } else { /* We need a bounce buffer. */ if (bounce == NULL) { - bounce = malloc (DISK_SECTOR_SIZE); + bounce = malloc (BLOCK_SECTOR_SIZE); if (bounce == NULL) break; } @@ -300,11 +300,11 @@ inode_write_at (struct inode *inode, const void *buffer_, off_t size, we're writing, then we need to read in the sector first. Otherwise we start with a sector of all zeros. */ if (sector_ofs > 0 || chunk_size < sector_left) - disk_read (filesys_disk, sector_idx, bounce); + block_read (fs_device, sector_idx, bounce); else - memset (bounce, 0, DISK_SECTOR_SIZE); + memset (bounce, 0, BLOCK_SECTOR_SIZE); memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size); - disk_write (filesys_disk, sector_idx, bounce); + block_write (fs_device, sector_idx, bounce); } /* Advance. */ diff --git a/src/filesys/inode.h b/src/filesys/inode.h index be7df63..cb42310 100644 --- a/src/filesys/inode.h +++ b/src/filesys/inode.h @@ -3,15 +3,15 @@ #include #include "filesys/off_t.h" -#include "devices/disk.h" +#include "devices/block.h" struct bitmap; void inode_init (void); -bool inode_create (disk_sector_t, off_t); -struct inode *inode_open (disk_sector_t); +bool inode_create (block_sector_t, off_t); +struct inode *inode_open (block_sector_t); struct inode *inode_reopen (struct inode *); -disk_sector_t inode_get_inumber (const struct inode *); +block_sector_t inode_get_inumber (const struct inode *); void inode_close (struct inode *); void inode_remove (struct inode *); off_t inode_read_at (struct inode *, void *, off_t size, off_t offset); diff --git a/src/lib/kernel/list.h b/src/lib/kernel/list.h index 2388f9a..82efbb5 100644 --- a/src/lib/kernel/list.h +++ b/src/lib/kernel/list.h @@ -109,6 +109,19 @@ struct list ((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next \ - offsetof (STRUCT, MEMBER.next))) +/* List initialization. + + A list may be initialized by calling list_init(): + + struct list my_list; + list_init (&my_list); + + or with an initializer using LIST_INITIALIZER: + + struct list my_list = LIST_INITIALIZER (my_list); */ +#define LIST_INITIALIZER(NAME) { { NULL, &(NAME).tail }, \ + { &(NAME).head, NULL } } + void list_init (struct list *); /* List traversal. */ diff --git a/src/lib/stdio.c b/src/lib/stdio.c index af80e51..8927c50 100644 --- a/src/lib/stdio.c +++ b/src/lib/stdio.c @@ -635,3 +635,21 @@ hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii) size -= n; } } + +/* Prints SIZE, which represents a number of bytes, in a + human-readable format, e.g. "256 kB". */ +void +print_human_readable_size (uint64_t size) +{ + if (size == 1) + printf ("1 byte"); + else + { + static const char *factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL}; + const char **fp; + + for (fp = factors; size >= 1024 && fp[1] != NULL; fp++) + size /= 1024; + printf ("%"PRIu64" %s", size, *fp); + } +} diff --git a/src/lib/stdio.h b/src/lib/stdio.h index 8288ff0..2739c0a 100644 --- a/src/lib/stdio.h +++ b/src/lib/stdio.h @@ -25,6 +25,7 @@ int puts (const char *); /* Nonstandard functions. */ void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii); +void print_human_readable_size (uint64_t sz); /* Internal functions. */ void __vprintf (const char *format, va_list args, diff --git a/src/tests/Make.tests b/src/tests/Make.tests index 76d63f6..358e697 100644 --- a/src/tests/Make.tests +++ b/src/tests/Make.tests @@ -55,13 +55,13 @@ TESTCMD = pintos -v -k -T $(TIMEOUT) TESTCMD += $(SIMULATOR) TESTCMD += $(PINTOSOPTS) ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) -TESTCMD += --fs-disk=$(FSDISK) +TESTCMD += $(FILESYSSOURCE) TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file))) endif ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) -TESTCMD += --swap-disk=4 +TESTCMD += --swap-size=4 endif -TESTCMD += -- -q +TESTCMD += -- -q TESTCMD += $(KERNELFLAGS) ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) TESTCMD += -f @@ -69,7 +69,7 @@ endif TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F)) TESTCMD += < /dev/null TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output -%.output: os.dsk +%.output: kernel.bin loader.bin $(TESTCMD) %.result: %.ck %.output diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests index 75a872b..e03b98d 100644 --- a/src/tests/filesys/extended/Make.tests +++ b/src/tests/filesys/extended/Make.tests @@ -20,7 +20,7 @@ $(foreach prog,$(tests/filesys/extended_TESTS), \ $(eval $(prog)_PUTFILES += tests/filesys/extended/tar)) # The version of GNU make 3.80 on vine barfs if this is split at # the last comma. -$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FSDISK = tmp.dsk)) +$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FILESYSSOURCE = --disk=tmp.dsk)) tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c @@ -34,10 +34,10 @@ GETTIMEOUT = 60 GETCMD = pintos -v -k -T $(GETTIMEOUT) GETCMD += $(PINTOSOPTS) GETCMD += $(SIMULATOR) -GETCMD += --fs-disk=$(FSDISK) +GETCMD += $(FILESYSSOURCE) GETCMD += -g fs.tar -a $(TEST).tar ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) -GETCMD += --swap-disk=4 +GETCMD += --swap-size=4 endif GETCMD += -- -q GETCMD += $(KERNELFLAGS) @@ -45,9 +45,9 @@ GETCMD += run 'tar fs.tar /' GETCMD += < /dev/null GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output -tests/filesys/extended/%.output: os.dsk +tests/filesys/extended/%.output: kernel.bin rm -f tmp.dsk - pintos-mkdisk tmp.dsk 2 + pintos-mkdisk tmp.dsk --filesys-size=2 $(TESTCMD) $(GETCMD) rm -f tmp.dsk diff --git a/src/tests/userprog/Make.tests b/src/tests/userprog/Make.tests index 8d64c05..caadd90 100644 --- a/src/tests/userprog/Make.tests +++ b/src/tests/userprog/Make.tests @@ -1,7 +1,7 @@ # -*- makefile -*- -tests/%.output: FSDISK = 2 -tests/%.output: PUTFILES = $(filter-out os.dsk, $^) +tests/%.output: FILESYSSOURCE = --filesys-size=2 +tests/%.output: PUTFILES = $(filter-out kernel.bin loader.bin, $^) tests/userprog_TESTS = $(addprefix tests/userprog/,args-none \ args-single args-multiple args-many args-dbl-space sc-bad-sp \ diff --git a/src/threads/Make.vars b/src/threads/Make.vars index dc45c2a..310c240 100644 --- a/src/threads/Make.vars +++ b/src/threads/Make.vars @@ -1,6 +1,6 @@ # -*- makefile -*- -os.dsk: DEFINES = +kernel.bin: DEFINES = KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS) TEST_SUBDIRS = tests/threads GRADING_FILE = $(SRCDIR)/tests/threads/Grading diff --git a/src/threads/init.c b/src/threads/init.c index d2d3e08..cebec2c 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -1,10 +1,10 @@ #include "threads/init.h" #include #include +#include #include #include #include -#include #include #include #include @@ -32,26 +32,32 @@ #include "tests/threads/tests.h" #endif #ifdef FILESYS -#include "devices/disk.h" +#include "devices/block.h" +#include "devices/ide.h" #include "filesys/filesys.h" #include "filesys/fsutil.h" #endif -/* Amount of physical memory, in 4 kB pages. */ -size_t init_ram_pages; - /* Page directory with kernel mappings only. */ uint32_t *init_page_dir; #ifdef FILESYS /* -f: Format the file system? */ static bool format_filesys; + +/* -filesys, -scratch, -swap: Names of block devices to use, + overriding the defaults. */ +static const char *filesys_bdev_name; +static const char *scratch_bdev_name; +#ifdef VM +static const char *swap_bdev_name; #endif +#endif /* FILESYS */ /* -ul: Maximum number of pages to put into palloc's user pool. */ static size_t user_page_limit = SIZE_MAX; -static void ram_init (void); +static void bss_init (void); static void paging_init (void); static char **read_command_line (void); @@ -59,6 +65,11 @@ static char **parse_options (char **argv); static void run_actions (char **argv); static void usage (void); +#ifdef FILESYS +static void locate_block_devices (void); +static void locate_block_device (enum block_type, const char *name); +#endif + int main (void) NO_RETURN; /* Pintos main program. */ @@ -66,9 +77,9 @@ int main (void) { char **argv; - - /* Clear BSS and get machine's RAM size. */ - ram_init (); + + /* Clear BSS. */ + bss_init (); /* Break command line into arguments and parse options. */ argv = read_command_line (); @@ -80,7 +91,7 @@ main (void) console_init (); /* Greet user. */ - printf ("Pintos booting with %'zu kB RAM...\n", + printf ("Pintos booting with %'"PRIu32" kB RAM...\n", init_ram_pages * PGSIZE / 1024); /* Initialize memory system. */ @@ -111,7 +122,8 @@ main (void) #ifdef FILESYS /* Initialize file system. */ - disk_init (); + ide_init (); + locate_block_devices (); filesys_init (format_filesys); #endif @@ -125,32 +137,23 @@ main (void) thread_exit (); } -/* Clear BSS and obtain RAM size from loader. */ +/* Clear the "BSS", a segment that should be initialized to + zeros. It isn't actually stored on disk or zeroed by the + kernel loader, so we have to zero it ourselves. + + The start and end of the BSS segment is recorded by the + linker as _start_bss and _end_bss. See kernel.lds. */ static void -ram_init (void) +bss_init (void) { - /* The "BSS" is a segment that should be initialized to zeros. - It isn't actually stored on disk or zeroed by the kernel - loader, so we have to zero it ourselves. - - The start and end of the BSS segment is recorded by the - linker as _start_bss and _end_bss. See kernel.lds. */ extern char _start_bss, _end_bss; memset (&_start_bss, 0, &_end_bss - &_start_bss); - - /* Get RAM size from loader. See loader.S. */ - init_ram_pages = *(uint32_t *) ptov (LOADER_RAM_PGS); } /* Populates the base page directory and page table with the kernel virtual mapping, and then sets up the CPU to use the new page directory. Points init_page_dir to the page - directory it creates. - - At the time this function is called, the active page table - (set up by loader.S) only maps the first 4 MB of RAM, so we - should not try to use extravagant amounts of memory. - Fortunately, there is no need to do so. */ + directory it creates. */ static void paging_init (void) { @@ -240,6 +243,14 @@ parse_options (char **argv) #ifdef FILESYS else if (!strcmp (name, "-f")) format_filesys = true; + else if (!strcmp (name, "-filesys")) + filesys_bdev_name = value; + else if (!strcmp (name, "-scratch")) + scratch_bdev_name = value; +#ifdef VM + else if (!strcmp (name, "-swap")) + swap_bdev_name = value; +#endif #endif else if (!strcmp (name, "-rs")) random_init (atoi (value)); @@ -351,14 +362,21 @@ usage (void) " cat FILE Print FILE to the console.\n" " rm FILE Delete FILE.\n" "Use these actions indirectly via `pintos' -g and -p options:\n" - " extract Untar from scratch disk into file system.\n" - " append FILE Append FILE to tar file on scratch disk.\n" + " extract Untar from scratch device into file system.\n" + " append FILE Append FILE to tar file on scratch device.\n" #endif "\nOptions:\n" " -h Print this help message and power off.\n" " -q Power off VM after actions or on panic.\n" " -r Reboot after actions.\n" - " -f Format file system disk during startup.\n" +#ifdef FILESYS + " -f Format file system device during startup.\n" + " -filesys=BDEV Use BDEV for file system instead of default.\n" + " -scratch=BDEV Use BDEV for scratch instead of default.\n" +#ifdef VM + " -swap=BDEV Use BDEV for swap instead of default.\n" +#endif +#endif " -rs=SEED Set random number seed to SEED.\n" " -mlfqs Use multi-level feedback queue scheduler.\n" #ifdef USERPROG @@ -367,3 +385,45 @@ usage (void) ); shutdown_power_off (); } + +#ifdef FILESYS +/* Figure out what block devices to cast in the various Pintos roles. */ +static void +locate_block_devices (void) +{ + locate_block_device (BLOCK_FILESYS, filesys_bdev_name); + locate_block_device (BLOCK_SCRATCH, scratch_bdev_name); +#ifdef VM + locate_block_device (BLOCK_SWAP, swap_bdev_name); +#endif +} + +/* Figures out what block device to use for the given ROLE: the + block device with the given NAME, if NAME is non-null, + otherwise the first block device in probe order of type + ROLE. */ +static void +locate_block_device (enum block_type role, const char *name) +{ + struct block *block = NULL; + + if (name != NULL) + { + block = block_get_by_name (name); + if (block == NULL) + PANIC ("No such block device \"%s\"", name); + } + else + { + for (block = block_first (); block != NULL; block = block_next (block)) + if (block_type (block) == role) + break; + } + + if (block != NULL) + { + printf ("%s: using %s\n", block_type_name (role), block_name (block)); + block_set_role (role, block); + } +} +#endif diff --git a/src/threads/init.h b/src/threads/init.h index da6cc04..8a3df90 100644 --- a/src/threads/init.h +++ b/src/threads/init.h @@ -6,9 +6,6 @@ #include #include -/* Physical memory size, in 4 kB pages. */ -extern size_t init_ram_pages; - /* Page directory with kernel mappings only. */ extern uint32_t *init_page_dir; diff --git a/src/threads/kernel.lds.S b/src/threads/kernel.lds.S index 6154d08..19082d5 100644 --- a/src/threads/kernel.lds.S +++ b/src/threads/kernel.lds.S @@ -5,17 +5,19 @@ OUTPUT_ARCH("i386") ENTRY(start) /* Kernel starts at "start" symbol. */ SECTIONS { - /* Specifies the virtual address for the kernel base. */ - . = LOADER_PHYS_BASE + LOADER_KERN_BASE; + /* Specify the kernel base address. */ + _start = LOADER_PHYS_BASE + LOADER_KERN_BASE; - _start = .; + /* Make room for the ELF headers. */ + . = _start + SIZEOF_HEADERS; /* Kernel starts with code, followed by read-only data and writable data. */ .text : { *(.start) *(.text) } = 0x90 .rodata : { *(.rodata) *(.rodata.*) . = ALIGN(0x1000); _end_kernel_text = .; } - .data : { *(.data) } + .data : { *(.data) + _signature = .; LONG(0xaa55aa55) } /* BSS (zero-initialized data) is after everything else. */ _start_bss = .; @@ -23,4 +25,6 @@ SECTIONS _end_bss = .; _end = .; + + ASSERT (_end - _start <= 512K, "Kernel image is too big.") } diff --git a/src/threads/loader.S b/src/threads/loader.S index b7842d3..a5819dd 100644 --- a/src/threads/loader.S +++ b/src/threads/loader.S @@ -1,349 +1,257 @@ -/* This file is derived from source code used in MIT's 6.828 - course. The original copyright notice is reproduced in full - below. */ - -/* - * Copyright (C) 1997 Massachusetts Institute of Technology - * - * This software is being provided by the copyright holders under the - * following license. By obtaining, using and/or copying this software, - * you agree that you have read, understood, and will comply with the - * following terms and conditions: - * - * Permission to use, copy, modify, distribute, and sell this software - * and its documentation for any purpose and without fee or royalty is - * hereby granted, provided that the full text of this NOTICE appears on - * ALL copies of the software and documentation or portions thereof, - * including modifications, that you make. - * - * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO - * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, - * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR - * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR - * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY - * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT - * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR - * DOCUMENTATION. - * - * The name and trademarks of copyright holders may NOT be used in - * advertising or publicity pertaining to the software without specific, - * written prior permission. Title to copyright in this software and any - * associated documentation will at all times remain with copyright - * holders. See the file AUTHORS which should have accompanied this software - * for a list of all copyright holders. - * - * This file may be derived from previously copyrighted software. This - * copyright applies only to those changes made by the copyright - * holders listed in the AUTHORS file. The rest of this file is covered by - * the copyright notices, if any, listed below. - */ - #include "threads/loader.h" - + #### Kernel loader. -#### This code should be stored in the first sector of the hard disk. +#### This code should be stored in the first sector of a hard disk. #### When the BIOS runs, it loads this code at physical address -#### 0x7c00-0x7e00 (512 bytes). Then it jumps to the beginning of it, -#### in real mode. This code switches into protected mode (32-bit -#### mode) so that all of memory can accessed, loads the kernel into -#### memory, and jumps to the first byte of the kernel, where start.S -#### is linked. - -/* Flags in control register 0. */ -#define CR0_PE 0x00000001 /* Protection Enable. */ -#define CR0_EM 0x00000004 /* (Floating-point) Emulation. */ -#define CR0_PG 0x80000000 /* Paging. */ -#define CR0_WP 0x00010000 /* Write-Protect enable in kernel mode. */ - - -.globl start -start: - -# Code runs in real mode, which is a 16-bit segment. +#### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it, +#### in real mode. The loader loads the kernel into memory and jumps +#### to its entry point, which is the start function in start.S. +#### +#### The BIOS passes in the drive that the loader was read from as +#### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives +#### numbered 0x80, 0x81, ... We want to support booting a kernel on +#### a different drive from the loader, so we don't take advantage of +#### this. + +# Runs in real mode, which is a 16-bit segment. .code16 -# Disable interrupts, because we will not be prepared to handle them -# in protected mode until much later. -# String instructions go upward (e.g. for "rep stosl" below). - - cli - cld - -# Set up data segments. - - subw %ax, %ax - movw %ax, %es - movw %ax, %ds - -# Set up stack segment. -# Stack grows downward starting from us. -# We don't ever use the stack, but we call into the BIOS, -# which might. - - movw %ax, %ss - movw $0x7c00, %sp - -#### Enable A20. Address line 20 is tied to low when the machine -#### boots, which prevents addressing memory about 1 MB. This code -#### fixes it. - -# Poll status register while busy. - -1: inb $0x64, %al - testb $0x2, %al - jnz 1b - -# Send command for writing output port. - - movb $0xd1, %al - outb %al, $0x64 - -# Poll status register while busy. - -1: inb $0x64, %al - testb $0x2, %al - jnz 1b - -# Enable A20 line. - - movb $0xdf, %al - outb %al, $0x60 - -#### Get memory size, via interrupt 15h function 88h. Returns CF -#### clear if successful, with AX = (kB of physical memory) - 1024. -#### This only works for memory sizes <= 65 MB, which should be fine -#### for our purposes. We cap memory at 64 MB because that's all we -#### prepare page tables for, below. - - movb $0x88, %ah - int $0x15 - jc panic - cli # BIOS might have enabled interrupts - addl $1024, %eax # Total kB memory - cmp $0x10000, %eax # Cap at 64 MB +# Set up segment registers. +# Set stack to grow downward from 60 kB (after boot, the kernel +# continues to use this stack for its initial thread). + + sub %ax, %ax + mov %ax, %ds + mov %ax, %ss + mov $0xf000, %esp + +# Configure serial port so we can report progress without connected VGA. +# See [IntrList] for details. + sub %dx, %dx # Serial port 0. + mov $0x00e3, %ax # 9600 bps, N-8-1. + int $0x14 # Destroys AX. + + call puts + .string "Pintos loader" + +#### Read the partition table on each system hard disk and scan for a +#### partition of type 0x20, which is the type that we use for a +#### Pintos kernel. +#### +#### Read [Partitions] for a description of the partition table format +#### that we parse. +#### +#### We print out status messages to show the disk and partition being +#### scanned, e.g. hda1234 as we scan four partitions on the first +#### hard disk. + + mov $0x80, %dl # Hard disk 0. +read_mbr: + sub %ebx, %ebx # Sector 0. + mov $0x2000, %ax # Use 0x20000 for buffer. + mov %ax, %es + call read_sector + jc no_such_drive + + # Print hd[a-z]. + call puts + .string " hd" + mov %dl, %al + add $'a' - 0x80, %al + call putc + + # Check for MBR signature--if not present, it's not a + # partitioned hard disk. + cmpw $0xaa55, %es:510 + jne next_drive + + mov $446, %si # Offset of partition table entry 1. + mov $'1', %al +check_partition: + # Is it an unused partition? + cmpl $0, %es:(%si) + je next_partition + + # Print [1-4]. + call putc + + # Is it a Pintos kernel partition? + cmpb $0x20, %es:4(%si) + jne next_partition + + # Is it a bootable partition? + cmpb $0x80, %es:(%si) + je load_kernel + +next_partition: + # No match for this partition, go on to the next one. + add $16, %si # Offset to next partition table entry. + inc %al + cmp $510, %si + jb check_partition + +next_drive: + # No match on this drive, go on to the next one. + inc %dl + jnc read_mbr + +no_such_drive: +no_boot_partition: + # Didn't find a Pintos kernel partition anywhere, give up. + call puts + .string "\rNot found\r" + + # Notify BIOS that boot failed. See [IntrList]. + int $0x18 + +#### We found a kernel. The kernel's drive is in DL. The partition +#### table entry for the kernel's partition is at ES:SI. Our job now +#### is to read the kernel from disk and jump to its start address. + +load_kernel: + call puts + .string "\rLoading" + + # Figure out number of sectors to read. A Pintos kernel is + # just an ELF format object, which doesn't have an + # easy-to-read field to identify its own size (see [ELF1]). + # But we limit Pintos kernels to 512 kB for other reasons, so + # it's easy enough to just read the entire contents of the + # partition or 512 kB from disk, whichever is smaller. + mov %es:12(%si), %ecx # EBP = number of sectors + cmp $1024, %ecx # Cap size at 512 kB jbe 1f - mov $0x10000, %eax -1: shrl $2, %eax # Total 4 kB pages - movl %eax, ram_pgs - -#### Create temporary page directory and page table and set page -#### directory base register. - -# Create page directory at 64 kB and fill with zeroes. - mov $0x1000, %ax + mov $1024, %cx +1: + + mov %es:8(%si), %ebx # EBX = first sector + mov $0x2000, %ax # Start load address: 0x20000 + +next_sector: + # Read one sector into memory. + mov %ax, %es # ES:0000 -> load address + call read_sector + jc read_failed + + # Print '.' as progress indicator once every 16 sectors == 8 kB. + test $15, %bl + jnz 1f + call puts + .string "." +1: + + # Advance memory pointer and disk sector. + add $0x20, %ax + inc %bx + loop next_sector + + call puts + .string "\r" + +#### Transfer control to the kernel that we loaded. We read the start +#### address out of the ELF header (see [ELF1]) and convert it from a +#### 32-bit linear address into a 16:16 segment:offset address for +#### real mode, then jump to the converted address. The 80x86 doesn't +#### have an instruction to jump to an absolute segment:offset kept in +#### registers, so in fact we store the address in a temporary memory +#### location, then jump indirectly through that location. To save 4 +#### bytes in the loader, we reuse 4 bytes of the loader's code for +#### this temporary pointer. + + mov $0x2000, %ax mov %ax, %es - subl %eax, %eax - subl %edi, %edi - movl $0x400, %ecx - rep stosl - -# Add PDEs to point to PTEs for the first 64 MB of RAM. -# Also add identical PDEs starting at LOADER_PHYS_BASE. -# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries" -# for a description of the bits in %eax. - - - movl $0x11007, %eax - movl $0x11, %ecx - subl %edi, %edi -1: movl %eax, %es:(%di) - movl %eax, %es:LOADER_PHYS_BASE >> 20(%di) - addw $4, %di - addl $0x1000, %eax - loop 1b - -# Set up one-to-map linear to physical map for the first 64 MB of RAM. -# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries" -# for a description of the bits in %eax. - - movw $0x1100, %ax - movw %ax, %es - movl $0x7, %eax - movl $0x4000, %ecx - subl %edi, %edi -1: movl %eax, %es:(%di) - addw $4, %di - addl $0x1000, %eax - loop 1b - -# Set page directory base register. - - movl $0x10000, %eax - movl %eax, %cr3 - -#### Switch to protected mode. - -# Note that interrupts are still off. - -# Point the GDTR to our GDT. Protected mode requires a GDT. -# We need a data32 prefix to ensure that all 32 bits of the GDT -# descriptor are loaded (default is to load only 24 bits). - - data32 lgdt gdtdesc - -# Then we turn on the following bits in CR0: -# PE (Protect Enable): this turns on protected mode. -# PG (Paging): turns on paging. -# WP (Write Protect): if unset, ring 0 code ignores -# write-protect bits in page tables (!). -# EM (Emulation): forces floating-point instructions to trap. -# We don't support floating point. - - movl %cr0, %eax - orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax - movl %eax, %cr0 - -# We're now in protected mode in a 16-bit segment. The CPU still has -# the real-mode code segment cached in %cs's segment descriptor. We -# need to reload %cs, and the easiest way is to use a far jump. -# Because we're not in a 32-bit segment the data32 prefix is needed to -# jump to a 32-bit offset. - - data32 ljmp $SEL_KCSEG, $1f + LOADER_PHYS_BASE - -# We're now in protected mode in a 32-bit segment. - - .code32 - -# Reload all the other segment registers and the stack pointer to -# point into our new GDT. - -1: movw $SEL_KDSEG, %ax - movw %ax, %ds - movw %ax, %es - movw %ax, %fs - movw %ax, %gs - movw %ax, %ss - movl $LOADER_PHYS_BASE + 0x30000, %esp - -#### Load kernel starting at physical address LOADER_KERN_BASE by -#### frobbing the IDE controller directly. - - movl $1, %ebx - movl $LOADER_KERN_BASE + LOADER_PHYS_BASE, %edi - -# Disable interrupt delivery by IDE controller, because we will be -# polling for data. -# (If we don't do this, Bochs 2.2.6 will never deliver any IDE -# interrupt to us later after we reset the interrupt controller during -# boot, even if we also reset the IDE controller.) - - movw $0x3f6, %dx - movb $0x02, %al - outb %al, %dx - -read_sector: - -# Poll status register while controller busy. - - movl $0x1f7, %edx -1: inb %dx, %al - testb $0x80, %al - jnz 1b - -# Read a single sector. - - movl $0x1f2, %edx - movb $1, %al - outb %al, %dx - -# Sector number to write in low 28 bits. -# LBA mode, device 0 in top 4 bits. - - movl %ebx, %eax - andl $0x0fffffff, %eax - orl $0xe0000000, %eax - -# Dump %eax to ports 0x1f3...0x1f6. - - movl $4, %ecx -1: incw %dx - outb %al, %dx - shrl $8, %eax - loop 1b - -# READ command to command register. - - incw %dx - movb $0x20, %al - outb %al, %dx - -# Poll status register while controller busy. - -1: inb %dx, %al - testb $0x80, %al - jnz 1b + mov %es:0x18, %dx + mov %dx, start + movw $0x2000, start + 2 + ljmp *start -# Poll status register until data ready. - -1: inb %dx, %al - testb $0x08, %al - jz 1b - -# Transfer sector. - - movl $256, %ecx - movl $0x1f0, %edx - rep insw - -# Next sector. - - incl %ebx - cmpl $KERNEL_LOAD_PAGES*8 + 1, %ebx - jnz read_sector - -#### Jump to kernel entry point. - - movl $LOADER_PHYS_BASE + LOADER_KERN_BASE, %eax - call *%eax - jmp panic - -#### GDT - -gdt: - .quad 0x0000000000000000 # null seg - .quad 0x00cf9a000000ffff # code seg - .quad 0x00cf92000000ffff # data seg - -gdtdesc: - .word 0x17 # sizeof (gdt) - 1 - .long gdt + LOADER_PHYS_BASE # address gdt - -#### Fatal error. -#### Print panic_message (with help from the BIOS) and spin. - -panic: .code16 # We only panic in real mode. - movw $panic_message, %si - movb $0xe, %ah - subb %bh, %bh -1: lodsb +read_failed: +start: + # Disk sector read failed. + call puts +1: .string "\rBad read\r" + + # Notify BIOS that boot failed. See [IntrList]. + int $0x18 + +#### Print string subroutine. To save space in the loader, this +#### subroutine takes its null-terminated string argument from the +#### code stream just after the call, and then returns to the byte +#### just after the terminating null. This subroutine preserves all +#### general-purpose registers. + +puts: xchg %si, %ss:(%esp) + push %ax +next_char: + mov %cs:(%si), %al + inc %si test %al, %al -2: jz 2b # Spin. + jz 1f + call putc + jmp next_char +1: pop %ax + xchg %si, %ss:(%esp) + ret + +#### Character output subroutine. Prints the character in AL to the +#### VGA display and serial port 0, using BIOS services (see +#### [IntrList]). Preserves all general-purpose registers. +#### +#### If called upon to output a carriage return, this subroutine +#### automatically supplies the following line feed. + +putc: pusha + +1: sub %bh, %bh # Page 0. + mov $0x0e, %ah # Teletype output service. int $0x10 + + mov $0x01, %ah # Serial port output service. + sub %dx, %dx # Serial port 0. + int $0x14 # Destroys AH. + + cmp $'\r', %al + jne popa_ret + mov $'\n', %al jmp 1b -panic_message: - .ascii "Panic!" - .byte 0 +#### Sector read subroutine. Takes a drive number in DL (0x80 = hard +#### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and +#### reads the specified sector into memory at ES:0000. Returns with +#### carry set on error, clear otherwise. Preserves all +#### general-purpose registers. -#### Physical memory size in 4 kB pages. -#### This is initialized by the loader and read by the kernel. - .org LOADER_RAM_PGS - LOADER_BASE -ram_pgs: - .long 0 +read_sector: + pusha + sub %eax, %eax + push %eax # LBA sector number [32:63] + push %ebx # LBA sector number [0:31] + push %es # Buffer segment + push %ax # Buffer offset (always 0) + push $1 # Number of sectors to read + push $16 # Packet size + mov $0x42, %ah # Extended read + mov %sp, %si # DS:SI -> packet + int $0x13 # Error code in CF + popa # Pop 16 bytes, preserve flags +popa_ret: + popa + ret # Error code still in CF #### Command-line arguments and their count. #### This is written by the `pintos' utility and read by the kernel. #### The loader itself does not do anything with the command line. .org LOADER_ARG_CNT - LOADER_BASE -arg_cnt: - .long 0 + .fill LOADER_ARG_CNT_LEN, 1, 0 + .org LOADER_ARGS - LOADER_BASE -args: - .fill 0x80, 1, 0 + .fill LOADER_ARGS_LEN, 1, 0 + +#### Partition table. + .org LOADER_PARTS - LOADER_BASE + .fill LOADER_PARTS_LEN, 1, 0 -#### Boot-sector signature. -#### The BIOS checks that this is set properly. +#### Boot-sector signature for BIOS inspection. .org LOADER_SIG - LOADER_BASE .word 0xaa55 diff --git a/src/threads/loader.h b/src/threads/loader.h index 5bd9813..1bfe111 100644 --- a/src/threads/loader.h +++ b/src/threads/loader.h @@ -6,28 +6,23 @@ #define LOADER_END 0x7e00 /* Physical address of end of loader. */ /* Physical address of kernel base. */ -#define LOADER_KERN_BASE 0x100000 /* 1 MB. */ +#define LOADER_KERN_BASE 0x20000 /* 128 kB. */ /* Kernel virtual address at which all physical memory is mapped. - - The loader maps the 4 MB at the bottom of physical memory to - this virtual base address. Later, paging_init() adds the rest - of physical memory to the mapping. - - This must be aligned on a 4 MB boundary. */ + Must be aligned on a 4 MB boundary. */ #define LOADER_PHYS_BASE 0xc0000000 /* 3 GB. */ /* Important loader physical addresses. */ #define LOADER_SIG (LOADER_END - LOADER_SIG_LEN) /* 0xaa55 BIOS signature. */ -#define LOADER_ARGS (LOADER_SIG - LOADER_ARGS_LEN) /* Command-line args. */ +#define LOADER_PARTS (LOADER_SIG - LOADER_PARTS_LEN) /* Partition table. */ +#define LOADER_ARGS (LOADER_PARTS - LOADER_ARGS_LEN) /* Command-line args. */ #define LOADER_ARG_CNT (LOADER_ARGS - LOADER_ARG_CNT_LEN) /* Number of args. */ -#define LOADER_RAM_PGS (LOADER_ARG_CNT - LOADER_RAM_PGS_LEN) /* # RAM pages. */ /* Sizes of loader data structures. */ #define LOADER_SIG_LEN 2 +#define LOADER_PARTS_LEN 64 #define LOADER_ARGS_LEN 128 #define LOADER_ARG_CNT_LEN 4 -#define LOADER_RAM_PGS_LEN 4 /* GDT selectors defined by loader. More selectors are defined by userprog/gdt.h. */ @@ -35,4 +30,11 @@ #define SEL_KCSEG 0x08 /* Kernel code selector. */ #define SEL_KDSEG 0x10 /* Kernel data selector. */ +#ifndef __ASSEMBLER__ +#include + +/* Amount of physical memory, in 4 kB pages. */ +extern uint32_t init_ram_pages; +#endif + #endif /* threads/loader.h */ diff --git a/src/threads/palloc.c b/src/threads/palloc.c index 177001f..4fc8394 100644 --- a/src/threads/palloc.c +++ b/src/threads/palloc.c @@ -7,7 +7,6 @@ #include #include #include -#include "threads/init.h" #include "threads/loader.h" #include "threads/synch.h" #include "threads/vaddr.h" @@ -46,12 +45,8 @@ static bool page_from_pool (const struct pool *, void *page); void palloc_init (size_t user_page_limit) { - /* End of the kernel as recorded by the linker. - See kernel.lds.S. */ - extern char _end; - - /* Free memory. */ - uint8_t *free_start = pg_round_up (&_end); + /* Free memory starts at 1 MB and runs to the end of RAM. */ + uint8_t *free_start = ptov (1024 * 1024); uint8_t *free_end = ptov (init_ram_pages * PGSIZE); size_t free_pages = (free_end - free_start) / PGSIZE; size_t user_pages = free_pages / 2; diff --git a/src/threads/start.S b/src/threads/start.S index 68c604c..29ffa7a 100644 --- a/src/threads/start.S +++ b/src/threads/start.S @@ -1,19 +1,204 @@ -#### The loader needs to have some way to know the kernel's entry -#### point, that is, the address to which it should jump to start the -#### kernel. We handle this by writing the linker script kernel.lds.S -#### so that this module appears at the very beginning of the kernel -#### image, and then using that as the entry point. - -.section .start - -.globl start + #include "threads/loader.h" + +#### Kernel startup code. + +#### The loader (in loader.S) loads the kernel at physical address +#### 0x20000 (128 kB) and jumps to "start", defined here. This code +#### switches from real mode to 32-bit protected mode and calls +#### main(). + +/* Flags in control register 0. */ +#define CR0_PE 0x00000001 /* Protection Enable. */ +#define CR0_EM 0x00000004 /* (Floating-point) Emulation. */ +#define CR0_PG 0x80000000 /* Paging. */ +#define CR0_WP 0x00010000 /* Write-Protect enable in kernel mode. */ + + .section .start + +# The following code runs in real mode, which is a 16-bit code segment. + .code16 + .func start - # Terminate the backtrace that debug_backtrace() would output. - movl $0, %ebp +.globl start +start: + +# The loader called into us with CS = 0x2000, SS = 0x0000, ESP = 0xf000, +# but we should initialize the other segment registers. + + mov $0x2000, %ax + mov %ax, %ds + mov %ax, %es + +# Set string instructions to go upward. + cld + +#### Get memory size, via interrupt 15h function 88h (see [IntrList]), +#### which returns AX = (kB of physical memory) - 1024. This only +#### works for memory sizes <= 65 MB, which should be fine for our +#### purposes. We cap memory at 64 MB because that's all we prepare +#### page tables for, below. + + movb $0x88, %ah + int $0x15 + addl $1024, %eax # Total kB memory + cmp $0x10000, %eax # Cap at 64 MB + jbe 1f + mov $0x10000, %eax +1: shrl $2, %eax # Total 4 kB pages + addr32 movl %eax, init_ram_pages - LOADER_PHYS_BASE - 0x20000 + +#### Enable A20. Address line 20 is tied low when the machine boots, +#### which prevents addressing memory about 1 MB. This code fixes it. + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +# Send command for writing output port. + + movb $0xd1, %al + outb %al, $0x64 + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +# Enable A20 line. + + movb $0xdf, %al + outb %al, $0x60 + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +#### Create temporary page directory and page table and set page +#### directory base register. + +# Create page directory at 0xf000 (60 kB) and fill with zeroes. + mov $0xf00, %ax + mov %ax, %es + subl %eax, %eax + subl %edi, %edi + movl $0x400, %ecx + rep stosl - # Call main. -start: call main +# Add PDEs to point to page tables for the first 64 MB of RAM. +# Also add identical PDEs starting at LOADER_PHYS_BASE. +# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries" +# for a description of the bits in %eax. + + movl $0x10007, %eax + movl $0x11, %ecx + subl %edi, %edi +1: movl %eax, %es:(%di) + movl %eax, %es:LOADER_PHYS_BASE >> 20(%di) + addw $4, %di + addl $0x1000, %eax + loop 1b + +# Set up page tables for one-to-map linear to physical map for the +# first 64 MB of RAM. +# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries" +# for a description of the bits in %eax. + + movw $0x1000, %ax + movw %ax, %es + movl $0x7, %eax + movl $0x4000, %ecx + subl %edi, %edi +1: movl %eax, %es:(%di) + addw $4, %di + addl $0x1000, %eax + loop 1b + +# Set page directory base register. + + movl $0xf000, %eax + movl %eax, %cr3 + +#### Switch to protected mode. + +# First, disable interrupts. We won't set up the IDT until we get +# into C code, so any interrupt would blow us away. + + cli + +# Protected mode requires a GDT, so point the GDTR to our GDT. +# We need a data32 prefix to ensure that all 32 bits of the GDT +# descriptor are loaded (default is to load only 24 bits). +# The CPU doesn't need an addr32 prefix but ELF doesn't do 16-bit +# relocations. + + data32 addr32 lgdt gdtdesc - LOADER_PHYS_BASE - 0x20000 + +# Then we turn on the following bits in CR0: +# PE (Protect Enable): this turns on protected mode. +# PG (Paging): turns on paging. +# WP (Write Protect): if unset, ring 0 code ignores +# write-protect bits in page tables (!). +# EM (Emulation): forces floating-point instructions to trap. +# We don't support floating point. + + movl %cr0, %eax + orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax + movl %eax, %cr0 + +# We're now in protected mode in a 16-bit segment. The CPU still has +# the real-mode code segment cached in %cs's segment descriptor. We +# need to reload %cs, and the easiest way is to use a far jump. +# Because we're not running in a 32-bit segment the data32 prefix is +# needed to jump to a 32-bit offset in the target segment. + + data32 ljmp $SEL_KCSEG, $1f + +# We're now in protected mode in a 32-bit segment. +# Let the assembler know. + + .code32 + +# Reload all the other segment registers and the stack pointer to +# point into our new GDT. + +1: mov $SEL_KDSEG, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + addl $LOADER_PHYS_BASE, %esp + movl $0, %ebp # Null-terminate main()'s backtrace + +#### Call main(). + + call main + +# main() shouldn't ever return. If it does, spin. - # main() should not return, but if it does, spin. 1: jmp 1b .endfunc + +#### GDT + + .align 8 +gdt: + .quad 0x0000000000000000 # Null segment. Not used by CPU. + .quad 0x00cf9a000000ffff # System code, base 0, limit 4 GB. + .quad 0x00cf92000000ffff # System data, base 0, limit 4 GB. + +gdtdesc: + .word gdtdesc - gdt - 1 # Size of the GDT, minus 1 byte. + .long gdt # Address of the GDT. + +#### Physical memory size in 4 kB pages. This is exported to the rest +#### of the kernel. +.globl init_ram_pages +init_ram_pages: + .long 0 + diff --git a/src/userprog/Make.vars b/src/userprog/Make.vars index 9db3cfa..e4dbb08 100644 --- a/src/userprog/Make.vars +++ b/src/userprog/Make.vars @@ -1,6 +1,6 @@ # -*- makefile -*- -os.dsk: DEFINES = -DUSERPROG -DFILESYS +kernel.bin: DEFINES = -DUSERPROG -DFILESYS KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base GRADING_FILE = $(SRCDIR)/tests/userprog/Grading diff --git a/src/utils/Pintos.pm b/src/utils/Pintos.pm new file mode 100644 index 0000000..70df40d --- /dev/null +++ b/src/utils/Pintos.pm @@ -0,0 +1,491 @@ +# Pintos helper subroutines. + +# Number of bytes available for the loader at the beginning of the MBR. +# Kernel command-line arguments follow the loader. +our $LOADER_SIZE = 314; + +# Partition types. +my (%role2type) = (KERNEL => 0x20, + FILESYS => 0x21, + SCRATCH => 0x22, + SWAP => 0x23); +my (%type2role) = reverse %role2type; + +# Order of roles within a given disk. +our (@role_order) = qw (KERNEL FILESYS SCRATCH SWAP); + +# Partitions. +# +# Valid keys are KERNEL, FILESYS, SCRATCH, SWAP. Only those +# partitions which are in use are included. +# +# Each value is a reference to a hash. If the partition's contents +# are to be obtained from a file (that will be copied into a new +# virtual disk), then the hash contains: +# +# FILE => name of file from which the partition's contents are copied +# (perhaps "/dev/zero"), +# OFFSET => offset in bytes in FILE, +# BYTES => size in bytes of contents from FILE, +# +# If the partition is taken from a virtual disk directly, then it +# contains the following. The same keys are also filled in once a +# file-based partition has been copied into a new virtual disk: +# +# DISK => name of virtual disk file, +# START => sector offset of start of partition within DISK, +# SECTORS => number of sectors of partition within DISK, which is usually +# greater than round_up (BYTES, 512) due to padding. +our (%parts); + +# set_part($opt, $arg) +# +# For use as a helper function for Getopt::Long::GetOptions to set +# disk sources. +sub set_part { + my ($opt, $arg) = @_; + my ($role, $source) = $opt =~ /^([a-z]+)(?:-([a-z]+))?/ or die; + + $role = uc $role; + $source = 'FILE' if $source eq ''; + + die "can't have two sources for \L$role\E partition" + if exists $parts{$role}; + + do_set_part ($role, $source, $arg); +} + +# do_set_part($role, $source, $arg) +# +# Sets partition $role as coming from $source (one of 'file', 'from', +# or 'size'). $arg is a file name for 'file' or 'from', a size in +# megabytes for 'size'. +sub do_set_part { + my ($role, $source, $arg) = @_; + + my ($p) = $parts{$role} = {}; + if ($source eq 'file') { + if (read_mbr ($arg)) { + print STDERR "warning: $arg looks like a partitioned disk "; + print STDERR "(did you want --$role-from=$arg or --disk=$arg?)\n" + } + + $p->{FILE} = $arg; + $p->{OFFSET} = 0; + $p->{BYTES} = -s $arg; + } elsif ($source eq 'from') { + my (%pt) = read_partition_table ($arg); + my ($sp) = $pt{$role}; + die "$arg: does not contain \L$role\E partition\n" if !defined $sp; + + $p->{FILE} = $arg; + $p->{OFFSET} = $sp->{START} * 512; + $p->{BYTES} = $sp->{SECTORS} * 512; + } elsif ($source eq 'size') { + $arg =~ /^\d+(\.\d+)?|\.\d+$/ or die "$arg: not a valid size in MB\n"; + + $p->{FILE} = "/dev/zero"; + $p->{OFFSET} = 0; + $p->{BYTES} = ceil ($arg * 1024 * 1024); + } else { + die; + } +} + +# set_geometry('HEADS,SPT') +# set_geometry('zip') +# +# For use as a helper function for Getopt::Long::GetOptions to set +# disk geometry. +sub set_geometry { + local ($_) = $_[1]; + if ($_ eq 'zip') { + @geometry{'H', 'S'} = (64, 32); + } else { + @geometry{'H', 'S'} = /^(\d+)[,\s]+(\d+)$/ + or die "bad syntax for geometry\n"; + $geometry{H} <= 255 or die "heads limited to 255\n"; + $geometry{S} <= 63 or die "sectors per track limited to 63\n"; + } +} + +# set_align('bochs|full|none') +# +# For use as a helper function for Getopt::Long::GetOptions to set +# partition alignment. +sub set_align { + $align = $_[1]; + die "unknown alignment type \"$align\"\n" + if $align ne 'bochs' && $align ne 'full' && $align ne 'none'; +} + +# assemble_disk(%args) +# +# Creates a virtual disk $args{DISK} containing the partitions +# described by @args{KERNEL, FILESYS, SCRATCH, SWAP}. +# +# Required arguments: +# DISK => output disk file name +# HANDLE => output file handle (will be closed) +# +# Normally at least one of the following is included: +# KERNEL, FILESYS, SCRATCH, SWAP => {input: +# FILE => file to read, +# OFFSET => byte offset in file, +# BYTES => byte count from file, +# +# output: +# DISK => output disk file name, +# START => sector offset in DISK, +# SECTORS => sector count in DISK}, +# +# Optional arguments: +# ALIGN => 'bochs' (default), 'full', or 'none' +# GEOMETRY => {H => heads, S => sectors per track} (default 16, 63) +# FORMAT => 'partitioned' (default) or 'raw' +# LOADER => $LOADER_SIZE-byte string containing the loader binary +# ARGS => ['arg 1', 'arg 2', ...] +sub assemble_disk { + my (%args) = @_; + + my (%geometry) = $args{GEOMETRY} || (H => 16, S => 63); + + my ($align); # Align partition start, end to cylinder boundary? + my ($pad); # Pad end of disk out to cylinder boundary? + if (!defined ($args{ALIGN}) || $args{ALIGN} eq 'bochs') { + $align = 0; + $pad = 1; + } elsif ($args{ALIGN} eq 'full') { + $align = 1; + $pad = 0; + } elsif ($args{ALIGN} eq 'none') { + $align = $pad = 0; + } else { + die; + } + + my ($format) = $args{FORMAT} || 'partitioned'; + die if $format ne 'partitioned' && $format ne 'raw'; + + # Check that we have apartitions to copy in. + my $part_cnt = grep (defined ($args{$_}), keys %role2type); + die "must have exactly one partition for raw output\n" + if $format eq 'raw' && $part_cnt != 1; + + # Calculate the disk size. + my ($total_sectors) = 0; + if ($format eq 'partitioned') { + $total_sectors += $align ? $geometry{S} : 1; + } + for my $role (@role_order) { + my ($p) = $args{$role}; + next if !defined $p; + + die if $p->{DISK}; + + my ($bytes) = $p->{BYTES}; + my ($start) = $total_sectors; + my ($end) = $start + div_round_up ($bytes, 512); + $end = round_up ($end, cyl_sectors (%geometry)) if $align; + + $p->{DISK} = $args{DISK}; + $p->{START} = $start; + $p->{SECTORS} = $end - $start; + $total_sectors = $end; + } + + # Write the disk. + my ($disk_fn) = $args{DISK}; + my ($disk) = $args{HANDLE}; + if ($format eq 'partitioned') { + # Pack loader into MBR. + my ($loader) = $args{LOADER} || "\xcd\x18"; + my ($mbr) = pack ("a$LOADER_SIZE", $loader); + + $mbr .= make_kernel_command_line (@{$args{ARGS}}); + + # Pack partition table into MBR. + $mbr .= make_partition_table (\%geometry, \%args); + + # Add signature to MBR. + $mbr .= pack ("v", 0xaa55); + + die if length ($mbr) != 512; + write_fully ($disk, $disk_fn, $mbr); + write_zeros ($disk, $disk_fn, 512 * ($geometry{S} - 1)) if $align; + } + for my $role (@role_order) { + my ($p) = $args{$role}; + next if !defined $p; + + my ($source); + my ($fn) = $p->{FILE}; + open ($source, '<', $fn) or die "$fn: open: $!\n"; + if ($p->{OFFSET}) { + sysseek ($source, $p->{OFFSET}, 0) == $p->{OFFSET} + or die "$fn: seek: $!\n"; + } + copy_file ($source, $fn, $disk, $disk_fn, $p->{BYTES}); + close ($source) or die "$fn: close: $!\n"; + + write_zeros ($disk, $disk_fn, $p->{SECTORS} * 512 - $p->{BYTES}); + } + if ($pad) { + my ($pad_sectors) = round_up ($total_sectors, cyl_sectors (%geometry)); + write_zeros ($disk, $disk_fn, ($pad_sectors - $total_sectors) * 512); + } + close ($disk) or die "$disk: close: $!\n"; +} + +# make_partition_table({H => heads, S => sectors}, {KERNEL => ..., ...}) +# +# Creates and returns a partition table for the given partitions and +# disk geometry. +sub make_partition_table { + my ($geometry, $partitions) = @_; + my ($table) = ''; + for my $role (@role_order) { + defined (my $p = $partitions->{$role}) or next; + + my $end = $p->{START} + $p->{SECTORS} - 1; + my $bootable = $role eq 'KERNEL'; + + $table .= pack ("C", $bootable ? 0x80 : 0); # Bootable? + $table .= pack_chs ($p->{START}, $geometry); # CHS of partition start + $table .= pack ("C", $role2type{$role}); # Partition type + $table .= pack_chs($end, $geometry); # CHS of partition end + $table .= pack ("V", $p->{START}); # LBA of partition start + $table .= pack ("V", $p->{SECTORS}); # Length in sectors + die if length ($table) % 16; + } + return pack ("a64", $table); +} + +# make_kernel_command_line(@args) +# +# Returns the raw bytes to write to an MBR at offset $LOADER_SIZE to +# set a Pintos kernel command line. +sub make_kernel_command_line { + my (@args) = @_; + my ($args) = join ('', map ("$_\0", @args)); + die "command line exceeds 128 bytes" if length ($args) > 128; + return pack ("V a128", scalar (@args), $args); +} + +# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size) +# +# Copies $size bytes from $from_handle to $to_handle. +# $from_file_name and $to_file_name are used in error messages. +sub copy_file { + my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_; + + while ($size > 0) { + my ($chunk_size) = 4096; + $chunk_size = $size if $chunk_size > $size; + $size -= $chunk_size; + + my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size); + write_fully ($to_handle, $to_file_name, $data); + } +} + +# read_fully($handle, $file_name, $bytes) +# +# Reads exactly $bytes bytes from $handle and returns the data read. +# $file_name is used in error messages. +sub read_fully { + my ($handle, $file_name, $bytes) = @_; + my ($data); + my ($read_bytes) = sysread ($handle, $data, $bytes); + die "$file_name: read: $!\n" if !defined $read_bytes; + die "$file_name: unexpected end of file\n" if $read_bytes != $bytes; + return $data; +} + +# write_fully($handle, $file_name, $data) +# +# Write $data to $handle. +# $file_name is used in error messages. +sub write_fully { + my ($handle, $file_name, $data) = @_; + my ($written_bytes) = syswrite ($handle, $data); + die "$file_name: write: $!\n" if !defined $written_bytes; + die "$file_name: short write\n" if $written_bytes != length $data; +} + +sub write_zeros { + my ($handle, $file_name, $size) = @_; + + while ($size > 0) { + my ($chunk_size) = 4096; + $chunk_size = $size if $chunk_size > $size; + $size -= $chunk_size; + + write_fully ($handle, $file_name, "\0" x $chunk_size); + } +} + +# div_round_up($x,$y) +# +# Returns $x / $y, rounded up to the nearest integer. +# $y must be an integer. +sub div_round_up { + my ($x, $y) = @_; + return int ((ceil ($x) + $y - 1) / $y); +} + +# round_up($x, $y) +# +# Returns $x rounded up to the nearest multiple of $y. +# $y must be an integer. +sub round_up { + my ($x, $y) = @_; + return div_round_up ($x, $y) * $y; +} + +# cyl_sectors(H => heads, S => sectors) +# +# Returns the number of sectors in a cylinder of a disk with the given +# geometry. +sub cyl_sectors { + my (%geometry) = @_; + return $geometry{H} * $geometry{S}; +} + +# read_loader($file_name) +# +# Reads and returns the first $LOADER_SIZE bytes in $file_name. +# If $file_name is undefined, tries to find the default loader. +# Makes sure that the loader is a reasonable size. +sub read_loader { + my ($name) = @_; + $name = find_file ("loader.bin") if !defined $name; + die "Cannot find loader\n" if !defined $name; + + my ($handle); + open ($handle, '<', $name) or die "$name: open: $!\n"; + -s $handle == $LOADER_SIZE || -s $handle == 512 + or die "$name: must be exactly $LOADER_SIZE or 512 bytes long\n"; + $loader = read_fully ($handle, $name, $LOADER_SIZE); + close ($handle) or die "$name: close: $!\n"; + return $loader; +} + +# pack_chs($lba, {H => heads, S => sectors}) +# +# Converts logical sector $lba to a 3-byte packed geometrical sector +# in the format used in PC partition tables (see [Partitions]) and +# returns the geometrical sector as a 3-byte string. +sub pack_chs { + my ($lba, $geometry) = @_; + my ($cyl, $head, $sect) = lba_to_chs ($lba, $geometry); + return pack ("CCC", $head, $sect | (($cyl >> 2) & 0xc0), $cyl & 0xff); +} + +# lba_to_chs($lba, {H => heads, S => sectors}) +# +# Returns the geometrical sector corresponding to logical sector $lba +# given the specified geometry. +sub lba_to_chs { + my ($lba, $geometry) = @_; + my ($hpc) = $geometry->{H}; + my ($spt) = $geometry->{S}; + + # Source: + # http://en.wikipedia.org/wiki/CHS_conversion + use integer; + my $cyl = $lba / ($hpc * $spt); + my $temp = $lba % ($hpc * $spt); + my $head = $temp / $spt; + my $sect = $temp % $spt + 1; + + # Source: + # http://www.cgsecurity.org/wiki/Intel_Partition_Table + if ($cyl <= 1023) { + return ($cyl, $head, $sect); + } else { + return (1023, 254, 63); ## or should this be (1023, $hpc, $spt)? + } +} + +# read_mbr($file) +# +# Tries to read an MBR from $file. Returns the 512-byte MBR if +# successful, otherwise numeric 0. +sub read_mbr { + my ($file) = @_; + my ($retval) = 0; + open (FILE, '<', $file) or die "$file: open: $!\n"; + if (-s FILE == 0) { + die "$file: file has zero size\n"; + } elsif (-s FILE >= 512) { + my ($mbr); + sysread (FILE, $mbr, 512) == 512 or die "$file: read: $!\n"; + $retval = $mbr if unpack ("v", substr ($mbr, 510)) == 0xaa55; + } + close (FILE); + return $retval; +} + +# interpret_partition_table($mbr, $disk) +# +# Parses the partition-table in the specified 512-byte $mbr and +# returns the partitions. $disk is used for error messages. +sub interpret_partition_table { + my ($mbr, $disk) = @_; + my (%parts); + for my $i (0...3) { + my ($bootable, $valid, $type, $lba_start, $lba_length) + = unpack ("C X V C x3 V V", substr ($mbr, 446 + 16 * $i, 16)); + next if !$valid; + + (print STDERR "warning: invalid partition entry $i in $disk\n"), + next if $bootable != 0 && $bootable != 0x80; + + my ($role) = $type2role{$type}; + (printf STDERR "warning: non-Pintos partition type 0x%02x in %s\n", + $type, $disk), + next if !defined $role; + + (print STDERR "warning: duplicate \L$role\E partition in $disk\n"), + next if exists $parts{$role}; + + $parts{$role} = {START => $lba_start, + SECTORS => $lba_length}; + } + return %parts; +} + +# find_file($base_name) +# +# Looks for a file named $base_name in a couple of likely spots. If +# found, returns the name; otherwise, returns undef. +sub find_file { + my ($base_name) = @_; + -e && return $_ foreach $base_name, "build/$base_name"; + return undef; +} + +# read_partition_table($file) +# +# Reads a partition table from $file and returns the parsed +# partitions. Dies if partitions can't be read. +sub read_partition_table { + my ($file) = @_; + my ($mbr) = read_mbr ($file); + die "$file: not a partitioned disk\n" if !$mbr; + return interpret_partition_table ($mbr, $file); +} + +# max(@args) +# +# Returns the numerically largest value in @args. +sub max { + my ($max) = $_[0]; + foreach (@_[1..$#_]) { + $max = $_ if $_ > $max; + } + return $max; +} + +1; diff --git a/src/utils/pintos b/src/utils/pintos index c71ebc2..4b385cd 100755 --- a/src/utils/pintos +++ b/src/utils/pintos @@ -5,6 +5,10 @@ use POSIX; use Fcntl; use File::Temp 'tempfile'; use Getopt::Long qw(:config bundling); +use Fcntl qw(SEEK_SET SEEK_CUR); + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } # Command-line options. our ($start_time) = time (); @@ -21,16 +25,17 @@ our (@puts); # Files to copy into the VM. our (@gets); # Files to copy out of the VM. our ($as_ref); # Reference to last addition to @gets or @puts. our (@kernel_args); # Arguments to pass to kernel. -our (%disks) = (OS => {DEF_FN => 'os.dsk'}, # Disks to give VM. - FS => {DEF_FN => 'fs.dsk'}, - SCRATCH => {DEF_FN => 'scratch.dsk'}, - SWAP => {DEF_FN => 'swap.dsk'}); -our (@disks_by_iface) = @disks{qw (OS FS SCRATCH SWAP)}; +our (%parts); # Partitions. +our ($make_disk); # Name of disk to create. +our ($tmp_disk) = 1; # Delete $make_disk after run? +our (@disks); # Extra disk images to pass to simulator. +our ($loader_fn); # Bootstrap loader. +our (%geometry); # IDE disk geometry. +our ($align); # Partition alignment. parse_command_line (); -find_disks (); prepare_scratch_disk (); -prepare_arguments (); +find_disks (); run_vm (); finish_scratch_disk (); @@ -39,7 +44,7 @@ exit 0; # Parses the command line. sub parse_command_line { usage (0) if @ARGV == 0 || (@ARGV == 1 && $ARGV[0] eq '--help'); - + @kernel_args = @ARGV; if (grep ($_ eq '--', @kernel_args)) { @ARGV = (); @@ -73,27 +78,41 @@ sub parse_command_line { "h|help" => sub { usage (0); }, - "os-disk=s" => \$disks{OS}{FILE_NAME}, - "fs-disk=s" => \$disks{FS}{FILE_NAME}, - "scratch-disk=s" => \$disks{SCRATCH}{FILE_NAME}, - "swap-disk=s" => \$disks{SWAP}{FILE_NAME}, + "kernel=s" => \&set_part, + "filesys=s" => \&set_part, + "swap=s" => \&set_part, + + "filesys-size=s" => \&set_part, + "scratch-size=s" => \&set_part, + "swap-size=s" => \&set_part, + + "kernel-from=s" => \&set_part, + "filesys-from=s" => \&set_part, + "swap-from=s" => \&set_part, - "0|disk-0|hda=s" => \$disks_by_iface[0]{FILE_NAME}, - "1|disk-1|hdb=s" => \$disks_by_iface[1]{FILE_NAME}, - "2|disk-2|hdc=s" => \$disks_by_iface[2]{FILE_NAME}, - "3|disk-3|hdd=s" => \$disks_by_iface[3]{FILE_NAME}) + "make-disk=s" => sub { $make_disk = $_[1]; + $tmp_disk = 0; }, + "disk=s" => sub { set_disk ($_[1]); }, + "loader=s" => \$loader_fn, + + "geometry=s" => \&set_geometry, + "align=s" => \&set_align) or exit 1; } $sim = "bochs" if !defined $sim; $debug = "none" if !defined $debug; - $vga = "window" if !defined $vga; + $vga = exists ($ENV{DISPLAY}) ? "window" : "none" if !defined $vga; undef $timeout, print "warning: disabling timeout with --$debug\n" if defined ($timeout) && $debug ne 'none'; print "warning: enabling serial port for -k or --kill-on-failure\n" if $kill_on_failure && !$serial; + + $align = "bochs", + print STDERR "warning: setting --align=bochs for Bochs support\n" + if $sim eq 'bochs' && defined ($align) && $align eq 'none'; } # usage($exitcode). @@ -128,15 +147,27 @@ Testing options: panic, test failure, or triple fault Configuration options: -m, --mem=N Give Pintos N MB physical RAM (default: 4) -File system commands (for `run' command): +File system commands: -p, --put-file=HOSTFN Copy HOSTFN into VM, by default under same name -g, --get-file=GUESTFN Copy GUESTFN out of VM, by default under same name -a, --as=FILENAME Specifies guest (for -p) or host (for -g) file name -Disk options: (name an existing FILE or specify SIZE in MB for a temp disk) - --os-disk=FILE Set OS disk file (default: os.dsk) - --fs-disk=FILE|SIZE Set FS disk file (default: fs.dsk) - --scratch-disk=FILE|SIZE Set scratch disk (default: scratch.dsk) - --swap-disk=FILE|SIZE Set swap disk file (default: swap.dsk) +Partition options: (where PARTITION is one of: kernel filesys scratch swap) + --PARTITION=FILE Use a copy of FILE for the given PARTITION + --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB + --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK + (There is no --kernel-size, --scratch, or --scratch-from option.) +Disk configuration options: + --make-disk=DISK Name the new DISK and don't delete it after the run + --disk=DISK Also use existing DISK (may be used multiple times) +Advanced disk configuration options: + --loader=FILE Use FILE as bootstrap loader (default: loader.bin) + --geometry=H,S Use H head, S sector geometry (default: 16,63) + --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot + (see http://syslinux.zytor.com/usbkey.php) + --align=bochs Pad out disk to cylinder to support Bochs (default) + --align=full Align partition boundaries to cylinder boundary to + let fdisk guess correct geometry and quiet warnings + --align=none Don't align partitions at all, to save space Other options: -h, --help Display this help message. EOF @@ -201,91 +232,170 @@ sub set_as { if defined $as_ref->[1]; $as_ref->[1] = $as; } + +# Sets $disk as a disk to be included in the VM to run. +sub set_disk { + my ($disk) = @_; + + push (@disks, $disk); + + my (%pt) = read_partition_table ($disk); + for my $role (keys %pt) { + die "can't have two sources for \L$role\E partition" + if exists $parts{$role}; + $parts{$role}{DISK} = $disk; + $parts{$role}{START} = $pt{$role}{START}; + $parts{$role}{SECTORS} = $pt{$role}{SECTORS}; + } +} # Locates the files used to back each of the virtual disks, # and creates temporary disks. sub find_disks { - for my $disk (values %disks) { - # If there's no assigned file name but the default file exists, - # try to assign a default file name. - if (!defined ($disk->{FILE_NAME})) { - for my $try_fn ($disk->{DEF_FN}, "build/" . $disk->{DEF_FN}) { - $disk->{FILE_NAME} = $try_fn, last - if -e $try_fn; - } - } - - # If there's no file name, we're done. - next if !defined ($disk->{FILE_NAME}); - - if ($disk->{FILE_NAME} =~ /^\d+(\.\d+)?|\.\d+$/) { - # Create a temporary disk of approximately the specified - # size in megabytes. - die "OS disk can't be temporary\n" if $disk == $disks{OS}; - - my ($mb) = $disk->{FILE_NAME}; - undef $disk->{FILE_NAME}; + # Find kernel, if we don't already have one. + if (!exists $parts{KERNEL}) { + my $name = find_file ('kernel.bin'); + die "Cannot find kernel\n" if !defined $name; + do_set_part ('KERNEL', 'file', $name); + } - my ($cyl_size) = 512 * 16 * 63; - extend_disk ($disk, ceil ($mb * 2) * $cyl_size); - } else { - # The file must exist and have nonzero size. - -e $disk->{FILE_NAME} or die "$disk->{FILE_NAME}: stat: $!\n"; - -s _ or die "$disk->{FILE_NAME}: disk has zero size\n"; - } + # Try to find file system and swap disks, if we don't already have + # partitions. + if (!exists $parts{FILESYS}) { + my $name = find_file ('filesys.dsk'); + set_disk ($name) if defined $name; + } + if (!exists $parts{SWAP}) { + my $name = find_file ('swap.dsk'); + set_disk ($name) if defined $name; } - # Warn about (potentially) missing disks. - die "Cannot find OS disk\n" if !defined $disks{OS}{FILE_NAME}; + # Warn about (potentially) missing partitions. if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) { if ((grep ($project eq $_, qw (userprog vm filesys))) - && !defined ($disks{FS}{FILE_NAME})) { + && !defined $parts{FILESYS}) { print STDERR "warning: it looks like you're running the $project "; - print STDERR "project, but no file system disk is present\n"; + print STDERR "project, but no file system partition is present\n"; } - if ($project eq 'vm' && !defined $disks{SWAP}{FILE_NAME}) { + if ($project eq 'vm' && !defined $parts{SWAP}) { print STDERR "warning: it looks like you're running the $project "; - print STDERR "project, but no swap disk is present\n"; + print STDERR "project, but no swap partition is present\n"; } } + + # Open disk handle. + my ($handle); + if (!defined $make_disk) { + ($handle, $make_disk) = tempfile (UNLINK => $tmp_disk, + SUFFIX => '.dsk'); + } else { + die "$make_disk: already exists\n" if -e $make_disk; + open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n"; + } + + # Prepare the arguments to pass to the Pintos kernel. + my (@args); + push (@args, shift (@kernel_args)) + while @kernel_args && $kernel_args[0] =~ /^-/; + push (@args, 'extract') if @puts; + push (@args, @kernel_args); + push (@args, 'append', $_->[0]) foreach @gets; + + # Make disk. + my (%disk); + our (@role_order); + for my $role (@role_order) { + my $p = $parts{$role}; + next if !defined $p; + next if exists $p->{DISK}; + $disk{$role} = $p; + } + $disk{DISK} = $make_disk; + $disk{HANDLE} = $handle; + $disk{ALIGN} = $align; + $disk{GEOMETRY} = %geometry; + $disk{FORMAT} = 'partitioned'; + $disk{LOADER} = read_loader ($loader_fn); + $disk{ARGS} = \@args; + assemble_disk (%disk); + + # Put the disk at the front of the list of disks. + unshift (@disks, $make_disk); + die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4; } # Prepare the scratch disk for gets and puts. sub prepare_scratch_disk { - if (@puts) { - # Write ustar header and data for each file. - put_scratch_file ($_->[0], - defined $_->[1] ? $_->[1] : $_->[0]) - foreach @puts; - - # Write end-of-archive marker. - write_fully ($disks{SCRATCH}{HANDLE}, $disks{SCRATCH}{FILE_NAME}, - "\0" x 1024); + return if !@gets && !@puts; + + my ($p) = $parts{SCRATCH}; + # Create temporary partition and write the files to put to it, + # then write an end-of-archive marker. + my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part'); + put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0], + $part_handle, $part_fn) + foreach @puts; + write_fully ($part_handle, $part_fn, "\0" x 1024); + + # Make sure the scratch disk is big enough to get big files + # and at least as big as any requested size. + my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512); + extend_file ($part_handle, $part_fn, $size); + close ($part_handle); + + if (exists $p->{DISK}) { + # Copy the scratch partition to the disk. + die "$p->{DISK}: scratch partition too small\n" + if $p->{SECTORS} * 512 < $size; + + my ($disk_handle); + open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; + open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n"; + my ($start) = $p->{START} * 512; + sysseek ($disk_handle, $start, SEEK_SET) == $start + or die "$p->{DISK}: seek: $!\n"; + copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size); + close ($disk_handle) or die "$p->{DISK}: close: $!\n"; + close ($part_handle) or die "$part_fn: close: $!\n"; + } else { + # Set $part_fn as the source for the scratch partition. + do_set_part ('SCRATCH', 'file', $part_fn); } - - # Make sure the scratch disk is big enough to get big files. - extend_disk ($disks{SCRATCH}, @gets * 1024 * 1024) if @gets; } # Read "get" files from the scratch disk. sub finish_scratch_disk { - # We need to start reading the scratch disk from the beginning again. - if (@gets) { - close ($disks{SCRATCH}{HANDLE}); - undef ($disks{SCRATCH}{HANDLE}); - } + return if !@gets; + + # Open scratch partition. + my ($p) = $parts{SCRATCH}; + my ($part_handle); + my ($part_fn) = $p->{DISK}; + open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; + sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512 + or die "$part_fn: seek: $!\n"; # Read each file. - # If reading fails, delete that file and all subsequent files. + # If reading fails, delete that file and all subsequent files, but + # don't die with an error, because that's a guest error not a host + # error. (If we do exit with an error code, it fouls up the + # grading process.) Instead, just make sure that the host file(s) + # we were supposed to retrieve is unlinked. my ($ok) = 1; + my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512; foreach my $get (@gets) { my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0]; - my ($error) = get_scratch_file ($name); - if ($error) { - print STDERR "getting $name failed ($error)\n"; - die "$name: unlink: $!\n" if !unlink ($name) && !$!{ENOENT}; - $ok = 0; + if ($ok) { + my ($error) = get_scratch_file ($name, $part_handle, $part_fn); + if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) { + $error = "$part_fn: scratch data overflows partition"; + } + if ($error) { + print STDERR "getting $name failed ($error)\n"; + $ok = 0; + } } + die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT}; } } @@ -313,13 +423,13 @@ sub calc_ustar_chksum { return unpack ("%32a*", $s); } -# put_scratch_file($src_file_name, $dst_file_name). +# put_scratch_file($src_file_name, $dst_file_name, +# $disk_handle, $disk_file_name). # -# Copies $src_file_name into the scratch disk for extraction as -# $dst_file_name. +# Copies $src_file_name into $disk_handle for extraction as +# $dst_file_name. $disk_file_name is used for error messages. sub put_scratch_file { - my ($src_file_name, $dst_file_name) = @_; - my ($disk_handle, $disk_file_name) = open_disk ($disks{SCRATCH}); + my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_; print "Copying $src_file_name to scratch partition...\n"; @@ -367,13 +477,13 @@ sub put_scratch_file { if $size % 512; } -# get_scratch_file($file). +# get_scratch_file($get_file_name, $disk_handle, $disk_file_name) # -# Copies from the scratch disk to $file. +# Copies from $disk_handle to $get_file_name (which is created). +# $disk_file_name is used for error messages. # Returns 1 if successful, 0 on failure. sub get_scratch_file { - my ($get_file_name) = @_; - my ($disk_handle, $disk_file_name) = open_disk ($disks{SCRATCH}); + my ($get_file_name, $disk_handle, $disk_file_name) = @_; print "Copying $get_file_name out of $disk_file_name...\n"; @@ -414,35 +524,6 @@ sub get_scratch_file { return 0; } -# Prepares the arguments to pass to the Pintos kernel, -# and then write them into Pintos bootloader. -sub prepare_arguments { - my (@args); - push (@args, shift (@kernel_args)) - while @kernel_args && $kernel_args[0] =~ /^-/; - push (@args, 'extract') if @puts; - push (@args, @kernel_args); - push (@args, 'append', $_->[0]) foreach @gets; - write_cmd_line ($disks{OS}, @args); -} - -# Writes @args into the Pintos bootloader at the beginning of $disk. -sub write_cmd_line { - my ($disk, @args) = @_; - - # Figure out command line to write. - my ($arg_cnt) = pack ("V", scalar (@args)); - my ($args) = join ('', map ("$_\0", @args)); - die "command line exceeds 128 bytes" if length ($args) > 128; - $args .= "\0" x (128 - length ($args)); - - # Write command line. - my ($handle, $file_name) = open_disk_copy ($disk); - print "Writing command line to $file_name...\n"; - sysseek ($handle, 0x17a, 0) == 0x17a or die "$file_name: seek: $!\n"; - syswrite ($handle, "$arg_cnt$args") or die "$file_name: write: $!\n"; -} - # Running simulators. # Runs the selected simulator. @@ -483,20 +564,14 @@ panic: action=fatal user_shortcut: keys=ctrlaltdel EOF print BOCHSRC "gdbstub: enabled=1\n" if $debug eq 'gdb'; - if ($realtime) { - print BOCHSRC "clock: sync=realtime\n"; - } else { - print BOCHSRC "clock: sync=none, time0=0\n"; - } - print_bochs_disk_line ("ata0-master", 0); - print_bochs_disk_line ("ata0-slave", 1); - if (defined ($disks_by_iface[2]{FILE_NAME}) - || defined ($disks_by_iface[3]{FILE_NAME})) { - print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ", - "ioaddr2=0x370, irq=15\n"; - print_bochs_disk_line ("ata1-master", 2); - print_bochs_disk_line ("ata1-slave", 3); - } + print BOCHSRC "clock: sync=", $realtime ? 'realtime' : 'none', + ", time0=0\n"; + print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n" + if @disks > 2; + print_bochs_disk_line ("ata0-master", $disks[0]); + print_bochs_disk_line ("ata0-slave", $disks[1]); + print_bochs_disk_line ("ata1-master", $disks[2]); + print_bochs_disk_line ("ata1-slave", $disks[3]); if ($vga ne 'terminal') { if ($serial) { my $mode = defined ($squish_pty) ? "term" : "file"; @@ -527,17 +602,11 @@ EOF } } -# print_bochs_disk_line($device, $iface) -# -# If IDE interface $iface has a disk attached, prints a bochsrc.txt -# line for attaching it to $device. sub print_bochs_disk_line { - my ($device, $iface) = @_; - my ($disk) = $disks_by_iface[$iface]; - my ($file) = $disk->{FILE_NAME}; - if (defined $file) { + my ($device, $disk) = @_; + if (defined $disk) { my (%geom) = disk_geometry ($disk); - print BOCHSRC "$device: type=disk, path=$file, mode=flat, "; + print BOCHSRC "$device: type=disk, path=$disk, mode=flat, "; print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "; print BOCHSRC "translation=none\n"; } @@ -550,11 +619,11 @@ sub run_qemu { print "warning: qemu doesn't support jitter\n" if defined $jitter; my (@cmd) = ('qemu'); - for my $iface (0...3) { - my ($option) = ('-hda', '-hdb', '-hdc', '-hdd')[$iface]; - push (@cmd, $option, $disks_by_iface[$iface]{FILE_NAME}) - if defined $disks_by_iface[$iface]{FILE_NAME}; - } + push (@cmd, '-no-kqemu'); + push (@cmd, '-hda', $disks[0]) if defined $disks[0]; + push (@cmd, '-hdb', $disks[1]) if defined $disks[1]; + push (@cmd, '-hdc', $disks[2]) if defined $disks[2]; + push (@cmd, '-hdd', $disks[3]) if defined $disks[3]; push (@cmd, '-m', $mem); push (@cmd, '-net', 'none'); push (@cmd, '-nographic') if $vga eq 'none'; @@ -583,8 +652,7 @@ sub run_player { player_unsup ("--kill-on-failure"), undef $kill_on_failure if defined $kill_on_failure; - # Memory size must be multiple of 4. - $mem = int (($mem + 3) / 4) * 4; + $mem = round_up ($mem, 4); # Memory must be multiple of 4 MB. open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n"; chmod 0777 & ~umask, "pintos.vmx"; @@ -601,8 +669,6 @@ gui.exitOnCLIHLT = TRUE gui.powerOnAtStartUp = TRUE EOF - - print VMX <{FILE_NAME}; - next if !defined $dsk; + my ($dsk) = $disks[$i]; + last if !defined $dsk; my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2); my ($pln) = "$device.pln"; @@ -631,7 +696,7 @@ EOF close (URANDOM); my ($cid) = unpack ("L", $bytes); - my (%geom) = disk_geometry ($disk); + my (%geom) = disk_geometry ($dsk); open (PLN, ">", $pln) or die "$pln: create: $!\n"; print PLN <{HANDLE})) { - if ($disk->{FILE_NAME}) { - sysopen ($disk->{HANDLE}, $disk->{FILE_NAME}, O_RDWR) - or die "$disk->{FILE_NAME}: open: $!\n"; - } else { - ($disk->{HANDLE}, $disk->{FILE_NAME}) = tempfile (UNLINK => 1, - SUFFIX => '.dsk'); - } - } - return ($disk->{HANDLE}, $disk->{FILE_NAME}); -} - -# open_disk_copy($disk) -# -# Makes a temporary copy of $disk and returns its file handle and file name. -sub open_disk_copy { - my ($disk) = @_; - die if !$disk->{FILE_NAME}; - - my ($orig_handle, $orig_file_name) = open_disk ($disk); - my ($cp_handle, $cp_file_name) = tempfile (UNLINK => 1, SUFFIX => '.dsk'); - copy_file ($orig_handle, $orig_file_name, $cp_handle, $cp_file_name, - -s $orig_handle); - return ($disk->{HANDLE}, $disk->{FILE_NAME}) = ($cp_handle, $cp_file_name); -} - -# extend_disk($disk, $size) -# -# Extends $disk, if necessary, so that it is at least $size bytes -# long. -sub extend_disk { - my ($disk, $size) = @_; - my ($handle, $file_name) = open_disk ($disk); +sub extend_file { + my ($handle, $file_name, $size) = @_; if (-s ($handle) < $size) { sysseek ($handle, $size - 1, 0) == $size - 1 or die "$file_name: seek: $!\n"; @@ -723,61 +751,18 @@ sub extend_disk { # Examines $file and returns a valid IDE disk geometry for it, as a # hash. sub disk_geometry { - my ($disk) = @_; - my ($file) = $disk->{FILE_NAME}; + my ($file) = @_; my ($size) = -s $file; die "$file: stat: $!\n" if !defined $size; - die "$file: size not a multiple of 512 bytes\n" if $size % 512; + die "$file: size $size not a multiple of 512 bytes\n" if $size % 512; my ($cyl_size) = 512 * 16 * 63; my ($cylinders) = ceil ($size / $cyl_size); - extend_disk ($disk, $cylinders * $cyl_size) if $size % $cyl_size; return (CAPACITY => $size / 512, C => $cylinders, H => 16, S => 63); } - -# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size) -# -# Copies $size bytes from $from_handle to $to_handle. -# $from_file_name and $to_file_name are used in error messages. -sub copy_file { - my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_; - - while ($size > 0) { - my ($chunk_size) = 4096; - $chunk_size = $size if $chunk_size > $size; - $size -= $chunk_size; - - my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size); - write_fully ($to_handle, $to_file_name, $data); - } -} - -# read_fully($handle, $file_name, $bytes) -# -# Reads exactly $bytes bytes from $handle and returns the data read. -# $file_name is used in error messages. -sub read_fully { - my ($handle, $file_name, $bytes) = @_; - my ($data); - my ($read_bytes) = sysread ($handle, $data, $bytes); - die "$file_name: read: $!\n" if !defined $read_bytes; - die "$file_name: unexpected end of file\n" if $read_bytes != $bytes; - return $data; -} - -# write_fully($handle, $file_name, $data) -# -# Write $data to $handle. -# $file_name is used in error messages. -sub write_fully { - my ($handle, $file_name, $data) = @_; - my ($written_bytes) = syswrite ($handle, $data); - die "$file_name: write: $!\n" if !defined $written_bytes; - die "$file_name: short write\n" if $written_bytes != length $data; -} # Subprocess utilities. diff --git a/src/utils/pintos-mkdisk b/src/utils/pintos-mkdisk index 662b2e5..87b1563 100755 --- a/src/utils/pintos-mkdisk +++ b/src/utils/pintos-mkdisk @@ -3,35 +3,132 @@ use strict; use warnings; use POSIX; -use Getopt::Long; +use Getopt::Long qw(:config bundling); use Fcntl 'SEEK_SET'; -GetOptions ("h|help" => sub { usage (0); }) +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +our ($disk_fn); # Output disk file name. +our (%parts); # Partitions. +our ($format); # "partitioned" (default) or "raw" +our (%geometry); # IDE disk geometry. +our ($align); # Align partitions on cylinders? +our ($loader_fn); # File name of loader. +our ($include_loader); # Include loader? +our (@kernel_args); # Kernel arguments. + +if (grep ($_ eq '--', @ARGV)) { + @kernel_args = @ARGV; + @ARGV = (); + while ((my $arg = shift (@kernel_args)) ne '--') { + push (@ARGV, $arg); + } +} + +GetOptions ("h|help" => sub { usage (0); }, + + "kernel=s" => \&set_part, + "filesys=s" => \&set_part, + "scratch=s" => \&set_part, + "swap=s" => \&set_part, + + "filesys-size=s" => \&set_part, + "scratch-size=s" => \&set_part, + "swap-size=s" => \&set_part, + + "kernel-from=s" => \&set_part, + "filesys-from=s" => \&set_part, + "scratch-from=s" => \&set_part, + "swap-from=s" => \&set_part, + + "format=s" => \$format, + "loader:s" => \&set_loader, + "no-loader" => \&set_no_loader, + "geometry=s" => \&set_geometry, + "align=s" => \&set_align) or exit 1; -usage (1) if @ARGV != 2; +usage (1) if @ARGV != 1; + +$disk_fn = $ARGV[0]; +die "$disk_fn: already exists\n" if -e $disk_fn; + +# Sets the loader to copy to the MBR. +sub set_loader { + die "can't specify both --loader and --no-loader\n" + if defined ($include_loader) && !$include_loader; + $include_loader = 1; + $loader_fn = $_[1] if $_[1] ne ''; +} + +# Disables copying a loader to the MBR. +sub set_no_loader { + die "can't specify both --loader and --no-loader\n" + if defined ($include_loader) && $include_loader; + $include_loader = 0; +} + +# Figure out whether to include a loader. +$include_loader = exists ($parts{KERNEL}) && $format eq 'partitioned' + if !defined ($include_loader); +die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw'; +die "can't write command-line arguments without --loader or --kernel\n" + if @kernel_args && !$include_loader; +print STDERR "warning: --loader only makes sense without --kernel " + . "if this disk will be used to load a kernel from another disk\n" + if $include_loader && !exists ($parts{KERNEL}); + +# Open disk. +my ($disk_handle); +open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n"; -my ($disk, $mb) = @ARGV; -die "$disk: already exists\n" if -e $disk; -die "\"$mb\" is not a valid size in megabytes\n" - if $mb <= 0 || $mb > 1024 || $mb !~ /^\d+(\.\d+)?|\.\d+/; +# Read loader. +my ($loader); +$loader = read_loader ($loader_fn) if $include_loader; -my ($cyl_cnt) = ceil ($mb * 2); -my ($cyl_bytes) = 512 * 16 * 63; -my ($bytes) = $cyl_bytes * $cyl_cnt; +# Write disk. +my (%disk) = %parts; +$disk{DISK} = $disk_fn; +$disk{HANDLE} = $disk_handle; +$disk{ALIGN} = $align; +$disk{GEOMETRY} = %geometry; +$disk{FORMAT} = $format; +$disk{LOADER} = $loader; +$disk{ARGS} = \@kernel_args; +assemble_disk (%disk); -open (DISK, '>', $disk) or die "$disk: create: $!\n"; -sysseek (DISK, $bytes - 1, SEEK_SET) or die "$disk: seek: $!\n"; -syswrite (DISK, "\0", 1) == 1 or die "$disk: write: $!\n"; -close (DISK) or die "$disk: close: $!\n"; +# Done. +exit 0; sub usage { print <<'EOF'; pintos-mkdisk, a utility for creating Pintos virtual disks -Usage: pintos DISKFILE MB -where DISKFILE is the file to use for the disk - and MB is the disk size in (approximate) megabytes. -Options: - -h, --help Display this help message. +Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...] +where DISK is the virtual disk to create, + each ARGUMENT is inserted into the command line written to DISK, + and each OPTION is one of the following options. +Partition options: (where PARTITION is one of: kernel filesys scratch swap) + --PARTITION=FILE Use a copy of FILE for the given PARTITION + --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB + --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK + (There is no --kernel-size option.) +Output disk options: + --format=partitioned Write partition table to output (default) + --format=raw Do not write partition table to output + (Pintos can only use partitioned disks.) +Partitioned format output options: + --loader[=FILE] Get bootstrap loader from FILE (default: loader.bin + if --kernel option is specified, empty otherwise) + --no-loader Do not include a bootstrap loader + --geometry=H,S Use H head, S sector geometry (default: 16, 63) + --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot + per http://syslinux.zytor.com/usbkey.php + --align=bochs Round size to cylinder for Bochs support (default) + --align=full Align partition boundaries to cylinder boundary to + let fdisk guess correct geometry and quiet warnings + --align=none Don't align partitions at all, to save space +Other options: + -h, --help Display this help message. EOF - exit (@_); + exit ($_[0]); } diff --git a/src/utils/pintos-set-cmdline b/src/utils/pintos-set-cmdline new file mode 100644 index 0000000..8c8f702 --- /dev/null +++ b/src/utils/pintos-set-cmdline @@ -0,0 +1,42 @@ +#! /usr/bin/perl -w + +use strict; +use Fcntl 'SEEK_SET'; + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +# Get command-line arguments. +usage (0) if @ARGV == 1 && $ARGV[0] eq '--help'; +usage (1) if @ARGV < 2 || $ARGV[1] ne '--'; +my ($disk, undef, @kernel_args) = @ARGV; + +# Open disk. +my ($handle); +open ($handle, '+<', $disk) or die "$disk: open: $!\n"; + +# Check that it's a partitioned disk with a Pintos loader. +my ($buffer) = read_fully ($handle, $disk, 512); +unpack ("x510 v", $buffer) == 0xaa55 or die "$disk: not a partitioned disk\n"; +$buffer =~ /Pintos/ or die "$disk: does not contain Pintos loader\n"; + +# Write the command line. +our ($LOADER_SIZE); +sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE + or die "$disk: seek: $!\n"; +write_fully ($handle, $disk, make_kernel_command_line (@kernel_args)); + +# Close disk. +close ($handle) or die "$disk: close: $!\n"; + +exit 0; + +sub usage { + print <<'EOF'; +pintos-set-cmdline, a utility for changing the command line in Pintos disks +Usage: pintos-set-cmdline DISK -- [ARGUMENT...] +where DISK is a bootable disk containing a Pintos loader + and each ARGUMENT is inserted into the command line written to DISK. +EOF + exit ($_[0]); +} diff --git a/src/vm/Make.vars b/src/vm/Make.vars index a5ea6b0..e3c33a7 100644 --- a/src/vm/Make.vars +++ b/src/vm/Make.vars @@ -1,6 +1,6 @@ # -*- makefile -*- -os.dsk: DEFINES = -DUSERPROG -DFILESYS -DVM +kernel.bin: DEFINES = -DUSERPROG -DFILESYS -DVM KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm TEST_SUBDIRS = tests/userprog tests/vm tests/filesys/base GRADING_FILE = $(SRCDIR)/tests/vm/Grading -- 2.30.2