johnl@proper.UUCP (John A. Limpert ) (02/03/84)
Is there any clean way for a program to determine whether or not the user has typed a character on the keyboard without blocking the program? The only scheme I have been able to come up with is to fork another process that reads characters from the tty and writes them into a file. The main process could then check the size of the file to determine if there were any new characters to process. I know that the kernel I/O drivers have this information, is there any way of getting it without spying on some magic location in /dev/kmem?
mark@elsie.UUCP (02/06/84)
If you have the Berkeley new tty driver you can do the following: #include <sgtty.h> foo() { int nchars = 0; for (;;) { ..loop code.. ioctl(0,FIONREAD,(struct sgttyb *) &nchars); if (nchars) { /* Something has been typed at the keyboard */ ..anything else.. } } } The FIONREAD causes the number of chars waiting at the terminal to be placed in nchars. NOTE: if you want to process single chars as they come into the terminal you must be in RAW or CBREAK mode. This is not terribly portable (I try to protect such code with "#ifdef berkeley"'s). I would be interested in knowing if there is a truly portable way of doing this. -- Mark J. Miller NIH/NCI/DCE/LEC UUCP: decvax!harpo!seismo!rlgvax!cvl!elsie!mark Phone: (301) 496-5688
guy@rlgvax.UUCP (Guy Harris) (02/06/84)
The closes thing to a portable non-blocking read is: #include <fcntl.h> frobozz(...) { register int i; i = fcntl(fdes, F_GETFL, 0); fcntl(fdes, F_SETFL, i | O_NDELAY); i = read(fdes, buf, count); #ifdef BSD4_2 if (i < 0 && errno == EWOULDBLOCK) { #else if (i == 0) { #endif /* * No characters currently available to be read. */ } else { /* * "i" contains the number of characters available to * be read. */ } This will work on System III (*if* you fix a bug in the terminal driver), System V, and 4.2BSD. *Warning*: the implementation of "fcntl" is 4.2BSD has a botch, in that the F_SETFL call is *supposed* to set the no-delay flag on the *file descriptor*, and is *only* supposed to affect *that* file descriptor (and, on S3/S5/other USG UNIX versions, it does do this); however, on 4.2BSD, it passes the no-delay flag to the driver which sets no-delay mode on the terminal port that the file descriptor refers to, so that *all* file descriptors referring to that terminal port are, in effect, set to no-delay mode. Guy Harris {seismo,ihnp4,allegra}!rlgvax!guy
ka@hou3c.UUCP (Kenneth Almquist) (02/06/84)
Various versions of UNIX provide a FIONREAD ioctl call which checks for pending input and/or an O_NDELAY flag which can be set by fcntl to provide nonblocking reads. Check your manual to see whether you have either of these. Kenneth Almquist
geoff@proper.UUCP (Geoff Kuenning) (02/07/84)
A request was made on the net for information on how to do a non-blocking read from a terminal. Under 4BSD and UniSoft ports, the FIONREAD 'ioctl' can be used to check for characters, albeit at a high cost in CPU time. Other solutions have been posted to the net. A related question is how to do asynchronous disk I/O for purposes of double buffering. System V provides an open/fcntl option for non-blocking (read asynchronous) I/O, but there is a *VERY* nasty catch: there is no way to find out when the I/O is finished! This makes the non-blocking feature pretty useless. Indeed, when I examined the double-buffering code in "volcopy" to find out how they did it, I discovered that they fork a copy of themselves and then write "r" and "w" characters across pipes to synchronize the two copies. Each copy is responsible for working with one of the two buffers, and writes a single character to the other copy when the buffer is complete. YUCCCCCCH! KLUDGE KLUDGE KLUDGE! How about it, Bell Labs? If you are going to provide non-blocking I/O, I really think you should give us a way to find out when the I/O is complete. I fully realize the difficulty of integrating such a feature into the Unix design; but I still think you should do it the right way.
moss%brl-vld@sri-unix.UUCP (02/07/84)
From: Gary S Moss ~Software Development Team~ <moss@brl-vld> The following is a way to do non-blocking reads on System V. Actually, I implemented it on Doug Gwyn's System V emulation on top of Berkeley 4.2 so its portable in the sense that System V is a standard of sorts and with Doug's emulation it travels still further. Let me mention that it is really frustrating that in order to figure out how to do this I had to jump around from fcntl(2) to ioctl(2) to read(2) in the User's Manual - System V and then to termio(7) in the Administrator's Manual - System V. #include <stdio.h> #include <sys/ioctl.h> #include <fcntl.h> static struct termio termBuf; /* Termio buffer. */ static unsigned short localModes; /* Save local modes. */ static int filStat; /* Save file status flags. */ main() { int c, done; (void) ioctl( 0, TCGETA, &termBuf ); /* Get termio parameters. */ localModes = termBuf.c_lflag; /* Save local modes. */ termBuf.c_lflag &= ~ICANON; /* Raw mode ON. */ termBuf.c_lflag &= ~ECHO; /* Echo mode OFF. */ (void) ioctl( 0, TCSETA, &termBuf ); /* Set local modes. */ filStat = fcntl( 0, F_GETFL, 0 ); /* Save file status flags. */ (void) fcntl( 0, F_SETFL, O_NDELAY );/* Set non-blocking read. */ for( done = 0; ! done ; ) { if( read( 0, &c, 1 ) ) done = ! do_something_with( c ); else update_screen(); } termBuf.c_lflag = localModes; /* Retrieve old setting. */ (void) ioctl( 0, TCSETA, &termBuf ); /* Reset local modes. */ (void) fcntl( 0, F_SETFL, filStat ); /* Restore file status. */ exit( 0 ); } - Moss.
54394gt@hocda.UUCP (G.TOMASEVICH) (02/09/84)
OK, Geoff! Let's hear it for non-blocking disk (and tape) reads for which you can find out when they are done. I read that 'volcopy' kludge, too.
moss%brl-vld@sri-unix.UUCP (02/09/84)
From: Gary S Moss ~Software Development Team~ <moss@brl-vld> > if the program somehow exits after the "fcntl( 0, F_SETFL, O_NDELAY )", > you will get spontaneously logged off. Good point Dave. When I first implemented the code, I didn't save the file status flags, and got logged out immediately upon exiting. Trapping interrupts is a good idea in any program that does some sort of clean-up, I may have left other details out in the attempt to be concise. Opening /dev/tty sounds like a good idea, I'll give it a try. One thing I neglected to mention, is that after clearing the ICANON bit in the termio structure, you will want to mask off the sign bit : #define CMASK 0377 int c; /* Good idea using int if comparing to constants. */ if( read( 0, (char *)&c, 1 ) ) switch( c & CMASK ) UP: ... - Moss.
stroyan@hpfcra.UUCP (02/12/84)
You can do non-blocking reads by using fcntl (in reference manual sections 2 and 7) do set the O_NDELAY status flag. Or you can open a file with O_NDELAY set if you aren't using stdin. I should warn you that if your program dies or absent-mindedly quits with O_NDELAY set for stdin, then the shell will mistake the return without delay for an EOF and log you off immediately. Mike Stroyan Hewlett Packard, Fort Collins Systems Division hpfcla!stroyan
edhall%rand-unix@sri-unix.UUCP (02/15/84)
From: Ed_Hall <edhall@rand-unix> Dup(0) will give you a new kernel file-structure to play with as well, but doesn't depend upon the terminal being attached to fd 0. I usually use /dev/tty either only as a last resort, or when I specifically want to override any file redirection. -Ed Hall edhall@rand-unix (ARPA) decvax!randvax!edhall (UUCP)
dbj%rice@sri-unix.UUCP (02/17/84)
From: Dave Johnson <dbj@rice> "Dup(0) will give you a new kernel file-structure to play with ..." WRONG! Dup(0) will only make another reference to the file structure that already exists for that file descriptor by incrementing the reference count in f_count and pointing to it by an element of the array u.u_ofile. It does NOT allocate a new "struct file" which is where the file's modes such as O_NDELAY are kept. Doing a dup(0) will not help the problem at all. The only solution (short of doing something gross to the kernel) is to either re-open the specific terminal by name (e.g., /dev/tty01) or (better yet) to open /dev/tty to get the new file structure to set into non-blocking mode. Dave Johnson Dept. of Math Science Rice University dbj@rice
edhall%rand-unix@sri-unix.UUCP (02/17/84)
From: Ed_Hall <edhall@rand-unix> I just looked it up in my 4.2 source and you're absolutely right. The only thing created separately for a dup()'d file descriptor is the close-on-exec flag. I can think of occasions where sharing a single offset pointer across dup's is desirable behavior, especially in implementing the shell. But not being able to set flags on a per-descriptor basis is a loss. Perhaps the FNDELAY flag should behave the same as the close-on-exec flag. Any comments on this? Anyone for a reopen() system call? -Ed Hall edhall@rand-unix (ARPA) decvax!randvax!edhall (UUCP)
guy@rlgvax.UUCP (Guy Harris) (02/19/84)
> One thing I neglected to mention, is that after clearing the ICANON bit > in the termio structure, you will want to mask off the sign bit : > #define CMASK 0377 > int c; /* Good idea using int if comparing to constants. */ > > if( read( 0, (char *)&c, 1 ) ) > switch( c & CMASK ) > UP: ... False. Turning on the ICANON bit has *no effect* on the width of the terminal- to-computer path in bits. That is affected by the various ISTRIP bits in c_iflag and the character width stuff in c_cflag. You may be confusing this with RAW mode, which was the only was in V6 UNIX to get characters as they were typed and which *did* turn off parity checking and parity-bit-stripping (at least in the later V6-based UNIXes, like PWB/UNIX). RAW turned off all that stuff in V7, but in V7 the way to get characters as they are typed is to turn on CBREAK which has no effect on output (modulo a small Berkeley UNIX hack) and which only affects canonicalization on input. ICANON has the same intent; disable canonicalization and don't do anything else. Furthermore, declaring "c" as an "int" is a very *bad* idea. This code will only work on a machine like those from the 11 family which store the bytes of a word from the bottom up, i.e. +--------+--------+ | Byte 1 | Byte 0 | a word +--------+--------+ MSB LSB "(char *)&c" always points to "byte 0" of a word. In the case of an 11 family- type machine, this means that filling in whatever "(char *)&c" points to will fill in the lower bits of the word, so if byte 1 is zero this means that "c" is the zero-extended value of the byte stuffed into "byte 0" of "c". However, on a machine like most of the other machines in the world (Motorola M68000, for instance; also note that the DOT Internet standard byte order is *not* the 11 family byte order!), the bytes of a word are stored from the top down, i.e. +--------+--------+ | Byte 0 | Byte 1 | a word +--------+--------+ MSB LSB "(char *)&c" still points to byte 0, but this is now the eight *most* significant bits of the word. Storing a value where "(char *)&c" points, assuming byte 1 is still zero, will now produce an integer value which is the value of the character times 256! This bug used to exist in early versions of UUCP, and *did* cause us a problem when putting that UUCP on a non-11 family machine. The System III UUCP fixed it by the simple expedient of declaring the variable to be a "char" rather than an "int". (For those of you on machines with 4-byte "int"s, this argument still holds, the diagrams just look a little different.) Furthermore, the reason it was being suggested was that "char"s may be sign- extended when compared with "int"s, so a "char" containing the bit pattern 0377 (all bits on) would be sign-extended so that on a machine with 16-bit "int"s which sign-extends "char"s it actually would be treated as if it had the value 0177777. As such, that "char" would *not* be equal to 0377 in a comparison. It *would*, however, be equal to '\377', because that constant is a "char" and would be sign-extended. The proper solution, unless you have an older compiler (like, I believe, the V7 PDP-11 C compiler), is to declare "c" to be an *unsigned* char. If what you wanted was a variable which holds a small unsigned number (0 to 255), declare it as an "unsigned char". If what you wanted was a variable which holds a small *signed* number (-128 to 127), declare it as a "char" *but* be forewarned that C does *not* guarantee that "char"s are signed. On the AT&T Technologies 3B series, "char"s are unsigned, so there is no way to declare something to be an 8-bit signed integer. If what you wanted was a variable which holds an ASCII (7-bit) character, declare it as a "char" because the sign bit will never be on and the question is moot anyway. Guy Harris {seismo,ihnp4,allegra}!rlgvax!guy