Date: 26 February 1988 From: Adrian Godwin, 78 Putnoe Street, Bedford, England. Subject: Additional C-Kermit Implementation Notes for Minix Modifying C-Kermit 4D-061 for use under Andrew Tanenbaum's 'MINIX' has required rather more changes to Minix than to Kermit. The C source files are included; they all began as the CK---.--- files for the 4D(061) distribution set. Here the names have been changed to MX---.--- . Hints, fixes and library changes are also attached - most of these are applicable for anyone implementing a serial i/o driver for Minix, and many library fixes are useful for porting other utilities. Kermit cannot be built under version 1.1 Minix, as it compiles to about 85K and the initial Minix assembler cannot separate I&D model output. The executable file was therefore built under MS-DOS using the Lattice 3.10 C compiler. Some care is needed in cross-compiling : see the notes in Tanenbaum's book about libraries, and read the enclosed Lattice makefile, cktker.mak. A port of this version to the latest C-Kermit version 4E(070) is now underway and will be released at some future time. Associated Files: ================= I have included with this help file (cktker.hlp) the following : mxcmai.c - Modified Kermit source files mxutio.c mxufio.c mxuusr.c mxuus3.c mxcfns.c mxtker.mak - Makefile for Lattice 'lmk' and 3.10 compiler. mxtker.inp - linker command file (included in mxtker.mak) mxtker.boo - boo encoding of cktker.out Changes to Kermit: ================== ckutio.c As usual, most of the changes are here. Fixes are largely a matter of picking a suitable option from the variety of code that exists. Use V7, but enable use of MYREAD and DON'T use the kmem (initrawq) functions. The uucp locks directory doesn't exist, but since it's a PC and you'll know if you're using the serial port, remove locking code altogether. For some reason, Lattice returns the unsigned char result of myread() expanded to a signed int. A badly read character can thus appear to other functions as an error code. Make myread return ((int)ch) & 0377. Ttychk and conchk need fixing to stop the accesses to kmem. Reorder ttychk so that if myread() finds nothing in it's buffer, it will execute other (system dependent) code, like FIONREAD, to determine if typahead exists in the system buffers. Then implement a FIONREAD ioctl call if you want. It makes a slight improvement to 'connect' performance, but is more worthwhile for conchk(), where nonblocking read is no use. The transfer escape characters can then be used. ckcmai.c Shorten the help message provided for remote help - Lattice can only compile strings less than 256 bytes long. Why isn't this list compiled from the command tables, like the command line interpreter's command completion? ckuusr.c Minix has a curious interpretation of printf, requiring ckuus3.c "%D" to indicate a long integer. Change all the "%ld" formats in these two files, or better still fix the do_printf() in the Minix library to accept K&R formats. Also calculate efficiency, etc. for statistics using long constants - Lattice makes a hash of it otherwise. ckcfns.c Calculate effective speed & efficiency for tlog using specific long arithmetic. Use a long for x, the argument for F101 in TLOG. ckufio.c No include file . Lattice would prefer the functions FILE *fopen(), *fdopen() to be declared as such. The function zclosif() attempts to kill a completed child process. If it has already completed, this fails, but MINIX still wants a wait() call to clean up the memory allocation, otherwise a fwe invocations of 'remote directory' will use up any remaining memory. This problem will also occur in the minix shell if you invoke all processes in the background. Use the shell command 'wait' to clean up completed processes (zombies). Changes to Minix library routines. ================================== I started developing this set of library fixes when building Micro- Emacs. A few of them may be unnecessary, but I recommend you fix them anyway. Many routines are here because they aren't in the standard library. The lattice makefile includes all these routines as an object called 'emlib' - this is for debugging purposes only (they should be built into the library). While not strictly a library change, fix dos2out before attempting to convert this large executable : the calculation of load_size will be wrong unless all the operands are forced to type long. The calculation of the total memory allocation, a_totb is also wrong, since it includes (for the separate I & D model) the text size a_text in the sum. This results in a 154K memory allocation, which is not permitted. chmem =20000 will fix it. Some include file changes. Stdio.h defines a macro 'puts()' as an fputs() to stdout. This is wrong, as puts() should append a newline while fputs() doesn't. Remove the macro and create a library function. There is no . It should contain: /* dir.h * * The structure of a directory entry. */ #define NAME_SIZE 14 /* defined in fs/const.h */ struct direct { inode_nr d_ino; /* inode number */ char d_name[NAME_SIZE]; /* name fills it out to 16 bytes */ } ; The file h/type.h is needed by kermit, but included as . Create a that includes h/type.h. Sgtty.h also needs some changes to define tty functions that don't exist in standard Minix. The kermit I have built used this version of sgtty.h, so if you want to use that executable, your tty driver must correspond. The i/o functions are an upwards compatible superset of the Minix sgtty functions, and are inspired by SCO Xenix. Note particularly the implementation of stty() and gtty() as macros. /* sgtty.h * * Data structures for IOCTL. */ struct sgttyb { char sg_ispeed; /* input speed (has precedence) */ char sg_ospeed; /* output speed (may be ignored) */ char sg_erase; /* erase character */ char sg_kill; /* kill character */ int sg_flags; /* mode flags */ }; /* simulate stty and gtty with ioctl */ #define stty(fd, arg) ioctl(fd, TIOCSETP, arg) #define gtty(fd, arg) ioctl(fd, TIOCGETP, arg) struct tchars { char t_intrc; /* SIGINT char */ char t_quitc; /* SIGQUIT char */ char t_startc; /* start output (initially CTRL-Q) */ char t_stopc; /* stop output (initially CTRL-S) */ char t_eofc; /* EOF (initially CTRL-D) */ char t_brkc; /* input delimiter (like nl) */ }; /* Fields in sg_flags. */ #define COOKED 0000000 /* neither CBREAK nor RAW */ #define TANDEM 0000001 /* XON XOFF flowcontrol on input */ #define CBREAK 0000002 /* enable cbreak mode */ #define LCASE 0000004 /* support lower case */ #define ECHO 0000010 /* echo input */ #define CRMOD 0000020 /* map lf to cr + lf */ #define RAW 0000040 /* enable raw mode */ #define ODDP 0000100 /* odd parity */ #define EVENP 0000200 /* even parity */ #define ANYP 0000300 /* parity not set / ignored */ #define XTABS 0006000 /* do tab expansion */ #define TIOCGETP (('t'<<8) | 8) #define TIOCSETP (('t'<<8) | 9) #define TIOCGETC (('t'<<8) | 18) #define TIOCSETC (('t'<<8) | 17) /* * I can't find a 'standard' definition for this one, so I've assigned * a '1' arbitrarily. */ #define FIONREAD (('f'<<8) | 1) /* Baud rate settings for async ttys. */ #define B0 0 /* hangup line (drop DTR) */ #define B50 1 #define B75 2 #define B110 3 #define B134 4 #define B150 5 #define B200 6 #define B300 7 #define B600 8 #define B1200 9 #define B1800 10 #define B2400 11 #define B4800 12 #define B9600 13 #define EXTA 14 #define B19200 EXTA #define EXTB 15 #define B38400 EXTB There are no time management calls in MINIX other than time, times() etc. I have defined asctime() and gmtime() functions based on the conversions done in date.c, but avoided support for timezones and daylight saving. /* time.h : structure and function definitions for time library calls. */ struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; /* not currently supported */ }; extern struct tm *localtime(); extern struct tm *gmtime(); extern char *asctime(); /* system(command) * * Code copied from the MINIX make utility 'mysystem' function, with * minor modifications. */ char *_defpath = "/bin/sh"; system(cmd) char *cmd; { int ccode,pid,status; char *shell, *getenv(); if ( (shell = getenv("SHELL")) == NULL) shell = _defpath; if ( (pid = fork()) == 0 ) { /* child execs a shell */ execl(shell,shell,(*cmd ? "-c" : "-i"),cmd,0); } if ( pid < 0 ) { /* parent waits for child */ return(pid); } else { while ( ((ccode = wait(&status)) != pid) && (ccode != -1)) ; return(status); } } /* This function copied from the MINIX library, but with some bugs fixed. */ char *getenv(name) register char *name; { extern char **environ; register char **v = environ, *p, *q; while ((p = *v++) != NULL) { /* fix : increment v */ q = name; while (*p++ == *q) if (*q++ == 0) break; /* fix : break rather than continue */ if (*(p - 1) != '=') continue; return(p); } return(0); } /* getc * * fixed to ensure _count always indicates number of chars in the buffer. * - used to indicate correctly only when zero. Decrement _count after * taking char from recently filled buffer. Note : fseek() had a bodge * which allowed it to work with this error. This also requires a fix. * * note when rebuilding library : this module should appear AFTER scanf, * which calls it, to ensure it can be found. */ #include "stdio.h" getc(iop) FILE *iop; { int ch; if ( testflag(iop, (_EOF | _ERR ))) return (EOF); if ( !testflag(iop, READMODE) ) return (EOF); if (--iop->_count < 0){ /* changed from <= 0 */ if ( testflag(iop, UNBUFF) ) iop->_count = read(iop->_fd,&ch,1); else iop->_count = read(iop->_fd,iop->_buf,BUFSIZ); if (iop->_count <= 0){ if (iop->_count == 0) iop->_flags |= _EOF; else iop->_flags |= _ERR; return (EOF); } else { iop->_ptr = iop->_buf; iop->_count--; /* inserted */ } } if (testflag(iop,UNBUFF)) return (ch & CMASK); else return (*iop->_ptr++ & CMASK); } /* ttyname() - missing library function. Gets the inode number of the * passed filedescriptor, then searches the /dev directory for a match. * Returns a pointer to the name (static data) or NULL. */ #include "stdio.h" #include "stat.h" #include "dir.h" char *ttyname(fd) int fd; { struct stat s; int n, dd; char *p = NULL; static struct direct d[2]; /* big enough to nul-terminate name */ if (fstat(fd, &s) != 0 || (s.st_mode&S_IFMT) != S_IFCHR) return(NULL); /* isatty. now search /dev for the inode. */ n = s.st_ino; if ((dd = open("/dev", 0)) < 0) return NULL; /* cannot read directory */ while (read( dd, d, sizeof(*d)) == sizeof(*d)) if (d[0].d_ino == n) { p = d[0].d_name; p[sizeof(*d)] = '\0'; break; } close(dd); return(p); } /* fdopen() - missing library function. Returns a file pointer set up from * the passed file descriptor, or NULL if it can't. */ #include "stdio.h" FILE *fdopen(fd,mode) int fd; char *mode; { register int i; FILE *fp; char *malloc(); int flags = 0; for (i = 0; _io_table[i] != 0 ; i++) if ( i >= NFILES ) return(NULL); switch(*mode){ case 'w': case 'a': flags |= WRITEMODE; break; case 'r': flags |= READMODE; break; default: return(NULL); } if (( fp = (FILE *) malloc (sizeof( FILE))) == NULL ) return(NULL); fp->_count = 0; fp->_fd = fd; fp->_flags = flags; fp->_buf = malloc( BUFSIZ ); if ( fp->_buf == NULL ) fp->_flags |= UNBUFF; else fp->_flags |= IOMYBUF; fp->_ptr = fp->_buf; _io_table[i] = fp; return(fp); } /* modified exit() which flushes stdio buffers before dying. This means * standard io library is included whether you wanted it or not - add a * dummy "_cleanup() {}" declaration to avoid it if you're writing very * small programs like the Minix utilities. This is more likely to * be compatible with other UNIX programs than the exit() in the standard * release. */ #include "../include/lib.h" int exit(status) int status; { _cleanup(); return callm1(MM, EXIT, status, 0, 0, NIL_PTR, NIL_PTR, NIL_PTR); } /* strncmp() - library module, changed to operate correctly when * n is zero. (causes problem in kermit interactive command processor) */ int strncmp(s1, s2, n) register char *s1, *s2; int n; { /* Compare two strings, but at most n characters. */ while (n-- != 0) { if (*s1 != *s2) return(*s1 - *s2); if (*s1 == 0) return(0); s1++; s2++; } return 0; } /* These functions based on the similar operations in date.c. No support * for timezones or daylight saving - localtime() currently just calls * gmtime() and returns the results unmodified. */ #include static int _days_per_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static char *_months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char *_days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static long _s_p_min = 60L; static long _s_p_hour = 60L * 60L; static long _s_p_day = 60L * 60L * 24L; static long _s_p_year = 60L * 60L * 24L * 365L; char *asctime(tmp) struct tm *tmp; { static char _asctbuf[30]; sprintf(_asctbuf,"%s %s %2d %02d:%02d:%02d %d\n", _days[tmp->tm_wday], _months[tmp->tm_mon], tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, tmp->tm_year + 1900); return _asctbuf; } struct tm *gmtime(ltp) long *ltp; { static struct tm _tm; char *p; long t = *ltp; for (p = (char *)&_tm; p < ((char *)&_tm) + sizeof(_tm); p++) *p = 0; /* get day-of-week. (Add 4 because 1.1.70 was a Thursday) */ _tm.tm_wday = ((t / _s_p_day)+4L) % 7L; /* reduce t by years and days, leaving seconds this year. */ while (t >= _s_p_year) { if (((_tm.tm_year + 2) % 4) == 0) t -= _s_p_day; _tm.tm_year += 1; t -= _s_p_year; } _tm.tm_yday = t / _s_p_day; /* adjust february for leap year and reduce to seconds this month */ if (((_tm.tm_year + 2) % 4) == 0) _days_per_month[1]++; while ( t >= (_days_per_month[_tm.tm_mon] * _s_p_day)) t -= _days_per_month[_tm.tm_mon++] * _s_p_day; _days_per_month[1] = 28; _tm.tm_year += 70; /* find day of month */ _tm.tm_mday = 1; while (t >= _s_p_day) { t -= _s_p_day; _tm.tm_mday++; } /* and time today */ while (t >= _s_p_hour) { t -= _s_p_hour; _tm.tm_hour++; } while (t >= _s_p_min) { t -= _s_p_min; _tm.tm_min++; } _tm.tm_sec = (int) t; return &_tm; } struct tm *localtime(ltp) long *ltp; { return gmtime(ltp); } The following fixes may be regarded as dubious, since they actually make the operation of minix less like standard unix. However, the performance of unbuffered tty output (as used in kermit help messages and command completion) is so appalling I felt something was needed. If a call is made to any of the string output routines (printf, puts ..), when the channel is unbuffered, then a temporary buffer is attached to the channel for the duration of the call and flushed at the end. Since setting stdout unbuffered freed it's buffer allocation, the buffer used for this is probably already available on the heap. The speed-up effect of this on unbuffered i/o (as used for all stderr in the system utilities) is startling, and I can't think why these changes should cause any incompatibilities. #include /* tmpbuf() creates a temporary buffer for the file, if possible. * Only call this if the file is unbuffered. */ static char *_locbuf = NULL; static int _locflags; static void tmpbuf(file) register FILE *file; { if (_locbuf == NULL) /* no buffer - get one */ _locbuf = malloc(BUFSIZ); if (_locbuf == NULL) /* no space - cannot buffer */ return; _locflags = file->_flags; file->_flags &= ~UNBUFF; file->_buf = file->_ptr = _locbuf; file->_count = 0; } /* endbuf() restores the FILE flags and flushes the buffer if required. */ static endbuf(file, fflag) register FILE *file; int fflag; { if (fflag || file->_buf == _locbuf) fflush(file); if (file->_buf == _locbuf && _locbuf != NULL) { /* temporary buffer used - restore old settings */ file->_buf = file->_ptr = NULL; file->_count = 0; file->_flags = _locflags; } } /* puts() - replaces macro in stdio.h, since a newline is required * after the given string. */ puts(s) register char *s; { if ( testflag(stdout, UNBUFF) ) tmpbuf(stdout); while ( *s ) putc(*s++,stdout); putc('\n',stdout); endbuf(stdout, 1); /* always flush */ } fputs(s,file) register char *s; FILE *file; { if ( testflag(file, UNBUFF) ) tmpbuf(file); while ( *s ) putc(*s++,file); endbuf(file, 0); } fprintf (file, fmt, args) FILE *file; char *fmt; int args; { if ( testflag(file, UNBUFF) ) tmpbuf(file); _doprintf (file, fmt, &args); endbuf(file, testflag(file, PERPRINTF)); } printf (fmt, args) char *fmt; int args; { if ( testflag(stdout, UNBUFF) ) tmpbuf(stdout); _doprintf (stdout, fmt, &args); endbuf(stdout, testflag(stdout, PERPRINTF)); } Changes to Minix ================ Here's where it gets more experimental: I've done the following fixes to MINIX in order to get Kermit working or to solve problems I've found along the way. Some may not be essential - I didn't want to rebuild with all my older fixes taken out just to determine which are necessary. Since all the fixes are for major problems, they're worth doing anyway. Any SIGALRM sent to a process will cancel all pending alarms. This has the effect of suspending forever (say) the update process if another process executes sleep(). MM/signal.c, check_sig() resets not only the alarm bit for process getting the signal, but for all others. Resetting the bit needs to be conditional on the state of the send_sig flag. The clock task looks in the message field CLOCK_PROC_NR for the process number of the task that needs the alarm signal. MM actually puts the number in the field PROC_NR, which is not the same. Hence the clock actually sends the signal to process 0, which is taken to mean all processes. It is possible for a message from FS to a task to be overwritten before it reaches its destination if the task supports multiple minor devices and process suspension, as a TTY driver with more than one line would. This will occur if the TTY driver has suspended a task, and is busy with an interrupt request (which will revive the task) when FS send-receives a message about a different minor device. FS will be forced to wait for the task until the REVIVE message appears. FS is then sent the message, since it appears to be waiting for a reply. The tty task will then look for more messages and will find the FS request, now overwritten with the REVIVE message. The TTY will be given its own REVIVE message and reject it as invalid. FS will continue to wait for a reply to it's TTY request (now lost) and hang. I have fixed this by treating REVIVE messages in the same way as signals they are rather similar, since they are sent to a caller which is not waiting for them. Instead of replying with a REVIVE message, I call set_revive(), a function similar to cause_sig() which sets a flag in the proc table p_flags and saves the reply status in a new member, p_status. Inform() is then called for FS as well as MM, and searches for the revive flag. Any pending revives are actioned as a HARDWARE message, just like a kernel signal. FS will then no longer get a reply from a previous message when send-receiving to a task, since the REVIVE call comes from HARDWARE. FS can thus be simplified in areas like rw_dev(). FS may panic with a "couldn't revive anyone" error when it recovers a process from waiting on a broken pipe. If the pipe is reading from two file descriptors (as in the Kermit procedure to pipe process stdout and stderr to a remote machine for the REMOTE HOST command), then FS/misc.c, do_exit() will call FS/open.c, do_close() for both descriptors. On each call, FS/pipe.c, release() will be called, setting the REVIVING flag for the reading process (e.g. the Kermit) twice, and also incrementing 'reviving'. FS/main.c, get_work() revives the reading process, then can't find a process to account for the other increment of 'reviving'. release() should check for the REVIVING flag before calling revive() again. FS can send a cancel message for an out-of-range tty number when a process is killed, due to FS/pipe.c, do_unpause() not setting up the global fp before calling get_filp(). The tty driver does not catch out-of-range line numbers on either messages or characters from the overrun buffer, and can index a tty structure that doesn't exist. Do_unpause() doesn't check that get_filp() returns a valid result. The return can be NULL if the file (that the process is paused on) has been closed. This happens if a signal killed the process. The distributed code of MM/signal.c, check_sig() differs from the printed listings in Tanenbaum's book in the order of execution of unpause() and sig_proc(). This means that FS expects do_unpause to have a process to restart, when the filehandles it should look at are already closed. It seems OK to return the order of the calls to their original state, but I'd like to know why they changed. CONTROL-S will pause the user process by waiting before replying to FS. This causes the entire system to get stuck waiting for FS to return from the TTY call. The TTY needs to SUSPEND the process on output, just as it does on input. Do_cancel() in the TTY driver also has to change to ensure that FS obtains a reply or a REVIVE when output is completed. The following comments are not bug reports, but code changes I have done to improve the system's performance at high interrupt rates. I found character processing at 9600 baud on an 8MHz 8086 a problem, and had to make several changes : When interrupts are arriving at high speed (characters at 9600 baud), some may be lost due to the time taken to process an interrupt in the kernel. This time may be reduced by moving the check for now-ready tasks in kernel/ proc.c, interrupt() to the end of mini-rec(). This reduces the number of calls to mini-send within the interrupt handler to 1. If a clock interrupt was pending, the clock processing can double or triple the time needed to process the call to interrupt(). If the tty task is often scheduled, the clock task is often behind, since tasks do not preempt each other. Thus clock interrupts often are pending when interrupt() is called at high repetition rates. However, placing this check in mini_rec() will mean that interrupts will always be serviced when the task becomes ready. This, while reducing the interrupt latency of a task, means that fast interrupts can make the task permanently computable. No other process can then obtain any time to read the incoming characters, and the task's buffer fills. Flowcontrol and possibly ignoring unbufferable input makes it possible to empty the buffer again. File transfer with Kermit is OK, because the packets are considerably smaller than the task's buffer, but long packets, sliding windows and connect sessions will have problems if flowcontrol is not possible. The mini_send() call in interrupt() should also be removed and the message copy be performed directly within interrupt(), avoiding the substantial checking performed on call parameters, which should be unnecessary on messages within the kernel. The message copy might also be performed by a faster version of copy_mess(), for use within the kernel, which need not span segments. Better still, since interrupts may overwrite each other and only affect a task's private data area, they tend to use a single known buffer for data transfer to the calling task. (I haven't made these last 2 changes yet). The following code fragments indicate most of the above fixes. Since the code was originally that distributed by Prentice-Hall, I have included their copyright notice. /* Copyright (C) 1987 by Prentice-Hall, Inc. Permission is hereby granted to * private individuals and educational institutions to modify and * redistribute the binary and source programs of this system to other * private individuals and educational institutions for educational and * research purposes. For corporate or commercial use, permission from * Prentice-Hall is required. In general, such permission will be granted, * subject to a few conditions. */ ============= Kernel/proc.c ============= /*===========================================================================* * interrupt * *===========================================================================*/ PUBLIC interrupt(task, m_ptr) int task; /* number of task to be started */ message *m_ptr; /* interrupt message to send to the task */ { /* An interrupt has occurred. Schedule the task that handles it. */ int this_bit; register struct proc *dest_ptr; #ifdef ibmpc /* Re-enable the 8259A interrupt controller. */ this_bit = 1 << (-task); port_out(INT_CTL, ENABLE); /* this re-enables the 8259A controller chip */ if (pc_at) port_out(INT2_CTL, ENABLE); /* re-enable second 8259A */ #endif /* Try to send the interrupt message to the indicated task. */ this_bit = 1 << (-task); dest_ptr = proc_addr(task); if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING) && (dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == HARDWARE) ) { /* Destination is indeed waiting for this message. */ cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys, m_ptr, dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf); dest_ptr->p_flags &= ~RECEIVING; /* deblock destination */ if (dest_ptr->p_flags == 0) ready(dest_ptr); busy_map &= ~this_bit; /* turn off the bit in case it was on */ } else { /* The message could not be sent to the task; it was not waiting. */ if (task == CLOCK) { lost_ticks++; } else { busy_map |= this_bit; /* mark task as busy */ task_mess[-task] = m_ptr; /* record message pointer */ } } /* If a task has just been readied and a user is running, run the task. */ if (rdy_head[TASK_Q] != NIL_PROC && (cur_proc >= 0 || cur_proc == IDLE)) pick_proc(); } /*===========================================================================* * mini_send * *===========================================================================*/ PUBLIC int mini_send(caller, dest, m_ptr) int caller; /* who is trying to send a message? */ int dest; /* to whom is message being sent? */ message *m_ptr; /* pointer to message buffer */ { /* Send a message from 'caller' to 'dest'. If 'dest' is blocked waiting for * this message, copy the message to it and unblock 'dest'. If 'dest' is not * waiting at all, or is waiting for another source, queue 'caller'. */ register struct proc *caller_ptr, *dest_ptr, *next_ptr; vir_bytes vb; /* message buffer pointer as vir_bytes */ vir_clicks vlo, vhi; /* virtual clicks containing message to send */ vir_clicks len; /* length of data segment in clicks */ /* User processes are only allowed to send to FS and MM. Check for this. */ if (caller >= LOW_USER && (dest != FS_PROC_NR && dest != MM_PROC_NR)) #ifdef DEBUG if (dest != SYSTASK) return(E_BAD_DEST); #else return(E_BAD_DEST); #endif caller_ptr = proc_addr(caller); /* pointer to source's proc entry */ dest_ptr = proc_addr(dest); /* pointer to destination's proc entry */ if (dest_ptr->p_flags & P_SLOT_FREE) return(E_BAD_DEST); /* dead dest */ /* Check for messages wrapping around top of memory or outside data seg. */ len = caller_ptr->p_map[D].mem_len; vb = (vir_bytes) m_ptr; vlo = vb >> CLICK_SHIFT; /* vir click for bottom of message */ vhi = (vb + MESS_SIZE - 1) >> CLICK_SHIFT; /* vir click for top of message */ if (vhi < vlo || vhi - caller_ptr->p_map[D].mem_vir >= len)return(E_BAD_ADDR); /* Check to see if 'dest' is blocked waiting for this message. */ if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING) && (dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == caller) ) { /* Destination is indeed waiting for this message. */ cp_mess(caller, caller_ptr->p_map[D].mem_phys, m_ptr, dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf); dest_ptr->p_flags &= ~RECEIVING; /* deblock destination */ if (dest_ptr->p_flags == 0) ready(dest_ptr); } else { /* Destination is not waiting. Block and queue caller. */ if (caller == HARDWARE) return(E_OVERRUN); caller_ptr->p_messbuf = m_ptr; caller_ptr->p_flags |= SENDING; unready(caller_ptr); /* Process is now blocked. Put in on the destination's queue. */ if ( (next_ptr = dest_ptr->p_callerq) == NIL_PROC) { dest_ptr->p_callerq = caller_ptr; } else { while (next_ptr->p_sendlink != NIL_PROC) next_ptr = next_ptr->p_sendlink; next_ptr->p_sendlink = caller_ptr; } caller_ptr->p_sendlink = NIL_PROC; } #ifdef DEBUG /* This message has been successfully handled. See if it should be saved * in the message log. */ if ((save_flags[caller+NR_TASKS] & CALL_LOG) || (save_flags[dest +NR_TASKS] & DEST_LOG)) save_msg(caller, dest, m_ptr); #endif return(OK); } /*===========================================================================* * mini_rec * *===========================================================================*/ PRIVATE int mini_rec(caller, src, m_ptr) int caller; /* process trying to get message */ int src; /* which message source is wanted (or ANY) */ message *m_ptr; /* pointer to message buffer */ { /* A process or task wants to get a message. If one is already queued, * acquire it and deblock the sender. If no message from the desired source * is available, block the caller. No need to check parameters for validity. * Users calls are always sendrec(), and mini_send() has checked already. * Calls from the tasks, MM, and FS are trusted. */ register struct proc *caller_ptr, *sender_ptr, *prev_ptr; int sender, this_bit; int locked = FALSE; caller_ptr = proc_addr(caller); /* pointer to caller's proc structure */ /* if we're ready to receive, find a sender. Else just block. */ if ((caller_ptr->p_flags & SENDING) == 0) { /* Check to see if a message from desired source is already available. */ sender_ptr = caller_ptr->p_callerq; while (sender_ptr != NIL_PROC) { sender = sender_ptr - proc - NR_TASKS; if (src == ANY || src == sender) { /* An acceptable message has been found. */ cp_mess(sender, sender_ptr->p_map[D].mem_phys, sender_ptr->p_messbuf, caller_ptr->p_map[D].mem_phys, m_ptr); sender_ptr->p_flags &= ~SENDING; /* deblock sender */ if (sender_ptr->p_flags == 0) ready(sender_ptr); if (sender_ptr == caller_ptr->p_callerq) caller_ptr->p_callerq = sender_ptr->p_sendlink; else prev_ptr->p_sendlink = sender_ptr->p_sendlink; return(OK); } prev_ptr = sender_ptr; sender_ptr = sender_ptr->p_sendlink; } /* If the caller is a task, there may be an interrupt waiting. * Check now, rather than before checking callers, so fast interrupts * give the process a chance to absorb them. Lock between determining the * state of busy_map and setting RECEIVING so an interrupt occurring * between cannot be forgotten. */ if (caller < HARDWARE && (src == ANY || src == HARDWARE)) { lock(); locked = TRUE; this_bit = 1 << (-caller); if (busy_map & this_bit) { cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys, task_mess[-caller], caller_ptr->p_map[D].mem_phys, m_ptr); busy_map &= ~this_bit; /* must be locked here too */ restore(); return(OK); } } } /* No suitable message is available. Block the process trying to receive. */ caller_ptr->p_getfrom = src; caller_ptr->p_messbuf = m_ptr; caller_ptr->p_flags |= RECEIVING; if (locked) restore(); unready(caller_ptr); /* If MM has just blocked and there are kernel signals pending, now is the * time to tell MM about them, since it will be able to accept the message. * Also applies to revive messages for FS. No problems with receive-before- * send, since (src == ANY) cannot be true for sendrec messages. */ if (rev_procs > 0 && caller == FS_PROC_NR && src == ANY) inform(FS_PROC_NR); if (sig_procs > 0 && caller == MM_PROC_NR && src == ANY) inform(MM_PROC_NR); return(OK); } =============== kernel\system.c =============== /*===========================================================================* * set_revive * *===========================================================================*/ PUBLIC set_revive(proc_nr, status) int proc_nr; int status; /* Similar function to cause_sig. Tasks sending a revive message directly * to FS may collide with a send in the opposite direction. Therefore, this * function saves the event and associated status (error code, bytes read etc) * in the target process' proc entry, and the kernel informs FS later with * inform(FS_PROC_NR). It would be nice to share the proc table's p_pending * member with MM, but the signals recorded in it may be ignored - only MM * knows. Note that the device's id is lost, and only FS may receive a message. * This is not currently a problem. */ { register struct proc *rp; rp = proc_addr(proc_nr); if (rp->p_flags & NEED_REVIVE) panic ("cannot revive twice",proc_nr); rp->p_flags |= NEED_REVIVE; rp->p_rstatus = status; rev_procs++; inform(FS_PROC_NR); /* perhaps it's safe to do it now ? */ } /*===========================================================================* * inform * *===========================================================================*/ PUBLIC inform(proc_nr) int proc_nr; /* MM_PROC_NR or FS_PROC_NR */ { /* When a signal is detected by the kernel (e.g., DEL), or generated by a task * (e.g. clock task for SIGALRM), cause_sig() is called to set a bit in the * p_pending field of the process to signal. Then inform() is called to see * if MM is idle and can be told about it. Whenever MM blocks, a check is * made to see if 'sig_procs' is nonzero; if so, inform() is called. * Likewise REVIVE messages for FS. These are sent only when FS is ready to * receive. Code here changed to check recipient is not blocked on send as * well as receive. */ register struct proc *rp, *mmp; int r; /* If MM/FS is not waiting for new input, forget it. */ mmp = proc_addr(proc_nr); if ( ((mmp->p_flags & RECEIVING) == 0) || mmp->p_getfrom != ANY) return; /* If MM is waiting for new input, find a process with pending signals. */ if (proc_nr == MM_PROC_NR) { for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++) if (rp->p_pending != 0) { m.m_type = KSIG; m.PROC1 = rp - proc - NR_TASKS; m.SIG_MAP = rp->p_pending; sig_procs--; if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK) panic("can't inform MM : ", r); rp->p_pending = 0; /* the ball is now in MM's court */ return; } } /* If FS is waiting for new input, find a process with pending revive. */ else if (proc_nr == FS_PROC_NR) { for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++) if (rp->p_flags & NEED_REVIVE) { m.m_type = REVIVE; m.REP_PROC_NR = rp - proc - NR_TASKS; m.REP_STATUS = rp->p_rstatus; rev_procs--; rp->p_flags &= ~NEED_REVIVE; if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK) panic("can't inform FS : ", r); return; /* the ball is now in FS's court */ } } } =========== mm/signal.c =========== /*===========================================================================* * check_sig * *===========================================================================*/ PRIVATE int check_sig(proc_id, sig_nr, send_uid) int proc_id; /* pid of process to signal, or 0 or -1 */ int sig_nr; /* which signal to send (1-16) */ uid send_uid; /* identity of process sending the signal */ { /* Check to see if it is possible to send a signal. The signal may have to be * sent to a group of processes. This routine is invoked by the KILL system * call, and also when the kernel catches a DEL or other signal. SIGALRM too. */ register struct mproc *rmp; int count, send_sig; unshort mask; extern unshort core_bits; if (sig_nr < 1 || sig_nr > NR_SIGS) return(EINVAL); count = 0; /* count # of signals sent */ mask = 1 << (sig_nr - 1); /* Search the proc table for processes to signal. Several tests are made: * - if proc's uid != sender's, and sender is not superuser, don't signal * - if specific process requested (i.e., 'procpid' > 0, check for match * - if a process has already exited, it can't receive signals * - if 'proc_id' is 0 signal everyone in same process group except caller */ for (rmp = &mproc[INIT_PROC_NR + 1]; rmp < &mproc[NR_PROCS]; rmp++ ) { if ( (rmp->mp_flags & IN_USE) == 0) continue; send_sig = TRUE; /* if it's FALSE at end of loop, don't signal */ if (send_uid != rmp->mp_effuid && send_uid != SUPER_USER)send_sig=FALSE; if (proc_id > 0 && proc_id != rmp->mp_pid) send_sig = FALSE; if (rmp->mp_flags & HANGING) send_sig = FALSE; /*don't wake the dead*/ if (proc_id == 0 && mp->mp_procgrp != rmp->mp_procgrp) send_sig = FALSE; if (send_uid == SUPER_USER && proc_id == -1) send_sig = TRUE; /* SIGALARM is a little special. When a process exits, a clock signal * can arrive just as the timer is being turned off. Also, turn off * ALARM_ON bit when timer goes off to keep it accurate. */ /* Change : only reset the ALARM_ON bit for processes that are * going to get the signal - don't wipe out all the other alarms! */ if (send_sig && sig_nr == SIGALRM) { if ( (rmp->mp_flags & ALARM_ON) == 0) continue; rmp->mp_flags &= ~ALARM_ON; } if (send_sig == FALSE || rmp->mp_ignore & mask) continue; /* If process is hanging on PAUSE, WAIT, tty, pipe, etc. release it. */ unpause(rmp - mproc); /* check to see if process is paused */ count++; /* Send the signal or kill the process, possibly with core dump. */ sig_proc(rmp, sig_nr); if (proc_id > 0) break; /* only one process being signalled */ } /* If the calling process has killed itself, don't reply. */ if ((mp->mp_flags & IN_USE) == 0 || (mp->mp_flags & HANGING))dont_reply =TRUE; return(count > 0 ? OK : ESRCH); } /*===========================================================================* * set_alarm * *===========================================================================*/ PUBLIC int set_alarm(proc_nr, sec) int proc_nr; /* process that wants the alarm */ unsigned sec; /* how many seconds delay before the signal */ { /* This routine is used by do_alarm() to set the alarm timer. It is also * to turn the timer off when a process exits with the timer still on. */ int remaining; m_sig.m_type = SET_ALARM; /*--- m_sig.PROC_NR = proc_nr; ---*/ m_sig.CLOCK_PROC_NR = proc_nr; /* clock uses different member */ m_sig.DELTA_TICKS = HZ * sec; if (sec != 0) mproc[proc_nr].mp_flags |= ALARM_ON; /* turn ALARM_ON bit on */ else mproc[proc_nr].mp_flags &= ~ALARM_ON; /* turn ALARM_ON bit off */ /* Tell the clock task to provide a signal message when the time comes. */ if (sendrec(CLOCK, &m_sig) != OK) panic("alarm er", NO_NUM); remaining = (int) m_sig.SECONDS_LEFT; return(remaining); } ========= fs/pipe.c ========= /*===========================================================================* * release * *===========================================================================*/ PUBLIC release(ip, call_nr, count) register struct inode *ip; /* inode of pipe */ int call_nr; /* READ or WRITE */ int count; /* max number of processes to release */ { /* Check to see if any process is hanging on the pipe whose inode is in 'ip'. * If one is, and it was trying to perform the call indicated by 'call_nr' * (READ or WRITE), release it. */ register struct fproc *rp; /* Search the proc table. */ for (rp = &fproc[0]; rp < &fproc[NR_PROCS]; rp++) { if (rp->fp_suspended == SUSPENDED && (rp->fp_fd & BYTE) == call_nr && rp->fp_revived != REVIVING && rp->fp_filp[rp->fp_fd>>8]->filp_ino == ip) { revive(rp - fproc, 0); susp_count--; /* keep track of who is suspended */ if (--count == 0) return; } } } /*===========================================================================* * do_unpause * *===========================================================================*/ PUBLIC int do_unpause() { /* A signal has been sent to a user who is paused on the file system. * Abort the system call with the EINTR error message. */ register struct fproc *rfp; int proc_nr, task, susfd; struct filp *f; dev_nr dev; extern struct filp *get_filp(); if (who > MM_PROC_NR) return(EPERM); proc_nr = pro; if (proc_nr < 0 || proc_nr >= NR_PROCS) panic("unpause err 1", proc_nr); rfp = &fproc[proc_nr]; if (rfp->fp_suspended == NOT_SUSPENDED) return(OK); task = -rfp->fp_task; if (task != XPIPE) { susfd = rfp->fp_fd >> 8; f = rfp->fp_filp[susfd]; /* don't use get_filp - fp is wrong */ if (susfd > NR_FDS || susfd < 0 || f == NIL_FILP) { panic("unpause err 4", proc_nr); } dev = f->filp_ino->i_zone[0]; /* device on which proc is hanging */ mess.TTY_LINE = (dev >> MINOR) & BYTE; mess.PROC_NR = proc_nr; mess.m_type = CANCEL; if (sendrec(task, &mess) != OK) panic("unpause err 2", NO_NUM); while (mess.REP_PROC_NR != proc_nr) { revive(mess.REP_PROC_NR, mess.REP_STATUS); if (receive(task, &m) != OK) panic("unpause err 3", NO_NUM); } revive(proc_nr, EINTR); /* signal interrupted call */ } return(OK); } I haven't included my asynchronous tty driver, as I don't use IBM hardware and anybody interested in Kermit on Minix probably has their own ideas. However, an outline of the driver follows for anybody who might be interested (I'm happy to answer any further queries or comments as I continue this development). The tty driver is split into three parts: tty.c Contains the 'device independent' part of the original driver. screen.c Contains hardware-specific code for console screen and keyboard driver. async.c Contains hardware-specific code for asynchronous serial tty driver. The tty_struct structure is extended, and now contains fields for ioctl function addresses (so do_ioctl sets characters, modes etc then calls the device - dependent code for baud rate, etc) and a single character echo function for that port. Tty_ramqueue is a union of the integer queue required for screen driving and a char queue for buffering output, ready for async interrupts. The flag used for RUNNING / STOPPED indication is now a bit mask, another bit being STALLED, to indicate that an XOFF has been sent to the device on the other end of the serial line. TANDEM flowcontrol is implemented in tty.c, flipping the STALLED bit and echoing an XOFF or XON when the buffer contents cross high and low water marks. Interrupt processing is sufficiently fast that XOFF does not need to be sent if the overrun queue is getting full. Transmitter ready interrupts cause a character to be read from tty_rwords and written to the UART. When tty_rwords is empty, a message is sent to the tty task, and the queue is refilled. In the meantime, the transmitter is disabled to avoid continuous interrupts. Characters and refill requests are both sent through the same interrupt message - use of the suggested TTY_O_DONE message would cause other interrupts to be overwritten and lost. The message is now a structure consisting of a count, a queue of ints for character and line number, and a bit map for refill requests. Thus refill requests are not lost if the input queue is full. The kernel interrupt() call is only made if the count of queued characters incremented from zero, or a bit was set for the first time in the bitmap. This reduces the interrupt processing time if an interrupt had already been sent, but had not been processed. A bug exists in the processing of input characters - DEL and QUIT send a signal to the proper process only if it is the only login. The calculation of the proc to be signalled as 'LOW_USER + line + 1' is wrong, since the process slot above the first user is occupied not by the second user's first shell but by the update process. For proper processing of tty signals in a multiple-user (or multiple screen) system, FS should tell the tty task when a tty is controlling a process family.