|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
boot.c |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Skip to line: 5050 - 5100 - 5150 - 5200 - 5250 - 5300 - 5350 - 5400 - 5450 - 5500 - 5550 - 5600 - 5650 - 5700 - 5750 - 5800 - 5850 - 5900 - 5950 - 6000 - 6050 - 6100 - 6150 - 6200 - 6250 - 6300 - 6350 - 6400 - 6450 - 6500 - 6550 - 6600 - 6650 - 6700 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| If you have a comment for boot.c, please click here. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5000 /* boot.c - Load and start Minix. Author: Kees J. Bot |
| 5001 * 27 Dec 1991 |
| 5002 * |
| 5003 * Copyright 1998 Kees J. Bot, All rights reserved. |
| 5004 * This package may be freely used and modified except that changes that |
| 5005 * do not increase the functionality or that are incompatible with the |
| 5006 * original may not be released to the public without permission from the |
| 5007 * author. Use of so called "C beautifiers" is explicitly prohibited. |
|
The key to understanding the boot sequence is understanding the function
boot()
(line 6605). boot() is called from boothead.s
; it is the first function from boot.c to be called. boot() initializes
the system with calls to
initialize(),
get_parameters(),
and r_super() before processing
commands from the bootparams sector (see lines 5848-5864) and commands
typed in by the user.
Study the code from boot() before returning to the beginning.
| ||
| 5009 |
| 5011 |
|
boot.c is used for both the boot monitor and edparams, a utility program.
Since the boot monitor runs independently of any operating system, it relies
on bios function calls for its input/output (BIOS=TRUE,
UNIX=FALSE).
edparams is an application that depends on the kernel; it relies on the
operating system for its input/output (UNIX=TRUE,
BIOS=FALSE).
In Makefile, edparams.c is compiled with the -D option. (Note that edparams.c is a link to boot.c.) The -DUNIX option sets UNIX equal to 1 (TRUE). boot.c, on the other hand, is not compiled with this option (see line 0126 of Makefile). UNIX is therefore undefined and FALSE. Different sections of code are parsed depending on whether UNIX or BIOS
is TRUE. For example, if BIOS is TRUE, lines 5030-5031 are parsed
and lines 5034-5039 are discarded. If UNIX is TRUE, lines 5034-5039
are parsed and lines 5030-5031 are discarded. I do not cover sections
of code that are specific to UNIX; for example, I do not cover lines 5143-5268.
| ||
| 5013 |
|
#define is a preprocessor command. The preprocessor replaces
all occurrences of the first string (in this case, "nil") with
the second string (in this case, "0") before compilation.
The string "nil" is used to increase readability.
| ||
|
_POSIX_SOURCE and _MINIX are macros that are used
by library routines. In fact, nearly all macros that begin with an
underscore (_) are specific to library routines.
The POSIX standard was created to improve portability between UNIX systems.
The 12th paragraph of section 2.6.2, lines 01184-011200, and lines 01241-01245
in the minix code in Operating
Systems describe the _POSIX_SOURCE and _MINIX macros.
| ||
|
A function must be either defined or declared in a file before it can
be used. Header files (files ending in .h) make the task of declaring
variables easier.
Before compilation begins, the preprocessor replaces any #include <filename.h> statement with the contents of filename.h. If the filename is enclosed in < and >, the preprocessor searches for the file in a default directory (typically /usr/include) and other directories specified by the -I option of the compiler (see line 5030). If the filename is quoted (see line 5041), the preprocessor looks for the include file in the same directory that the source file is found. (In this case, boot.c is the source file and is found in the /usr/src/boot directory.) As an example, strlen() (see line 5613) is declared in the file string.h (see line 5022). string.h is located in the directory /usr/include. Header files, in addition to containing function declarations, also
frequently contain #defines. For example, RATIO
(see line 5130) is
#defined in boot.h.
Since boot.h is quoted (see line 5044), the preprocessor searches for boot.h
in the same directory as boot.c. Indeed, both files are in the /usr/src/boot
directory.
| ||
|
5018 #include <sys/types.h>
5019 #include <sys/stat.h> 5020 #include <stdlib.h> 5021 #include <limits.h> 5022 #include <string.h> |
| errno is declared in errno.h (see line 00230 in the book) as extern. Memory for a variable can be allocated in only one file (i.e. the variable is "defined" once) but the variable must be declared as extern in every other file that accesses it. However, memory is not allocated for errno in boothead.s, boot.c, bootimage.c, or rawfs.c. If you understand how memory is allocated for errno, please submit a comment to the site which will be displayed below. | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
5024 #include <ibm/partition.h>
5025 #include <minix/config.h> 5026 #include <minix/type.h> 5027 #include <minix/const.h> 5028 #include <minix/minlib.h> 5029 #if BIOS |
|
If a filename is enclosed in < and >, the preprocessor searches for
the file in the default directory (typically /usr/include) and other directories
specified by the -I option of the compiler. In Makefile
, the parent directory (..) is specified by the -I option. Makefile
is found in /usr/src/boot; therefore, the parent directory is /usr/src.
After the preprocessor unsuccessfully looks for const.h in the /usr/include/kernel
directory, the preprocessor looks for (and finds) const.h in the /usr/src/kernel
directory. Note that the preprocessor can't find const.h in /usr/src/kernel
since this directory doesn't exist.
| ||
|
5031 #include <kernel/type.h>
5032 #endif |
|
I do not cover sections of code that are specific to UNIX (lines 5034-5040).
| ||
|
5034 #include <stdio.h>
5035 #include <time.h> 5036 #include <unistd.h> 5037 #include <fcntl.h> 5038 #include <signal.h> 5039 #include <termios.h> 5040 #endif 5041 #include "rawfs.h" |
|
Memory for a variable can be allocated in only one file (i.e. the variable
is "defined") but the variable must be declared as extern in every
other file that accesses it. To accomplish this, the macro EXTERN
is #defined as the empty string. This prevents the EXTERN
macro from being #defined as extern in boot.h
when boot.h is #included in boot.c. boot.h is also #included
in bootimage.c. Since EXTERN is not #defined (and
is therefore undefined), EXTERN is replaced by extern
in bootimage.c. This mechanism ensures that memory for a variable
is allocated only once.
| ||
|
5043 #define EXTERN /* Empty */
5044 #include "boot.h" 5045 |
|
The sizeof operator returns the size of its argument (in this
case, the array a and the first element of a).
For example, if a has 10 elements and each element is 2 bytes
(a short):
arraysize(a) = sizeof(a)/sizeof(a[0]) = 20/2 = 10
| ||
|
arraylimit(a) returns the address of the first byte after the
last element of array a.
Let's say we have an array of shorts, short_array[], that has 10 elements. As the above example shows, arraysize(short_array) = 10. So if the address of the first byte of short_array[] was, for example, 0x1100000, then arraylimit(a) = a + arraysize(a) = 10 = 0x1100000 + 10 = 0x1100000 + 0xA = 0x110000A. This is incorrect. Pointer arithmetic can be confusing to the uninitiated. The following program: #include <stdio.h> #define arraysize(a) (sizeof(a)/sizeof((a)[0]))
int main()
size_of_array= arraysize(short_array);
short_ptr= arraylimit(short_array);
} produces the output: the size of short_array= 5
short_ptr equals 0xbffffc5A and not 0xbffffc55.
In other words, 0xbffffc50+5 (in this scenario) equals 0xbffffc5A
and not 0xbffffc55. Any value added to a pointer is first
multiplied by the size of the type that the pointer references. A
short
is 2 bytes and short_array has 5 elements. So 0xbffffc50+2*5
= 0xbffffc50+10 = 0xbffffc50+0x0A = 0xbffffc5A.
| ||
|
between() returns TRUE if c is between a
and z.
| ||
| 5049 |
| 5050 #if BIOS |
|
The functions in boothead.s (for example, dev_open())
return an error code in ax if something goes wrong. bios_err()
converts this number to a readable string.
For example, on line 6131, the return value of dev_open() is
stored in r. If an error occurred, r will be nonzero.
On line 6133, bios_err() converts this number to a readable string
so that printf() (on the next line: line 6134) prints something
meaningful.
| ||
|
5052 /* Translate BIOS error code to a readable
string. (This is a rare trait
5053 * known as error checking and reporting. Take a good look at it, you 5054 * won't see it often.) 5055 */ 5056 { |
|
The static declaration specifies that the variable (in this
case, the array errlist[]) remains in existence after the function
returns. Since the contents of errlist[] don't change from
one invocation of bios_err() to the next, it is preferable that
the array remain in existence. It saves the time for reinitialization
that would otherwise be necessary.
The static declaration has a different meaning for external
variables. The scope of an external static variable is only
within the file in which the variable is declared. Note that "external"
variables are variables that are declared outside of all functions.
| ||
|
5058
int err;
5059 char *what; 5060 } errlist[] = { 5061 #if !DOS 5062 { 0x00, "No error" }, 5063 { 0x01, "Invalid command" }, 5064 { 0x02, "Address mark not found" }, 5065 { 0x03, "Disk write-protected" }, 5066 { 0x04, "Sector not found" }, 5067 { 0x05, "Reset failed" }, 5068 { 0x06, "Floppy disk removed" }, 5069 { 0x07, "Bad parameter table" }, 5070 { 0x08, "DMA overrun" }, 5071 { 0x09, "DMA crossed 64 KB boundary" }, 5072 { 0x0A, "Bad sector flag" }, 5073 { 0x0B, "Bad track flag" }, 5074 { 0x0C, "Media type not found" }, 5075 { 0x0D, "Invalid number of sectors on format" }, 5076 { 0x0E, "Control data address mark detected" }, 5077 { 0x0F, "DMA arbitration level out of range" }, 5078 { 0x10, "Uncorrectable CRC or ECC data error" }, 5079 { 0x11, "ECC corrected data error" }, 5080 { 0x20, "Controller failed" }, 5081 { 0x40, "Seek failed" }, 5082 { 0x80, "Disk timed-out" }, 5083 { 0xAA, "Drive not ready" }, 5084 { 0xBB, "Undefined error" }, 5085 { 0xCC, "Write fault" }, 5086 { 0xE0, "Status register error" }, 5087 { 0xFF, "Sense operation failed" } 5088 #else /* DOS */ 5089 { 0x00, "No error" }, 5090 { 0x01, "Function number invalid" }, 5091 { 0x02, "File not found" }, 5092 { 0x03, "Path not found" }, 5093 { 0x04, "Too many open files" }, 5094 { 0x05, "Access denied" }, 5095 { 0x06, "Invalid handle" }, 5096 { 0x0C, "Access code invalid" }, |
| 5097 #endif /* DOS */ |
|
errp is a pointer to a struct errlist (see line 5060).
| ||
|
The for loop compares the argument err with the err
field of each element of the array errlist[]. When a match
is found, the string what is returned. If the for
loop goes through the entire array errlist[] without finding a
match, bios_err() returns the string "Unknown error".
| ||
|
5105 }
5106 |
|
5107 char *unix_err(int
err)
5108 /* Translate the few errors rawfs can give. */ 5109 { 5110 switch (err) { 5111 case ENOENT: return "No such file or directory"; 5112 case ENOTDIR: return "Not a directory"; 5113 default: return "Unknown error"; 5114 } 5115 } 5116 5117 void rwerr(char *rw, off_t sec, int err) 5118 { 5119 printf("\n%s error 0x%02x (%s) at sector %ld absolute\n", 5120 rw, err, bios_err(err), sec); 5121 } 5122 |
|
readerr() and writerr() provide convenient interfaces
to rwerr() and bios_err().
Notice the order of the functions bios_err(), rwerr(),
and readerr(). readerr() calls rwerr()
which calls bios_err(). A function must be declared or defined
before it can be called. bios_err() is defined (lines 5051-5105)
before rwerr() calls it (line 5120) and rwerr() is defined
(lines 5117-5121) before readerr() calls it. The need to
carefully order the functions can be avoided if the functions are declared
in a header file #included at the beginning of the file.
(In this case, boot.h would be the appropriate header file.)
| ||
|
5124 void writerr(off_t
sec, int err) {
rwerr("Write",
sec, err); }
5125 |
|
readblock() reads blk into the buffer buf.
Note that blk is not an absolute block on the hard drive; blk
is the block offset from the beginning of the partition. The first
sector in the partition is lowsec. (2 sectors = 1 block
= 2*512 bytes = 1024 bytes)
| ||
|
5127 /* Read blocks for the rawfs package. */
5128 { 5129 int r; |
|
sec is the absolute sector address of the first sector of
blk.
| ||
| 5131 |
|
int readsectors(u32_t bufaddr, u32_t sector, U8_t count) reads
count
bytes beginning at absolute sector address sector into absolute
memory address bufaddr.
u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address. RATIO = 2. Both sectors of blk are read.
| ||
|
istty, alarm(n), and pause() expand to TRUE,
FALSE, and FALSE, respectively.
| ||
|
5140
5141 #endif /* BIOS */ 5142 |
|
I do not cover UNIX-specific sections of code (lines 5143-5268).
I may cover these sections at a later time.
| ||
|
5144
5145 /* The Minix boot block must start with these bytes: */ 5146 char boot_magic[] = { 0x31, 0xC0, 0x8E, 0xD8, 0xFA, 0x8E, 0xD0, 0xBC }; 5147 5148 struct biosdev { |
| 5149 char *name; /* Name of device. */ |
| 5150 int device; /* Device to edit parameters. */ |
|
5151 }
bootdev;
5152 5153 struct termios termbuf; 5154 int istty; 5155 5156 void quit(int status) 5157 { 5158 if (istty) (void) tcsetattr(0, TCSANOW, &termbuf); 5159 exit(status); 5160 } 5161 5162 #define exit(s) quit(s) 5163 5164 void report(char *label) 5165 /* edparams: label: No such file or directory */ 5166 { 5167 fprintf(stderr, "edparams: %s: %s\n", label, strerror(errno)); 5168 } 5169 5170 void fatal(char *label) 5171 { 5172 report(label); 5173 exit(1); 5174 } 5175 5176 void *alloc(void *m, size_t n) 5177 { 5178 m= m == nil ? malloc(n) : realloc(m, n); 5179 if (m == nil) fatal(""); 5180 return m; 5181 } 5182 5183 #define malloc(n) alloc(nil, n) 5184 #define realloc(m, n) alloc(m, n) 5185 5186 #define mon2abs(addr) ((void *) (addr)) 5187 5188 int rwsectors(int rw, void *addr, u32_t sec, int nsec) 5189 { 5190 ssize_t r; 5191 size_t len= nsec * SECTOR_SIZE; 5192 5193 if (lseek(bootdev.device, sec * SECTOR_SIZE, SEEK_SET) == -1) 5194 return errno; 5195 5196 if (rw == 0) { 5197 r= read(bootdev.device, (char *) addr, len); 5198 } else { 5199 r= write(bootdev.device, (char *) addr, len); |
| 5200 } |
|
5201
if (r == -1) return errno;
5202 if (r != len) return EIO; 5203 return 0; 5204 } 5205 5206 #define readsectors(a, s, n) rwsectors(0, (a), (s), (n)) 5207 #define writesectors(a, s, n) rwsectors(1, (a), (s), (n)) 5208 #define readerr(sec, err) (errno= (err), report(bootdev.name)) 5209 #define writerr(sec, err) (errno= (err), report(bootdev.name)) 5210 #define putch(c) putchar(c) 5211 #define unix_err(err) strerror(err) 5212 5213 void readblock(off_t blk, char *buf) 5214 /* Read blocks for the rawfs package. */ 5215 { 5216 errno= EIO; 5217 if (lseek(bootdev.device, blk * BLOCK_SIZE, SEEK_SET) == -1 5218 || read(bootdev.device, buf, BLOCK_SIZE) != BLOCK_SIZE) 5219 { 5220 fatal(bootdev.name); 5221 } 5222 } 5223 5224 int trapsig; 5225 5226 void trap(int sig) 5227 { 5228 trapsig= sig; 5229 signal(sig, trap); 5230 } 5231 5232 int escape(void) 5233 { 5234 if (trapsig == SIGINT) { 5235 trapsig= 0; 5236 return 1; 5237 } 5238 return 0; 5239 } 5240 5241 int getch(void) 5242 { 5243 char c; 5244 5245 fflush(stdout); 5246 5247 switch (read(0, &c, 1)) { 5248 case -1: 5249 if (errno != EINTR) fatal(""); |
| 5250 return(ESC); |
|
5251
case 0:
5252 if (istty) putch('\n'); 5253 exit(0); 5254 default: 5255 if (istty && c == termbuf.c_cc[VEOF]) { 5256 putch('\n'); 5257 exit(0); 5258 } 5259 return c & 0xFF; 5260 } 5261 } 5262 5263 #define get_tick() ((u32_t) time(nil)) 5264 #define clear_screen() printf("[clear]"); 5265 #define boot_device(device) printf("[boot %s]\n", device); 5266 #define bootminix() (run_trailer() && printf("[boot]\n")) 5267 5268 #endif /* UNIX */ 5269 5270 char *readline(void) 5271 /* Read a line including a newline with echoing. */ 5272 { 5273 char *line; 5274 size_t i, z; 5275 int c; 5276 5277 i= 0; 5278 z= 20; |
|
void *malloc(size_t size) allocates size bytes from
the heap and returns a pointer to the first byte of the allocated memory.
malloc()
is declared in stdlib.h. (The heap is the region between
the stack and the bss.)
Space for 20 char's (20 bytes) is initially allocated. If this
is used up, another 20 bytes are allocated. If the 40 bytes are used
up, 40 additional bytes are allocated. Each reallocation of memory
doubles the total amount of memory allocated. See lines 5298-5299.
| ||
|
5280
|