From 6af21e6a4f2251e71353562d5df7f376fdffc270 Mon Sep 17 00:00:00 2001 From: Freya Murphy Date: Tue, 25 Mar 2025 17:36:52 -0400 Subject: initial checkout from wrc --- user/Make.mk | 64 +++++++++++ user/README | 25 +++++ user/idle.c | 51 +++++++++ user/init.c | 185 +++++++++++++++++++++++++++++++ user/progABC.c | 68 ++++++++++++ user/progDE.c | 56 ++++++++++ user/progFG.c | 55 +++++++++ user/progH.c | 66 +++++++++++ user/progI.c | 104 +++++++++++++++++ user/progJ.c | 52 +++++++++ user/progKL.c | 61 ++++++++++ user/progMN.c | 72 ++++++++++++ user/progP.c | 51 +++++++++ user/progQ.c | 43 ++++++++ user/progR.c | 99 +++++++++++++++++ user/progS.c | 50 +++++++++ user/progTUV.c | 169 ++++++++++++++++++++++++++++ user/progW.c | 57 ++++++++++ user/progX.c | 48 ++++++++ user/progY.c | 49 +++++++++ user/progZ.c | 57 ++++++++++ user/shell.c | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ user/user.ld | 51 +++++++++ 23 files changed, 1876 insertions(+) create mode 100644 user/Make.mk create mode 100644 user/README create mode 100644 user/idle.c create mode 100644 user/init.c create mode 100644 user/progABC.c create mode 100644 user/progDE.c create mode 100644 user/progFG.c create mode 100644 user/progH.c create mode 100644 user/progI.c create mode 100644 user/progJ.c create mode 100644 user/progKL.c create mode 100644 user/progMN.c create mode 100644 user/progP.c create mode 100644 user/progQ.c create mode 100644 user/progR.c create mode 100644 user/progS.c create mode 100644 user/progTUV.c create mode 100644 user/progW.c create mode 100644 user/progX.c create mode 100644 user/progY.c create mode 100644 user/progZ.c create mode 100644 user/shell.c create mode 100644 user/user.ld (limited to 'user') diff --git a/user/Make.mk b/user/Make.mk new file mode 100644 index 0000000..648de69 --- /dev/null +++ b/user/Make.mk @@ -0,0 +1,64 @@ +# +# Makefile fragment for the user components of the system. +# +# THIS IS NOT A COMPLETE Makefile - run GNU make in the top-level +# directory, and this will be pulled in automatically. +# + +SUBDIRS += user + +################### +# FILES SECTION # +################### + +# order here must match the order of program names in the +# 'user_e' enum defined in include/userids.h!!! +USER_SRC := user/init.c user/idle.c user/shell.c \ + user/progABC.c user/progDE.c user/progFG.c user/progH.c \ + user/progI.c user/progJ.c user/progKL.c user/progMN.c \ + user/progP.c user/progQ.c user/progR.c user/progS.c \ + user/progTUV.c user/progW.c user/progX.c user/progY.c \ + user/progZ.c + +USER_OBJ := $(patsubst %.c, $(BUILDDIR)/%.o, $(USER_SRC)) + +USER_BIN := $(basename $(USER_SRC)) +USER_BIN := $(addprefix $(BUILDDIR)/, $(USER_BIN)) + +ULDFLAGS := -T user/user.ld +ULIBS := -luser -lcommon + +################### +# RULES SECTION # +################### + +userland: $(USER_BIN) + +$(BUILDDIR)/user/%.o: user/%.c $(BUILDDIR)/.vars.CFLAGS + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILDDIR)/user/%: $(BUILDDIR)/user/%.o + @mkdir -p $(@D) + $(LD) $(ULDFLAGS) $(LDFLAGS) -o $@ $@.o $(ULIBS) + $(OBJDUMP) -S $@ > $@.asm + $(NM) -n $@ > $@.sym + $(READELF) -a $@ > $@.info + +# +# Remake the "user blob". When this happens, we also generate a new +# version of the userids.h header file; we don't copy it over the +# previous version if it is the same, to avoid triggering remakes +# of the rest of the system. +# +user.img: $(USR_BIN) mkblob + ./mkblob $(USER_BIN) + @./listblob -e $@ > $(BUILDDIR)/new_userids.h + -@sh -c 'cmp -s include/userids.h $(BUILDDIR)/new_userids.h || \ + (cp $(BUILDDIR)/new_userids.h include/userids.h; \ + echo "\n*** NOTE - updated include/userids.h, rebuild\!" ; \ + rm -f $(BUILDDIR)/new_userids.h)' + +# some debugging assist rules +user.hex: user.img + hexdump -C $< > $@ diff --git a/user/README b/user/README new file mode 100644 index 0000000..548aac4 --- /dev/null +++ b/user/README @@ -0,0 +1,25 @@ +This directory contains the source code for all user-level processes, +split out by main function. + +Naming convention: + + idle() classic 'idle' process; ensures there is always a + runnable process to dispatch (vs., for instance, having + dispatch() pause when there is nothing to dispatch). + + init() classic 'init' process; starts the idle process, and + starts (and restarts) the user shell program. + + shell() "user shell" process, for spawning individual tests + + progN() program source code for user process(es) 'N' + +All of these expect at least one command-line argument. All are invoked +with command lines of this form: + + name x n + +Each of these is designed to be compiled and linked separately, with the +resulting load modules bundled into a blob for automatic loading by the +bootstrap. Each will typically use one or more library functions from the +../lib directory. diff --git a/user/idle.c b/user/idle.c new file mode 100644 index 0000000..dbce885 --- /dev/null +++ b/user/idle.c @@ -0,0 +1,51 @@ +#include + +/** +** Idle process: write, getpid, gettime, exit +** +** Reports itself, then loops forever delaying and printing a character. +** MUST NOT SLEEP, as it must always be available in the ready queue +** when there is no other process to dispatch. +** +** Invoked as: idle +*/ + +USERMAIN( main ) { + // this is the character we will repeatedly print + char ch = '.'; + + // ignore the command-line arguments + (void) argc; + (void) argv; + + // get some current information + uint_t pid = getpid(); + uint32_t now = gettime(); + enum priority_e prio = getprio(); + + char buf[128]; + sprint( buf, "Idle [%d], started @ %u\n", pid, prio, now ); + cwrites( buf ); + + // report our presence on the console + cwrites( "Idle started\n" ); + + write( CHAN_SIO, &ch, 1 ); + + // idle() should never block - it must always be available + // for dispatching when we need to pick a new current process + + for(;;) { + DELAY(LONG); + write( CHAN_SIO, &ch, 1 ); + } + + // we should never reach this point! + now = gettime(); + sprint( buf, "Idle [%d] EXITING @ %u!?!?!\n", pid, now ); + cwrites( buf ); + + exit( 1 ); + + return( 42 ); +} diff --git a/user/init.c b/user/init.c new file mode 100644 index 0000000..56330b1 --- /dev/null +++ b/user/init.c @@ -0,0 +1,185 @@ +#include + +/** +** Initial process; it starts the other top-level user processes. +** +** Prints a message at startup, '+' after each user process is spawned, +** and '!' before transitioning to wait() mode to the SIO, and +** startup and transition messages to the console. It also reports +** each child process it collects via wait() to the console along +** with that child's exit status. +*/ + +/* +** "Spawn table" process entry. Similar to the one in shell.c, but +** this version has a field to hold the PID of the spawned process +** to allow 'init' to respawn it when it terminates. +*/ +typedef struct proc_s { + uint_t index; // process table index + uint_t pid; // its PID (when spawned) + uint8_t e_prio; // process priority + char select[3]; // identifying character, NUL, extra + char *args[MAX_ARGS]; // argument vector strings +} proc_t; + +/* +** Create a spawn table entry for a process with a string literal +** as its argument buffer. We rely on the fact that the C standard +** ensures our array of pointers will be filled out with NULLs +*/ +#define PROCENT(e,p,s,...) { e, 0, p, s, { __VA_ARGS__ , NULL } } + +// sentinel value for the end of the table - must be updated +// if you have more than 90,210 user programs in the table +#define TBLEND 90210 + +/* +** This table contains one entry for each process that should be +** started by 'init'. Typically, this includes the 'idle' process +** and a 'shell' process. +*/ +static proc_t spawn_table[] = { + + // the idle process; it runs at Deferred priority, + // so it will only be dispatched when there is + // nothing else available to be dispatched + PROCENT( Idle, PRIO_DEFERRED, "!", "idle", "." ), + + // the user shell + PROCENT( Shell, PRIO_STD, "@", "shell" ), + + // PROCENT( 0, 0, 0, 0 ) + { TBLEND } +}; + +// character to be printed by init when it spawns a process +static char ch = '+'; + +/** +** process - spawn all user processes listed in the supplied table +** +** @param proc pointer to the spawn table entry to be used +*/ + +static void process( proc_t *proc ) +{ + char buf[128]; + + // kick off the process + int32_t p = fork(); + if( p < 0 ) { + + // error! + sprint( buf, "INIT: fork for #%d failed\n", + (uint32_t) (proc->index) ); + cwrites( buf ); + + } else if( p == 0 ) { + + // change child's priority + (void) setprio( proc->e_prio ); + + // now, send it on its way + exec( proc->index, proc->args ); + + // uh-oh - should never get here! + sprint( buf, "INIT: exec(0x%08x) failed\n", + (uint32_t) (proc->index) ); + cwrites( buf ); + + } else { + + // parent just reports that another one was started + swritech( ch ); + + proc->pid = p; + + } +} + +/* +** The initial user process. Should be invoked with zero or one +** argument; if provided, the first argument should be the ASCII +** character 'init' will print to indicate the spawning of a process. +*/ +USERMAIN( main ) { + char buf[128]; + + // check to see if we got a non-standard "spawn" character + if( argc > 1 ) { + // maybe - check it to be sure it's printable + uint_t i = argv[1][0]; + if( i > ' ' && i < 0x7f ) { + ch = argv[1][0]; + } + } + + cwrites( "Init started\n" ); + + // home up, clear on a TVI 925 + swritech( '\x1a' ); + + // wait a bit + DELAY(SHORT); + + // a bit of Dante to set the mood :-) + swrites( "\n\nSpem relinquunt qui huc intrasti!\n\n\r" ); + + /* + ** Start all the user processes + */ + + cwrites( "INIT: starting user processes\n" ); + + proc_t *next; + for( next = spawn_table; next->index != TBLEND; ++next ) { + process( next ); + } + + swrites( " !!!\r\n\n" ); + + /* + ** At this point, we go into an infinite loop waiting + ** for our children (direct, or inherited) to exit. + */ + + cwrites( "INIT: transitioning to wait() mode\n" ); + + for(;;) { + int32_t status; + int whom = waitpid( 0, &status ); + + // PIDs must be positive numbers! + if( whom <= 0 ) { + sprint( buf, "INIT: waitpid() returned %d???\n", whom ); + cwrites( buf ); + } else { + + // got one; report it + sprint( buf, "INIT: pid %d exit(%d)\n", whom, status ); + cwrites( buf ); + + // figure out if this is one of ours + for( next = spawn_table; next->index != TBLEND; ++next ) { + if( next->pid == whom ) { + // one of ours - reset the PID field + // (in case the spawn attempt fails) + next->pid = 0; + // and restart it + process( next ); + break; + } + } + } + } + + /* + ** SHOULD NEVER REACH HERE + */ + + cwrites( "*** INIT IS EXITING???\n" ); + exit( 1 ); + + return( 1 ); // shut the compiler up +} diff --git a/user/progABC.c b/user/progABC.c new file mode 100644 index 0000000..4f7b6e6 --- /dev/null +++ b/user/progABC.c @@ -0,0 +1,68 @@ +#include + +/** +** User function main #1: exit, write +** +** Prints its ID, then loops N times delaying and printing, then exits. +** Verifies the return byte count from each call to write(). +** +** Invoked as: main1 x n +** where x is the ID character +** n is the iteration count +*/ + +USERMAIN( main ) { + int count = 30; // default iteration count + char ch = '1'; // default character to print + char buf[128]; // local char buffer + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "%s: argc %d, args: ", argv[0], argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + int n = swritech( ch ); + if( n != 1 ) { + sprint( buf, "== %c, write #1 returned %d\n", ch, n ); + cwrites( buf ); + } + + // iterate and print the required number of other characters + for( int i = 0; i < count; ++i ) { + DELAY(STD); + n = swritech( ch ); + if( n != 1 ) { + sprint( buf, "== %c, write #2 returned %d\n", ch, n ); + cwrites( buf ); + } + } + + // all done! + exit( 0 ); + + // should never reach this code; if we do, something is + // wrong with exit(), so we'll report it + + char msg[] = "*1*"; + msg[1] = ch; + n = write( CHAN_SIO, msg, 3 ); /* shouldn't happen! */ + if( n != 3 ) { + sprint( buf, "User %c, write #3 returned %d\n", ch, n ); + cwrites( buf ); + } + + // this should really get us out of here + return( 42 ); +} diff --git a/user/progDE.c b/user/progDE.c new file mode 100644 index 0000000..a1aa0b3 --- /dev/null +++ b/user/progDE.c @@ -0,0 +1,56 @@ +#include + +/** +** User function main #2: write +** +** Prints its ID, then loops N times delaying and printing, then returns +** without calling exit(). Verifies the return byte count from each call +** to write(). +** +** Invoked as: main2 x n +** where x is the ID character +** n is the iteration count +*/ + +USERMAIN( main ) { + int n; + int count = 30; // default iteration count + char ch = '2'; // default character to print + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "main2: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + n = swritech( ch ); + if( n != 1 ) { + sprint( buf, "== %c, write #1 returned %d\n", ch, n ); + cwrites( buf ); + } + + // iterate and print the required number of other characters + for( int i = 0; i < count; ++i ) { + DELAY(STD); + n = swritech( ch ); + if( n != 1 ) { + sprint( buf, "== %c, write #2 returned %d\n", ch, n ); + cwrites( buf ); + } + } + + // all done! + return( 0 ); +} diff --git a/user/progFG.c b/user/progFG.c new file mode 100644 index 0000000..a43ca67 --- /dev/null +++ b/user/progFG.c @@ -0,0 +1,55 @@ +#include + +/** +** User function main #3: exit, sleep, write +** +** Prints its ID, then loops N times sleeping and printing, then exits. +** +** Invoked as: main3 x n s +** where x is the ID character +** n is the iteration count +** s is the sleep time in seconds +*/ + +USERMAIN( main ) { + char ch = '3'; // default character to print + int nap = 10; // default sleep time + int count = 30; // iteration count + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 4: nap = str2int( argv[3], 10 ); + // FALL THROUGH + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "main3: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + int n = swritech( ch ); + if( n != 1 ) { + sprint( buf, "=== %c, write #1 returned %d\n", ch, n ); + cwrites( buf ); + } + + write( CHAN_SIO, &ch, 1 ); + + for( int i = 0; i < count ; ++i ) { + sleep( SEC_TO_MS(nap) ); + write( CHAN_SIO, &ch, 1 ); + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progH.c b/user/progH.c new file mode 100644 index 0000000..386144d --- /dev/null +++ b/user/progH.c @@ -0,0 +1,66 @@ +#include + +/** +** User function H: exit, fork, exec, sleep, write +** +** Prints its ID, then spawns 'n' children; exits before they terminate. +** +** Invoked as: userH x n +** where x is the ID character +** n is the number of children to spawn +*/ + +USERMAIN( main ) { + int32_t ret = 0; // return value + int count = 5; // child count + char ch = 'h'; // default character to print + char buf[128]; + int whom; + + // process the argument(s) + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userH: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + swritech( ch ); + + // we spawn user Z and then exit before it can terminate + // userZ 'Z' 10 + + char *argsz[] = { "userZ", "Z", "10", NULL }; + + for( int i = 0; i < count; ++i ) { + + // spawn a child + whom = spawn( ProgZ, argsz ); + + // our exit status is the number of failed spawn() calls + if( whom < 0 ) { + sprint( buf, "!! %c spawn() failed, returned %d\n", ch, whom ); + cwrites( buf ); + ret += 1; + } + } + + // yield the CPU so that our child(ren) can run + sleep( 0 ); + + // announce our departure + swritech( ch ); + + exit( ret ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progI.c b/user/progI.c new file mode 100644 index 0000000..c37eddf --- /dev/null +++ b/user/progI.c @@ -0,0 +1,104 @@ +#include + +#ifndef MAX_CHILDREN +#define MAX_CHILDREN 50 +#endif + +/** +** User function I: exit, fork, exec, kill, sleep, waitpid, write +** +** Reports, then loops spawing userW, sleeps, kills two children, then +** loops checking the status of all its children +** +** Invoked as: userI [ x [ n ] ] +** where x is the ID character (defaults to 'i') +** n is the number of children to spawn (defaults to 5) +*/ + +USERMAIN( main ) { + int count = 5; // default child count + char ch = 'i'; // default character to print + int nap = 5; // nap time + char buf[128]; + char ch2[] = "*?*"; + uint_t children[MAX_CHILDREN]; + int nkids = 0; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + case 1: // just use the defaults + break; + default: + sprint( buf, "userI: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // secondary output (for indicating errors) + ch2[1] = ch; + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + // set up the argument vector + // we run: userW 10 5 + + char *argsw[] = { "userW", "W", "10", "5", NULL }; + + for( int i = 0; i < count; ++i ) { + int whom = spawn( ProgW, argsw ); + if( whom < 0 ) { + swrites( ch2 ); + } else { + swritech( ch ); + children[nkids++] = whom; + } + } + + // let the children start + sleep( SEC_TO_MS(nap) ); + + // kill two of them + int32_t status = kill( children[1] ); + if( status ) { + sprint( buf, "!! %c: kill(%d) status %d\n", ch, children[1], status ); + cwrites( buf ); + children[1] = -42; + } + status = kill( children[3] ); + if( status ) { + sprint( buf, "!! %c: kill(%d) status %d\n", ch, children[3], status ); + cwrites( buf ); + children[3] = -42; + } + + // collect child information + while( 1 ) { + int n = waitpid( 0, NULL ); + if( n == E_NO_CHILDREN ) { + // all done! + break; + } + for( int i = 0; i < count; ++i ) { + if( children[i] == n ) { + sprint( buf, "== %c: child %d (%d)\n", ch, i, children[i] ); + cwrites( buf ); + } + } + sleep( SEC_TO_MS(nap) ); + }; + + // let init() clean up after us! + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progJ.c b/user/progJ.c new file mode 100644 index 0000000..6eb4464 --- /dev/null +++ b/user/progJ.c @@ -0,0 +1,52 @@ +#include + +/** +** User function J: exit, fork, exec, write +** +** Reports, tries to spawn lots of children, then exits +** +** Invoked as: userJ x [ n ] +** where x is the ID character +** n is the number of children to spawn (defaults to 2 * N_PROCS) +*/ + +USERMAIN( main ) { + int count = 2 * N_PROCS; // number of children to spawn + char ch = 'j'; // default character to print + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userJ: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + // set up the command-line arguments + char *argsy[] = { "userY", "Y", "10", NULL }; + + for( int i = 0; i < count ; ++i ) { + int whom = spawn( ProgY, argsy ); + if( whom < 0 ) { + write( CHAN_SIO, "!j!", 3 ); + } else { + write( CHAN_SIO, &ch, 1 ); + } + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progKL.c b/user/progKL.c new file mode 100644 index 0000000..6bfb987 --- /dev/null +++ b/user/progKL.c @@ -0,0 +1,61 @@ +#include + +/** +** User function main #4: exit, fork, exec, sleep, write +** +** Loops, spawning N copies of userX and sleeping between spawns. +** +** Invoked as: main4 x n +** where x is the ID character +** n is the iteration count (defaults to 5) +*/ + +USERMAIN( main ) { + int count = 5; // default iteration count + char ch = '4'; // default character to print + int nap = 30; // nap time + char msg2[] = "*4*"; // "error" message to print + char buf[32]; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "main4: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + // argument vector for the processes we will spawn + char *arglist[] = { "userX", "X", buf, NULL }; + + for( int i = 0; i < count ; ++i ) { + + write( CHAN_SIO, &ch, 1 ); + + // second argument to X is 100 plus the iteration number + sprint( buf, "%d", 100 + i ); + int whom = spawn( ProgX, arglist ); + if( whom < 0 ) { + swrites( msg2 ); + } else { + write( CHAN_SIO, &ch, 1 ); + } + + sleep( SEC_TO_MS(nap) ); + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progMN.c b/user/progMN.c new file mode 100644 index 0000000..38ccd31 --- /dev/null +++ b/user/progMN.c @@ -0,0 +1,72 @@ +#include + +/** +** User function main #5: exit, fork, exec, write +** +** Iterates spawning copies of userW (and possibly userZ), reporting +** their PIDs as it goes. +** +** Invoked as: main5 x n b +** where x is the ID character +** n is the iteration count +** b is the w&z boolean +*/ + +USERMAIN( main ) { + int count = 5; // default iteration count + char ch = '5'; // default character to print + int alsoZ = 0; // also do userZ? + char msgw[] = "*5w*"; + char msgz[] = "*5z*"; + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 4: alsoZ = argv[3][0] == 't'; + // FALL THROUGH + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "main5: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // update the extra message strings + msgw[1] = msgz[1] = ch; + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + // set up the argument vector(s) + + // W: 15 iterations, 5-second sleep + char *argsw[] = { "userW", "W", "15", "5", NULL }; + + // Z: 15 iterations + char *argsz[] = { "userZ", "Z", "15", NULL }; + + for( int i = 0; i < count; ++i ) { + write( CHAN_SIO, &ch, 1 ); + int whom = spawn( ProgW, argsw ); + if( whom < 1 ) { + swrites( msgw ); + } + if( alsoZ ) { + whom = spawn( ProgZ, argsz ); + if( whom < 1 ) { + swrites( msgz ); + } + } + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progP.c b/user/progP.c new file mode 100644 index 0000000..5a264a5 --- /dev/null +++ b/user/progP.c @@ -0,0 +1,51 @@ +#include "common.h" + +/** +** User function P: exit, sleep, write, gettime +** +** Reports itself, then loops reporting itself +** +** Invoked as: userP x [ n [ t ] ] +** where x is the ID character +** n is the iteration count (defaults to 3) +** t is the sleep time (defaults to 2 seconds) +*/ + +USERMAIN( main ) { + int count = 3; // default iteration count + char ch = 'p'; // default character to print + int nap = 2; // nap time + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 4: nap = str2int( argv[3], 10 ); + // FALL THROUGH + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userP: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + uint32_t now = gettime(); + sprint( buf, " P@%u", now ); + swrites( buf ); + + for( int i = 0; i < count; ++i ) { + sleep( SEC_TO_MS(nap) ); + write( CHAN_SIO, &ch, 1 ); + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progQ.c b/user/progQ.c new file mode 100644 index 0000000..ecfcffc --- /dev/null +++ b/user/progQ.c @@ -0,0 +1,43 @@ +#include + +/** +** User function Q: exit, write, bogus +** +** Reports itself, then tries to execute a bogus system call +** +** Invoked as: userQ x +** where x is the ID character +*/ + +USERMAIN( main ) { + char ch = 'q'; // default character to print + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userQ: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + // try something weird + bogus(); + + // should not have come back here! + sprint( buf, "!!!!! %c returned from bogus syscall!?!?!\n", ch ); + cwrites( buf ); + + exit( 1 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progR.c b/user/progR.c new file mode 100644 index 0000000..1d47f6b --- /dev/null +++ b/user/progR.c @@ -0,0 +1,99 @@ +#include + +/** +** User function R: exit, sleep, write, fork, getpid, getppid, +** +** Reports itself and its sequence number, along with its PID and +** its parent's PID. It then delays, forks, delays, reports again, +** and exits. +** +** Invoked as: userR x n [ s ] +** where x is the ID character +** n is the sequence number of the initial incarnation +** s is the initial delay time (defaults to 10) +*/ + +USERMAIN( main ) { + char ch = 'r'; // default character to print + int delay = 10; // initial delay count + int seq = 99; // my sequence number + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 4: delay = str2int( argv[3], 10 ); + // FALL THROUGH + case 3: seq = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userR: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + /* + ** C oddity: a label cannot immediately precede a declaration. + ** + ** Declarations are not considered "statements" in C. Prior to + ** C99, all declarations had to precede any statements inside a + ** block. Labels can only appear before statements. C99 allowed + ** the mixing of declarations and statements, but did not relax + ** the requirement that labels precede only statements. + ** + ** That's why the declarations of these variables occur before the + ** label, but their initializations occur after the label. + ** + ** As the PSA says on TV, "The more you know..." :-) + */ + + int32_t pid; + int32_t ppid; + + restart: + + // announce our presence + pid = getpid(); + ppid = getppid(); + + sprint( buf, " %c[%d,%d,%d]", ch, seq, pid, ppid ); + swrites( buf ); + + sleep( SEC_TO_MS(delay) ); + + // create the next child in sequence + if( seq < 5 ) { + ++seq; + int32_t n = fork(); + switch( n ) { + case -1: + // failure? + sprint( buf, "** R[%d] fork code %d\n", pid, n ); + cwrites( buf ); + break; + case 0: + // child + goto restart; + default: + // parent + --seq; + sleep( SEC_TO_MS(delay) ); + } + } + + // final report - PPID may change, but PID and seq shouldn't + pid = getpid(); + ppid = getppid(); + sprint( buf, " %c[%d,%d,%d]", ch, seq, pid, ppid ); + swrites( buf ); + + exit( 0 ); + + return( 42 ); // shut the compiler up! + +} diff --git a/user/progS.c b/user/progS.c new file mode 100644 index 0000000..d220955 --- /dev/null +++ b/user/progS.c @@ -0,0 +1,50 @@ +#include + +/** +** User function S: exit, sleep, write +** +** Reports itself, then loops forever, sleeping on each iteration +** +** Invoked as: userS x [ s ] +** where x is the ID character +** s is the sleep time (defaults to 20) +*/ + +USERMAIN( main ) { + char ch = 's'; // default character to print + int nap = 20; // nap time + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 3: nap = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userS: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + sprint( buf, "userS sleeping %d(%d)\n", nap, SEC_TO_MS(nap) ); + cwrites( buf ); + + for(;;) { + sleep( SEC_TO_MS(nap) ); + write( CHAN_SIO, &ch, 1 ); + } + + sprint( buf, "!! %c exiting!?!?!?\n", ch ); + cwrites( buf ); + exit( 1 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progTUV.c b/user/progTUV.c new file mode 100644 index 0000000..3d5ed49 --- /dev/null +++ b/user/progTUV.c @@ -0,0 +1,169 @@ +#include + +/** +** User function main #6: exit, fork, exec, kill, waitpid, sleep, write +** +** Reports, then loops spawing userW, sleeps, then waits for or kills +** all its children. +** +** Invoked as: main6 x c b +** where x is the ID character +** c is the child count +** b is wait/kill indicator ('w', 'W', or 'k') +*/ + +#ifndef MAX_CHILDREN +#define MAX_CHILDREN 50 +#endif + +USERMAIN( main ) { + int count = 3; // default child count + char ch = '6'; // default character to print + int nap = 8; // nap time + bool_t waiting = true; // default is waiting by PID + bool_t bypid = true; + char buf[128]; + uint_t children[MAX_CHILDREN]; + int nkids = 0; + char ch2[] = "*?*"; + + // process the command-line arguments + switch( argc ) { + case 4: waiting = argv[3][0] != 'k'; // 'w'/'W' -> wait, else -> kill + bypid = argv[3][0] != 'w'; // 'W'/'k' -> by PID + // FALL THROUGH + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "main6: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // fix the secondary output message (for indicating errors) + ch2[1] = ch; + + // announce our presence + write( CHAN_SIO, &ch, 1 ); + + // set up the argument vector + char *argsw[] = { "userW", "W", "10", "5", NULL }; + + for( int i = 0; i < count; ++i ) { + int whom = spawn( ProgW, argsw ); + if( whom < 0 ) { + swrites( ch2 ); + } else { + children[nkids++] = whom; + } + } + + // let the children start + sleep( SEC_TO_MS(nap) ); + + // collect exit status information + + // current child index + int n = 0; + + do { + int this; + int32_t status; + + // are we waiting for or killing it? + if( waiting ) { + this = waitpid( bypid ? children[n] : 0, &status ); + } else { + // always by PID + this = kill( children[n] ); + } + + // what was the result? + if( this < SUCCESS ) { + + // uh-oh - something went wrong + + // "no children" means we're all done + if( this != E_NO_CHILDREN ) { + if( waiting ) { + sprint( buf, "!! %c: waitpid(%d) status %d\n", + ch, bypid ? children[n] : 0, this ); + } else { + sprint( buf, "!! %c: kill(%d) status %d\n", + ch, children[n], this ); + } + } else { + sprint( buf, "!! %c: no children\n", ch ); + } + + // regardless, we're outta here + break; + + } else { + + // locate the child + int ix = -1; + + // were we looking by PID? + if( bypid ) { + // we should have just gotten the one we were looking for + if( this != children[n] ) { + // uh-oh + sprint( buf, "** %c: wait/kill PID %d, got %d\n", + ch, children[n], this ); + cwrites( buf ); + } else { + ix = n; + } + } + + // either not looking by PID, or the lookup failed somehow + if( ix < 0 ) { + int i; + for( i = 0; i < nkids; ++i ) { + if( children[i] == this ) { + ix = i; + break; + } + } + } + + // if ix == -1, the PID we received isn't in our list of children + + if( ix < 0 ) { + + // didn't find an entry for this PID??? + sprint( buf, "!! %c: child PID %d term, NOT FOUND\n", + ch, this ); + + } else { + + // found this PID in our list of children + if( ix != n ) { + // ... but it's out of sequence + sprint( buf, "== %c: child %d (%d,%d) status %d\n", + ch, ix, n, this, status ); + } else { + sprint( buf, "== %c: child %d (%d) status %d\n", + ch, ix, this, status ); + } + } + + } + + cwrites( buf ); + + ++n; + + } while( n < nkids ); + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progW.c b/user/progW.c new file mode 100644 index 0000000..5903663 --- /dev/null +++ b/user/progW.c @@ -0,0 +1,57 @@ +#include + +/** +** User function W: exit, sleep, write, getpid, gettime +** +** Reports its presence, then iterates 'n' times printing identifying +** information and sleeping, before exiting. +** +** Invoked as: userW x [ n [ s ] ] +** where x is the ID character +** n is the iteration count (defaults to 20) +** s is the sleep time (defaults to 3 seconds) +*/ + +USERMAIN( main ) { + int count = 20; // default iteration count + char ch = 'w'; // default character to print + int nap = 3; // nap length + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 4: nap = str2int( argv[3], 10 ); + // FALL THROUGH + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userW: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + int pid = getpid(); + uint32_t now = gettime(); + sprint( buf, " %c[%d,%u]", ch, pid, now ); + swrites( buf ); + + write( CHAN_SIO, &ch, 1 ); + + for( int i = 0; i < count ; ++i ) { + now = gettime(); + sprint( buf, " %c[%d,%u] ", ch, pid, now ); + swrites( buf ); + sleep( SEC_TO_MS(nap) ); + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progX.c b/user/progX.c new file mode 100644 index 0000000..ed356b9 --- /dev/null +++ b/user/progX.c @@ -0,0 +1,48 @@ +#include + +/** +** User function X: exit, write, getpid +** +** Prints its PID at start and exit, iterates printing its character +** N times, and exits with a status of 12. +** +** Invoked as: userX x n +** where x is the ID character +** n is the iteration count +*/ + +USERMAIN( main ) { + int count = 20; // iteration count + char ch = 'x'; // default character to print + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "userX: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + int pid = getpid(); + sprint( buf, " %c[%d]", ch, pid ); + swrites( buf ); + + for( int i = 0; i < count ; ++i ) { + swrites( buf ); + DELAY(STD); + } + + exit( 12 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progY.c b/user/progY.c new file mode 100644 index 0000000..194f188 --- /dev/null +++ b/user/progY.c @@ -0,0 +1,49 @@ +#include + +/** +** User function Y: exit, sleep, write, getpid +** +** Reports its PID, then iterates N times printing 'Yx' and +** sleeping for one second, then exits. +** +** Invoked as: userY x [ n ] +** where x is the ID character +** n is the iteration count (defaults to 10) +*/ + +USERMAIN( main ) { + int count = 10; // default iteration count + char ch = 'y'; // default character to print + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "?: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // report our presence + int pid = getpid(); + sprint( buf, " %c[%d]", ch, pid ); + swrites( buf ); + + for( int i = 0; i < count ; ++i ) { + swrites( buf ); + DELAY(STD); + sleep( SEC_TO_MS(1) ); + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/progZ.c b/user/progZ.c new file mode 100644 index 0000000..cc394c4 --- /dev/null +++ b/user/progZ.c @@ -0,0 +1,57 @@ +#include + +/** +** User function Z: exit, sleep, write, getpid +** +** Prints its ID, then records PID and PPID, loops printing its ID, +** and finally re-gets PPID for comparison. Yields after every second +** ID print in the loop. +** +** This code is used as a handy "spawn me" test routine; it is spawned +** by several of the standard test processes. +** +** Invoked as: userZ x [ n ] +** where x is the ID character +** n is the iteration count (defaults to 10) +*/ + +USERMAIN( main ) { + int count = 10; // default iteration count + char ch = 'z'; // default character to print + char buf[128]; + + // process the command-line arguments + switch( argc ) { + case 3: count = str2int( argv[2], 10 ); + // FALL THROUGH + case 2: ch = argv[1][0]; + break; + default: + sprint( buf, "?: argc %d, args: ", argc ); + cwrites( buf ); + for( int i = 0; i <= argc; ++i ) { + sprint( buf, " %s", argv[argc] ? argv[argc] : "(null)" ); + cwrites( buf ); + } + cwrites( "\n" ); + } + + // announce our presence + int pid = getpid(); + sprint( buf, " %c[%d]", ch, pid ); + swrites( buf ); + + // iterate for a while; occasionally yield the CPU + for( int i = 0; i < count ; ++i ) { + sprint( buf, " %c[%d]", ch, i ); + swrites( buf ); + DELAY(STD); + if( i & 1 ) { + sleep( 0 ); + } + } + + exit( 0 ); + + return( 42 ); // shut the compiler up! +} diff --git a/user/shell.c b/user/shell.c new file mode 100644 index 0000000..5412033 --- /dev/null +++ b/user/shell.c @@ -0,0 +1,343 @@ +#include + +// should we keep going? +static bool_t time_to_stop = false; + +// number of spawned but uncollected children +static int children = 0; + +/* +** For the test programs in the baseline system, command-line arguments +** follow these rules. The first two entries are as follows: +** +** argv[0] the name used to "invoke" this process +** argv[1] the "character to print" (identifies the process) +** +** Most user programs have one or more additional arguments. +** +** See the comment at the beginning of each user-code source file for +** information on the argument list that code expects. +*/ + +/* +** "Spawn table" process entry. Similar to that in init.c, +** except this one has no place to store the PID of the child. +*/ +typedef struct proc_s { + uint_t index; // process table index + int8_t prio; // process priority + char select[3]; // identifying character, NUL, extra + char *args[MAX_ARGS]; // argument vector strings +} proc_t; + +/* +** Create a spawn table entry for a process with a string literal +** as its argument buffer. We rely on the fact that the C standard +** ensures our array of pointers will be filled out with NULLs +*/ +#define PROCENT(e, p, s, ...) { e, p, s, { __VA_ARGS__ , NULL } } + +// sentinel value for the end of the table - must be updated +// if you have more than 90,210 user programs in the table +#define TBLEND 90210 + +/* +** The spawn table contains entries for processes that are started +** by the shell. +*/ +static proc_t spawn_table[] = { + + // Users A-C each run ProgABC, which loops printing its character +#if defined(SPAWN_A) + PROCENT( ProgABC, PRIO_STD, "A", "userA", "A", "30" ), +#endif +#if defined(SPAWN_B) + PROCENT( ProgABC, PRIO_STD, "B", "userB", "B", "30" ), +#endif +#if defined(SPAWN_C) + PROCENT( ProgABC, PRIO_STD, "C", "userC", "C", "30" ), +#endif + + // Users D and E run ProgDE, which is like ProgABC but doesn't exit() +#if defined(SPAWN_D) + PROCENT( ProgDE, PRIO_STD, "D", "userD", "D", "20" ), +#endif +#if defined(SPAWN_E) + PROCENT( ProgDE, PRIO_STD, "E", "userE", "E", "20" ), +#endif + + // Users F and G run ProgFG, which sleeps between write() calls +#if defined(SPAWN_F) + PROCENT( ProgFG, PRIO_STD, "F", "userF", "F", "20" ), +#endif +#if defined(SPAWN_G) + PROCENT( ProgFG, PRIO_STD, "G", "userG", "G", "10" ), +#endif + + // User H tests reparenting of orphaned children +#if defined(SPAWN_H) + PROCENT( ProgH, PRIO_STD, "H", "userH", "H", "4" ), +#endif + + // User I spawns several children, kills one, and waits for all +#if defined(SPAWN_I) + PROCENT( ProgI, PRIO_STD, "I", "userI", "I" ), +#endif + + // User J tries to spawn 2 * N_PROCS children +#if defined(SPAWN_J) + PROCENT( ProgJ, PRIO_STD, "J", "userJ", "J" ), +#endif + + // Users K and L iterate spawning userX and sleeping +#if defined(SPAWN_K) + PROCENT( ProgKL, PRIO_STD, "K", "userK", "K", "8" ), +#endif +#if defined(SPAWN_L) + PROCENT( ProgKL, PRIO_STD, "L", "userL", "L", "5" ), +#endif + + // Users M and N spawn copies of userW and userZ via ProgMN +#if defined(SPAWN_M) + PROCENT( ProgMN, PRIO_STD, "M", "userM", "M", "5", "f" ), +#endif +#if defined(SPAWN_N) + PROCENT( ProgMN, PRIO_STD, "N", "userN", "N", "5", "t" ), +#endif + + // There is no user O + + // User P iterates, reporting system time and stats, and sleeping +#if defined(SPAWN_P) + PROCENT( ProgP, PRIO_STD, "P", "userP", "P", "3", "2" ), +#endif + + // User Q tries to execute a bad system call +#if defined(SPAWN_Q) + PROCENT( ProgQ, PRIO_STD, "Q", "userQ", "Q" ), +#endif + + // User R reports its PID, PPID, and sequence number; it + // calls fork() but not exec(), with each child getting the + // next sequence number, to a total of five copies +#if defined(SPAWN_R) + PROCENT( ProgR, PRIO_STD, "R", "userR", "R", "20", "1" ), +#endif + + // User S loops forever, sleeping 13 sec. on each iteration +#if defined(SPAWN_S) + PROCENT( ProgS, PRIO_STD, "S", "userS", "S", "13" ), +#endif + + // Users T-V run ProgTUV(); they spawn copies of userW + // User T waits for any child + // User U waits for each child by PID + // User V kills each child +#if defined(SPAWN_T) + PROCENT( ProgTUV, PRIO_STD, "T", "userT", "T", "6", "w" ), +#endif +#if defined(SPAWN_U) + PROCENT( ProgTUV, PRIO_STD, "U", "userU", "U", "6", "W" ), +#endif +#if defined(SPAWN_V) + PROCENT( ProgTUV, PRIO_STD, "V", "userV", "V", "6", "k" ), +#endif + + // a dummy entry to use as a sentinel + { TBLEND } + + // these processes are spawned by the ones above, and are never + // spawned directly. + + // PROCENT( ProgW, PRIO_STD, "?", "userW", "W", "20", "3" ), + // PROCENT( ProgX, PRIO_STD, "?", "userX", "X", "20" ), + // PROCENT( ProgY, PRIO_STD, "?", "userY", "Y", "10" ), + // PROCENT( ProgZ, PRIO_STD, "?", "userZ", "Z", "10" ) +}; + +/* +** usage function +*/ +static void usage( void ) { + swrites( "\nTests - run with '@x', where 'x' is one or more of:\n " ); + proc_t *p = spawn_table; + while( p->index != TBLEND ) { + swritech( ' ' ); + swritech( p->select[0] ); + } + swrites( "\nOther commands: @* (all), @h (help), @x (exit)\n" ); +} + +/* +** run a program from the program table, or a builtin command +*/ +static int run( char which ) { + char buf[128]; + register proc_t *p; + + if( which == 'h' ) { + + // builtin "help" command + usage(); + + } else if( which == 'x' ) { + + // builtin "exit" command + time_to_stop = true; + + } else if( which == '*' ) { + + // torture test! run everything! + for( p = spawn_table; p->index != TBLEND; ++p ) { + int status = spawn( p->index, p->args ); + if( status > 0 ) { + ++children; + } + } + + } else { + + // must be a single test; find and run it + for( p = spawn_table; p->index != TBLEND; ++p ) { + if( p->select[0] == which ) { + // found it! + int status = spawn( p->index, p->args ); + if( status > 0 ) { + ++children; + } + return status; + } + } + + // uh-oh, made it through the table without finding the program + sprint( buf, "shell: unknown cmd '%c'\n", which ); + swrites( buf ); + usage(); + } + + return 0; +} + +/** +** edit - perform any command-line editing we need to do +** +** @param line Input line buffer +** @param n Number of valid bytes in the buffer +*/ +static int edit( char line[], int n ) { + char *ptr = line + n - 1; // last char in buffer + + // strip the EOLN sequence + while( n > 0 ) { + if( *ptr == '\n' || *ptr == '\r' ) { + --n; + } else { + break; + } + } + + // add a trailing NUL byte + if( n > 0 ) { + line[n] = '\0'; + } + + return n; +} + +/** +** shell - extremely simple shell for spawning test programs +** +** Scheduled by _kshell() when the character 'u' is typed on +** the console keyboard. +*/ +USERMAIN( main ) { + + // keep the compiler happy + (void) argc; + (void) argv; + + // report that we're up and running + swrites( "Shell is ready\n" ); + + // print a summary of the commands we'll accept + usage(); + + // loop forever + while( !time_to_stop ) { + char line[128]; + char *ptr; + + // the shell reads one line from the keyboard, parses it, + // and performs whatever command it requests. + + swrites( "\n> " ); + int n = read( CHAN_SIO, line, sizeof(line) ); + + // shortest valid command is "@?", so must have 3+ chars here + if( n < 3 ) { + // ignore it + continue; + } + + // edit it as needed; new shortest command is 2+ chars + if( (n=edit(line,n)) < 2 ) { + continue; + } + + // find the '@' + int i = 0; + for( ptr = line; i < n; ++i, ++ptr ) { + if( *ptr == '@' ) { + break; + } + } + + // did we find an '@'? + if( i < n ) { + + // yes; process any commands that follow it + ++ptr; + + for( ; *ptr != '\0'; ++ptr ) { + char buf[128]; + int pid = run( *ptr ); + + if( pid < 0 ) { + // spawn() failed + sprint( buf, "+++ Shell spawn %c failed, code %d\n", + *ptr, pid ); + cwrites( buf ); + } + + // should we end it all? + if( time_to_stop ) { + break; + } + } // for + + // now, wait for all the spawned children + while( children > 0 ) { + // wait for the child + int32_t status; + char buf[128]; + int whom = waitpid( 0, &status ); + + // figure out the result + if( whom == E_NO_CHILDREN ) { + break; + } else if( whom < 1 ) { + sprint( buf, "shell: waitpid() returned %d\n", whom ); + } else { + --children; + sprint( buf, "shell: PID %d exit status %d\n", + whom, status ); + } + // report it + swrites( buf ); + } + } // if i < n + } // while + + cwrites( "!!! shell exited loop???\n" ); + exit( 1 ); +} diff --git a/user/user.ld b/user/user.ld new file mode 100644 index 0000000..9e31dff --- /dev/null +++ b/user/user.ld @@ -0,0 +1,51 @@ +/* +** Simple linker script for user-level programs. +*/ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS +{ + /* user text begins at the second page of the address space */ + . = 0x1000; + + .text : { + KEEP(*(.text .stub .text.* .gnu.linkonce.t.*)) + } + + /* define some standard symbols */ + PROVIDE(etext = .); + PROVIDE(_etext = .); + + /* read-only data will go at the end of the text section */ + .rodata : { + KEEP(*(.rodata .rodata.* .gnu.linkonce.r.*)) + } + + /* Align the data segment at the next page boundary */ + . = ALIGN(0x1000); + + .data : { + KEEP(*(.data)) + } + + PROVIDE(edata = .); + PROVIDE(_edata = .); + + /* Page-align the BSS segment */ + . = ALIGN(0x1000); + + PROVIDE(__bss_start = .); + + .bss : { + KEEP(*(.bss)) + } + + PROVIDE(_end = .); + + /DISCARD/ : { + *(.stab .stab_info .stabstr .eh_frame .note.GNU-stack .note.gnu.property .comment) + } +} -- cgit v1.2.3-freya