1 #include "u.h" 2 #include "tos.h" 3 #include "../port/lib.h" 4 #include "mem.h" 5 #include "dat.h" 6 #include "fns.h" 7 #include "../port/error.h" 8 #include "edf.h" 9 10 #include <a.out.h> 11 12 int shargs(char*, int, char**); 13 14 Ref sysr1ref; 15 16 long 17 sysr1(ulong *arg) 18 { 19 long a; 20 21 a = *arg; 22 if(a > 0) 23 return incref(&sysr1ref); 24 if(a < 0) 25 return decref(&sysr1ref); 26 return sysr1ref.ref; 27 28 /* 29 extern int chandebug; 30 extern void dumpmount(void); 31 32 print("[%s %s %lud] r1 = %lud\n", up->user, up->text, up->pid, arg[0]); 33 chandebug=!chandebug; 34 if(chandebug) 35 dumpmount(); 36 37 return 0; 38 */ 39 } 40 41 long 42 sysrfork(ulong *arg) 43 { 44 Proc *p; 45 int n, i; 46 Fgrp *ofg; 47 Pgrp *opg; 48 Rgrp *org; 49 Egrp *oeg; 50 ulong pid, flag; 51 Mach *wm; 52 53 flag = arg[0]; 54 /* Check flags before we commit */ 55 if((flag & (RFFDG|RFCFDG)) == (RFFDG|RFCFDG)) 56 error(Ebadarg); 57 if((flag & (RFNAMEG|RFCNAMEG)) == (RFNAMEG|RFCNAMEG)) 58 error(Ebadarg); 59 if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG)) 60 error(Ebadarg); 61 62 if((flag&RFPROC) == 0) { 63 if(flag & (RFMEM|RFNOWAIT)) 64 error(Ebadarg); 65 if(flag & (RFFDG|RFCFDG)) { 66 ofg = up->fgrp; 67 if(flag & RFFDG) 68 up->fgrp = dupfgrp(ofg); 69 else 70 up->fgrp = dupfgrp(nil); 71 closefgrp(ofg); 72 } 73 if(flag & (RFNAMEG|RFCNAMEG)) { 74 opg = up->pgrp; 75 up->pgrp = newpgrp(); 76 if(flag & RFNAMEG) 77 pgrpcpy(up->pgrp, opg); 78 /* inherit noattach */ 79 up->pgrp->noattach = opg->noattach; 80 closepgrp(opg); 81 } 82 if(flag & RFNOMNT) 83 up->pgrp->noattach = 1; 84 if(flag & RFREND) { 85 org = up->rgrp; 86 up->rgrp = newrgrp(); 87 closergrp(org); 88 } 89 if(flag & (RFENVG|RFCENVG)) { 90 oeg = up->egrp; 91 up->egrp = smalloc(sizeof(Egrp)); 92 up->egrp->ref = 1; 93 if(flag & RFENVG) 94 envcpy(up->egrp, oeg); 95 closeegrp(oeg); 96 } 97 if(flag & RFNOTEG) 98 up->noteid = incref(¬eidalloc); 99 return 0; 100 } 101 102 p = newproc(); 103 104 p->fpsave = up->fpsave; 105 p->scallnr = up->scallnr; 106 p->s = up->s; 107 p->nerrlab = 0; 108 p->slash = up->slash; 109 p->dot = up->dot; 110 incref(p->dot); 111 112 memmove(p->note, up->note, sizeof(p->note)); 113 p->privatemem = up->privatemem; 114 p->noswap = up->noswap; 115 p->nnote = up->nnote; 116 p->notified = 0; 117 p->lastnote = up->lastnote; 118 p->notify = up->notify; 119 p->ureg = up->ureg; 120 p->dbgreg = 0; 121 122 /* Make a new set of memory segments */ 123 n = flag & RFMEM; 124 qlock(&p->seglock); 125 if(waserror()){ 126 qunlock(&p->seglock); 127 nexterror(); 128 } 129 for(i = 0; i < NSEG; i++) 130 if(up->seg[i]) 131 p->seg[i] = dupseg(up->seg, i, n); 132 qunlock(&p->seglock); 133 poperror(); 134 135 /* File descriptors */ 136 if(flag & (RFFDG|RFCFDG)) { 137 if(flag & RFFDG) 138 p->fgrp = dupfgrp(up->fgrp); 139 else 140 p->fgrp = dupfgrp(nil); 141 } 142 else { 143 p->fgrp = up->fgrp; 144 incref(p->fgrp); 145 } 146 147 /* Process groups */ 148 if(flag & (RFNAMEG|RFCNAMEG)) { 149 p->pgrp = newpgrp(); 150 if(flag & RFNAMEG) 151 pgrpcpy(p->pgrp, up->pgrp); 152 /* inherit noattach */ 153 p->pgrp->noattach = up->pgrp->noattach; 154 } 155 else { 156 p->pgrp = up->pgrp; 157 incref(p->pgrp); 158 } 159 if(flag & RFNOMNT) 160 up->pgrp->noattach = 1; 161 162 if(flag & RFREND) 163 p->rgrp = newrgrp(); 164 else { 165 incref(up->rgrp); 166 p->rgrp = up->rgrp; 167 } 168 169 /* Environment group */ 170 if(flag & (RFENVG|RFCENVG)) { 171 p->egrp = smalloc(sizeof(Egrp)); 172 p->egrp->ref = 1; 173 if(flag & RFENVG) 174 envcpy(p->egrp, up->egrp); 175 } 176 else { 177 p->egrp = up->egrp; 178 incref(p->egrp); 179 } 180 p->hang = up->hang; 181 p->procmode = up->procmode; 182 183 /* Craft a return frame which will cause the child to pop out of 184 * the scheduler in user mode with the return register zero 185 */ 186 forkchild(p, up->dbgreg); 187 188 p->parent = up; 189 p->parentpid = up->pid; 190 if(flag&RFNOWAIT) 191 p->parentpid = 0; 192 else { 193 lock(&up->exl); 194 up->nchild++; 195 unlock(&up->exl); 196 } 197 if((flag&RFNOTEG) == 0) 198 p->noteid = up->noteid; 199 200 p->fpstate = up->fpstate; 201 pid = p->pid; 202 memset(p->time, 0, sizeof(p->time)); 203 p->time[TReal] = MACHP(0)->ticks; 204 205 kstrdup(&p->text, up->text); 206 kstrdup(&p->user, up->user); 207 /* 208 * since the bss/data segments are now shareable, 209 * any mmu info about this process is now stale 210 * (i.e. has bad properties) and has to be discarded. 211 */ 212 flushmmu(); 213 p->basepri = up->basepri; 214 p->priority = up->basepri; 215 p->fixedpri = up->fixedpri; 216 p->mp = up->mp; 217 wm = up->wired; 218 if(wm) 219 procwired(p, wm->machno); 220 ready(p); 221 sched(); 222 return pid; 223 } 224 225 static ulong 226 l2be(long l) 227 { 228 uchar *cp; 229 230 cp = (uchar*)&l; 231 return (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | cp[3]; 232 } 233 234 long 235 sysexec(ulong *arg) 236 { 237 Segment *s, *ts; 238 ulong t, d, b; 239 int i; 240 Chan *tc; 241 char **argv, **argp; 242 char *a, *charp, *args, *file; 243 char *progarg[sizeof(Exec)/2+1], *elem, progelem[64]; 244 ulong ssize, spage, nargs, nbytes, n, bssend; 245 int indir; 246 Exec exec; 247 char line[sizeof(Exec)]; 248 Fgrp *f; 249 Image *img; 250 ulong magic, text, entry, data, bss; 251 Tos *tos; 252 253 validaddr(arg[0], 1, 0); 254 file = (char*)arg[0]; 255 indir = 0; 256 elem = nil; 257 if(waserror()){ 258 free(elem); 259 nexterror(); 260 } 261 for(;;){ 262 tc = namec(file, Aopen, OEXEC, 0); 263 if(waserror()){ 264 cclose(tc); 265 nexterror(); 266 } 267 if(!indir) 268 kstrdup(&elem, up->genbuf); 269 270 n = devtab[tc->type]->read(tc, &exec, sizeof(Exec), 0); 271 if(n < 2) 272 error(Ebadexec); 273 magic = l2be(exec.magic); 274 text = l2be(exec.text); 275 entry = l2be(exec.entry); 276 if(n==sizeof(Exec) && (magic == AOUT_MAGIC)){ 277 if((text&KZERO) == KZERO 278 || entry < UTZERO+sizeof(Exec) 279 || entry >= UTZERO+sizeof(Exec)+text) 280 error(Ebadexec); 281 break; /* for binary */ 282 } 283 284 /* 285 * Process #! /bin/sh args ... 286 */ 287 memmove(line, &exec, sizeof(Exec)); 288 if(indir || line[0]!='#' || line[1]!='!') 289 error(Ebadexec); 290 n = shargs(line, n, progarg); 291 if(n == 0) 292 error(Ebadexec); 293 indir = 1; 294 /* 295 * First arg becomes complete file name 296 */ 297 progarg[n++] = file; 298 progarg[n] = 0; 299 validaddr(arg[1], BY2WD, 1); 300 arg[1] += BY2WD; 301 file = progarg[0]; 302 if(strlen(elem) >= sizeof progelem) 303 error(Ebadexec); 304 strcpy(progelem, elem); 305 progarg[0] = progelem; 306 poperror(); 307 cclose(tc); 308 } 309 310 data = l2be(exec.data); 311 bss = l2be(exec.bss); 312 t = (UTZERO+sizeof(Exec)+text+(BY2PG-1)) & ~(BY2PG-1); 313 d = (t + data + (BY2PG-1)) & ~(BY2PG-1); 314 bssend = t + data + bss; 315 b = (bssend + (BY2PG-1)) & ~(BY2PG-1); 316 if(((t|d|b) & KZERO) == KZERO) 317 error(Ebadexec); 318 319 /* 320 * Args: pass 1: count 321 */ 322 nbytes = sizeof(Tos); /* hole for profiling clock at top of stack (and more) */ 323 nargs = 0; 324 if(indir){ 325 argp = progarg; 326 while(*argp){ 327 a = *argp++; 328 nbytes += strlen(a) + 1; 329 nargs++; 330 } 331 } 332 evenaddr(arg[1]); 333 argp = (char**)arg[1]; 334 validaddr((ulong)argp, BY2WD, 0); 335 while(*argp){ 336 a = *argp++; 337 if(((ulong)argp&(BY2PG-1)) < BY2WD) 338 validaddr((ulong)argp, BY2WD, 0); 339 validaddr((ulong)a, 1, 0); 340 nbytes += (vmemchr(a, 0, 0x7FFFFFFF) - a) + 1; 341 nargs++; 342 } 343 ssize = BY2WD*(nargs+1) + ((nbytes+(BY2WD-1)) & ~(BY2WD-1)); 344 345 /* 346 * 8-byte align SP for those (e.g. sparc) that need it. 347 * execregs() will subtract another 4 bytes for argc. 348 */ 349 if((ssize+4) & 7) 350 ssize += 4; 351 spage = (ssize+(BY2PG-1)) >> PGSHIFT; 352 353 /* 354 * Build the stack segment, putting it in kernel virtual for the moment 355 */ 356 if(spage > TSTKSIZ) 357 error(Enovmem); 358 359 qlock(&up->seglock); 360 if(waserror()){ 361 qunlock(&up->seglock); 362 nexterror(); 363 } 364 up->seg[ESEG] = newseg(SG_STACK, TSTKTOP-USTKSIZE, USTKSIZE/BY2PG); 365 366 /* 367 * Args: pass 2: assemble; the pages will be faulted in 368 */ 369 tos = (Tos*)(TSTKTOP - sizeof(Tos)); 370 tos->cyclefreq = m->cyclefreq; 371 cycles((uvlong*)&tos->pcycles); 372 tos->pcycles = -tos->pcycles; 373 tos->kcycles = tos->pcycles; 374 tos->clock = 0; 375 argv = (char**)(TSTKTOP - ssize); 376 charp = (char*)(TSTKTOP - nbytes); 377 args = charp; 378 if(indir) 379 argp = progarg; 380 else 381 argp = (char**)arg[1]; 382 383 for(i=0; i<nargs; i++){ 384 if(indir && *argp==0) { 385 indir = 0; 386 argp = (char**)arg[1]; 387 } 388 *argv++ = charp + (USTKTOP-TSTKTOP); 389 n = strlen(*argp) + 1; 390 memmove(charp, *argp++, n); 391 charp += n; 392 } 393 394 free(up->text); 395 up->text = elem; 396 elem = nil; /* so waserror() won't free elem */ 397 USED(elem); 398 399 /* copy args; easiest from new process's stack */ 400 n = charp - args; 401 if(n > 128) /* don't waste too much space on huge arg lists */ 402 n = 128; 403 a = up->args; 404 up->args = nil; 405 free(a); 406 up->args = smalloc(n); 407 memmove(up->args, args, n); 408 if(n>0 && up->args[n-1]!='\0'){ 409 /* make sure last arg is NUL-terminated */ 410 /* put NUL at UTF-8 character boundary */ 411 for(i=n-1; i>0; --i) 412 if(fullrune(up->args+i, n-i)) 413 break; 414 up->args[i] = 0; 415 n = i+1; 416 } 417 up->nargs = n; 418 419 /* 420 * Committed. 421 * Free old memory. 422 * Special segments are maintained across exec 423 */ 424 for(i = SSEG; i <= BSEG; i++) { 425 putseg(up->seg[i]); 426 /* prevent a second free if we have an error */ 427 up->seg[i] = 0; 428 } 429 for(i = BSEG+1; i < NSEG; i++) { 430 s = up->seg[i]; 431 if(s != 0 && (s->type&SG_CEXEC)) { 432 putseg(s); 433 up->seg[i] = 0; 434 } 435 } 436 437 /* 438 * Close on exec 439 */ 440 f = up->fgrp; 441 for(i=0; i<=f->maxfd; i++) 442 fdclose(i, CCEXEC); 443 444 /* Text. Shared. Attaches to cache image if possible */ 445 /* attachimage returns a locked cache image */ 446 img = attachimage(SG_TEXT|SG_RONLY, tc, UTZERO, (t-UTZERO)>>PGSHIFT); 447 ts = img->s; 448 up->seg[TSEG] = ts; 449 ts->flushme = 1; 450 ts->fstart = 0; 451 ts->flen = sizeof(Exec)+text; 452 unlock(img); 453 454 /* Data. Shared. */ 455 s = newseg(SG_DATA, t, (d-t)>>PGSHIFT); 456 up->seg[DSEG] = s; 457 458 /* Attached by hand */ 459 incref(img); 460 s->image = img; 461 s->fstart = ts->fstart+ts->flen; 462 s->flen = data; 463 464 /* BSS. Zero fill on demand */ 465 up->seg[BSEG] = newseg(SG_BSS, d, (b-d)>>PGSHIFT); 466 467 /* 468 * Move the stack 469 */ 470 s = up->seg[ESEG]; 471 up->seg[ESEG] = 0; 472 up->seg[SSEG] = s; 473 qunlock(&up->seglock); 474 poperror(); /* seglock */ 475 poperror(); /* elem */ 476 s->base = USTKTOP-USTKSIZE; 477 s->top = USTKTOP; 478 relocateseg(s, USTKTOP-TSTKTOP); 479 480 /* 481 * '/' processes are higher priority (hack to make /ip more responsive). 482 */ 483 if(devtab[tc->type]->dc == L'/') 484 up->basepri = PriRoot; 485 up->priority = up->basepri; 486 poperror(); 487 cclose(tc); 488 489 /* 490 * At this point, the mmu contains info about the old address 491 * space and needs to be flushed 492 */ 493 flushmmu(); 494 qlock(&up->debug); 495 up->nnote = 0; 496 up->notify = 0; 497 up->notified = 0; 498 up->privatemem = 0; 499 procsetup(up); 500 qunlock(&up->debug); 501 if(up->hang) 502 up->procctl = Proc_stopme; 503 504 return execregs(entry, ssize, nargs); 505 } 506 507 int 508 shargs(char *s, int n, char **ap) 509 { 510 int i; 511 512 s += 2; 513 n -= 2; /* skip #! */ 514 for(i=0; s[i]!='\n'; i++) 515 if(i == n-1) 516 return 0; 517 s[i] = 0; 518 *ap = 0; 519 i = 0; 520 for(;;) { 521 while(*s==' ' || *s=='\t') 522 s++; 523 if(*s == 0) 524 break; 525 i++; 526 *ap++ = s; 527 *ap = 0; 528 while(*s && *s!=' ' && *s!='\t') 529 s++; 530 if(*s == 0) 531 break; 532 else 533 *s++ = 0; 534 } 535 return i; 536 } 537 538 int 539 return0(void*) 540 { 541 return 0; 542 } 543 544 long 545 syssleep(ulong *arg) 546 { 547 548 int n; 549 550 n = arg[0]; 551 if(n <= 0) { 552 if (up->edf && (up->edf->flags & Admitted)) 553 edfyield(); 554 else 555 yield(); 556 return 0; 557 } 558 if(n < TK2MS(1)) 559 n = TK2MS(1); 560 tsleep(&up->sleep, return0, 0, n); 561 return 0; 562 } 563 564 long 565 sysalarm(ulong *arg) 566 { 567 return procalarm(arg[0]); 568 } 569 570 long 571 sysexits(ulong *arg) 572 { 573 char *status; 574 char *inval = "invalid exit string"; 575 char buf[ERRMAX]; 576 577 status = (char*)arg[0]; 578 if(status){ 579 if(waserror()) 580 status = inval; 581 else{ 582 validaddr((ulong)status, 1, 0); 583 if(vmemchr(status, 0, ERRMAX) == 0){ 584 memmove(buf, status, ERRMAX); 585 buf[ERRMAX-1] = 0; 586 status = buf; 587 } 588 } 589 poperror(); 590 591 } 592 pexit(status, 1); 593 return 0; /* not reached */ 594 } 595 596 long 597 sys_wait(ulong *arg) 598 { 599 int pid; 600 Waitmsg w; 601 OWaitmsg *ow; 602 603 if(arg[0] == 0) 604 return pwait(nil); 605 606 validaddr(arg[0], sizeof(OWaitmsg), 1); 607 evenaddr(arg[0]); 608 pid = pwait(&w); 609 if(pid >= 0){ 610 ow = (OWaitmsg*)arg[0]; 611 readnum(0, ow->pid, NUMSIZE, w.pid, NUMSIZE); 612 readnum(0, ow->time+TUser*NUMSIZE, NUMSIZE, w.time[TUser], NUMSIZE); 613 readnum(0, ow->time+TSys*NUMSIZE, NUMSIZE, w.time[TSys], NUMSIZE); 614 readnum(0, ow->time+TReal*NUMSIZE, NUMSIZE, w.time[TReal], NUMSIZE); 615 strncpy(ow->msg, w.msg, sizeof(ow->msg)); 616 ow->msg[sizeof(ow->msg)-1] = '\0'; 617 } 618 return pid; 619 } 620 621 long 622 sysawait(ulong *arg) 623 { 624 int i; 625 int pid; 626 Waitmsg w; 627 ulong n; 628 629 n = arg[1]; 630 validaddr(arg[0], n, 1); 631 pid = pwait(&w); 632 if(pid < 0) 633 return -1; 634 i = snprint((char*)arg[0], n, "%d %lud %lud %lud %q", 635 w.pid, 636 w.time[TUser], w.time[TSys], w.time[TReal], 637 w.msg); 638 639 return i; 640 } 641 642 long 643 sysdeath(ulong*) 644 { 645 pprint("deprecated system call\n"); 646 pexit("Suicide", 0); 647 return 0; /* not reached */ 648 } 649 650 void 651 werrstr(char *fmt, ...) 652 { 653 va_list va; 654 655 if(up == nil) 656 return; 657 658 va_start(va, fmt); 659 vseprint(up->syserrstr, up->syserrstr+ERRMAX, fmt, va); 660 va_end(va); 661 } 662 663 static long 664 generrstr(char *buf, uint nbuf) 665 { 666 char tmp[ERRMAX]; 667 668 if(nbuf == 0) 669 error(Ebadarg); 670 validaddr((ulong)buf, nbuf, 1); 671 if(nbuf > sizeof tmp) 672 nbuf = sizeof tmp; 673 memmove(tmp, buf, nbuf); 674 675 /* make sure it's NUL-terminated */ 676 tmp[nbuf-1] = '\0'; 677 memmove(buf, up->syserrstr, nbuf); 678 buf[nbuf-1] = '\0'; 679 memmove(up->syserrstr, tmp, nbuf); 680 return 0; 681 } 682 683 long 684 syserrstr(ulong *arg) 685 { 686 return generrstr((char*)arg[0], arg[1]); 687 } 688 689 /* compatibility for old binaries */ 690 long 691 sys_errstr(ulong *arg) 692 { 693 return generrstr((char*)arg[0], 64); 694 } 695 696 long 697 sysnotify(ulong *arg) 698 { 699 if(arg[0] != 0) 700 validaddr(arg[0], sizeof(ulong), 0); 701 up->notify = (int(*)(void*, char*))(arg[0]); 702 return 0; 703 } 704 705 long 706 sysnoted(ulong *arg) 707 { 708 if(arg[0]!=NRSTR && !up->notified) 709 error(Egreg); 710 return 0; 711 } 712 713 long 714 syssegbrk(ulong *arg) 715 { 716 int i; 717 ulong addr; 718 Segment *s; 719 720 addr = arg[0]; 721 for(i = 0; i < NSEG; i++) { 722 s = up->seg[i]; 723 if(s == 0 || addr < s->base || addr >= s->top) 724 continue; 725 switch(s->type&SG_TYPE) { 726 case SG_TEXT: 727 case SG_DATA: 728 case SG_STACK: 729 error(Ebadarg); 730 default: 731 return ibrk(arg[1], i); 732 } 733 } 734 735 error(Ebadarg); 736 return 0; /* not reached */ 737 } 738 739 long 740 syssegattach(ulong *arg) 741 { 742 return segattach(up, arg[0], (char*)arg[1], arg[2], arg[3]); 743 } 744 745 long 746 syssegdetach(ulong *arg) 747 { 748 int i; 749 ulong addr; 750 Segment *s; 751 752 qlock(&up->seglock); 753 if(waserror()){ 754 qunlock(&up->seglock); 755 nexterror(); 756 } 757 758 s = 0; 759 addr = arg[0]; 760 for(i = 0; i < NSEG; i++) 761 if(s = up->seg[i]) { 762 qlock(&s->lk); 763 if((addr >= s->base && addr < s->top) || 764 (s->top == s->base && addr == s->base)) 765 goto found; 766 qunlock(&s->lk); 767 } 768 769 error(Ebadarg); 770 771 found: 772 /* Check we are not detaching the current stack segment */ 773 if((ulong)arg >= s->base && (ulong)arg < s->top) { 774 qunlock(&s->lk); 775 error(Ebadarg); 776 } 777 up->seg[i] = 0; 778 qunlock(&s->lk); 779 putseg(s); 780 qunlock(&up->seglock); 781 poperror(); 782 783 /* Ensure we flush any entries from the lost segment */ 784 flushmmu(); 785 return 0; 786 } 787 788 long 789 syssegfree(ulong *arg) 790 { 791 Segment *s; 792 ulong from, to; 793 794 from = arg[0]; 795 s = seg(up, from, 1); 796 if(s == nil) 797 error(Ebadarg); 798 to = (from + arg[1]) & ~(BY2PG-1); 799 from = PGROUND(from); 800 801 if(to > s->top) { 802 qunlock(&s->lk); 803 error(Ebadarg); 804 } 805 806 mfreeseg(s, from, (to - from) / BY2PG); 807 qunlock(&s->lk); 808 flushmmu(); 809 810 return 0; 811 } 812 813 /* For binary compatibility */ 814 long 815 sysbrk_(ulong *arg) 816 { 817 return ibrk(arg[0], BSEG); 818 } 819 820 long 821 sysrendezvous(ulong *arg) 822 { 823 ulong tag; 824 ulong val; 825 Proc *p, **l; 826 827 tag = arg[0]; 828 l = &REND(up->rgrp, tag); 829 up->rendval = ~0UL; 830 831 lock(up->rgrp); 832 for(p = *l; p; p = p->rendhash) { 833 if(p->rendtag == tag) { 834 *l = p->rendhash; 835 val = p->rendval; 836 p->rendval = arg[1]; 837 838 while(p->mach != 0) 839 ; 840 ready(p); 841 unlock(up->rgrp); 842 return val; 843 } 844 l = &p->rendhash; 845 } 846 847 /* Going to sleep here */ 848 up->rendtag = tag; 849 up->rendval = arg[1]; 850 up->rendhash = *l; 851 *l = up; 852 up->state = Rendezvous; 853 unlock(up->rgrp); 854 855 sched(); 856 857 return up->rendval; 858 } 859