|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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
5281 do { 5282 c= getch(); 5283 |
|
char *strchr(char *cs, char c) returns a pointer to the
first occurrence of c in cs or nil (0) if not
present. strchr() is declared in string.h.
ctrl-U and ctrl-X erase the entire line.
| ||
|
5285
/* Backspace, DEL, ctrl-U, or ctrl-X. */
5286 do { 5287 if (i == 0) break; |
|
"\b \b" backspaces, prints a space, and then backspaces again.
This erases the previous character and backs up one space. Note that
no single ascii character can replace this sequence.
| ||
|
5289
i--;
5290 } while (c == '\25' || c == '\30'); 5291 } else |
|
5292-5293
'\7' is the bell. Backspace, DEL, ctrl-U, ctrl-X and newline are the only allowed control characters. All other control characters are forbidden. int putch(int c) prints c at the current cursor position.
If c is the bell, the speaker beeps and the cursor doesn't advance.
| ||
|
5294
} else {
5295 putch(c); 5296 line[i++]= c; 5297 if (i == z) { 5298 z*= 2; 5299 line= realloc(line, z * sizeof(char)); |
| 5300 } |
| 5301 } |
|
The line is read until a newline ('\n') is typed.
| ||
|
A terminating 0 is appended to line.
| ||
|
5304
return line;
5305 } 5306 |
|
Commands for the boot monitor must be parsed into "tokens" and placed
in a command chain before the commands can be processed. Here are
some sample commands for the boot monitor:
hd2a> rootdev = hd2a
and here is how the commands are broken up into tokens: token1 token2 token3
token4
token1 token2 token3
token1 token2 token3
token4 token5 token6
token1 token2 token3
token4 token5 token6
token1 token2 token3
token4 token5 token6
These tokens are chained together with the struct token (lines 5356-5359). Here is the first command (rootdev = hd2a).
Commands are separated not only by newlines ('\n') but also by semicolons(;). For example, on lines 5861-5862, the tokens ":", "leader", and "main" are separated by semicolons. Sector 1 (PARAMSEC) of any bootable partition is the "bootparams
sector". (Sector 0 of any bootable partition is the bootblock
code.) The PARAMSEC sector contains commands that were previously
saved. Before the boot monitor goes into interactive mode (i.e. before
the prompt appears), the bootparams sector is tokenized (see lines 5848-5854)
and the commands are processed. Tokens in the bootparams sector are
separated by newlines (see line 5920).
| ||
| 5308 /* Recognize special tokens. */ |
| 5309 { |
|
char *strchr(char *cs, char c) returns a pointer to the
first occurrence of c in cs or nil (0) if not
present. strchr() is declared in string.h. Note that
only a single character (tok[0]) is being compared and not an
entire string.
| ||
| 5311 } |
| 5312 |
|
onetoken() is called on line 5372. It is a little difficult
(for people inexperienced with pointers) to understand why a pointer to
a pointer (char **aline) is the parameter rather than just a pointer.
In the function tokenize() (line 5361), line must be advanced in each call to onetoken(). (Note that this refers to the variable line from tokenize(), not the variable line from onetoken().) Study the following program: #include <stdio.h> int main()
int function1(int var)
The output of this program is:
The value of var1 cannot be changed by altering the value of var in function1(). The reason var1 can't be changed here is that function arguments in C are passed "by value." Called functions are given the value of their arguments in temporary variables rather than the originals. For this reason, changes to var in function1() are not reflected in var1 from main(). However, there is a way to change the value of var1: #include <stdio.h> int main()
int function1(int *var)
The output of this program is:
Note that the value of var1 has been changed by passing in the address of var1 rather than var1 itself. function1() dereferences var to change the value of var1. Since the function onetoken() must change the value of line (see line 5372), the address of line is passed in rather than line itself. Note that line from tokenize() is a pointer to a char; therefore a pointer to a pointer to a char is passed in. When you see a pointer to a pointer as a parameter to a function, check to see if the function changes the value of a pointer by dereferencing the pointer to a pointer. For example, onetoken() changes the value of line (the variable line from tokenize(), not onetoken()) by dereferencing aline on line 5350. I understand that this stuff is confusing. We'll run into this
again; I'll continue to give detailed explanations in case you didn't understand
it this time.
| ||
| 5314 /* Returns a string with one token for tokenize. */ |
| 5315 { |
|
line keeps track of the current position in the token.
On line 5350, it's copied to *aline.
| ||
| 5317 size_t n; |
|
On lines 5345-5346, memory is allocated for tok and the token
is copied to the allocated memory. On line 5351, tok is
returned.
| ||
| 5319 |
|
All spaces are skipped. If there are several newlines ('\n')
in a row, every newline until the last is skipped. The last newline
is tokenized.
On line 5348, newlines are replaced with semicolons (;). Semicolons
and newlines have the same meaning here; they are both command separators.
| ||
|
5322
5323 *aline= line; 5324 5325 /* Don't do odd junk (nor the terminating 0!). */ 5326 if ((unsigned) *line < ' ' && *line != '\n') return nil; 5327 |
|
Study the examples in the comment for line 5307. These examples
will help you to understand what constitutes a single token. Also,
I refer to them in the next lines.
If the first character is a left parenthesis ('('), then the
token is something like (d,MS-DOS), (=, MINIX), or ()
(see comments for line 5307).
| ||
|
5329
/* Function argument, anything goes but () must match. */
5330 int depth= 0; 5331 5332 while ((unsigned) *line >= ' ') { 5333 if (*line == '(') depth++; 5334 if (*line++ == ')' && --depth == 0) break; 5335 } 5336 } else 5337 if (sugar(line)) { 5338 /* Single character token. */ 5339 line++; |
|
These are character strings like boot, minix, 5000,
hd2a,
etc. (see comments for line 5307).
| ||
|
5341
/* Multicharacter token. */
5342 do line++; while ((unsigned) *line > ' ' && !sugar(line)); 5343 } |
|
Let's look at an example. Suppose the token is "hd2a".
line
would have advanced four characters beyond *aline. So 4
char's
would be allocated for "hd2a" and one char for the terminating
0.
| ||
|
Newlines ('\n') and semicolons (;) have the same meaning
- they're both command separators. We standardize by converting newlines
to semicolons.
| ||
|
This advances *aline to point to the next token. Since
aline
points to line (from tokenize(); see line 5372), advancing
*aline
advances line (from tokenize()).
| ||
|
5351
return tok;
5352 } |
| 5353 |
| ||
|
5355
5356 typedef struct token { 5357 struct token *next; /* Next in a command chain. */ 5358 char *token; 5359 } token; 5360 |
|
tokenize() is called for the first time on line 5854 in get_parameters()(line
5773) when the boot parameters from sector PARAMSEC (=1) are tokenized.
cmds
(line 5382) is a pointer to the first token in the command chain.
After sector PARAMSEC is tokenized, the string ":;leader;main"
is tokenized and appended to the command chain (see lines 5861-5862).
Again, we see a function parameter (token **acmds) that is a pointer to a pointer. We saw this previously with onetoken() (line 5313). And again, we wish to change a pointer variable (for example, cmds on line 5854) so we pass in a pointer to this pointer variable. The pointer manipulations in tokenize() are extremely tricky. I hope that this slide show helps. This slide show assumes that a pointer to cmds is passed into acmds. This is the case if the command chain is empty. The command chain is empty before the boot parameters from sector PARAMSEC are tokenized (see line 5854). The command chain is also empty before the boot monitor reads a line and tokenizes it (see lines 6591-6598). | ||
|
5362 /* Takes a line apart to form tokens. The tokens
are inserted into a
5363 * command chain at *acmds. Tokenize returns a reference to where 5364 * another line could be added. Tokenize looks at spaces as token 5365 * separators and recognizes only ';', '=', '{', '}', and '\n' as single 5366 * character tokens. One token is formed from '(' and ')' with anything 5367 * in between as long as more match */ 5368 { 5369 char *tok; 5370 token *newcmd; 5371 5372 while ((tok= onetoken(&line)) != nil) { 5373 newcmd= malloc(sizeof(*newcmd)); 5374 newcmd->token= tok; 5375 newcmd->next= *acmds; 5376 *acmds= newcmd; 5377 acmds= &newcmd->next; 5378 } 5379 return acmds; 5380 } 5381 |
|
cmds points to the first token in the command chain.
| ||
| 5384 |
|
poptoken() removes the first token from the command chain and
returns the string. For example, if the command chain is:
token1 token2 token3
token4 token5 token6
poptoken() would remove token1 from the command chain and return a pointer to "rootdev". In the figure below, note that there are two memory objects, one beginning at address token1 and one beginning at address a (the string). The two memory objects are independent. Freeing (i.e. deallocating) the memory object that begins at address token1 does not free the memory object that begins at address a. poptoken() frees the memory for the token. voidtoken() frees the memory for the token and the string.
| ||
|
5386 /* Pop one token off the command chain. */
5387 { 5388 token *cmd= cmds; 5389 char *tok= cmd->token; 5390 |
|
cmds is advanced to the next token. In the example in
the comments for line 5385, cmds would point to token2.
| ||
|
Note that the memory that cmd references is freed (i.e. deallocated)
but the memory that tok references is not freed.
| ||
|
5393
5394 return tok; 5395 } 5396 |
|
voidtoken() removes the first token from the command chain
and frees the memory of the token's string. After a command has been
processed, voidtoken() removes the command from the command chain
(see lines 6379, 6426).
| ||
|
5398 /* Remove one token from the command chain.
*/
5399 { |
|
5401 }
5402 |
|
interrupt() returns 1 (TRUE) if the ESC key has been pressed.
interrupt()
sets the variable err (line 5383).
Occasionally, the boot monitor needs to check if the ESC (escape) key has been pressed. The boot monitor allows the delay command (see line 6225) and the menu command (see line 6270) to be escaped (see line 6225). The delay msec command pauses for msec milliseconds before executing the next command in the command chain. The menu command shows the selection of kernels that can be booted. Escaping the menu command sends the user back to the boot monitor prompt. The command echo \w also calls interrupt(). echo
\w waits until the escape key is pressed or a newline is entered (see
line 6486).
| ||
| 5404 /* Clean up after an ESC has been typed. */ |
| 5405 { |
|
escape() returns 1 (TRUE) if the ESC key has been pressed.
| ||
|
5407
printf("[ESC]\n");
5408 err= 1; 5409 return 1; 5410 } 5411 return 0; 5412 } 5413 5414 #if BIOS 5415 5416 int activate; 5417 |
|
bootdev is the partition or drive (in the case of a floppy)
from which this boot monitor program originated. If the user chooses
to boot another partition or drive, tmpdev is the chosen partition
or drive (see exec_bootstrap() on line 6070).
bootdev is set in initialize() (line 5450). tmpdev is set in name2dev() (line 5979). name will be something like "fd1" (second floppy drive) or "hd7" (second partition on second hard drive; see line 5549) or "hd3a" (first subpartition on third partition on first hard drive). device, primary and secondary for "fd1", "hd7", and "hd3a" are: name= "fd1"
name= "hd7"
name= "hd3a"
| ||
| 5422 |
|
The master boot record (MBR) is found in the first sector (sector 0)
of a partitioned disk. If a partition has subpartitions, an MBR will
also be found in the first sector of the partition.
An MBR has two parts: the code and the partition table. The MBR is 1 sector (=512 bytes) long and the partition table starts at byte 0x1BE (=446). A partition table has 4 entries. Here is an entry from the partition table:
get_master() copies the entire MBR (code + partition table) from disk into the memory address referenced by master and copies the memory addresses of the 4 partition entries from the partition table into the array referenced by table. The figure below shows the variables and memory layout after get_parameter() has finished the copy.
get_master() is called on line 5519 and line 6085. On
line 5519, get_master() is called to determine the name of the
partition (e.g. "hd2a"). On line 6085, get_master() is called
to determine where to find the bootstrap that corresponds to tmpdev
(see line 5418). The code on lines 5519 and 6085 is concerned only
with the partition table and ignores the code from the MBR.
| ||
| 5424 /* Read a master boot sector and its partition table. */ |
| 5425 { |
| 5426 int r, n; |
|
struct part_entry is declared in <ibm/partition.h>
(see line 04100 in the book).
| ||
| 5428 |
|
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.
| ||
| 5430 |
|
pe is initialized to point to the first partition table entry.
PART_TABLE_OFFSET and NR_PARTITIONS are #defined
in
<ibm/partition.h>.
NR_PARTITIONS = 4.
| ||
The for loop fills in the array at address f (see
figure above).
| ||
| 5433 |
| 5434 /* DOS has the misguided idea that partition tables must be sorted. */ |
|
If a partition is subpartitioned and the partition has a master boot
record, the partition table from the master boot record does not need to
be sorted.
| ||
| 5436 |
|
Here's a review of partition table entries:
The partition table entries are sorted with the bubble sort algorithm. Here is how the bubble sort algorithm works: Partition table entry 1 and partition table entry 2 are compared. If the first entry has a greater lowsec (lowsec is the sector number of the first sector in a partition) or is not being used (sysind=NO_PART), the two entries are swapped. If the first entry has a lower lowsec and is being used, the first two entries are not swapped. Entries 2 and 3 are next compared and swapped if entry 2 has a greater lowsec or is not being used. Entries 3 and 4 are compared and swapped if entry 3 has a greater lowsec or is not being used. Next, the process starts again and entries 1 and 2 are compared and swapped if appropriate. Here is a figure that describes the entire sorting process. Four partitions with lowsec's of 1, 509, 890, and 1500 are sorted. The partition with a lowsec of 890 is not being used (NP).
| ||
|
5438
do {
5439 for (pt= table; pt < table + NR_PARTITIONS-1; pt++) { 5440 if (pt[0]->sysind == NO_PART 5441 || (pt[0]->lowsec > pt[1]->lowsec 5442 && pt[1]->sysind != NO_PART)) { 5443 pe= pt[0]; pt[0]= pt[1]; pt[1]= pe; 5444 } 5445 } 5446 } while (--n > 0); 5447 return 0; 5448 } |
|
initialize() is called from boot() on line 6609.
initialize()
does 3 things:
1) Relocates the boot monitor to the upper end of low memory. Low memory is memory that is under 640KB. 2) Removes the boot monitor from the memory map that is passed to the kernel. This prevents the kernel from allocating the memory that the boot monitor occupies. 3) Initializes bootdev (see comments for line 5418).
| ||
| 5451 { |
|
The master boot record (MBR) is copied into master[] on line
5519. For a description of the MBR, see the comments for get_master()
(line 5423).
| ||
|
table[] is an array of pointers to partition table entries
(struct part_entry). table[] has NR_PARTITIONS
(=4) elements and is filled in by get_master() (see line 5519).
| ||
| 5454 int r, p; |
|
masterpos is the absolute sector number of a sector that holds
a master boot record (MBR). The first sector of a partitioned disk
always holds an MBR. If a partition has subpartitions, the first
sector of the partition holds an MBR.
| ||
| 5456 static char sub[]= "a"; |
|
argp is specific to DOS.
| ||
| 5458 |
|
mem[] is declared in boot.h
and initialized in boothead.s.
mem[]
describes the available memory and is the "memory map" that is passed to
the kernel. The boot monitor is removed from the memory map on lines
5482-5488.
caddr, daddr and runsize are also declared
in boot.h and
initialized in boothead.s
. caddr is the absolute memory address of the beginning
of the boot monitor. Since the boot monitor is relocated to the upper
end of low memory, caddr will change (see line 5473). daddr
is the absolute memory address of the beginning of the data segment of
the boot monitor. runsize is the size (in bytes) of the
boot monitor.
| ||
| 5463 u32_t oldaddr= caddr; |
|
The L in ~0x000FL stands for Long. In Minix, the type
long
is 32-bits. ~0x000FL = ~0x0000000F = 0xFFFFFFF0. Note
that ~0x000F (without the L) is equal to 0xFFF0 and not
0xFFFFFFF0.
Since memend and runsize are 32-bit values, newaddr
= (memend-runsize) & ~0x000FL will be on a 16-byte boundary.
Since the boot monitor runs in real mode, the beginning of a segment (in
this case, the code segment) must be on a 16-byte boundary.
| ||
| 5466 #if !DOS |
|
(memend-1) is the last (upper) byte of low memory. dma64k
= (memend-1) & ~0x0FFFFL rounds down to the nearest 64K boundary.
The figure below demonstrates the scenario that we wish to avoid.
(daddr-caddr) is the size of the code segment. Since newaddr + (size of code segment) < dma64k (and therefore the data segment crosses a 64KB boundary) in the figure above, an adjustment is necessary. The figure below shows the adjustment.
I don't understand why the data segment of the boot monitor isn't allowed to cross a 64KB boundary. If you understand why the data segment isn't allowed to cross a 64KB boundary, please submit a comment to the site which will be displayed below. |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
5469
/* Check if data segment crosses a 64K boundary. */
5470 if (newaddr + (daddr - caddr) < dma64k) newaddr= dma64k - runsize; 5471 #endif 5472 /* Set the new caddr for relocate. */ 5473 caddr= newaddr; 5474 5475 /* Copy code and data. */ |
|
void raw_copy(u32_t dstaddr, u32_t srcaddr, u32_t count) copies
count
bytes from absolute memory address srcaddr to absolute memory
address dstaddr. raw_copy() copies the boot monitor
from its current location to the upper end of low memory.
| ||
| 5477 |
| 5478 /* Make the copy running. */ |
|
relocate() forces the jump to the upper end of low memory.
relocate()
is an interesting function worthy of careful study.
| ||
| 5480 |
|
If there is any extended memory (mem[1].size > 0), the boot
monitor is taken out of the memory map. This protects the boot monitor
from being overwritten and allows a return to the boot monitor.
If a return to the boot monitor (to either bios13 or int86) from the kernel is made in order to make a bios interrupt call, the bios interrupt vectors must be at memory locations 0x0000-0x03FF and the bios data area must be at 0x0400-0x04FF. This accounts for 1.25K. I don't know about the next .25K and I'm not really sure why .5K is reserved for older a.out headers. If you understand why the last .75K is reserved, please submit a comment to the site which will be displayed below. |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
5486
if (mem[1].size
> 0) mem[0].size
= newaddr; 5487 mem[0].base += 2048; 5488 mem[0].size -= 2048; 5489 |
|
| ||
devopen() sets sectors and secspcyl
for the floppy or hard drive specified by
device
. sectors is the number of sectors per track of the device.
secspcyl
is the number of sectors per cylinder of the device.
| ||
| 5492 |
|
|
Lines 5494-5552 fill in bootdev. Here's a review of 3
possible sets of values for bootdev:
name= "fd1"
name= "hd7"
name= "hd3a"
| ||
|
I'm not sure why name[0] is initialized to 0. strcpy()
on lines 5502 and 5548 doesn't require this initialization.
| ||
|
device is set in boothead.s.
The bootstrap (bootblock.s) passes the device number in register dl
to the secondary boot (the boot monitor - this program). The bootstrap
also passes the segment:offset address of the partition table entry of
the booted partition in es:si.
| ||
|
If device has no partitions (for example, if device
is a floppy drive), primary remains -1. If the hard drive
partition is not subpartitioned, secondary remains -1. (See
the examples for the comment for line 5493.)
| ||
| 5498 |
|
If device is a floppy drive, name is set to either
"fd0" or "fd1". primary and secondary remain -1.
| ||
| 5500 /* Floppy. */ |
| 5501 strcpy(bootdev.name, "fd"); |
|
char *strcat(s1, s2) concatenates string s2 to the
end of string s1 and returns s1. strcat()
is declared in <string.h>.
| ||
|
5503
return;
5504 } 5505 5506 /* Get the partition table from the very first sector, and determine 5507 * the partition we booted from using the information from the booted 5508 * partition entry as passed on by the bootstrap (rem_part). All we 5509 * need from it is the partition offset. 5510 */ |
|
rem_part is set in boothead.s.
rem_part
holds the segment:offset address of the partition table entry for the partition
being booted. raw_copy() copies the lowsec field
of this partition table entry (see figure below) into the global variable
lowsec
. vec2abs() converts this segment:offset address into an absolute
address.
void raw_copy(u32_t dstaddr, u32_t srcaddr, u32_t count) copies count bytes from absolute memory address srcaddr to absolute memory address dstaddr. u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address. int offsetof(struct struct1, field) returns the offset of field within struct1. part_entry is shown on line 04104 in the book. Since lowsec is the 9th field after 8 fields of type unsigned char, offsetof(struct part_entry, lowsec) returns 8. (An unsigned char is 1 byte.) Since lowsec
has type u32_t,
sizeof(lowsec) returns 4.
| ||
| 5514 |
|
At this point, it is known that a partition (or subpartition) from hard
drive device has been booted and the lowest sector number (lowsec)
of this partition (or subpartition) is also known. However, the partition
number (or the subpartition number) is not known. In other words,
it is not known if partition hd3 or subpartition hd2a or subpartition hd4b
has been booted.
In order to determine the partition (or subpartition) number, the partition
table is copied from the master boot record (MBR) on device's
sector 0 and lowsec is compared with the lowsec field
of the partition table entries. If the active partition is subpartitioned,
the partition table from the MBR on the first sector of the active partition
must also be analyzed.
| ||
| 5516 |
|
The for loop loops around once if the booted partition is subpartitioned.
If the booted partition is not subpartitioned, the for loop does
not loop around.
| ||
| 5518 /* Extract the partition table from the master boot sector. */ |
|
int get_master(char *master, struct part_entry **table, u32_t pos)
(line 5423) loads the master boot record (MBR) at disk sector number pos
into master and loads table[] with pointers to the partition
table entries from the partition table in the MBR. get_master()
also sorts the pointers according to the entry's lowsec.
After get_master() returns, the memory layout will be as shown
in the figure below.
| ||
|
5520
readerr(masterpos, r); exit(1);
5521 } 5522 5523 /* See if you can find "lowsec" back. */ 5524 for (p= 0; p < NR_PARTITIONS; p++) { |
|
If (lowsec - table[p]->lowsec < table[p].size), there are
two possibilities (provided there is no error):
1) lowsec == table[p]->lowsec The partition or subpartition has been found (see line 5528). 2) lowsec > table[p]->lowsec table[p] points to the partition table entry whose partition contains the booted subpartition (i.e. the subpartition with a lowest sector of lowsec). | ||
|
5526
}
5527 5528 if (lowsec == table[p]->lowsec) { /* Found! */ 5529 if (bootdev.primary < 0) 5530 bootdev.primary= p; 5531 else 5532 bootdev.secondary= p; 5533 break; 5534 } 5535 |
|
Something went wrong. A value of -1 for bootdev.device
indicates an error.
| ||
|
5537
/* The boot partition cannot be named, this only means
5538 * that "bootdev" doesn't work. 5539 */ 5540 bootdev.device= -1; 5541 return; 5542 } 5543 |
|
Unless an error has occurred or will occur, the partition that contains
the booted subpartition has been found. The code loops around to
line 5517 to look for the subpartition.
| ||
| 5545 bootdev.primary= p; |
| 5546 masterpos= table[p]->lowsec; |
| 5547 } |
|
Here are 2 examples of (device, primary, secondary)
triplets to name conversions:
name= "hd7"
name= "hd3a"
NR_PARTITIONS (=4) is declared in <ibm/partition.h>. char *strcat(s1, s2) concatenates string s2 to the end of string s1 and returns s1. strcat() is declared in <string.h>. char *ul2a10(u32_t n) (line 5746) converts n (an unsigned
long) to an ascii string (base 10).
| ||
| 5553 |
|
I do not cover DOS-specific sections of code (lines 5555-5593).
I may cover these sections at a later time.
| ||
|
5555 /* Take the monitor out of
the memory map if we have memory to spare,
5556 * note that only half our PSP is needed at the new place, the first 5557 * half is to be kept in its place. 5558 */ 5559 if (mem[1].size > 0) mem[0].size = newaddr + 0x80 - mem[0].base; 5560 5561 /* Parse the command line. */ 5562 argp= PSP + 0x81; 5563 argp[PSP[0x80]]= 0; 5564 while (between('\1', *argp, ' ')) argp++; 5565 vdisk= argp; 5566 while (!between('\0', *argp, ' ')) argp++; 5567 while (between('\1', *argp, ' ')) *argp++= 0; 5568 if (*vdisk == 0) { 5569 printf("\nUsage: boot <vdisk> [commands ...]\n"); 5570 exit(1); 5571 } 5572 drun= *argp == 0 ? "main" : argp; 5573 5574 if ((r= dev_open()) != 0) { 5575 printf("\n%s: Error %02x (%s)\n", vdisk, r, bios_err(r)); 5576 exit(1); 5577 } 5578 5579 /* Find the active partition on the virtual disk. */ 5580 if ((r= get_master(master, table, 0)) != 0) { 5581 readerr(0, r); exit(1); 5582 } 5583 5584 strcpy(bootdev.name, "dosd0"); 5585 bootdev.primary= -1; 5586 for (p= 0; p < NR_PARTITIONS; p++) { 5587 if (table[p]->bootind != 0 && table[p]->sysind == MINIX_PART) { 5588 bootdev.primary= p; 5589 bootdev.name[4]= '1' + p; 5590 lowsec= table[p]->lowsec; 5591 break; 5592 } 5593 } 5594 #endif /* DOS */ 5595 } 5596 5597 #endif /* BIOS */ 5598 |
|
null[] is a single char, '\0'. This
is referred to as the "null string."
In order to signify that a string has no value, the string is set to the null string. For example, on line 5693, the third argument for b_setenv() is the null string. The third parameter to b_setenv()(line 5653) is char *arg and is used to pass a function argument. Since b_setvar() (line 5690) is specifically for variables and for functions without arguments, passing in the null string for char *arg is appropriate. A null string (i.e. a single char, '\0') could be created each
time we wished to signify that a string had no value. However, as
the comment suggests, this would be a significant waste of memory.
On the other hand, care must be taken when memory is freed. On line
5604, sfree() checks to make sure it's not deallocating the global
variable
null[], since many variables will undoubtedly be pointed
to null[].
| ||
|
For the reasons given for line 5599, care must be taken not to free
null[].
| ||
|
copystr() allocates memory (using malloc()) for a
new string and copies the string s (using strcpy()) to
it. If the string s is a null string, copystr()
returns null.
| ||
|
5609 {
5610 char *c; 5611 5612 if (*s == 0) return null; |
|
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.)
| ||
|
char *strcpy(char *s1,char *s2) copies string s2 to
string s1, including '\0' and returns s1.
| ||
| 5615 return c; |
| 5616 } |
| 5617 |
|
Lines 5618-5864 are principally concerned with environment variables
and functions. Environment variables and functions for the boot monitor
can be broken into 3 categories:
1) Variables and functions that the minix kernel needs. Examples are processor, bus, video, and mem[]. These are system parameters that the boot monitor determined that the kernel needs to know. params2params() in bootimage.c packages the environment variables so that they can be passed as an argument to the minix kernel . 2) Variables and functions that the boot monitor needs. These are typically commands that perform a small task. Examples are leader, main, and trailer (see lines 5825). The variable image (see line 5826) also falls into this category. 3) Reserved names. The names of built-in commands (like boot, menu, and ls) are protected and their function cannot be changed by the set command. struct environment is declared in boot.h . The flags field describes the behavior of the environment variable or function. The following attributes (bits) are the most difficult to understand: E_RESERVED(=0x04): Environment variables and functions that have the E_RESERVED bit set cannot be altered. These are the names of built-in commands (see lines 5837-5846). E_STICKY(=0x07): Once the E_STICKY bit is set for a variable or function, the bit cannot be unset. However, the E_STICKY bit is meaningless otherwise. The E_STICKY bit is never checked and none of the environment variables or functions set in get_parameters() (line 5773) have the E_STICKY bit set. The E_STICKY bit was perhaps significant in an earlier version of the boot monitor. E_SPECIAL(0x01): An environment variable with the E_SPECIAL bit set cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675). Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value. Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field. If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712). The defval field of an E_SPECIAL environment variable
or function is initially nil and becomes nil again if
the field value is restored with its default value (with the unset
command). is_default() can therefore look at the field defval
to determine if the environment variable or function has its "default"
value.
| ||
|
searchenv() searches through the environment variables and
functions for name and returns a pointer to a pointer to the corresponding
environment.
| ||
| 5624 { |
|
After aenv is initialized to &env, the memory
layout is as shown:
| ||
| 5626 |
|
Here is a slideshow
that shows the while loop:
| ||
|
5628
aenv= &(*aenv)->next;
5629 } 5630 5631 return aenv; 5632 } 5633 |
|
b_getenv() is a macro that dereferences the return value of
searchenv()
(line 5623). The return value of b_getenv() is a pointer
to an environment
rather than a pointer to a pointer to an environment. b_getenv()
is used in the next function, b_value() (line 5637).
xvc | ||
| 5635/* Return the environment *structure* belonging to name, nil if not found.*/ |
| 5636 |
|
b_value() returns the value of the environment variable name
or nil (0) if it is a function or it doesn't exist.
| ||
|
5639 {
5640 environment *e= b_getenv(name); 5641 5642 return e == nil || !(e->flags & E_VAR) ? nil : e->value; 5643 } 5644 |
|
b_body() returns the value of the environment function name
or nil (0) if it is a variable or it doesn't exist.
| ||
|
5647 {
5648 environment *e= b_getenv(name); 5649 |
|
5650
return e == nil || !(e->flags & E_FUNCTION)
? nil : e->value;
5651 } 5652 |
|
b_setenv() changes the value of an environment variable or
function or creates a new environment variable or function.
The flags field of environment describes the behavior of the environment variable or function. The following attributes (bits) are the most difficult to understand: E_RESERVED(=0x04): Environment variables and functions that have the E_RESERVED bit set cannot be altered. These are the names of built-in commands (see lines 5837-5846). (See comments for lines 5696-5699). E_STICKY(=0x07): Once the E_STICKY bit is set for a variable or function, the bit cannot be unset. However, the E_STICKY bit is meaningless otherwise. The E_STICKY bit is never checked and none of the environment variables or functions set in get_parameters() (line 5773) have the E_STICKY bit set. The E_STICKY bit was perhaps significant in an earlier version of the boot monitor. E_SPECIAL(0x01): An environment variable with the E_SPECIAL
bit set cannot be changed to a function and an environment function with
the
E_SPECIAL bit set cannot be changed to a variable (see lines
5670-5675). Also, an environment variable or function with the E_SPECIAL
bit set "remembers" its initial value. Before the value
field of an E_SPECIAL environment variable or function is changed
for the first time, the value field is copied to the defval
field. If the E_SPECIAL environment variable or function
is later unset, instead of removing the variable or function from
the environment, the defval field is copied back to the value
field (see lines 5705-5712).
| ||
| 5657 { |
| 5658 environment **aenv, *e; |
| 5659 |
|
If *aenv==nil, the environment variable name does
not exist and must be created.
| ||
|
The sizeof operator is flexible. The argument for sizeof
can be a type (like int, char, etc.), a variable, or
a dereferenced pointer (in this case, *e).
As noted above, sizeof is an operator and not a function.
What this means is that sizeof is a part of the C language itself,
and not a function that needs to be declared in a header file.
| ||
|
copystr() (line 5607) allocates memory (using malloc())
for a new string and copies the string name (using strcpy())
to it.
| ||
|
The environment variable name already exists. Be careful
with E_RESERVED and E_SPECIAL environment variables and
functions.
| ||
| 5668 e= *aenv; |
| 5669 |
|
E_RESERVED(=0x04): Environment variables and functions
that have the E_RESERVED bit set cannot be altered. These
are the names of built-in commands (see lines 5837-5846). (See comments
for lines 5696-5699).
E_SPECIAL(0x01): An environment variable with the E_SPECIAL
bit set cannot be changed to a function and an environment function with
the E_SPECIAL bit set cannot be changed to a variable (see lines
5670-5675). Also, an environment variable or function with the E_SPECIAL
bit set "remembers" its initial value. Before the value
field of an E_SPECIAL environment variable or function is changed
for the first time, the value field is copied to the defval
field. If the E_SPECIAL environment variable or function
is later unset, instead of removing the variable or function from
the environment, the defval field is copied back to the value
field (see lines 5705-5712).
| ||
|
5673
if (e->flags & E_RESERVED
|| (e->flags & E_SPECIAL
5674 && (e->flags & E_FUNCTION) != (flags & E_FUNCTION) 5675 )) return e->flags; 5676 |
|
E_STICKY(=0x07): Once the E_STICKY bit is set
for a variable or function, the bit cannot be unset. However, the
E_STICKY
bit is meaningless otherwise. The E_STICKY bit is never
checked and none of the environment variables or functions set in get_parameters()
(line 5773) have the E_STICKY bit set. The E_STICKY
bit was perhaps significant in an earlier version of the boot monitor.
| ||
| 5678 if (is_default(e)) { |
|
As noted above, an environment variable or function with the E_SPECIAL
bit set "remembers" its initial value. Before the value
field of an E_SPECIAL environment variable or function is changed
for the first time, the value field is copied to the defval
field. If the E_SPECIAL environment variable or function
is later unset, instead of removing the variable or function from
the environment, the defval field is copied back to the value
field (see lines 5705-5712).
| ||
| 5680 } else { |
|
e->value gets its new value on line 5686.
| ||
| 5682 } |
|
e->arg gets its new value on line 5685.
| ||
|
arg for int b_setenv(int flags, char *name, char *arg,
char *value) doesn't exist for a variable or a simple function (i.e.
a function without arguments). null is passed in for arg.
| ||
|
E_RESERVED(=0x04): Environment variables and functions
that have the E_RESERVED bit set cannot be altered. These
are the names of built-in commands (see lines 5837-5846).
E_SPECIAL(0x01): An environment variable with the E_SPECIAL bit set cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675). Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value. Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field. If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712). Strangely enough, b_unset() does not check to see if it's unsetting an E_RESERVED environment variable. hd2a>name1() {echo hello}
(system boots) Since execute() (see line 6535) checks for built-in commands
(like "boot") before user-defined commands, the built-in boot
takes precedence over the user-defined boot and the system boots
instead of echoing "hello".
| ||
|
5700 {
5701 environment **aenv, *e; 5702 5703 if ((e= *(aenv= searchenv(name))) == nil) return; 5704 5705 if (e->flags & E_SPECIAL) { 5706 if (e->defval != nil) { 5707 sfree(e->arg); 5708 e->arg= null; 5709 sfree(e->value); 5710 e->value= e->defval; 5711 e->defval= nil; 5712 } 5713 } else { 5714 sfree(e->name); 5715 sfree(e->arg); 5716 sfree(e->value); |
|
5718
free(e);
5719 } 5720 } 5721 |
|
a2l() converts a string (like "-1023") to a long.
I'm not sure why this function is described as "cheap." Perhaps
atol()
(from <stdlib.h>) performs error-checking. a2l() performs
no error-checking.
| ||
|
5724 {
5725 int sign= 1; 5726 long n= 0; 5727 5728 if (*a == '-') { sign= -1; a++; } 5729 |
|
between() (line 5048) returns TRUE if *a (the value
at address a) is between '0' and '9'.
*a++ dereferences pointer a and then increments a.
It does not increment the value at address a.
| ||
|
5731
5732 return sign * n; 5733 } 5734 |
|
ul2a() converts an unsigned long to an ascii string.
b
specifies the format of the number. For example, if b=0x10=16,
n=23
is converted to "17"; if b=0x08=8, n=23 is converted
to "27".
The ascii string is held in num[], which is 12 char's
(bytes) (see comments for line 5738). If b < 8, 12 bytes
is not enough to hold the largest possible value of n.
| ||
| 5737 { |
|
CHAR_BIT (=8) is declared in <limits.h>.
It is the number of bits in a char.
(CHAR_BIT*sizeof(n)+2)/3 + 1 = (8*4+2)/3 + 1
An internal static variable retains its value from one invocation
of the function to the next. The static declaration has
a different meaning for external variables (see comments for line 5057).
| ||
|
The least significant digit is determined first. We start at the
end of num[] and work our way back.
% is the modulus (remainder) operator. 11%3=2. *a-- dereferences pointer a and then decrements a.
It does not decrement the value at address a.
| ||
|
5740
static char hex[16] = "0123456789ABCDEF";
5741 5742 do *--a = hex[(int) (n % b)]; while ((n/= b) > 0); 5743 return a; 5744 } 5745 5746 char *ul2a10(u32_t n) 5747 /* Transform a long number to ascii at base 10. */ 5748 { 5749 return ul2a(n, 10); |
| 5750 } |
| 5751 |
|
a2x() converts ascii strings in hexadecimal notation to unsigned's.
The comment "Ascii to hex" is a little confusing.
| ||
|
5754 {
5755 unsigned n= 0; 5756 int c; 5757 5758 for (;;) { 5759 c= *a; 5760 if (between('0', c, '9')) c= c - '0' + 0x0; 5761 else 5762 if (between('A', c, 'F')) c= c - 'A' + 0xA; 5763 else 5764 if (between('a', c, 'f')) c= c - 'a' + 0xa; 5765 else 5766 break; |
|
Shifting a value to the left 4 bits is the equivalent of multiplying
the value by 16.
| ||
|
5768
a++;
5769 } 5770 return n; 5771 } 5772 |
|
get_parameters() is called in boot() (see line 6611).
The name get_parameters() is a little misleading since it sets
many environment variables and functions in addition to getting parameters
(from the bootparams sector - see line 5848).
| ||
| 5774 { |
|
readsectors() (see line 5849) reads the bootparams sector into
params[].
The "+1" is for the terminating '\0'.
| ||
|
cmds (line 5382) points to the first token. acmds
points to where another token and another line can be added (see line 5854).
| ||
| 5777 int r; |
| 5778 memory *mp; |
|
An internal static variable (like the array bus_type[][])
retains its value from one invocation of the function to the next.
The static declaration has a different meaning for global variables
(see comments for line 5057).
bus_type[0][0] = 'x'; bus_type[0][1] = 't'; bus_type[0][2] = '\0' bus_type could have been declared as bus_type[3][4]
instead of bus_type[][4] since there are 3 rows: "xt",
"at", and "mca". Instead, the compiler determines
the number of rows and does the work for us. 4 is the number of char's
(bytes) that the largest row ("mca") takes up ('m', 'c',
'a', '\0').
| ||
|
5780
"xt", "at", "mca"
5781 }; 5782 static char vid_type[][4] = { 5783 "mda", "cga", "ega", "ega", "vga", "vga" 5784 }; 5785 static char vid_chrome[][6] = { 5786 "mono", "color" 5787 }; 5788 |
|
The E_SPECIAL and E_RESERVED variables are set here.
Here's a review:
E_SPECIAL(0x01): An environment variable with the E_SPECIAL bit set in the flags field (of environment cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675). Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value. Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field. If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712). E_RESERVED(=0x04): Environment variables and functions that have the E_RESERVED bit set cannot be altered. These are the names of built-in commands (see lines 5837-5846). (See comments for lines 5696-5699). params2params() in bootimage.c packages the environment variables so that they can be passed as an argument to the minix kernel. A good description of these environment variables and boot monitor commands
can be found in the man
page.
| ||
|
5790
b_setvar(E_SPECIAL|E_VAR|E_DEV,
"rootdev", "ram");
5791 b_setvar(E_SPECIAL|E_VAR|E_DEV, "ramimagedev", "bootdev"); 5792 b_setvar(E_SPECIAL|E_VAR, "ramsize", "0"); 5793 #if BIOS |
|
get_processor() returns 86 for an 8086, 286 for an 80286, etc.
ula10() (line 5746) converts an unsigned long to an
ascii string with base 10.
| ||
|
5795
b_setvar(E_SPECIAL|E_VAR,
"bus", bus_type[get_bus()]);
5796 b_setvar(E_SPECIAL|E_VAR, "video", vid_type[get_video()]); 5797 b_setvar(E_SPECIAL|E_VAR, "chrome", vid_chrome[get_video() & 1]); |
| 5798 params[0]= 0; |
|
mem[] was set in boothead.s.
The value field of environment
for memory will be something like: "800:7FF00,100000:800000,1000000:400000"
(Note that this is a string). The first of the three (base,size)
pairs is for lower memory (memory under 1MB). The second (base, size)
pair is for memory between 1MB and 16MB. The third (base, size) pair
is for memory greater than 16MB. If the memory of the second and
third regions is contiguous, then the memory from the third region is aggregated
into the second region. If there is no memory above 1MB, then there
will only be a single (base, size) pair.
| ||
|
5800
if (mp->size == 0) continue;
5801 if (params[0] != 0) strcat(params, ","); 5802 strcat(params, ul2a(mp->base, 0x10)); 5803 strcat(params, ":"); 5804 strcat(params, ul2a(mp->size, 0x10)); 5805 } 5806 b_setvar(E_SPECIAL|E_VAR, "memory", params); |
|
I do not cover DOS-specific sections of code (line 5808). I may
cover these sections at a later time.
| ||
| 5808 b_setvar(E_SPECIAL|E_VAR, "dosd0", vdisk); |
| 5809 #else /* !DOS */ |
|
memory (see line 5806) replaces memsize and emssize.
| ||
|
5811
b_setvar(E_SPECIAL|E_VAR,
"memsize",
5812 ul2a10((mem[0].base + mem[0].size) / 1024)); 5813 b_setvar(E_SPECIAL|E_VAR, "emssize", ul2a10(mem[1].size / 1024)); 5814 #endif 5815 5816 #endif |
|
I do not cover UNIX-specific sections of code (lines 5818-5822).
I may cover these sections at a later time.
| ||
|
5818
b_setvar(E_SPECIAL|E_VAR, "processor", "?");
5819 b_setvar(E_SPECIAL|E_VAR, "bus", "?"); 5820 b_setvar(E_SPECIAL|E_VAR, "video", "?"); 5821 b_setvar(E_SPECIAL|E_VAR, "chrome", "?"); 5822 b_setvar(E_SPECIAL|E_VAR, "memory", "?"); 5823 #endif 5824 |
| 5825 /* Variables boot needs: */ |
|
minix is not an image file but a directory (/minix).
The boot monitor searches through the /minix directory and boots
the image file with the most recent modification time. To boot a
specific image, reset image:
hd2a> image = /minix/minix_386_09282000
| ||