I'll be adding this document to the project information page shortly,
but in the meantime here's the info I promised regarding passing
command-line arguments to user programs.
Robert
--------
Several of you have asked about problem three: specifically, the
convention for passing command line arguments to a newly created
user process.
The basic procedure is fairly simple in principle (the arguments are
placed on the stack of the new process' address space), but there are
several confounding factors. As we go through the procedure, let us
consider the following example command: "/bin/ls -l *.h *.c"
The first thing to do is to break the command line into individual
strings: "/bin/ls", "-l", "*.h", and "*.c". These constitute the
arguments of the command (including the program name itself).
These individual, null-terminated strings should be placed on the user
stack. They may be placed in any order, but for simplicity let's
assume they are in reverse order (keeping in mind that the stack grows
downward). As we copy the arguments onto the stack, we record their
(virtual) stack addresses. These addresses constitute the members of
the argument vector, or argv.
After we push all of the strings onto the stack, we adjust the stack
pointer so that it is word-aligned: that is, we move it down to the
next 4-word boundary. This is required because we will next be
placing several words of data on the stack, and they must be aligned
in order to be read correctly. In our example, as you'll see below,
the strings start at address 0xffed, so the stack pointer would
initially be set to 0xffe9. But word alignment requires us to move
the stack pointer down to 0xffe8.
Once we align the stack pointer, we then push the elements of the
argument vector (that is, the addresses of the strings "/bin/ls",
"-l", "*.h", and "*.c" on the stack) onto the stack. This must be
done in reverse order, such that argv[0] is at the lowest virtual
address (again, because the stack is growing downward). After we
finish, we note the stack address of the first element of the argument
vector, which is argv itself.
Finally, we push argv (that is, the address of the first element of
the argv array on the stack) onto the stack, along with the length of
the argument vector (the argument count, argc -- 4 in this example).
In addition, we place argc in machine register 4 and argv in register
5; and we adjust the stack pointer to point to the new top of the
stack, the location directly below argc.
All of which may sound very confusing, so here's a picture which will
hopefully clarify what's going on. This represents the state of the
stack and the relevant registers right before the beginning of the
user program (assuming for this example a 16-bit virtual address space
with addresses from 0x0000 to 0xffff):
------------- ------------- ------------- -------------
*argv[argc-1] 0xfffc *argv[3] *.c\0
------------- ------------- ------------- -------------
*argv[argc-2] 0xfff8 *argv[2] *.h\0
------------- ------------- ------------- -------------
... 0xfff5 *argv[1] -l\0
------------- ------------- ------------- -------------
*argv[0] 0xffed *argv[0] /bin/ls\0
------------- ------------- ------------- -------------
<word-align> 0xffec <word-align> \0
------------- ------------- ------------- -------------
argv[argc-1] 0xffe8 argv[3] 0xfffc
------------- ------------- ------------- -------------
argv[argc-2] 0xffe4 argv[2] 0xfff8
------------- ------------- ------------- -------------
... 0xffe0 argv[1] 0xfff5
------------- ------------- ------------- -------------
argv[0] 0xffdc argv[0] 0xffed
------------- ------------- ------------- -------------
argv 0xffd8 argv 0xffdc
------------- ------------- ------------- -------------
argc 0xffd4 argc 4
------------- ------------- ------------- -------------
Stack pointer: 0xffd0
Register r4: argc 4
Register r5: argv 0xffdc
There's one final complication with placing the argv array elements,
argv, and argc on the stack. The simulated machine and the host
machine use different byte orders; so if we copy the integer
"0x0000ffdc" directly from the kernel onto the stack, the simulated
machine will actually interpret it as "0xdcff0000". In order to avoid
this problem, we need to use the function "WordToMachine", which
performs the conversion. So, for example, the following code would
push argc onto the stack and decrement the stack pointer accordingly:
int argc = 4;
int stackArgc = WordToMachine(argc);
int sp = machine->ReadRegister(StackReg);
addrSpace->CopyIn((int) &stackArgc, sp, 4);
machine->WriteRegister(StackReg, sp-4);
Where this example assumes the existence of some function
"void AddrSpace::CopyIn(int kernelSrc, int userDst, int length)" to
copy data from a kernel buffer to a user buffer -- your implementation
may vary.
Note that this byte-swapping is not required for accesses to machine
registers -- only the main memory. And it's not required for the
strings, either, since they aren't affected by byte order (because
they're character-based, and characters are single-byte quantities).
This is all fairly complicated, but try starting at this and thinking
about it for awhile. If you're still confused, send us an email or
come talk to one of the TAs at office hours.