|
||||||||||||||
|
||||||||||||||
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. |
5008 */ |
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 |
5012 #define BIOS (!UNIX) /* Either uses BIOS or UNIX syscalls. */ |
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 |
5014 #define nil 0 |
#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.
| ||
5015 #define _POSIX_SOURCE 1 |
5016 #define _MINIX 1 |
_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.
| ||
5017 #include <stddef.h> |
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> |
5023 #include <errno.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 |
5030 #include <kernel/const.h> |
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 |
5033 #if UNIX |
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" |
5042 #undef EXTERN |
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 |
5046 #define arraysize(a) (sizeof(a) / sizeof((a)[0])) |
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.
| ||
5048 #define between(a, c, z) ((unsigned) ((c) - (a)) <= ((z) - (a))) |
between() returns TRUE if c is between a
and z.
| ||
5049 |
5050 #if BIOS |
5051 char *bios_err(int err) |
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 { |
5057 static struct errlist { |
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 */ |
5099 struct errlist *errp; |
errp is a pointer to a struct errlist (see line 5060).
| ||
5101 for (errp= errlist; errp < arraylimit(errlist); errp++) { |
5102 if (errp->err == err) return errp->what; |
5103 } |
5104 return "Unknown error"; |
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 |
5126 void readblock(off_t blk, char *buf) |
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 |
5132 if ((r= readsectors(mon2abs(buf), sec, 1 * RATIO)) != 0) { |
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.
| ||
5137 #define istty (1) |
5138 #define alarm(n) (0) |
5139 #define pause() (0) |
istty, alarm(n), and pause() expand to TRUE,
FALSE, and FALSE, respectively.
| ||
5140
5141 #endif /* BIOS */ 5142 |
5143 #if UNIX |
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; |
5279 line= malloc(z * sizeof(char)); |
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; |
5288 printf("\b \b"); |
"\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 if (c < ' ' && c != '\n') { |
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 } |
5302 } while (c != '\n'); |
The line is read until a newline ('\n') is typed.
| ||
5303 line[i]= 0; |
A terminating 0 is appended to line.
| ||
5304
return line;
5305 } 5306 |
5307 int sugar(char *tok) |
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 |
5313 char *onetoken(char **aline) |
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 { |
5316 char *line= *aline; |
line keeps track of the current position in the token.
On line 5350, it's copied to *aline.
| ||
5317 size_t n; |
5318 char *tok; |
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 |
5320 /* Skip spaces and runs of newlines. */ |
5321 while (*line == ' '||(*line =='\n'&&line[1] == '\n')) line++; |
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 |
5328 if (*line == '(') { |
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++; |
5340 } else { |
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 } |
5344 n= line - *aline; |
5345 tok= malloc((n + 1) * sizeof(char)); |
5346 memcpy(tok, *aline, n); |
5347 tok[n]= 0; |
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.
| ||
5348 if (tok[0] == '\n') tok[0]= ';'; /* ';' same as '\n' */ |
Newlines ('\n') and semicolons (;) have the same meaning
- they're both command separators. We standardize by converting newlines
to semicolons.
| ||
5350 *aline= line; |
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 |
5354 /* Typed commands form strings of tokens. */ |
| ||
5355
5356 typedef struct token { 5357 struct token *next; /* Next in a command chain. */ 5358 char *token; 5359 } token; 5360 |
5361 token **tokenize(token **acmds, char *line) |
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 |
5385 char *poptoken(void) |
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.
| ||
5392 free(cmd); |
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 |
5397 void voidtoken(void) |
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 |
5403 int interrupt(void) |
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 |
5418 struct biosdev { |
5419 char name[6]; |
5420 int device, primary, secondary; |
5421 } bootdev, tmpdev; |
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 |
5423 int get_master(char *master, struct part_entry **table, u32_t pos) |
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; |
5427 struct part_entry *pe, **pt; |
struct part_entry is declared in <ibm/partition.h>
(see line 04100 in the book).
| ||
5428 |
5429 if ((r= readsectors(mon2abs(master), pos, 1)) != 0) return r; |
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 |
5431 pe= (struct part_entry *) (master + PART_TABLE_OFF); |
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.
| ||
5432 for (pt= table; pt < table + NR_PARTITIONS; pt++) *pt= pe++; |
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. */ |
5435 if (pos != 0) return 0; /* But only the primary. */ |
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 |
5437 n= NR_PARTITIONS; |
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 } |
5450 void initialize(void) |
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 { |
5452 char master[SECTOR_SIZE]; |
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).
| ||
5453 struct part_entry *table[NR_PARTITIONS]; |
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; |
5455 u32_t masterpos; |
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"; |
5457 char *argp; |
argp is specific to DOS.
| ||
5458 |
5459 /* Copy the boot program to the far end of low memory, this must be |
5460 * done to get out of the way of Minix, and to put the data area |
5461 * cleanly inside a 64K chunk if using BIOS I/O (no DMA problems). |
5462 */ |
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 |
5467 u32_t dma64k= (memend - 1) & ~0x0FFFFL; |
(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 |
5481 #if !DOS |
5482 /* Take the monitor out of the memory map if we have memory to spare, |
5483 * and also keep the BIOS data area safe (1.5K), plus a bit extra for |
5484 * where we may have to put a.out headers for older kernels. |
5485 */ |
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 |
5490 /* Set the parameters for the BIOS boot device. */ |
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 |
5493 /* Find out what the boot device and partition was. */ |
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 |
5515 masterpos= 0; /* Master bootsector position. */ |
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 |
5517 for (;;) { |
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. */ |
5519 if ((r= get_master(master, table, masterpos)) != 0) { |
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 |
5544 /* See if the primary partition is subpartitioned. */ |
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 |
5554 #else /* DOS */ |
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 |
5599 char null[]= ""; /* This kludge saves lots of memory. */ |
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[].
| ||
5601 void sfree(char *s) |
5602 /* Free a non-null string. */ |
For the reasons given for line 5599, care must be taken not to free
null[].
| ||
5607 char *copystr(char *s) |
5608 /* Copy a non-null string using malloc. */ |
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; |
5613 c= malloc((strlen(s) + 1) * sizeof(char)); |
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.)
| ||
5614 strcpy(c, s); |
char *strcpy(char *s1,char *s2) copies string s2 to
string s1, including '\0' and returns s1.
| ||
5615 return c; |
5616 } |
5617 |
5618 int is_default(environment *e) |
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.
| ||
5623 environment **searchenv(char *name) |
searchenv() searches through the environment variables and
functions for name and returns a pointer to a pointer to the corresponding
environment.
| ||
5624 { |
5625 environment **aenv= &env; |
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 |
5637 char *b_value(char *name) |
5638 /* The value of a variable. */ |
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 |
5645 char *b_body(char *name) |
5646 /* The value of a function. */ |
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 |
5653 int b_setenv(int flags, char *name, char *arg, char *value) |
5654 /* Change the value of an environment variable. Returns the flags of the |
5655 * variable if you are not allowed to change it, 0 otherwise. |
5656 */ |
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.
| ||
5661 e= malloc(sizeof(*e)); |
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.
| ||
5667 } else { |
The environment variable name already exists. Be careful
with E_RESERVED and E_SPECIAL environment variables and
functions.
| ||
5668 e= *aenv; |
5669 |
5670 /* Don't touch reserved names and don't change special |
5671 * variables to functions or vv. |
5672 */ |
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)) { |
5679 e->defval= e->value; |
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.
| ||
5690 int b_setvar(int flags, char *name, char *value) |
5691 /* Set variable or simple function. */ |
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.
| ||
5696 void b_unset(char *name) |
5697 /*Remove a variable from the environment. A special variable is reset to |
5698 * its default value. |
5699 */ |
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); |
5717 *aenv= e->next; |
5718
free(e);
5719 } 5720 } 5721 |
5722 long a2l(char *a) |
5723 /* Cheap atol(). */ |
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 |
5735 char *ul2a(u32_t n, unsigned b) |
5736 /* Transform a long number to ascii at base b, (b >= 8). */ |
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 { |
5738 static char num[(CHAR_BIT * sizeof(n) + 2) / 3 + 1]; |
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).
| ||
5739 char *a= arraylimit(num) - 1; |
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 |
5752 unsigned a2x(char *a) |
5753 /* Ascii to hex. */ |
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; |
5767 n= (n<<4) | c; |
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 |
5773 void get_parameters(void) |
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 { |
5775 char params[SECTOR_SIZE + 1]; |
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; |
5779 static char bus_type[][4] = { |
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 |
5789 /* Variables that Minix needs: */ |
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; |
5799 for (mp= mem; mp < arraylimit(mem); mp++) { |
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); |
5807 #if DOS |
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 */ |
5810 /* Obsolete memory size variables. */ |
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 |
5817 #if UNIX |
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
| ||
5827 b_setvar(E_SPECIAL|E_FUNCTION, "leader", |
5828 "echo \\cMinix boot monitor \\v\\n" |
leader is the second to last command that the boot monitor
runs at system power-up and main is the last (see line 5862).
In order to create a string with a backslash ('\') followed by a character, the backslash must be preceded by another backslash. For example, "\\n" is a string consisting of a backslash followed by the character 'n' followed by the terminating 0. In contrast, "\n" is a string consisting of a newline followed by the terminating 0. For the boot monitor command echo, \c clears the screen,
\v
prints the version number, and \n prints a newline (see lines
6465-6498).
| ||
5830 b_setvar(E_SPECIAL|E_FUNCTION, "main", "menu"); |
main is the last command that the boot monitor runs at system
power-up. By default, main is the menu command.
The menu command shows a simple menu (see line 6235).
If you wish to go directly to the minix OS at system power-up, replace menu with boot: hd2a>main() {boot}
The next time the system is powered up, it will go directly to the minix OS. To reset boot to menu: hd2a>unset boot
trailer clears the screen. run_trailer() (line 6573) inserts trailer into the
command chain and processes the commands in the command chain. run_trailer()
is called from bootimage.c
after loading the OS image and before jumping to the kernel.
| ||
5831 b_setvar(E_SPECIAL|E_FUNCTION, "trailer", "echo \\c"); |
5832 |
5833 /* Default menu function: */ |
By default, \1 is the only command that the menu command
(see line 6235) shows, since \1 is the only environment function
with an arg that is not nil. (Note that
\1
is set using b_setenv() (line 5653) and the other environment
variables and functions are set using b_setvar() (line 5690).
I'm not sure why this function is named "\1". However,
since "\1" is never printed to the screen, it doesn't matter (see
menu()
on line 6235).
| ||
5834 b_setenv(E_RESERVED|E_FUNCTION, "\1", "=,Start Minix", "boot"); |
5835 |
5836 /* Reserved names: */ |
E_RESERVED environment variables and functions cannot be altered.
These are the names of built-in commands. (See comments for lines
5696-5699).
| ||
5837
b_setvar(E_RESERVED,
"boot", null);
5838 b_setvar(E_RESERVED, "menu", null); 5839 b_setvar(E_RESERVED, "set", null); 5840 b_setvar(E_RESERVED, "unset", null); 5841 b_setvar(E_RESERVED, "save", null); 5842 b_setvar(E_RESERVED, "ls", null); 5843 b_setvar(E_RESERVED, "echo", null); 5844 b_setvar(E_RESERVED, "trap", null); 5845 b_setvar(E_RESERVED, "help", null); 5846 b_setvar(E_RESERVED, "exit", null); |
5848 /* Tokenize bootparams sector. */ |
5849 if ((r= readsectors(mon2abs(params), lowsec+PARAMSEC, 1)) != 0) { |
5852 } |
5853 params[SECTOR_SIZE]= 0; |
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.
PARAMSEC=1. The first sector (sector 0) of an active partition or subpartition is the bootstrap sector and the second sector (sector 1) is the bootparams sector. The environment variables and functions from this sector are tokenized and put in the command chain on line 5854. Environment variables and functions are saved (with the save command - see line 5889) to this sector and separated by newlines (see line 5920). params[] is an array of size SECTOR_SIZE+1.
The last byte is the terminating 0.
| ||
5855
5856 /* Stuff the default action into the command chain. */ |
5857 #if UNIX
5858 (void) tokenize(acmds, ":;"); 5859 #elif DOS 5860 tokenize(tokenize(acmds, ":;leader;"), drun); 5861 #else /* BIOS */ |
The last two commands that the boot monitor processes at system start-up
are the leader and main commands (see lines 5827-5830).
There doesn't seem to be much point to the ":" command (see
lines 6549-6550).
| ||
5863 #endif |
5864 } |
5865 |
5866 void remote_code(void) |
5867 /* A rebooting Minix returns a bit of code for the monitor. */ |
remote_code() is called from boot() (see line 6621).
The "code" that is returned is actually a string of boot monitor commands
separated by semicolons (;). Here is an example of returned code:
var1=5;trailer;menu
| ||
5868 {
5869 #if BIOS 5870 if (reboot_code != 0) { |
5871 char code[SECTOR_SIZE + 2]; |
The second to last byte of code[] is for the terminating 0
(see line 5874) and the last byte is for the terminating semicolon (see
line 5875).
| ||
5872 |
5873 raw_copy(mon2abs(code), reboot_code, SECTOR_SIZE); |
5874
code[SECTOR_SIZE]=
0;
5875 strcat(code, ";"); 5876 (void) tokenize(&cmds, code); 5877 reboot_code= 0; 5878 } 5879 #endif 5880 } 5881 |
5882 char *addptr; |
addptr is used as a pointer in the params[] array
(see lines 5900-5901). As environment variables and functions are
copied to params[], addptr is advanced (see lines 5886
and 5920).
| ||
5883 |
5884 void addparm(char *n) |
addparm() copies one char of an environment variable
or function into params[]. If *n==0, the end of
the variable or function name, arg, or value
has been reached (see lines 5906, 5909, 5912, and 5915). If *addptr==0,
the end of params[] has been reached (see lines 5900 and 5916).
| ||
5889 void save_parameters(void) |
5890 /* Save nondefault environment variables to the bootparams sector. */ |
The following is true for nondefault environment variables and functions:
1) The E_RESERVED bit is not set for flags (see comment for line 5618). 2) If the E_SPECIAL bit is set, the defval field
must not be nil (see comment for line 5618).
| ||
5891 { |
5892 environment *e; |
5893 char params[SECTOR_SIZE + 1]; |
All nondefault environment variables and functions are copied to params[],
which is then copied to the bootparams sector. The last byte is the
terminating 0 (see line 5900).
save_parameters() is invoked from execute() (see line
6544) by the save command.
| ||
5894 int r; |
5895 |
5896 /* Default filling: */ |
5897 memset(params, '\n', SECTOR_SIZE); |
void *memset(void *s, unsigned char c, size_t n) places character
c
into the first n characters of s and returns s.
memset()
is declared in <string.h>.
| ||
5899 /* Don't touch the 0! */ |
5900 params[SECTOR_SIZE]= 0; |
addptr is used as a pointer in the params[] array
(see lines 5900-5901). As environment variables and functions are
copied to params[], addptr is advanced (see lines 5886
and 5920).
| ||
5902 |
5904 if (e->flags & E_RESERVED || is_default(e)) continue; |
This for loop loops through the environment variables and functions.
Only the nondefault environment variables are copied to
params[].
is_default() (line 5618) returns TRUE if the E_SPECIAL
bit of flags for the environment variable or function is set and
its defval field is nil (see comment for line 5618).
| ||
5905 |
Environment variables in params[] have either the form:
var1=value or (if the environment variable is a device): var2=d value Environment functions in params[] have the form: function1(arg)value value is a string of commands separated by spaces.
| ||
5907 if (e->flags & E_FUNCTION) { |
5908 addparm("("); |
5909 addparm(e->arg); |
5910 addparm(")"); |
5911 } else { |
5913 ? "=" : "=d "); |
5914 } |
5915 addparm(e->value); |
5916 if (*addptr == 0) { |
5917 printf("The environment is too big\n"); |
5918 return; |
5919 } |
Environment variables and functions in params[] are separated
by newlines ('\n').
| ||
5921 } |
5922 |
5923 /* Save the parameters on disk. */ |
5924 if ((r= writesectors(mon2abs(params), lowsec+PARAMSEC, 1)) != 0) { |
int writesectors(u32_t bufaddr, u32_t sector, u8_t count) copies
count
disk sectors from device
beginning with absolute sector number sector into absolute memory
address bufaddr.
u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address. The first sector of the active partition is lowsec. PARAMSEC=1. The first sector (sector 0) of an active
partition or subpartition is the bootstrap sector and the second sector
(sector 1) is the bootparams sector.
| ||
5930 void show_env(void) |
5931 /* Show the environment settings. */ |
show_env() is invoked from execute() (see line 6545)
by the set command. show_env() prints all environment
variables and functions whose flag
field doesn't have the E_RESERVED bit set.
| ||
5932 {
5933 environment *e; 5934 5935 for (e= env; e != nil; e= e->next) { 5936 if (e->flags & E_RESERVED) continue; |
5937 if (!istty && is_default(e)) continue; |
istty=1 (TRUE) if we're using the BIOS (see line 5137).
show_env()
(for BIOS) therefore prints out all E_SPECIAL environment variables
and functions (that aren't also E_RESERVED).
| ||
5938 |
5939 if (e->flags & E_FUNCTION) { |
Environment functions are printed in the format:
name(arg) value is_default() (line 5618) returns TRUE if the E_SPECIAL bit of flags for the environment variable or function is set and its defval field is nil (see comment for line 5618). Default environment variables are printed in the format: name=(value) Nondefault environment variable are printed in the format: name=value
| ||
5940
printf("%s(%s) %s\n", e->name, e->arg, e->value);
5941 } else { 5942 printf(is_default(e) ? "%s = (%s)\n" : "%s = %s\n", 5943 e->name, e->value); 5944 } 5945 } 5946 } |
5948 int numprefix(char *s, char **ps) |
5949 /* True iff s is a string of digits.*ps will be set to the first nondigit |
5950 * if non-nil, otherwise the string should end. |
5951 */ |
Since we wish to modify a pointer to a char, the second parameter (ps)
is a pointer to a pointer to a char.
numprefix() is called on lines 5967 and 6022. Line 6022
is the more interesting case, since the second argument isn't nil.
On line 6022, a reference to s (a pointer to a char)
is the second argument; in other words, the second argument is a pointer
to a pointer to a char.
| ||
5952 { |
5953 char *n= s; |
5954 |
between() returns TRUE if *n (the value at address
n)
is between '0' and '9'.
| ||
5956
5957 if (n == s) return 0; 5958 5959 if (ps == nil) return *n == 0; 5960 |
5961 *ps= n; |
On line 6022, this assignment changes the value of s. s
is set to the address of the first nondigit.
| ||
Since numprefix() (line 5948) expects a pointer to a pointer
to a char as the second argument, nil is cast to a pointer
to a pointer to a char.
| ||
5968 }
5969 5970 #if BIOS 5971 |
5972 /* Device numbers of standard Minix devices. */ |
SCSI drives have the same
numbering convention as hard drive partitions and subpartitions, but begin
at 0x0A00 instead of 0x0300. Up until this point, device values of 0x00 and 0x01 referred to the first and second floppy drives and 0x80, 0x81, 0x82, and 0x83 referred to the first through fourth hard drives. These are the values that the bios for IBM-compatibles expects. However, since minix runs on several different platforms, an architecture-independent numbering system for the devices is needed. This architecture-independent numbering system is explained in section 5.6.9 (beginning with the 4th paragraph) in Operating Systems. | ||
5973 #define DEV_RAM
0x0100
5974 #define DEV_FD0 0x0200 5975 #define DEV_HD0 0x0300 5976 #define DEV_SD0 0x0A00 5977 #define minor_1a 128 5978 |
5979 dev_t name2dev(char *name) |
name2dev() accomplishes 2 things:
1) Fills in the global variable tmpdev (line 5418) so that boot_device() (line 6115) knows which partition to boot. 2) Returns the architecture-independent device number for the device. This value is used in bootimage.c to set the value of the E_DEV environment variables that are passed to the minix kernel. The architecture-independent device numbers are: Up until this point, device values of 0x00 and 0x01 referred to the first and second floppy drives and 0x80, 0x81, 0x82, and 0x83 referred to the first through fourth hard drives. These are the values that the bios for IBM-compatibles expects. However, since minix runs on several different platforms, an architecture-independent numbering system for the devices is needed. Note that the values for tmpdev.device and bootdev.device continue to be 0x00 and 0x01 for floppy drives and 0x80, 0x81, 0x82, and 0x83 for hard drives. Only the return value for name2dev() is an architecture-independent device number. If name cannot be resolved to an architecture-independent device number, -1 is returned (see line 6060). Here are some possible values for name: "ram"
"hd6"
Note that "/dev/" is redundant in name. In other
words, "/dev/hd1a" and "hd1a" are the same device.
| ||
5980 /* Translate, say, /dev/hd3 to a device number.
If the name can't be
5981 * found on the boot device, then do some guesswork. The global structure 5982 * "tmpdev" will be filled in based on the name, so that "boot hd6" knows 5983 * what device to boot without interpreting device numbers. 5984 */ 5985 { |
5986 dev_t dev; |
dev is the architecture-independent device number.
| |||
5987 ino_t ino; |
ino_t is declared in <sys/types.h>. ino
is the i-node number of a (normal or special) file or directory.
Read the I-nodes section on page 418 of Operating
Systems and section 5.6.4 to better understand the function of i-nodes.
| ||
5988 int drive; |
drive isn't used in name2dev().
| ||
5989 struct stat st; |
stat is declared in <sys/stat.h> (see line 02300
in the book). r_stat() (see line 6048) returns information
about an i-node in st.
| ||
5990 char *n, *s; |
5991 |
5992 /* "boot *hd3" means: make partition 3 active before you boot it. */ |
activate (line 5416) is a global variable that indicates if
tmpdev
should be made active (see lines 6096-6110).
| ||
5994 |
5995 /* The special name "bootdev" must be translated to the boot device. */ |
bootdev is set in initialize() (see line 5493) and
is the drive or partition from which this code originated.
| ||
5996
if (strcmp(name, "bootdev") == 0) {
5997 if (bootdev.device == -1) { 5998 printf("The boot device could not be named\n"); 5999 errno= 0; |
6000 return -1; |
6001
}
6002 name= bootdev.name; 6003 } 6004 6005 /* If our boot device doesn't have a file system, or we want to know 6006 * what a name means for the BIOS, then we need to interpret the 6007 * device name ourselves: "fd" = floppy, "hd" = hard disk, etc. 6008 */ |
6010 dev= -1; |
Here are some possible values for tempdev:
device, primary and secondary for "fd1", "hd7", and "hd3a" are: name= "fd1"
name= "hd7"
name= "hd3a"
device, primary, secondary and dev
are all initialized to -1.
| ||
6011 n= name; |
6012 if (strncmp(n, "/dev/", 5) == 0) n+= 5; |
int strncmp(char *s1, char *s2, size_t n) compares at most
n
characters of string s1 to string s2 and returns 0 if
the first n characters of the two strings are the same. strncmp()
is declared in <string.h>.
The following two commands are identical: hd2a>boot hd2
Since "/dev/" is redundant, it is ignored by advancing n
five characters.
| ||
6013 |
6014 if (strcmp(n, "ram") == 0) { |
The next few if statements determine if name is a
ram disk, a floppy drive, a hard drive partition or subpartition, or a
SCSI drive partition or subpartition. Some possible values for name
include:
"ram"
"hd6"
int strcmp(char *s1, char *s2) compares string s1
to string s2 and returns 0 if the two strings are the same.
| ||
6015
dev= DEV_RAM;
6016 } else 6017 if (n[0] == 'f' && n[1] == 'd' && numeric(n+2)) { 6018 /* Floppy. */ 6019 tmpdev.device= a2l(n+2); 6020 dev= DEV_FD0 + tmpdev.device; 6021 } else |
numprefix() returns the address of the first nondigit from
the string n+2 in s. The value at address s
(*s) is either 'a', 'b', 'c', 'd',
or '\0'; if the value at address s is '\0',
name
refers to a partition and not a subpartition.
| ||
6024 ) { |
6025 /* Hard disk. */ |
6026 dev= a2l(n+2); |
NR_PARTITIONS is #defined in <ibm/partition.h>.
NR_PARTITIONS
= 4.
Again, here are some possible values of device, primary, and secondary: name= "hd7"
name= "hd3a"
| ||
6028
tmpdev.primary= (dev % (1 + NR_PARTITIONS)) - 1;
6029 if (*s != 0) { 6030 /* Subpartition. */ 6031 tmpdev.secondary= *s - 'a'; 6032 dev= minor_1a 6033 + (tmpdev.device * NR_PARTITIONS 6034 + tmpdev.primary) * NR_PARTITIONS 6035 + tmpdev.secondary; 6036 } 6037 tmpdev.device+= 0x80; |
Again, here are possible values of dev:
| |||
6039 } |
6040 |
6041 /* Look the name up on the boot device for the UNIX device number. */ |
6043 /* The current working directory is "/dev". */ |
fsok is set in boot() (see line 6615). fsok
is nonzero (TRUE) if a minix version 1 (SUPER_MAGIC) or minix version 2
(SUPER_V2) file system is on device (the floppy drive, hard drive,
or scsi drive from which this code originated).
The code inside the if block (lines 6043-6055) appears to be
an alternative to the calculation that is completed on line 6038.
Both methods determine the value of dev.
| ||
ROOT_INO (=1) is declared in "rawfs.h".
ROOT_INO
is the i-node number of the root directory ("/").
ino_t r_lookup(ino_t cwd, char *path) translates path to an i-node number. r_lookup() begins its search in the directory whose i-node is cwd. r_lookup(ROOT_INO, "dev") returns the i-node number of the
directory /dev and the outer r_lookup() returns the i-node
number of the file name located in the directory /dev.
| ||
6045 |
6046 if (ino != 0) { |
6047 /* Name has been found, extract the device number. */ |
void r_stat(ino_t file, struct stat *stp) returns information
in *stp about the file whose i-node number is file (see
comments for line 5989). Note that, in this case, *stp is
st.
r_stat()
is declared in "rawfs.h".
The only 2 fields of the stat struct (declared in <sys/stat.h>;
see line 02300 in the book) that are of interest here are st_mode
and st_rdev. S_ISBLK returns TRUE if st_mode
corresponds to a block device. S_ISBLK is #defined
in <sys/stat.h>. (Remember that disk drives are examples
of block devices; printers and modems are examples of character devices.)
st_rdev
is the architecture-independent device number.
| ||
6049 if (!S_ISBLK(st.st_mode)) { |
6050
printf("%s is not a block device\n", name);
6051 errno= 0; 6052 return (dev_t) -1; 6053 } 6054 dev= st.st_rdev; 6055 } 6056 } 6057 |
A partition or subpartition is activated (made bootable) by modifying
its partition table entry (see lines 6096-6110). If primary ==
-1, tmpdev is not a hard drive (or scsi drive) partition
or subpartition. So the asterik (*) in the command:
hd2a>boot *fd1 is meaningless and ignored.
| ||
6059
6060 if (dev == -1) { 6061 printf("Can't recognize '%s' as a device\n", name); 6062 errno= 0; 6063 } 6064 return dev; 6065 } 6066 6067 #if !DOS |
6068 #define B_NOSIG -1 /* "No signature" error code. */ |
SIGNATURE (AA55) must be at an offset of SIGNATOFF
(510) within a bootstrap or master boot record (MBR). SIGNATURE
and SIGNATOFF are #defined in boot.h
.
| ||
6069 |
6070 int exec_bootstrap(void) |
6072 execute it.*/ |
exec_bootstrap() is called from boot_device() (see
line 6131). boot_device() first calls name2dev()
to fill in the global variable tmpdev (line 5418); exec_bootstrap()
then boots the device described by tmpdev.
exec_bootstrap() does not return unless something goes wrong.
| ||
6073 { |
6074 int r, n, dirty= 0; |
If a partition table entry is changed in memory, dirty is set
to 1 so that the changes are written back to disk (see line 6109).
| ||
6075 char master[SECTOR_SIZE]; |
6076 struct part_entry *table[NR_PARTITIONS], dummy, *active= &dummy; |
get_master() (line 5423) writes a master boot record (MBR)
to master[] (see line 6085). An MBR is the first sector
on a hard drive and, if a partition is subpartitioned, the first sector
in a partition. An MBR consists of code and a partition table.
The code for the minix MBR is in masterboot.s.
The code for the minix bootstrap is in bootblock.s.
The partition table is located at offset 460 within the master boot record. get_master() (line 5423) fills in table[] with pointers to the partition table entries. The memory layout after the call to get_master() is shown below:
struct part_entry is declared in <ibm/partition.h>
(see line 04100 in the book).
| ||
6077 u32_t masterpos; |
6078 |
6079 active->lowsec= 0; |
Since active points to dummy (line 6076), this line
fills in dummy.lowsec.
| ||
6080 |
6081 /* Select a partition table entry. */ |
The first time through the while block (lines 6083-6093), the
master boot record (MBR) of the hard drive is loaded and examined .
The (optional) second time through the while block, the MBR for
a partition is then loaded and examined.
| ||
6083
masterpos= active->lowsec;
6084 6085 if ((r= get_master(master, table, masterpos)) != 0) return r; 6086 6087 active= table[tmpdev.primary]; 6088 6089 /* How does one check a partition table entry? */ |
6090 if (active->sysind == NO_PART) return B_NOSIG; |
NO_PART indicates that a partition or subpartition is not being
used. If it's not being used, there is (of course) no signature
| ||
If an entry is changed in the partition table in memory, then the change
needs to be written back to disk (see line 6109).
| ||
6097
for (n= 0; n < NR_PARTITIONS; n++) table[n]->bootind= 0;
6098 active->bootind= ACTIVE_FLAG; 6099 dirty= 1; |
6100
}
6101 6102 /* Read the boot sector. */ |
6103 if ((r= readsectors(BOOTPOS, active->lowsec, 1)) != 0) return r; |
active points to the partition table entry of the partition
or subpartition that we wish to boot. active->lowsec is
the first sector of this partition or subpartition and contains the bootstrap
and is loaded into memory at address BOOTPOS (=0x07C00). BOOTPOS
is #defined in boot.h.
| ||
6104 |
6105 /* Check signature word. */ |
SIGNATURE (AA55) must be at an offset of SIGNATOFF
(510) within a bootstrap or master boot record (MBR). SIGNATURE
and SIGNATOFF are #defined in boot.h
.
| ||
6107 |
6108 /* Write the partition table if a member must be made active. */ |
If an entry is changed in the partition table in memory, then the change
must be written back to disk (see line 6098).
| ||
6109 if (dirty && (r= writesectors(mon2abs(master), masterpos, 1)) != 0) |
6110 return r; |
6111 |
bootstrap() jumps to the bootstrap that was loaded into memory
(see line 6103). This function does not return.
| ||
6113 } |
6114 |
6115 void boot_device(char *devname) |
6116 /* Boot the device named by devname. */ |
boot_device() is invoked by the boot monitor boot
command (see line 6513). For the command:
hd2a>boot hd2a devname is the string "hd2a". boot_device() first calls name2dev() to fill in the global variable tmpdev (line 5418); exec_bootstrap() (see line 6131) then boots the device described by tmpdev. boot_device() does not return unless something goes wrong.
| ||
6117 { |
name2dev() (line 6979) fills in the global variable tmpdev.
Here are some possible values for tmpdev:
device, primary and secondary for "fd1", "hd7", and "hd3a" are: devname= "fd1"
devname= "hd7"
devname= "hd3a"
Note that dev is never used in boot_device().
| ||
If something goes wrong, the value of the global variable device
(declared in boot.h) is restored on line 6137. device is
the hard drive or floppy from which this code originated.
| ||
6120 int r; |
6121 char *err; |
6122 |
If tmpdev.device < 0, name2dev() couldn't match
up
devname with a device.
| ||
6124
if (dev != -1) printf("Can't boot from %s\n", devname);
6125 return; 6126 } 6127 6128 /* Change current device and try to load and execute its bootstrap. */ |
dev_open() (see line 6131) opens the device specified by the
global variable device (declared in boot.h
).
| ||
6130 |
6131 if ((r= dev_open()) == 0) r= exec_bootstrap(); |
dev_open() determines the number of sectors (the variable
sectors)
and the number of sectors per cylinder (the variable
secspcyl)
of device.
exec_bootstrap() (line 6070) loads the new bootstrap and jumps
to it. Unless something goes wrong, exec_bootstrap() does
not return. The rest of this function (boot_device()) reports
the error that occurred and restores the system to the state previous to
the call to boot_device().
| ||
6132 |
If device was bootable, the error must be a bios (I/O) error.
bios_err()
(line 5051) translates a bios error code to a readable string.
| ||
6134
printf("Can't boot %s: %s\n", devname, err);
6135 |
6141 #else /* DOS */ |
I do not cover DOS-specific sections of code (lines 6141-6149).
I may cover these sections at a later time.
| ||
6142
6143 void boot_device(char *devname) 6144 /* No booting of other devices under DOS */ 6145 { 6146 printf("Can't boot devices under MS-DOS\n"); 6147 } 6148 6149 #endif /* DOS */ |
6150 #endif /* BIOS */
6151 6152 void ls(char *dir) 6153 /* List the contents of a directory. */ 6154 { |
6155 ino_t ino; |
ino_t is declared in <sys/types.h>. ino
is the i-node number of a (normal or special) file or directory.
Read the I-nodes section on page 418 of Operating
Systems and section 5.6.4 to better understand the function of i-nodes.
| ||
6156 struct stat st; |
stat is declared in <sys/stat.h> (see line 02300
in the book). r_stat() (see line 6048) returns information
about an i-node in st.
| ||
6157 char name[NAME_MAX+1]; |
r_readdir() (see line 6162) reads the name of a file into
name[].
| ||
6158 |
fsok is set in boot() (see line 6615). fsok
is nonzero (TRUE) if a minix version 1 (SUPER_MAGIC) or minix version 2
(SUPER_V2) file system is on device (the floppy drive, hard drive,
or scsi drive from which this code originated).
| ||
6160 |
ino_t r_lookup(ino_t cwd, char *path) translates path
to an i-node number. r_lookup() begins its search in the
directory whose i-node is cwd.
void r_stat(ino_t file, struct stat *stp) returns information in *stp about the file or directory whose i-node number is file (see comments for line 5989). Note that, in this case, *stp is st. r_stat() is declared in "rawfs.h". r_stat() also fills in a variable (curfil) with information about this file or directory. r_readdir() reads a single directory entry into name[] from this directory (hopefully it will be a directory and not a file) and advances to the next directory entry in preparation for the next call to r_readdir(). r_readdir() returns -1 if curfil is a file and not a directory. The first time r_readdir() is called (and curfil is
a valid directory), it returns "." (dot - the current directory)
and the second time r_readdir() is called (line 6167), it returns
".." (dot dot - the parent directory). Neither of these
listings are printed (see line 6169).
| ||
6163
) {
6164 printf("ls: %s: %s\n", dir, unix_err(errno)); 6165 return; 6166 } 6167 (void) r_readdir(name); /* Skip ".." too. */ 6168 |
All directory entries (except for "." and "..") are
printed.
r_readdir() returns 0 if there are no more directory entries.
| ||
6170 } |
6171 |
6172 u32_t milli_time(void) |
milli_time() is called by milli_since() (line 6177)
and schedule() (line 6197). milli_time() returns
the number of milliseconds since midnight.
| ||
6173 { |
6174 return get_tick() * MSEC_PER_TICK; |
get_tick() returns the number of ticks since midnight.
There are 18.2 ticks per second.
| ||
6175 } |
6176 |
6177 u32_t milli_since(u32_t base) |
base is a time (in milliseconds) returned by get_tick()
(see lines 6219 and 6225). milli_since() returns the number
of milliseconds since base.
milli_since() is called by expired() (line 6207) and
delay()
(line 6213).
| ||
6178 {
6179 return (milli_time() + (TICKS_PER_DAY*MSEC_PER_TICK) - base) 6180 % (TICKS_PER_DAY*MSEC_PER_TICK); 6181 } 6182 |
6183 char *Thandler; |
6184 u32_t Tbase, Tcount; |
Only one command can be scheduled at any given time. If a command
is scheduled, Thandler is the command that is executed Tcount
milliseconds after Tbase (see lines6201-6203).
| ||
6185
6186 void unschedule(void) 6187 /* Invalidate a waiting command. */ 6188 { |
alarm(n) expands to 0 (see line 5138) for the BIOS. In
other words, it's a nop (no operation; it does nothing).
| ||
6190 |
A value of nil for Thandler means that nothing is
scheduled.
| ||
6197 void schedule(long msec, char *cmd) |
6198 /* Schedule command at a certain time from now. */ |
schedule() is invoked by the boot monitor trap command
(see line 6525). For example,
hd2a>trap 5000 boot schedules the boot command to be executed in 5 seconds.
| ||
6199 { |
6200 unschedule(); |
Only one command can be scheduled at any given time. schedule()
unschedules a previously scheduled command before scheduling Thandler.
| ||
6202 Tbase= milli_time(); |
Thandler is the command that is executed Tcount milliseconds
after Tbase.
Thandler is nil if no commands are scheduled.
| ||
alarm(n) expands to 0 (see line 5138) for the BIOS.
| ||
6205 } |
6206 |
6207 int expired(void) |
expired() is also called by delay() (see line 6225).
getch()
returns the ascii code for the escape key (ESC) if expired() returns
TRUE.
| ||
6209 {
6210 return (Thandler != nil && milli_since(Tbase) >= Tcount); 6211 } 6212 |
6213 void delay(char *msec) |
6214 /* Delay for a given time. */ |
delay() is invoked by the boot monitor delay command
(see line 6512).
hd2a>delay 5000 waits 5 seconds before returning. The delay command is
handy in scripts - delay gives users time to see screen output
before scrolling continues.
| ||
6215 {
6216 u32_t base, count; 6217 |
a2l() converts a string (like "1023") to a long.
(a2l() also converts negative values, although msec should
not be negative)
| ||
6219
base= milli_time();
6220 6221 alarm(1); 6222 6223 do { |
pause() expands to 0 (see line 5139). The delay is caused
by the conditional statement on line 6225.
The boot monitor delay command returns when: 1) the escape key (interrupt() (line 5403)) is pressed. 2) another command is scheduled (expired() (line 6207)) to be executed. 3) the delay is complete (milli_since(base) < count).
| ||
6225
} while (!interrupt()
&& !expired() && milli_since(base)
< count);
6226 } 6227 |
6228 enum whatfun { NOFUN, SELECT, DEFFUN, USERFUN }menufun(environment *e) |
enum whatfun {NOFUN, SELECT, DEFFUN, USERFUN} declares NOFUN
to be equal to 0, SELECT to be equal to 1, DEFFUN to
be equal to 2 and USERFUN to be equal to 3. Therefore menufun()
returns a value of 0, 1, 2, or 3.
struct environment is declared in boot.h
.
| ||
6229 { |
6230 if (!(e->flags & E_FUNCTION) || e->arg[0] == 0) return NOFUN; |
If e is an environment variable or a simple function (a function
with arg equal to null), menufun() returns NOFUN.
| ||
6231 if (e->arg[1] != ',') return SELECT; |
6232 return e->flags & E_RESERVED ? DEFFUN : USERFUN; |
Environment functions can be defined in two ways:
hd2a>image1(a) {image=image_file_1; boot} hd2a>image2(b,boot image_file_2) {image=image_file_2; boot} The first command defines a SELECT function since arg[1] != ','. The second command defines a USERFUN function since arg[1] == ','. Since the user has defined this function, it is not a DEFFUN (default) function. The only DEFFUN function is defined on line 5834 with setenv(). The term NOFUN is misleading since an environment function
with arg equal to null (a simple function) is NOFUN
and the term SELECT is misleading since a SELECT function
is also a user-defined function.
| ||
6233 } |
6234 |
6235 void menu(void) |
menu() is invoked by the boot monitor menu command
(see line 6543). By default, the only option the menu command
shows is:
= Start Minix If the user types "=", the command boot is executed (see line 5834). If there are several images and the user wishes to be given a choice, he can define his own functions: hd2a>image1(a) {image=image_file_1; boot} hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot} The menu command would show the following choices: a Select image1 kernel
Note that the "= Start Minix" command is not shown. If the user defines any functions of the form: hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot} then no default functions are shown (see comment for line 6231). Also note that the menu command shows no simple functions (simple functions are functions whose arg is null). The following function would not be shown as an option with the menu command: hd2a>image3() {image=image_file_3; boot}
| ||
6236 /* By default: Show a simple menu.
6237 * Multiple kernels/images: Show extra selection options. 6238 * User defined function: Kill the defaults and show these. 6239 * Wait for a keypress and execute the given function. 6240 */ 6241 { 6242 int c, def= 1; 6243 char *choice= nil; |
6244 environment *e; |
struct environment is declared in boot.h
.
| ||
6245 |
6246 /* Just a default menu? */ |
If the user defines any functions of the form:
hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot} no default functions are shown (see comments for lines 6231, 6235, and
6255). def is set to 0 (FALSE) if there are any USERFUN
functions.
| ||
6248
6249 printf("\nHit a key as follows:\n\n"); |
menufun() (line 6228) returns NOFUN, SELECT,
DEFFUN,
or USERFUN, depending on e->arg.
| ||
6254 case DEFFUN: |
6255 if (!def) break; |
6256 /*FALL THROUGH*/ |
6257 case USERFUN: |
If the user defined the following two commands:
hd2a>image1(a) {image=image_file_1; boot} hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot} then the menu command would show the following choices: a Select image1 kernel
| ||
6258
printf(" %c %s\n", e->arg[0], e->arg+2);
6259 break; 6260 case SELECT: 6261 printf(" %c Select %s kernel\n", e->arg[0],e->name); 6262 break; 6263 default:; 6264 } 6265 } 6266 6267 /* Wait for a keypress. */ |
6268 do { |
getch() waits for a character to be typed. If a command
has been scheduled, get_char() returns before a character is typed
and menu() returns (see expired() on line 6207).
If the character typed is the escape key (ESC), menu() also returns
(see interrupt() on line 5403).
| ||
6271 |
6272 unschedule(); |
If the user types in a choice before a scheduled command is ready to
execute, the command is unscheduled.
| ||
6273 |
All the environment variables are searched to find a match with the
character (c) typed.
| ||
6275
switch (menufun(e)) {
6276 case DEFFUN: 6277 if (!def) break; 6278 case USERFUN: 6279 case SELECT: 6280 if (c == e->arg[0]) choice= e->value; 6281 } 6282 } |
If the user types in a character that is not given as an menu option,
the choice is ignored and the wait continues for a valid choice.
| ||
6284 |
6285 /* Execute the chosen function. */ |
6286 printf("%c\n", c); |
tokenize() (line 5361) breaks a string into "tokens" so that
execute()
(see lines 6330 and 6620) can call the appropriate function. See
the comments for line 5307 for an explanation of tokens.
| ||
6288 } |
6289 |
6290 void help(void) |
6291 /* Not everyone is a rocket scientist. */ |
6292 {
6293 struct help { 6294 char *thing; 6295 char *help; |
6296 } *pi; |
pi is used to move from one element of info[] to the
next (see lines 6324-6327).
| ||
6297 static struct help info[] = { |
The static declaration specifies that the variable (in this
case, the array info[]) remains in existence after the function
returns. Since the contents of info[] don't change from
one invocation of help() to the next, it is preferable that the
array remain in existence. It saves the time for reinitialization
that would otherwise be necessary.
| ||
6298
{ nil,
"Names:" },
6299 { "rootdev", "Root device" }, |
6300
{ "ramimagedev",
"RAM disk image if root is RAM" },
6301 { "ramsize", "RAM disk size if root is not RAM" }, 6302 { "bootdev", "Special name for the boot device" }, 6303 { "fd0, hd3, hd2a", "Devices (as in /dev)" }, |
6304 { "image", "Name of the kernel image" }, |
The environment variable image is initialized to "minix".
Note that this is the directory /minix and not a file. If
image
is a directory and not a file, the boot monitor searches through the the
directory and boots the image file with the most recent modification time.
To boot a specific image, reset image to a filename:
hd2a> image = /minix/minix_386_09282000
| ||
6305
{ "main",
"Startup function" },
6306 { nil, "Commands:" }, 6307 { "name = [device] value", "Set environment variable" }, 6308 { "name() { ... }", "Define function" }, 6309 { "name(key,text) { ... }", 6310 "A menu function like: minix(=,Start Minix) {boot}" }, 6311 { "name", "Call function" }, 6312 { "boot [device]", "Boot Minix or another O.S." }, 6313 { "delay [msec]", "Delay (500 msec default)" }, 6314 { "echo word ...", "Print the words" }, 6315 { "ls [directory]", "List contents of directory" }, 6316 { "menu", "Choose a menu function" }, 6317 { "save", "Save environment" }, 6318 { "set", "Show environment" }, 6319 { "trap msec command", "Schedule command" }, 6320 { "unset name ...", "Unset variable or set to default" }, 6321 { "exit", "Exit the Monitor" }, 6322 }; 6323 |
6324 for (pi= info; pi < arraylimit(info); pi++) { |
arraylimit(info) (line 5047) returns the address of the first
byte after the last element of array info.
| ||
6325
if (pi->thing != nil) printf(" %-24s-
", pi->thing);
6326 printf("%s\n", pi->help); 6327 } 6328 } 6329 |
6330 void execute(void) |
6331 /* Get one command from the command chain and execute it. */ |
execute() is called in run_trailer() (see line 6582)
and boot() (see line 6620). execute() processes
commands from the bootparams sector (see line 5848) and commands entered
at the command prompt (see monitor() on line 6587).
Before execute() is called, commands are tokenized (see line 5307) and the tokens are linked together in a command chain. cmds (line 5382) points to the first token in the command chain. execute() translates the first command in the command chain to an action. For example, the command: hd2a>help invokes help() (see line 6546). The command: hd2a>echo \v prints the version number of the boot monitor to the screen (see line 6478). execute() can determine the nature of the first command in the command chain from: 1) the first four tokens (if there are that many). 2) the number of tokens until the first separator or the end of the command chain. For example, if the second token is a command separator (either a semicolon or a newline), the first command must be a simple command like boot or menu (see line 6535). If the first character of the second token is the left parenthesis ('(') and the first token is not a "sugar" (a sugar is a single character special token - see line 5307), then the command defines a function: hd2a>name1(args) {command1; command2; ... } and appropriate action is taken (see line 6383).
| ||
6332 { |
token is declared on line 5356:
typedef struct token {
sep is used on line 6349 to find the number of tokens in the
first command. second, third, and fourth
are set on lines 6351-6353.
| ||
6334 char *name= cmds->token; |
6335 size_t n= 0; |
6336 |
The global variable err is declared on line 5383. err
can be set for a number of reasons; one reason is if the escape key (ESC)
has been pressed (see lines 6448 and 6600).
| ||
6338 /* An error occured, stop interpreting. */ |
Clear out the command chain.
| ||
6340 return; |
6341 } |
6342 |
6345 unschedule(); |
6346 } |
expired() returns TRUE if a command (Thandler) is
scheduled to be executed at this time.
The global variable cmds points to the first token in the command chain. Since we wish to change the value of a pointer (cmds), we pass in a reference to cmds. The command Thandler is inserted into the beginning of the command chain and a semicolon (separator) is inserted directly after Thandler. Thandler can be unscheduled since it has been placed in the
command chain.
| ||
6348 /* There must be a separator lurking somewhere. */ |
6353 fourth= third->next; |
The command chain for the command:
hd2a>name1=value1 is shown below. n=3 since there are 3 tokens in the command.
| ||
6354 |
6355 /* Null command? */ |
6356 if (n == 0) { |
From this line until line 6566 (near the end of execute())
is a giant if - else if - else if - ... statement. Each
condition looks at a subset of name, first, second,
third,
fourth
and n (see comments for lines 6348-6353) to determine what action
to take.
If n=0, the first token is a semicolon or the command chain
is empty.
| ||
6357 voidtoken(); |
6358 return; |
6359 } else |
6360 /* name = [device] value? */ |
token1 token2 token3
token4
name1 = dev value1 or token1 token2 token3
| ||
6361 if ((n == 3 || n == 4) |
The following 2 if blocks (until line 6429) define environment
variables and functions.
| ||
A sugar is a single character special token (see line 5307).
| ||
6363
&& second->token[0] == '='
6364 && !sugar(third->token) 6365 && (n == 3 || (n == 4 && third->token[0] == 'd' 6366 && !sugar(fourth->token) 6367 ))) { 6368 char *value= third->token; 6369 int flags= E_VAR; 6370 6371 if (n == 4) { value= fourth->token; flags|= E_DEV; } 6372 |
b_setvar() (line 5690) sets an environment variable or a simple
function (a simple function is a function without an argument). b_setvar()
can fail (return a nonzero value) if:
1) an attempt is made to change the value of an E_RESERVED variable or function. 2) an attempt is made to change an E_SPECIAL function
to a variable (or vice versa).
| ||
6374
printf("%s is a %s\n", name,
6375 flags & E_RESERVED ? "reserved word" : 6376 "special function"); 6377 err= 1; 6378 } |
Clear out the command chain.
| ||
6380 return; |
6381 } else |
6382 /* name '(arg)' ... ? */ |
The following if block defines a function. After a function
is defined, it can be used as a boot monitor command. For example,
if a function is defined:
hd2a>name1() { echo hello } it can be called: hd2a>name1
If a function has one or two arguments: hd2a>image1(a) {image=image_file_1; boot}
the function must be invoked by the boot monitor menu command (see line 6235). token1 token2 token3
... tokenN
Note that arg can be the empty string; in other words, token2
can be "()".
| ||
6383
if (n >= 3
6384 && !sugar(name) 6385 && second->token[0] == '(' 6386 ) { 6387 token *fun; 6388 int c, flags, depth; 6389 char *body; 6390 size_t len; 6391 6392 sep= fun= third; 6393 depth= 0; 6394 len= 1; |
The use of curly braces is flexible. For example, the following
function definitions are the same:
hd2a>name1() boot;
| ||
6396 if ((c= sep->token[0]) == ';' && depth == 0) break; |
This if statement is for the case of a function definition
without curly braces:
hd2a>name1() boot;
| ||
6397 len+= strlen(sep->token) + 1; |
The third token through the last token are concatenated to form the
string body. The tokens are separated by spaces (which accounts
for the "+1" - see line 6410).
size_t strlen(char *s) returns the length of the string s.
strlen()
is declared in <string.h>.
| ||
6398 sep= sep->next; |
6399 if (c == '{') depth++; |
6400 if (c == '}' && --depth == 0) break; |
depth allows for the use of nested curly braces.
| ||
6401 } |
6402 |
6403 body= malloc(len * sizeof(char)); |
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.)
body is used as an argument for b_setenv() (see lines
6419-6420).
| ||
6404
*body= 0;
6405 6406 while (fun != sep) { 6407 strcat(body, fun->token); 6408 if (!sugar(fun->token) 6409 && !sugar(fun->next->token) 6410 ) strcat(body, " "); 6411 fun= fun->next; 6412 } |
6413 second->token[strlen(second->token)-1]= 0; |
token1 token2 token3
... tokenN
name1 (arg) { ... } token2 is (arg). Note that arg can be the empty string; in other words, token2 can be "()". Let's look at an example. If second->token is "(+)",
this line replaces the right parenthesis with '\0' (a terminating 0).
The third argument to b_setenv() on line 6420 is second->token+1
(which skips the left parenthesis). The string "+" is therefore the
second argument to b_setenv().
| ||
6414
6415 if (depth != 0) { 6416 printf("Missing '}'\n"); 6417 err= 1; 6418 } else |
6419 if ((flags= b_setenv(E_FUNCTION, name, |
b_setenv() (line 5690) sets an environment variable or a function.
b_setvar()
can fail (return a nonzero value) if:
1) an attempt is made to change the value of an E_RESERVED variable or function. 2) an attempt is made to change an E_SPECIAL variable
to a function (or vice versa).
| ||
6420
second->token+1, body)) != 0) {
6421 printf("%s is a %s\n", name, 6422 flags & E_RESERVED ? "reserved word" : 6423 "special variable"); 6424 err= 1; 6425 } |
Clear out the command chain.
| ||
6427 free(body); |
6428 return; |
6429 } else |
6430 /* Grouping? */ |
token1 ...
tokenN
{ ... } Curly braces can be used to string commands together. The following command: hd2a>{{echo hello\n} echo hello\n}
is the same as: hd2a>echo hello\n; echo hello\n;
(See line 6475 for an explanation of "\n".) This if block removes the first left curly brace (with voidtoken() on line 6444), replaces the last right curly brace with a semicolon (on line 6441) and then returns. Note that the commands are not executed; only the curly braces are eliminated. Other than the 2 curly braces, the command chain is left unchanged (in other words, cmds != nil). After execute() returns on line 6620, execute() is immediately called again. hd2a>{{echo hello\n} echo hello\n} is broken up into the following tokens: token1 token2 token3
token4
token5 token6 token7
token8
The first time execute() is called, it removes the first curly brace and replaces the second curly brace with a semicolon: token1 token2 token3
token4 token5 token6
token7
The second time execute() is called, it again removes the first curly brace and replaces the second curly brace with a semicolon: token1 token2
token3 token4 token5
token6
The next time execute() is called, it processes the echo hello\n command and the last time execute() is called, it processes the echo hello\n command again. Note that the two while loops on lines 6617 and 6619 are very
important. Their significance is discussed in boot() (line
6605).
| ||
6431 if (name[0] == '{') { |
token is declared on line 5356:
typedef struct token {
| ||
6433
char *t;
6434 int depth= 1; 6435 6436 /* Find and remove matching '}' */ 6437 depth= 1; 6438 while (*acmds != nil) { 6439 t= (*acmds)->token; 6440 if (t[0] == '{') depth++; 6441 if (t[0] == '}' && --depth == 0) { t[0]= ';'; break; } |
6442 acmds= &(*acmds)->next; |
token is declared on line 5356:
typedef struct token {
| ||
6443 } |
voidtoken() (line 5397) removes the first token from the command
chain.
| ||
6445 return; |
6446 } else |
6447 /* Command coming up, check if ESC typed. */ |
interrupt() returns 1 (TRUE) if the ESC key has been pressed.
interrupt()
sets the variable err (line 5383).
| ||
6449 return; |
6450 } else |
6451 /* unset name ..., echo word ...? */ |
6452 if (n >= 1 && ( |
6453 strcmp(name, "unset") == 0 |
6454 || strcmp(name, "echo") == 0 |
int strcmp(char *s1, char *s2) compares string s1
to string s2 and returns 0 if the two strings are the same.
| ||
6455 )) { |
6456 int cmd= name[0]; |
poptoken() (line 5385) pops the first token off the command
chain. In this case, the token is either the string "unset" or "echo".
Subsequent calls to poptoken()on line 6462 pop the arguments to
the boot monitor unset or echo commands.
| ||
6458 |
6459 for (;;) { |
for(;;) loops are endless loops. while(1) loops
are also endless loops (see line 6617).
| ||
6460
free(arg);
6461 if (cmds == sep) break; 6462 arg= poptoken(); 6463 if (cmd == 'u') { /* unset arg */ 6464 b_unset(arg); 6465 } else { /* echo arg */ 6466 p= arg; |
6467 while (*p != 0) { |
One character at a time is printed to the screen.
| ||
6468 if (*p != '\\') { |
'\\' is a single backslash character.
| ||
6469 putch(*p); |
6470 } else |
6471 switch (*++p) { |
6472 case 0: |
6474 continue; |
If an argument ends in a backslash, the backslash is ignored:
hd2a>echo hello\
| ||
6475 case 'n': |
If you wish to print two lines with echo, insert newlines:
hd2a>hello\n world
| ||
6476 putch('\n'); |
6477 break; |
6478 case 'v': |
hd2a>echo version \v
version 2.11 | ||
6479 printf(version); |
6480 break; |
6481 case 'c': |
The boot monitor leader command (see lines 5827-5829) clears
the screen.
| ||
6482 clear_screen(); |
6483 break; |
6484 case 'w': |
The command:
hd2a>echo hello \w world
waits until the user hits the Return (or Enter) key or the escape key
(ESC). All other keys are ignored. The Return key prints "world"
before returning to the prompt; the escape key returns to the prompt without
printing "world".
| ||
6485
for (;;) {
6486 if (interrupt()) 6487 return; 6488 if (getch() == '\n') 6489 break; 6490 } 6491 break; |
6492 default: |
To print a backslash:
hd2a>echo hello\\
| ||
6493
putch(*p);
6494 } 6495 p++; 6496 } |
A newline is printed before echo finishes. If newlines
weren't appended, the following would occur:
hd2a>echo hello
| ||
6498
}
6499 } |
6500 return; |
6501 } else |
6502 /* boot -opts? */ |
6503 if (n == 2 && strcmp(name, "boot") == 0 && second->token[0] == '-') { |
The command:
hd2a>boot -option1 sets the environment variable bootopts to "option1"
and then boots the OS image specified by the environment variable image.
| ||
6504 static char optsvar[]= "bootopts"; |
b_setvar() (line 5690) sets an environment variable or a simple
function (a simple function is a function without an argument).
| ||
bootminix() boots the OS image specified by the environment
variable image. bootminix() returns when the OS
returns to the boot monitor.
| ||
The environment variable bootopts is in existence only until
the OS returns to the boot monitor.
| ||
Remove the two tokens from the command chain.
| ||
6510 return; |
6511
} else
6512 /* boot device, ls dir, delay msec? */ |
6513
if (n == 2 && (
6514 strcmp(name, "boot") == 0 6515 || strcmp(name, "delay") == 0 6516 || strcmp(name, "ls") == 0 6517 )) { |
6518 if (name[0] == 'b') boot_device(second->token); |
void boot_device(char *devname) (line 6115) boots the partition
specified by devname. boot_device() does not return
unless the partition cannot be booted.
| ||
void delay(char *msec) (line 6213) delays for msec
milliseconds.
| ||
void ls(char *dir) (line 6152) lists the contents of directory
dir.
| ||
Remove the two tokens from the command chain.
| ||
6523 return; |
6524 } else |
6525 /* trap msec command? */ |
The command:
hd2a>trap 5000 boot schedules the boot monitor boot command to be executed in 5
seconds.
| ||
6526 if (n ==
3 && strcmp(name, "trap") == 0 && numeric(second->token))
{
6527 long msec= a2l(second->token); 6528 6529 voidtoken(); 6530 voidtoken(); |
void schedule(long msec, char *cmd) (line 6197) schedules the
boot monitor cmd command to be executed in msec milliseconds.
poptoken() returns the name of the command to be executed.
| ||
6532
return;
6533 } else 6534 /* Simple command. */ |
6535 if (n == 1) { |
6537 char *body; |
If cmd is a user-defined function, body is set to
the corresponding command string on line 6553 and the command string is
tokenized on line 6554.
| ||
6538 int ok= 0; |
If ok isn't set by line 6557, something's wrong.
| ||
6539 |
bootminix() boots the OS image specified by the environment
variable image. bootminix() returns when the OS
returns to the boot monitor.
| ||
The default for the boot monitor delay command is 500 milliseconds
(1/2 second). void delay(char *msec) (line 6213) delays
for msec milliseconds.
| ||
The default for the boot monitor ls delay is to list the contents
of the root directory ('/'). void ls(char *dir) (line 6152)
lists the contents of directory dir.
| ||
menu() (line 6235) (typically) shows a list of different OS
images the user can boot.
| ||
6544 if (strcmp(cmd, "save") == 0) { save_parameters(); ok= 1; } |
save_parameters() (line 5889) saves non-default environment
variables to the bootparams sector.
| ||
show_env() (line 5930) prints all environment variables and
functions whose flag
field doesn't have the E_RESERVED bit set.
| ||
help() (line 6290) lists a few environment variables and their
descriptions and also shows the available boot monitor commands.
| ||
exit() reboots the system.
| ||
6548 |
6549 /* Command to check bootparams: */ |
It is unclear to me what this function does. It does not appear to check anything. If you feel otherwise, please submit a comment to the site. | |||||
6550 if (strcmp(cmd, ":") == 0) ok= 1; |
6551 |
6552 /* User defined function. */ |
If cmd doesn't match any of the built-in commands above, it
must be a user-defined function. body is tokenized and successive
calls to execute() process the commands within body.
char *b_body(char *name) (line 5645) returns the value
of the environment function name. value contains
the function's commands. If name doesn't exist or is not
a function, b_body() returns nil.
| ||
6553
if (!ok && (body= b_body(cmd)) != nil)
{
6554 (void) tokenize(&cmds, body); 6555 ok= 1; 6556 } 6557 if (!ok) printf("%s: unknown function", cmd); 6558 free(cmd); 6559 if (ok) return; 6560 } else { 6561 /* Syntax error. */ 6562 printf("Can't parse:"); |
The attempted command is printed and voided.
| ||
6565
}
6566 } 6567 6568 /* Getting here means that the command is not understood. */ 6569 printf("\nTry 'help'\n"); 6570 err= 1; 6571 } 6572 |
6573 int run_trailer(void) |
run_trailer() is called from exec_image() in bootimage.c
right after the OS image has been loaded into memory and before jumping
to the minix kernel.
run_trailer() executes the boot monitor trailer command (see line 5831), which clears the screen. run_trailer() temporarily replaces the current command chain
with a command chain containing a single command - trailer.
After trailer is processed, the current command chain is reactivated.
| ||
6574 /* Run the trailer function between loading Minix and handing control to it. |