hutch@fps.com (Jim Hutchison) (07/28/90)
I have been reading through the systemVr2 streams porting doc for the 3b2, in an effort to better understand the streams code in Suns 4.1 source. I noted that there is some logic to avoid deadlocks which result from all the message buffer space being in use. There is a pool, and a certain amount which is reserved for high-priority messages. Can someone with real-life experience tell me what the relationship is between activity and the proportion of the size of the pool to the size of the reserved high-priority part? I'd imagine that by counting the high priority sections (areas that can't sleep) which allocate buffers per second, I'd get the number of high-priority buffers that would have to be available in that second. This number would rely on certain factors such as the message rate and system load as seen by the streams. Any takers? -- - Jim Hutchison {dcdwest,ucbvax}!ucsd!fps!hutch Disclaimer: I am not an official spokesman for FPS computing
sar0@cbnewsl.att.com (stephen.a.rago) (08/02/90)
In article <10332@celit.fps.com>, hutch@fps.com (Jim Hutchison) writes: > I have been reading through the systemVr2 streams porting doc for the 3b2, > in an effort to better understand the streams code in Suns 4.1 source. I hope you mean SVR3 because streams wasn't in SVR2. > I noted that there is some logic to avoid deadlocks which result from all > the message buffer space being in use. There is a pool, and a certain > amount which is reserved for high-priority messages. It's not for deadlocks. It's because recovery from message allocation failure is clumsy at best and necessitates either a delay or loss of data, so the caller indicates how badly the message is needed. > Can someone with real-life experience tell me what the relationship is > between activity and the proportion of the size of the pool to the size of > the reserved high-priority part? I'd imagine that by counting the high > priority sections (areas that can't sleep) which allocate buffers per > second, I'd get the number of high-priority buffers that would have to be > available in that second. This number would rely on certain factors such > as the message rate and system load as seen by the streams. The percentages are tunable via the kernel master file, so administrators can modify the cutoff where failures start occurring. I've never heard of a system where the defaults were not used. BPRI_LO is used by the stream head during writes because the stream head can easily sleep. BPRI_MED is used by modules and drivers for most types of messages. BPRI_HI is used by modules and drivers for high-priority messages. It is not expected that many high-priority messages would be needed under normal circumstances, but again, these are cases where the caller probably doesn't want the delay and doesn't want to forget the event. The number of messages needed per buffer class (size) is something that can only be determined statistically or empirically. In the absence of information like inter-arrival rate of allocation requests and message hold times, the best way to proceed is to start with an educated guess of how many messages you may need. For example, if you have one ethernet card, you know that packets are probably going to be using the streams buffers in the 2K range and the 64 or 128 byte range. You also know that the ethernet driver can afford to drop a few packets on the floor, but you'd like to avoid this situation. You can configure lots of streams messages, but you'll be ultimately limited by the number of buffers on the I/O card and the transmission rate of the media. You also need to consider that the more memory you devote to streams messages, the less memory will be available for other things in the system, and paging activity will increase. You might start with 32 2K buffers and say, 64 64-byte buffers and 64 128-byte buffers. The next step is to run your workloads and then use the strstat function of crash(1M) to see if there were any allocation failures for any buffer sizes. These will need to be increased. If there are any classes where the maximum number of buffers in use at one time was far less that the number configured, then you can decrease the number allocated for that class. Some people at AT&T were using an Erlang traffic model to configure their streams buffers, but ever since SVR4, it's become a moot point. (They're allocated dynamically in SVR4). Steve Rago sar@attunix.att.com
lm@snafu.Sun.COM (Larry McVoy) (08/10/90)
In article <1990Aug2.043059.578@cbnewsl.att.com> sar0@cbnewsl.att.com (stephen.a.rago) writes: >The number of messages needed per buffer class (size) is something that >can only be determined statistically or empirically. In the absence of >information like inter-arrival rate of allocation requests and message >hold times, the best way to proceed is to start with an educated guess >of how many messages you may need. Here's a package I wrote a few years ago when tuning STREAMS for SCO XENIX (I was porting the LAI TCP/IP to XENIX and tuning was really critical). Use this while your system is under a "normal" load and it will give you a pretty good idea of where to set things. No promises that it works - it worked on SCO XENIX a while ago, beyond that you're on your own. If you have SCO XENIX & TCP/IP you should have this program already. # This is a shell archive. Remove anything before this line, then # unpack it by saving it in a file and typing "sh file". (Files # unpacked will be owned by you and have default permissions.) # # This archive contains: # Makefile mode.c sw.1 sw.c term.h termcap.c echo x - Makefile cat > "Makefile" << '//E*O*F Makefile//' O = sw.o termcap.o mode.o S = Makefile sw.1 sw.c termcap.c mode.c term.h #CFLAGS=-DUNIX=\"/xenix\" -DKMEM=\"/dev/mem\" -DSAVECNT=0 BIN=/usr/local/bin all sw: $O cc $O -ltermlib -o sw install: sw cp sw $(BIN) chown root $(BIN)/sw chmod 4755 $(BIN)/sw clean: rm -f $O a.out core shar clobber: clean rm -f sw shar: shar $S > shar //E*O*F Makefile// echo x - mode.c cat > "mode.c" << '//E*O*F mode.c//' /* * copyright (C) 1986 by Larry McVoy * MUST be distributed in source form only. */ # include <stdio.h> # include <sgtty.h> # include <fcntl.h> static struct sgttyb buf; static done = 0; delay(on) { if (on) { int flags; fcntl(0, F_GETFL, &flags); flags &= ~O_NDELAY; return fcntl(0, F_SETFL, flags); } else { return fcntl(0, F_SETFL, O_NDELAY); } } cbreak(on) { if (!done) { ioctl(fileno(stdin), TIOCGETP, &buf); done++; } if (on) { buf.sg_flags |= CBREAK; ioctl(fileno(stdin), TIOCSETP, &buf); } else { buf.sg_flags &= ~CBREAK; ioctl(fileno(stdin), TIOCSETP, &buf); } } echo(on) { if (!done) { ioctl(fileno(stdin), TIOCGETP, &buf); done++; } if (on) { buf.sg_flags |= ECHO; ioctl(fileno(stdin), TIOCSETP, &buf); } else { buf.sg_flags &= ~ECHO; ioctl(fileno(stdin), TIOCSETP, &buf); } } //E*O*F mode.c// echo x - sw.1 cat > "sw.1" << '//E*O*F sw.1//' .TH SW 1 .UC 4 .SH NAME sw - (stream watch) watch streams resources on System V .SH SYNOPSIS .B sw .SH DESCRIPTION .I Sw digs into kmem to find out how many streams, queues, message blocks, and data blocks are in use. It find this in the \fIstruct strstat strst\fR variable. For each category mentioned above the following fields are printed: .IP Use (strst.use) How many of the resource in question are in use. .IP "Ave10, Ave30, Ave60, Ave120" As above, only the value is averaged over the last N iterations (an iteration is about one second). .IP Total (strst.total) The total number of the resource used since boot time or the last time it was cleared. .IP Max (strst.max) The maximum number of the resource allocated at the same time. .IP Fail (strst.fail) The number of times a request was made, for the resource, that could not be granted. .PP The data block section is further broken down into sub classes. This section has a slightly different format; in the title section the field is \fISiz<#> Count <#>\fR, where the first number is the data block size and the second number is the number of data blocks statically allocated. .PP The screen is managed by curses. It will respond to: .IP c clear the total max & fail fields (you have to have write permission on /dev/mem). .IP ^L redraw the screen. .IP N Where N is 0-9. Set the number of seconds between interations. .IP q (quit) Quit the program. .IP ^L (Control-L) Refresh the screen. .SH FILES .DT /dev/kmem kernel memory .br /unix for getting variable addresses .SH BUGS The definitions of the various fields is my best guess, they do not reflect any AT&T documentation that I've read. .SH COPYRIGHT \fBSw\fR is copyright 1988 by Larry McVoy. Permission is hereby granted to publish strings in source or object form as long as all copyright notices are retained. Object-only distributions are permitted only if the source is also freely available from the distributer. Any fee charged for such publication may consist only of a reasonable charge for any media used. .SH AUTHOR Larry McVoy (lm@eng.sun.com) //E*O*F sw.1// echo x - sw.c cat > "sw.c" << '//E*O*F sw.c//' /* * copyright 1988 by Larry McVoy. All rights reserved. * If you redistribute this you must distribute in source form. */ #include "term.h" #include <signal.h> #ifdef M_XENIX #include <a.out.h> #else #include <nlist.h> #endif #include <ctype.h> #include <stdio.h> #include <sys/types.h> #include <sys/stream.h> #include <sys/stropts.h> #include <sys/strstat.h> #include <sys/var.h> #if defined(M_XENIX) || defined(sys5) #include <sys/utsname.h> #endif #ifndef UNIX #define UNIX "/vmunix" #endif #ifndef KMEM #define KMEM "/dev/kmem" #endif #ifdef M_XENIX #define v_nblk4096 v_nblk8192 #endif #ifdef sun #define NCLASS 9 #endif #ifndef SAVECNT #define SAVECNT 121 #endif #define kbytes(x) ( ((x)+1023) >> 10) #ifdef M_XENIX #define nlist xlist #define n_value xl_value #define n_name xl_name struct nlist nl[] = { #define NL_STRST 0 {0,0,0,"_strst"}, #define NL_RBSIZE 1 {0,0,0,"_rbsize"}, #define NL_V 2 {0,0,0,"_v"}, #define NL_DBALLOC 3 {0,0,0,"_dballoc"}, #define NL_NMBLOCK 4 {0,0,0,"_nmblock"}, {0,0,0,(char *) 0}, }; #else #ifdef sys5 struct nlist nl[] = { #define NL_STRST 0 {"_strst"}, #define NL_RBSIZE 1 {"_rbsize"}, #define NL_V 2 {"_v"}, #define NL_DBALLOC 3 {"_dballoc"}, #define NL_NMBLOCK 4 {"_nmblock"}, { 0 }, }; #else #ifdef sun char* nl_names[] = { #define NL_STRST 0 "_strst", #define NL_RBSIZE 1 "_rbsize", #define NL_NDBLKS 2 "_ndblks", #define NL_DBALLOC 3 "_dballoc", #define NL_NMBLOCK 4 "_nmblock", "", }; struct nlist nl[sizeof(nl_names)/sizeof(char*)]; #endif /* sun */ #endif /* sys5 */ #endif /* M_XENIX */ ushort rbsize[NCLASS]; short cnt[NCLASS]; struct dbalcst dballoc[NCLASS]; int total, ndblock, nmblock, fd, sleeptime = 1; #ifndef sun struct var v; #endif typedef struct { alcdat stream; alcdat queue; alcdat mblock; alcdat dblock; alcdat dblk[NCLASS]; } Strstat; Strstat strst; /* * It's the main thing... */ main(ac, av) char** av; { int* p; int i; int done(); for (i=1; i<ac; ++i) { if (av[i][0] == '-') if (isdigit(av[i][1])) sleeptime = atoi(&av[i][1]); } /* * get stuff from kmem */ # ifdef sun for (i=0; i<sizeof(nl_names)/sizeof(char*); ++i) nl[i].n_name = nl_names[i]; # endif if (nlist(UNIX, nl)) error("nlist"); if (((fd = open(KMEM, 2)) == -1) && ((fd = open(KMEM, 0)) == -1)) error(KMEM); readstuff(); # ifdef sun for (i=0; i<NCLASS; ndblock += cnt[i++]) ; # else for (i=0, p= &v.v_nblk4096; i<NCLASS; ndblock += (cnt[NCLASS - ++i] = *p++)) ; # endif /* * screen/mode stuff */ termcap();; echo(0); cbreak(1); delay(0); signal(SIGHUP, done); signal(SIGINT, done); signal(SIGQUIT, done); signal(SIGTERM, done); screen(); /* * display until quit */ for ( ;; ) { if (lseek(fd, nl[NL_STRST].n_value, 0) == -1) error("lseek"); if (read(fd, &strst, sizeof(strst)) != sizeof(strst)) error("read"); dump(&strst); for (i=0; i<sleeptime; ++i) { if (input()) break; sleep(1); } input(); } } /* * look for commands; expect to be in cbreak mode */ input() { char c; if (read(0, &c, 1) != 1) return 0; switch (c) { case 'c': clearstuff(); return 1; case '': screen(); return 1; case 'q': done(); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': sleeptime = c - '0'; Pause(); return 1; case '?': case 'h': clear(); tprint("", 0, 0); printf("\nOptions are\n"); printf("\tc\tclear total, max, and fail fields\n"); printf("\t^L\trefresh the screen\n"); printf("\tq\tquit\n"); printf("\tN\twhere N is a number 0..9 for the delay between refresh\n"); printf("Hit any key to continue....\n"); while (read(0, &c, 1) != 1) sleep(1); screen(); return 1; default: return 0; } } /* * clear the total, max, and fail fields * * N.B. assumes acldat is use, total, max, fail * and there are NCLASS+4 acldat's in a strstat */ clearstuff() { static int clr[3]; register i; clear(); lseek(fd, nl[NL_STRST].n_value+sizeof(int), 0); for (i=0; i<NCLASS+4; ++i) { if (write(fd, clr, sizeof(clr)) != sizeof(clr)) perror("write"); lseek(fd, sizeof(int), 1); } screen(); } /* * display the "once only" stuff */ screen() { char buf[200]; register i; #if defined(M_XENIX) || defined(sys5) struct utsname u; #else char host[100]; #endif clear(); #if defined(M_XENIX) || defined(sys5) uname(&u); sprintf(buf, "Host=%s", u.nodename); #else gethostname(host, sizeof(host)); sprintf(buf, "Host=%s", host); #endif tprint(buf, 50, 0); # if SAVECNT > 0 sprintf(buf, "%11s%5s%6s%6s%6s%6s%7s%8s%8s%8s", "Resource", "Cnt", "Use", "Ave10", "Ave30", "Ave60", "Ave120", "Total", "Max", "Fail"); # else sprintf(buf, "%11s%5s%10s%8s%8s%8s", "Resource", "Cnt", "Use", "Total", "Max", "Fail"); # endif tprint(buf, 0, 2); # ifdef sun sprintf(buf, "%11s:%4s", "stream", "?"); tprint(buf, 0, 3); sprintf(buf, "%11s:%4s", "queue", "?"); tprint(buf, 0, 4); # else sprintf(buf, "%11s:%4d", "stream", v.v_nstream); tprint(buf, 0, 3); sprintf(buf, "%11s:%4d", "queue", v.v_nqueue); tprint(buf, 0, 4); # endif sprintf(buf, "%11s:%4d", "mblock", nmblock); tprint(buf, 0, 5); sprintf(buf, "%11s:%4d", "dblk totals", ndblock); tprint(buf, 0, 6); # if SAVECNT > 0 sprintf(buf, "%4s%4s%4s%4s%6s%6s%6s%6s%7s%8s%8s%8s", "Size", "Cnt", "Med", "Low", "Use", "Ave10", "Ave30", "Ave60", "Ave120", "Total", "Max", "Fail"); # else sprintf(buf, "%-4s%4s%4s%4s%4s%6s%8s%8s%8s", "Mem", "Size", "Cnt", "Med", "Low", "Use", "Total", "Max", "Fail"); # endif tprint(buf, 0, 8); for (total=i=0; i<NCLASS; ++i) { register lo = dballoc[i].dba_lo; register med = dballoc[i].dba_med; total += rbsize[i] * cnt[i]; # if SAVECNT > 0 sprintf(buf, "%4d %3d %3d %3d", rbsize[i], cnt[i], med, lo); # else sprintf(buf, "%3d %4d %3d %3d %3d", kbytes(rbsize[i] * cnt[i]), rbsize[i], cnt[i], med, lo); # endif tprint(buf, 0, 9 + i); } sprintf(buf, "Buffers (used/total) = "); tprint(buf, 0, 23); Pause(); } Pause() { char buf[40]; sprintf(buf, "Pause=%d", sleeptime); tprint(buf, 0, 0); } # if SAVECNT == 0 /* * display the information, called once per second (about) * * No averaging version */ dump(s) register struct strstat* s; { char buf[80]; register i, mem = 0; static calls = 0; sprintf(buf, "%6d%8d%8d%8d", s->stream.use, s->stream.total, s->stream.max, s->stream.fail); tprint(buf, 20, 3); sprintf(buf, "%6d%8d%8d%8d", s->queue.use, s->queue.total, s->queue.max, s->queue.fail); tprint(buf, 20, 4); sprintf(buf, "%6d%8d%8d%8d", s->mblock.use, s->mblock.total, s->mblock.max, s->mblock.fail); tprint(buf, 20, 5); sprintf(buf, "%6d%8d%8d%8d", s->dblock.use, s->dblock.total, s->dblock.max, s->dblock.fail); tprint(buf, 20, 6); for (i=0; i<NCLASS; ++i) { mem += s->dblk[i].use * rbsize[i]; sprintf(buf, "%6d%8d%8d%8d", s->dblk[i].use, s->dblk[i].total, s->dblk[i].max, s->dblk[i].fail); tprint(buf, 20, 9 + i); } sprintf(buf, "%d/%d Kbytes", kbytes(mem), kbytes(total)); tprint(buf, 23, 23); calls++; sprintf(buf, "Calls=%d", calls); tprint(buf, 10, 0); } # else /* * display the information, called once per second (about) * * Averaging version */ dump(s) register struct strstat* s; { char buf[80]; register i, j, b10, b30, b60, b120, mem = 0; static struct strstat pst[SAVECNT]; static struct strstat sum10, sum30, sum60, sum120; static calls = 0; j = calls % SAVECNT; b10 = (j + SAVECNT - 10) % SAVECNT; b30 = (j + SAVECNT - 30) % SAVECNT; b60 = (j + SAVECNT - 60) % SAVECNT; b120 = (j + SAVECNT - 120) % SAVECNT; memcpy(&pst[j], s, sizeof(struct strstat)); addstrst(s, &sum10); addstrst(s, &sum30); addstrst(s, &sum60); addstrst(s, &sum120); if (!calls) { mulstrst(&sum10, 10); mulstrst(&sum30, 30); mulstrst(&sum60, 60); mulstrst(&sum120, 120); for (i=1; i<SAVECNT; ++i) memcpy(&pst[i], s, sizeof(struct strstat)); } else { substrst(&pst[b10], &sum10); substrst(&pst[b30], &sum30); substrst(&pst[b60], &sum60); substrst(&pst[b120], &sum120); } sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d", s->stream.use, sum10.stream.use / 10, sum30.stream.use / 30, sum60.stream.use / 60, sum120.stream.use / 120, s->stream.total, s->stream.max, s->stream.fail); tprint(buf, 16, 3); sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d", s->queue.use, sum10.queue.use / 10, sum30.queue.use / 30, sum60.queue.use / 60, sum120.queue.use / 120, s->queue.total, s->queue.max, s->queue.fail); tprint(buf, 16, 4); sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d", s->mblock.use, sum10.mblock.use / 10, sum30.mblock.use / 30, sum60.mblock.use / 60, sum120.mblock.use / 120, s->mblock.total, s->mblock.max, s->mblock.fail); tprint(buf, 16, 5); sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d", s->dblock.use, sum10.dblock.use / 10, sum30.dblock.use / 30, sum60.dblock.use / 60, sum120.dblock.use / 120, s->dblock.total, s->dblock.max, s->dblock.fail); tprint(buf, 16, 6); for (i=0; i<NCLASS; ++i) { mem += s->dblk[i].use * rbsize[i]; sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d", s->dblk[i].use, sum10.dblk[i].use / 10, sum30.dblk[i].use / 30, sum60.dblk[i].use / 60, sum120.dblk[i].use / 120, s->dblk[i].total, s->dblk[i].max, s->dblk[i].fail); tprint(buf, 16, 9 + i); } sprintf(buf, "%d/%d Kbytes", kbytes(mem), kbytes(total)); tprint(buf, 23, 23); calls++; sprintf(buf, "Calls=%d", calls); tprint(buf, 10, 0); } /* * add a to be, leave result in b * * N.B. Assumes that elements are ints */ addstrst(a, b) register alcdat* a; register alcdat* b; { register i; for (i=sizeof(struct strstat)/sizeof(*a); i--; (b++)->use += (a++)->use) ; } /* * subtract a from be, result in b */ substrst(a, b) register alcdat* a; register alcdat* b; { register i; for (i=sizeof(struct strstat)/sizeof(*a); i--; (b++)->use -= (a++)->use) ; } /* * multiply a by b, result in a */ mulstrst(a, b) register alcdat* a; register int b; { register i; for (i=sizeof(struct strstat)/sizeof(*a); i--; (a++)->use *= b) ; } # endif /* SAVECNT */ /* * nasty cleanup */ error(s) char* s; { perror(s); done(); } /* * nice cleanup */ done() { echo(1); cbreak(0); delay(1); tprint("", cols -1, lines - 1); printf("\n"); exit(0); } /* * read all once only stuff from kmem */ readstuff() { register i; if (!nl[NL_RBSIZE].n_value) { printf("%s: no value\n", nl[NL_RBSIZE].n_name); exit(1); } if (!nl[NL_STRST].n_value) { printf("%s: no value\n", nl[NL_STRST].n_name); exit(1); } # ifdef sun if (!nl[NL_NDBLKS].n_value) { printf("%s: no value\n", nl[NL_NDBLKS].n_name); # else if (!nl[NL_V].n_value) { printf("%s: no value\n", nl[NL_V].n_name); # endif exit(1); } if (!nl[NL_DBALLOC].n_value) { printf("%s: no value\n", nl[NL_DBALLOC].n_name); exit(1); } if (!nl[NL_NMBLOCK].n_value) { printf("%s: no value\n", nl[NL_NMBLOCK].n_name); exit(1); } if (lseek(fd, nl[NL_RBSIZE].n_value, 0) == -1) error("lseek"); if (read(fd, rbsize, sizeof(rbsize)) != sizeof(rbsize)) error("read"); # ifdef sun if (lseek(fd, nl[NL_NDBLKS].n_value, 0) == -1) error("lseek"); if (read(fd, cnt, sizeof(cnt)) != sizeof(cnt)) error("read"); # else if (lseek(fd, nl[NL_V].n_value, 0) == -1) error("lseek"); if (read(fd, &v, sizeof(v)) != sizeof(v)) error("read"); # endif # ifdef sun if (lseek(fd, nl[NL_DBALLOC].n_value, 0) == -1) error("lseek"); if (read(fd, &nl[NL_DBALLOC].n_value, sizeof(int)) != sizeof(int)) error("read"); # endif if (lseek(fd, nl[NL_DBALLOC].n_value, 0) == -1) error("lseek"); if (read(fd, dballoc, sizeof(dballoc)) != sizeof(dballoc)) error("read"); if (lseek(fd, nl[NL_NMBLOCK].n_value, 0) == -1) error("lseek"); if (read(fd, &nmblock, sizeof(nmblock)) != sizeof(nmblock)) error("read"); # ifdef sun if (lseek(fd, nl[NL_STRST].n_value, 0) == -1) error("lseek"); if (read(fd, &nl[NL_STRST].n_value, sizeof(int)) != sizeof(int)) error("read"); # endif } //E*O*F sw.c// echo x - term.h cat > "term.h" << '//E*O*F term.h//' # ifndef _TERM_H_ #include <stdio.h> #include <sgtty.h> char *getenv(); char *tgetstr(); char PC; short ospeed; short lines; short cols; char ceolbuf[20]; char clbuf[20]; char pcbuf[20]; char cmbuf[20]; #undef putchar int putchar(); char term_buf[1024]; # endif _TERM_H_ //E*O*F term.h// echo x - termcap.c cat > "termcap.c" << '//E*O*F termcap.c//' /* * copyright (C) 1986 by Larry McVoy * MUST be distributed in source form only. */ # include "term.h" char* tgoto(); /*------------------------------------------------------------------15/Dec/86-* * init_term - read in the termcap stuff *----------------------------------------------------------------larry mcvoy-*/ termcap() { char *cp = getenv("TERM"); char *foo; char garbage[10]; struct sgttyb tty; gtty(1, &tty); ospeed = tty.sg_ospeed; if (cp == (char *) 0) return -1; if (tgetent(term_buf, cp) != 1) exit(1); foo = garbage; foo = tgetstr("pc", &foo); if (foo) PC = *foo; foo = clbuf; tgetstr("cl", &foo); foo = ceolbuf; tgetstr("ce", &foo); foo = cmbuf; tgetstr("cm", &foo); lines = tgetnum("li"); cols = tgetnum("co"); return 0; } /* clear to end of line */ ceol(col, row) { char *foo = tgoto(cmbuf, col, row); write(1, foo, strlen(foo)); tputs(ceolbuf, 1, putchar); } /* clear screen */ clear() { tputs(clbuf, lines, putchar); } /*------------------------------------------------------------------15/Dec/86-* * tputchar(c, col, row) - put a single char on the screen * * Inputs ----> (char), (int), (int) * * Bugs ------> Assumes that the coords make sense * * Revisions: *----------------------------------------------------------------larry mcvoy-*/ tputchar(c, col, row) char c; { register char* foo; foo = tgoto(cmbuf, col, row); tputs(foo, lines, putchar); write(1, &c, 1); } /*------------------------------------------------------------------15/Dec/86-* * tprint(s, col, row) - put a string on the screen * * Inputs ----> (char), (int), (int) * * Results ---> Puts the string out iff it will fit. * * Revisions: *----------------------------------------------------------------larry mcvoy-*/ tprint(s, col, row) register char* s; { register char* foo; if (row > lines || col > cols) return -1; if (strlen(s) > cols - col) return -2; foo = tgoto(cmbuf, col, row); tputs(foo, lines, putchar); return write(1, s, strlen(s)); } /* fake putchar for tputs */ putchar(c) char c; { write(1, &c, 1); } //E*O*F termcap.c// echo Possible errors detected by \'wc\' [hopefully none]: temp=/tmp/shar$$ trap "rm -f $temp; exit" 0 1 2 3 15 cat > $temp <<\!!! 17 53 336 Makefile 57 120 878 mode.c 63 359 2087 sw.1 563 1521 12403 sw.c 19 36 278 term.h 104 281 2203 termcap.c 823 2370 18185 total !!! wc Makefile mode.c sw.1 sw.c term.h termcap.c | sed 's=[^ ]*/==' | diff -b $temp - exit 0 --- Larry McVoy, Sun Microsystems (415) 336-7627 ...!sun!lm or lm@sun.com