summaryrefslogtreecommitdiff
path: root/user
diff options
context:
space:
mode:
Diffstat (limited to 'user')
-rw-r--r--user/Make.mk64
-rw-r--r--user/README25
-rw-r--r--user/idle.c51
-rw-r--r--user/init.c185
-rw-r--r--user/progABC.c68
-rw-r--r--user/progDE.c56
-rw-r--r--user/progFG.c55
-rw-r--r--user/progH.c66
-rw-r--r--user/progI.c104
-rw-r--r--user/progJ.c52
-rw-r--r--user/progKL.c61
-rw-r--r--user/progMN.c72
-rw-r--r--user/progP.c51
-rw-r--r--user/progQ.c43
-rw-r--r--user/progR.c99
-rw-r--r--user/progS.c50
-rw-r--r--user/progTUV.c169
-rw-r--r--user/progW.c57
-rw-r--r--user/progX.c48
-rw-r--r--user/progY.c49
-rw-r--r--user/progZ.c57
-rw-r--r--user/shell.c343
-rw-r--r--user/user.ld51
23 files changed, 1876 insertions, 0 deletions
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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+#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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+/**
+** 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 <common.h>
+
+// 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)
+ }
+}