460 Last LAB ASSIGNMENT
LAST LAB ASSIGNMENT
DUE and DEMO: CLOSE WEEK
0. MTX in ~samples/SH/mtx.gz:
Download the files in ~samples/SH/ directory.
mtx.gz is the MTX that can be run on real PC or dosemu or vmware.
Other files are helps to the last assignment.
1. Syscalls in MTX Kernel
The following table lists the syscall numbers and functions of the MTX kernel.
The functions are (almost) identical to their counterparts in Unix. The entries
marked with * are exceptions as noted below.
Number Function
--- --------------------------
0 pid = getpid()
1 ps() // ps in Kmode
2 chname(char *newname)
3 kmode() /* go Kmode */
4 switch() /* switch task */
5 pid = ufork() /* fork an identical child task */
* 6 exec(char *cmdLine) /* change image to filename */
* 20 pid = wait(int *status) /* wait for child to die */
21 mkdir(char *dirname)
22 rmdir(char *dirname)
* 23 creat(char *filename)
24 rm(char *filename)
26 chdir(char *dirname)
* 28 fd = open(char *filename, int mode) /* mode=0|1|2|3 */
29 close(int fd)
* 30 lseek(int fd, long offset)
33 chmod(char *filename, 0xxx);
34 chown(char *filename, int uid);
35 n = read(fd, buf, nbytes)
36 n = write(fd, buf, nbytes)
38 stat(char *filename, struct stat *s)
39 getcwd(char *s) /* get cwd string */
40 pipe(int pd[2])
41 dup(fd)
42 dup2(fd1, fd2)
43 ps() // print proc status in Kmode
44 uid=getuid(); // getuid
45 setuid(int uid); // set uid
46 gettty(char *tty); // get /dev/ttyX string
47 settty(char *tty); // set PROC.tty string
48 type=fdtype(int fd); // fd's open type (0-3:file),(4-5:pipe)
50 kill(int signal#, int pid) // deliver signal to pid
51 signal(int signal#, void catcher()) // install catcher()
52 sleep(int t) // pause for t seconds
53 settimer(int t) // set timer for t seconds
90 sync() // flush I/O buffers to disk
96 hits // buffer cache hit ratio
99 exit(value); /* enter kernel to die */
=============================================================================
EXCEPTIONS:
exec(char *cmdLine): Recall that in Unix if you enter a cmdLine
a.out arg1 arg2 ... argn
it is used in execl() as
execl(a.out, a.out, arg1, arg2,...,argn, 0);
In our MTX, the entire cmdLine is used in the exec() call.
For example, if you enter cat /a/b/c/d to sh (kcsh, that is),
the entire line "cat /a/b/c/d" is used in the exec() syscall.
Furthermore, the entire line is passed to the new image, which can be
written as
main(cmdLine) char *cmdLine; /* MINIX style C */
{
...................
}
Once in main(), you can recover the original cmdLine = "cat /a/b/c/d"
--------------------------------------------------------------------
creat(char *filename) : default file permissions=0644
--------------------------------------------------------
fd = open(char *filename, int mode) : mode=0/1/2/3 for r/w/rw/append
------------------------------------------------------------
lseek(int fd, long offset) : always from beginning of opened file
-------------------------------------------------------------
2. Operation of the MTX system:
Download ~samples/SH/mtx.gz, dd it to a FF disk. The MTX image supports all
the syscalls listed above.
When MTX starts, it will try to mount /dev/fd0 as the root file system.
Thus, the boot disk must be a valid EXT2 file system with the following
contents:
/|
|----bin/ : All binary executables are in /bin
|
|---dev/ : special files tty0 c 4 0
| ttyS0 c 5 0
| ttyS1 c 5 1
| lp0 c 6 0
|---etc/ : passwd file
|
|---boot/: bootable MTX images
|
|---user/: users HOME directories
After mounting the root file system, mainProc creats Task1, whose Umode
image is the /bin/init program. Task1 will go Umode directly to execute
/bin/init, in which it forks a child Task2 and waits for Task2 to die.
Henceforth, Task1 plays the role of the INIT process (P1) in Unix.
Task2:
(1). It opens the special file "/dev/tty0" as stdin, stdout and stderr.
(So the special file /dev/tty0 MUST exist. If not, use the Linux
command
mknod /dev/tty0 c 4 0
to create the special file. To support serial I/O and printer,
/dev/ttyS0, /dev/ttyS1 and /dev/lp0 must also exist).
(2). It exec() to execute the file /bin/login, in which it displays (to its
stdout)
login:
and waits for a user to login. When a user tries to login, it reads the
user name and password (from its stdin), opens the /etc/passwd file to
authenticate the user. Each line of /etc/passwd has the format:
username:password:gid:uid:fullname:HOMEDIR:program
e.g. root:xxxxxxx:1000:0:superuser:/root:sh
(Use plain text for password OR devise your own encryption schemes)
If the user has a valid account in /etc/passwd, Task2 becomes his/her
process. It should cd to HOMEDIR and execute the listed program, such
as sh (/bin/sh).
(3). then it loops forever (until "logout" or Contro-D):
{
prompts for a command line, e.g. cmdLine="cat filename"
if (cmd == "logout")
syscall to die;
// if just ONE cmd:
pid = ufork();
if (pid==0)
exec(cmdLine);
else
pid = wait(&status);
}
(3). When the child task terminates (via die syscall to MTX kernel),
it wakes up sh, which prompts for another cmdLine, etc.
(4). When sh dies, it wakes up its parent, INIT, which forks another
login process.
==========================================================================
3. OBJECTIVES:
The purpose of this assignment is for you to write YOUR OWN
INIT, login, sh and other user command programs. Among these, sh is the
most important. Accordingly, it will carry the most weight. A sample sh.c
is shown here to help you get started:
char cmdLine[64];
#include "ucode.c" /* contains utility functions */
int menu()
{
printf("#############################################################\n");
printf("# ls cd pwd cat more cp mv > >> #\n");
printf("# mkdir rmdir creat rm chmod chown lpr < | #\n");
printf("#############################################################\n");
}
main()
{
int stdin, stdout, stderr, i, j, pid, cid, me, status;
me = getpid();
printf("Enter ? for help menu\n");
while(1){
printf("input command : ");
gets(cmdLine);
if (cmdLine[0]==0)
continue;
token(cmdLine); /* break up cmdLine into name[0], name[1],.... */
if (strcmp(name[0], "?")==0 || strcmp(name[0], "help")==0){
menu(); continue;
}
if (strcmp(name[0], "logout")==0)
exit(0);
/****************************************************************
NOTE: some commands, e.g. cd, MUST be done by sh itself.
OTHER commands will be done as shown below:
****************************************************************/
/***** fork a child to execute the command ******/
pid = ufork();
if (pid){
printf("parent sh waits for child to die\n");
pid = wait(&status);
}
else{
printf("child task %d exec to %s\n", getpid(), name[0]);
exec(cmdLine);
printf("exec failed\n");
}
}
}
=============================================================================
As usual, you must compile sh.c and link it with u.s to generate an executable
a.out. cp it AS IS (i.e. do NOT delete header because my loader needs the
file header for sizes to allocate memory) to /fd0/bin/sh
A sample u.s file is shown below.
|======================================================================
| u.s file
|======================================================================
.globl begtext, begdata, begbss | needed by linker
.globl _main, _syscall, _exit0 | IMPORT EXPORT symbols
.text | these tell as:
begtext: | text,data,bss segments
.data | are all the same.
begdata:
.bss
begbss:
.text
call _main
call _exit0 | call exit(0) in C to die
_syscall:
int 80
ret
|======================================================================
In addition to sh.c, I used a separate ucode.c file for the common utility
functions, such as
gets(), printf()
and the (MODIFIED) getc()/putc() functions using stdin and stdout.
int getc()
{
int c;
read(0, &c, 1);
return (c & 0x7F);
}
int putc(c) int c;
{
write(1, &c, 1);
}
NOTE!!! these are NO LONGER calls to BIOS anymore, but read/write syscalls
to the MTX kernel. It depends on what the file descriptors 0 and 1 are at this
moment, which may NOT be /dev/tty0. This makes I/O redirection possible.
============================================================================
4. Command Programs:
Each of the commands (except >, <, >>, |) shown in the Menu is an executable
file in the /bin directory. The programs are developed in exactly the same
way as that of sh. For example, the mkdir program is shown below.
===================== mkdir.c file ==============================
#include "ucode.c"
main(s) char *s;
{
printf("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
printf(" This is KCW's mkdir in action \n");
printf("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
token(s); /* break up s into name[0], name[1],,... */
/* for the mkdir program, the original cmdLine = "mkdir dirname"
if (!name[1]){
/* show usage and exit(1) */
}
mkdir(name[1]); /* which is syscall(21, name[1], 0, 0); */
}
==============================================================================
Similarly for other commands.
5. ASSIGNMENTS:
DUE in CLOSED week
ORAL EXAM during DEMO
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
5-1. Develop YOUR OWN programs for
init // for process P1
login // for login process
cat [filename] /* cat filename or stdin to stdout */
more filename /* more as in Unix */
grep pattern filename /* print lines containing pattern */
cp f1 f2
5-2. Write YOUR OWN sh.c to support I/O redirections and pipes:
Examples: cat [filename] > newfile
cat [filename] >> appendFile
a.out < inFile /* read inputs from inFile */
cat filename | more
cat filename | grep test
cat filename | grep print | more
cat filename > /dev/lp0 /* print filename */
cp filename /dev/lp0 /* print filename */
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//*************************************************************************
// Logic of init.c
// NOTE: this init.c creates only ONE login process on console=/dev/tty0
// YOUR init.c must also create login processes on serial ports /dev/ttyS0
// and /dev/ttyS1..
//************************************************************************
int pid, child, status;
int stdin, stdout;
#include "ucode.c"
main(s) char *s;
{
// open /dev/tty0 as 0 (READ) and 1 (WRTIE) in order to display messages
stdin = open("/dev/tty0", 0);
stdout = open("/dev/tty0", 1);
// Now we can use printf, which calls putc(), which writes to stdout
printf("KCINIT : fork a login task on console\n");
child = ufork();
if (child)
parent();
else /* login task */
login();
}
int login()
{
exec("login /dev/tty0");
}
int parent()
{
printf("KCINIT : waiting .....\n");
while(1){
pid = wait(&status);
// if (pid == child)
ufork another login child
// else
// printf("INIT : buried an orphan child %d\n", pid);
}
}
//***********************************************************************
// LOGIC of login.c file
//***********************************************************************
char *tty;
main(s) char *s; // invoked by exec("login /dev/ttyxx")
{
token(s); // break up s as name[0], name[1]
tty = name[1];
// close 0,1,2 in case INIT opened them before
close(0); close(1); close(2);
// open tty (passed in by INIT) as stdin, stdout, stderr
stdin = myopen(tty, 0);
stdout = myopen(tty, 1);
stderr = myopen(tty, 1);
settty(tty); // store tty string in PROC.tty[] for putc()
// NOW we can use printf, which calls putc() to our tty
printf("KCLOGIN : open %s as stdin, stdout, stderr\n", tty);
signal(2,1); // syscall(51, 2, 1); ignore Control-C interrupts so that
// Control-C KILLs other procs on this tty but not the main sh
while(1){
1. show login: to stdout
2. read user nmae from stdin
3. show passwd:
4. read user passwd
5. verify user name and passwd from /etc/passwd file
6. if (user account valid){
chuid to user uid.
chdir to user HOME directory.
exec to the program in users's account
}
printf("login failed, try again\n");
}
}