/* Simulator for the protocols in chapter 3 of * "Computer Networks, 3rd ed. by Andrew S. Tanenbaum. * * April 11, 1996 * wchan: add two arguments for redirecting p0 and p1 outputs. */ #include #include #include #include #include #include #include #include #include "common.h" #define DEADLOCK (3 * timeout_interval) /* defines what a deadlock is */ #define MAX_PROTOCOL 8 /* highest protocol being simulated */ bigint tick = 0; /* the current time, measured in events */ bigint last_tick; /* when to stop the simulation */ int exited[2]; /* set if exited (for each worker) */ int hanging[2]; /* # times a process has done nothing */ struct sigaction act, oact; FILE *tty[2]; /* wchan: file descriptor for output */ /* I orignally intended these for tty only, but have since allowed redirection to arbitrary files. The name stuck. */ /* Prototypes. */ void main(int argc, char *argv[]); int parse_args(int argc, char *argv[]); void set_up_pipes(void); void fork_off_workers(void); void terminate(char *s); void sender2(void); void receiver2(void); void sender3(void); void receiver3(void); void protocol4(void); void protocol5(void); void protocol6(void); void sender7(void); void receiver7(void); void sender8(void); void receiver8(void); void main(int argc, char *argv[]) { /* The simulator has three processes: main, M0, and M1, all of which run * independently. Set them all up first. Once set up, main maintains the * clock (tick), and picks a process to run. Then it writes a 32-bit word * to that process to tell it to run. The process sends back an answer * when it is done. Main then picks another process, and the cycle repeats. */ int process = 0; /* whose turn is it */ int rfd, wfd; /* file descriptor for talking to workers */ bigint word; /* message from worker */ srandom(time(0)); act.sa_handler = SIG_IGN; setvbuf(stdout, (char *) 0, _IONBF, (size_t) 0); /* disable buffering*/ if (parse_args(argc, argv) < 0) exit(1); /* check args; store in mem */ set_up_pipes(); /* create five pipes */ fork_off_workers(); /* fork off the worker processes */ /* Main simulation loop. */ while (tick = DEADLOCK && hanging[1] >= DEADLOCK) terminate("A deadlock has been detected"); /* Write the time to the selected process to tell it to run. */ wfd = (process == 0 ? w3 : w5); if (write(wfd, &tick, TICK_SIZE) != TICK_SIZE) terminate("Main could not write to worker"); } /* Simulation run has finished. */ terminate("End of simulation"); } int parse_args(int argc, char *argv[]) { /* Inspect args on the command line and save them. */ if (argc > 9 || argc < 7) { printf("Usage: sim protocol events timeout loss cksum debug [p0_file p1_file]\n"); return(-1); } protocol = atoi(argv[1]); if (protocol < 2 || protocol > MAX_PROTOCOL) { printf("Protocol %d is not valid.\n", protocol); return(-1); } /* Each event uses DELTA ticks to make it possible for each timeout to * occur at a different tick. For example, with DELTA = 10, ticks will * occur at 0, 10, 20, etc. This makes it possible for a timeout in * protocol 5 to schedule multiple timeouts for the future, all at unique * times, e.g. 1070, 1071, 1072, 1073, etc. This property is needed to * make sure timers go off in the order they were set. As a consequence, * internally, the variable tick is bumped by DELTA on each event. Thus * asking for a simulation run of 1000 events will give 1000 events, but * they internally they will be called 0 to 10,000. */ last_tick = DELTA * atol(argv[2]); /* each event uses DELTA ticks */ if ((long) last_tick < 0) { printf("Number of simulation events must be positive\n"); return(-1); } /* Convert from external units to internal units so the user does not see * the internal units at all. */ timeout_interval = DELTA * atoi(argv[3]); if ((long)timeout_interval < 0 || (protocol > 2 && timeout_interval == 0) ){ printf("Timeout interval must be positive\n"); return(-1); } /* Packet loss takes place at the sender. Packets selected for being lost * are not put on the wire at all. Internally, pkt_loss and garbled are * from 0 to 990 so they can be compared to 10 bit random numbers. The * inaccuracy here is about 2.4% because 1000 != 1024. In effect, it is * not possible to say that all packets are lost. The most that can be * be lost is 990/1024. */ pkt_loss = atoi(argv[4]); /* percent of sends that chuck pkt out */ if (pkt_loss < 0 || pkt_loss > 99) { printf("Packet loss rate must be between 0 and 99\n"); return(-1); } pkt_loss = 10 * pkt_loss; /* for our purposes, 1000 == 1024 */ /* This arg tells what fraction of arriving packets are garbled. Thus if * pkt_loss is 50 and garbled is 50, half of all packets (actually, * 500/1024 of all packets) will not be sent at all, and of the ones that * are sent, 500/1024 will arrive garbled. */ garbled = atoi(argv[5]); if (garbled < 0 || garbled > 99) { printf("Packet cksum rate must be between 0 and 99\n", garbled); return(-1); } garbled = 10 * garbled; /* for our purposes, 1000 == 1024 */ /* Turn tracing options on or off. The bits are defined in worker.c. */ debug_flags = atoi(argv[6]); if (debug_flags < 0) { printf("Debug flags may not be negative\n", debug_flags); return(-1); } /* wchan: set up tty's */ if (argc == 7) { tty[0] = tty[1] = stdout; printf("\n\nProtocol %d. Events: %u Parameters: %u %d %u\n", protocol, last_tick/DELTA, timeout_interval/DELTA, pkt_loss/10, garbled/10); } else { if (strcmp(argv[7],"-")==0) tty[0] = stdout; else tty[0] = fopen(argv[7],"w"); if (tty[0]==NULL) { printf("Cannot open %s for output of process 0\n",argv[7]); return(-1); } if (argc == 9) { if (strcmp(argv[8],"-")==0) tty[1] = stdout; else tty[1] = fopen(argv[8],"w"); if (tty[1]==NULL) { printf("Cannot open %s for output of process 1\n",argv[8]); return(-1); } printf("\n\nProtocol %d. Events: %u Parameters: %u %d %u %s %s\n", protocol, last_tick/DELTA, timeout_interval/DELTA, pkt_loss/10, garbled/10, argv[7], argv[8]); } else { tty[1] = tty[0]; printf("\n\nProtocol %d. Events: %u Parameters: %u %d %u %s\n", protocol, last_tick/DELTA, timeout_interval/DELTA, pkt_loss/10, garbled/10, argv[7]); } } return(0); /* no errors in command line parameters */ } void set_up_pipes(void) { /* Create six pipes so main, M0 and M1 can communicate pairwise. */ int fd[2]; pipe(fd); r1 = fd[0]; w1 = fd[1]; /* M0 to M1 for frames */ pipe(fd); r2 = fd[0]; w2 = fd[1]; /* M1 to M0 for frames */ pipe(fd); r3 = fd[0]; w3 = fd[1]; /* main to M0 for go-ahead */ pipe(fd); r4 = fd[0]; w4 = fd[1]; /* M0 to main to signal readiness */ pipe(fd); r5 = fd[0]; w5 = fd[1]; /* main to M1 for go-ahead */ pipe(fd); r6 = fd[0]; w6 = fd[1]; /* M1 to main to signal readiness */ } void fork_off_workers(void) { /* Fork off the two workers, M0 and M1. */ if (fork() != 0) { /* This is the Parent. It will become main, but first fork off M1. */ if (fork() != 0) { /* This is main. */ sigaction(SIGPIPE, &act, &oact); setvbuf(stdout, (char *)0, _IONBF, (size_t)0);/*don't buffer*/ close(r1); close(w1); close(r2); close(w2); close(r3); close(w4); close(r5); close(w6); return; } else { /* This is the code for M1. Run protocol. */ sigaction(SIGPIPE, &act, &oact); setvbuf(stdout, (char *)0, _IONBF, (size_t)0);/*don't buffer*/ close(w1); close(r2); close(r3); close(w3); close(r4); close(w4); close(w5); close(r6); id = 1; /* M1 gets id 1 */ mrfd = r5; /* fd for reading time from main */ mwfd = w6; /* fd for writing reply to main */ prfd = r1; /* fd for reading frames from worker 0 */ switch(protocol) { case 2: receiver2(); break; case 3: receiver3(); break; case 4: protocol4(); break; case 5: protocol5(); break; case 6: protocol6(); break; case 7: receiver7(); break; case 8: receiver8(); break; } terminate("Impossible. Protocol terminated"); } } else { /* This is the code for M0. Run protocol. */ sigaction(SIGPIPE, &act, &oact); setvbuf(stdout, (char *)0, _IONBF, (size_t)0);/*don't buffer*/ close(r1); close(w2); close(w3); close(r4); close(r5); close(w5); close(r6); id = 0; /* M0 gets id 0 */ mrfd = r3; /* fd for reading time from main */ mwfd = w4; /* fd for writing reply to main */ prfd = r2; /* fd for reading frames from worker 1 */ switch(protocol) { case 2: sender2(); break; case 3: sender3(); break; case 4: protocol4(); break; case 5: protocol5(); break; case 6: protocol6(); break; case 7: sender7(); break; case 8: sender8(); break; } terminate("Impossible. protocol terminated"); } } void terminate(char *s) { /* End the simulation run by sending each worker a 32-bit zero command. */ write(w3, &zero, TICK_SIZE); write(w5, &zero, TICK_SIZE); sleep(3); if (strlen(s) > 0) printf("%s. Time=%u\n",s, tick/DELTA); exit(1); }