#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[N_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); }