#ifndef lint static char sccsid[] = "@(#)sched.c 9.9 88/02/10"; #endif /* * Copyright (c) 1987 by Sun Microsystems, Inc. */ /* * PostScript lightweight process scheduler * * sched.c, Tue Feb 25 17:06:14 PST 1986 * */ #ifdef GPROF_HOOKS asm(".globl gP_RunSchedA, gP_RunSchedB, gP_RunSchedC"); asm(".globl gP_RunProcess, gP_RunProcBottom"); #endif #include "PostScript.h" #include "input.h" #include #include #include #include #include #include /* * FIXME: When there is a list of scenes then loop on each for the calls * to osi_lockdevice and osi_unlockdevice below. */ extern int errno, #ifdef REF msdev, /* mouse fd */ kbdev, /* keyboard fd */ #endif vsdev; /* the sunwindows root window, for input */ int timeout_ticks, /* seconds to timeout a process */ timeout_value; /* initializer for timeout_ticks */ #define DEFAULT_TIMEOUT 15 /* seconds */ /* FD & Mask manipulations, now that masks can be greater than 32 bits */ #define MAX_FDS 256 /* some margin for safety -- it's cheap */ #define MASK_BITS BITS(unsigned) #define MASK_SIZE (MAX_FDS/MASK_BITS) #define WORD_FOR_BIT(mask,i) (mask + (i / MASK_BITS)) #define BIT_FOR_INDEX(i) (1 << (i % MASK_BITS)) unsigned select_read_mask[MASK_SIZE], select_write_mask[MASK_SIZE]; int select_read_count, select_write_count; int maximum_fd, /* 1 + the highest interesting fd */ collapse_motion, damage_inhibited, distribution_count, io_ready, /* to be set by SIGIO handler */ skip_select_limit; #define SELECT_SKIPS 20 /* The set of processes waiting for a read to complete. Indexed by file ID */ struct execution_environment *process_readers[32]; struct body *damage_keyword, *eventq; struct timeval next_time, startup_time, poll; fract last_event_time, last_event_x, last_event_y; static int event_serial_number, max_fds; #ifdef PERFORMANCE extern int queue_length; extern int max_queue_length; extern long queue_event_count; extern int queue_compares; #endif RunScheduler() { extern struct scene *CurrentInputScene; register struct execution_environment *ee, *runq; register struct body *e; register int n; fract now; int errno_tmp; int selects_skipped = 0; unsigned read_mask[MASK_SIZE]; while (1) { cs_cursorup(); now = currenttime(0); if (cs_firstdamaged() != 0 && !damage_inhibited) distribute_damage(); if (process_set == 0 && eventq == 0 && select_read_count == 0 && select_write_count == 0) break; /* no work, so retire */ /* * Give device specific code a chance to release exclusive access * rights to the display. XXX When there is a list of scenes then each * should be given a chance to unlock (see osi_lockdevice below too). */ #ifndef REF osi_unlockdevice(CurrentInputScene); #endif #ifdef GPROF_HOOKS asm("gP_RunSchedA:"); #endif if (io_ready || process_set == 0 || (eventq != 0 && eventq->body.event.time > now) || ++selects_skipped > skip_select_limit) { struct timeval interval; set_alarm(now); for (n = (maximum_fd - 1)/MASK_BITS; n >= 0; n--) read_mask[n] = select_read_mask[n]; io_ready = 0; selects_skipped = 0; n = select(maximum_fd, read_mask, 0, 0, (process_set ? &poll : &next_time)); /* * Give device specific code a chance to gain exclusive access * rights to the display. XXX When there is a list of scenes then * each should be given a chance to lock (see osi_unlockdevice * above too). */ errno_tmp = errno; #ifndef REF osi_lockdevice(CurrentInputScene); #endif errno = errno_tmp; switch (n) { case -1: /* error or interrupt */ if (errno != EINTR) { perror("select"); } continue; case 0: /* timeout */ default: /* file i/o ready */ if (n > 0) run_readers(read_mask); } } #ifndef REF else /* * Give device specific code a chance to gain exclusive access * rights to the display. XXX When there is a list of scenes then * each should be given a chance to lock (see osi_lockdevice above * too). */ osi_lockdevice(CurrentInputScene); #endif #ifdef GPROF_HOOKS asm("gP_RunSchedB:"); #endif timeout_ticks = timeout_value; if (input_queue_lock.count > 0 && input_queue_lock.timer < now) { input_queue_lock.count = -input_queue_lock.count; printf("input queue lock broken!\n"); } if (input_queue_lock.count <= 0) { while (distribution_count == 0 && (e = eventq) != 0 && e->body.event.time <= now) { #ifdef PERFORMANCE if (queue_length > max_queue_length) max_queue_length = queue_length; queue_length--; #endif eventq = e->body.event.next; e->body.event.next = 0; e->body.event.serial = ++event_serial_number; if (e->body.event.time > last_event_time) last_event_time = e->body.event.time; if (e->body.event.name.type == keyword_type && (body_of(&e->body.event.name) == motion_keyword || body_of(&e->body.event.name) == enter_keyword || body_of(&e->body.event.name) == exit_keyword) ) { last_event_x = e->body.event.pos.x; last_event_y = e->body.event.pos.y; } if (raw_keyboard) determine_keystate(e); e->body.event.is_queued = 0; distributeevent(e); decref(e); } } #ifdef GPROF_HOOKS asm("gP_RunSchedC:"); #endif distribution_count = 0; runq = process_set; /* snapshot the queue */ process_set = 0; while (runq) { /* and run all these */ cs_cursorup(); ee = runq->next; if (ee == runq) runq = 0; else runq->next = ee->next; ee->next = 0; run_process(ee); } /* then accumulate new prospects: */ cs_cursorup(); } printf("Sic Transit Gloria PostScript\n"); restore_keyboard(); exit(0); } selectable_file(fd, read_flag) int fd, read_flag; { register unsigned *mask = (read_flag ? select_read_mask : select_write_mask); return((*(WORD_FOR_BIT(mask, fd))) & BIT_FOR_INDEX(fd)); } void add_selectable_file(fd, read_flag) int fd, read_flag; { register unsigned *mask = (read_flag ? select_read_mask : select_write_mask); (*(WORD_FOR_BIT(mask, fd))) |= BIT_FOR_INDEX(fd); if (read_flag) select_read_count++; else select_write_count++; } void remove_selectable_file(fd, read_flag) int fd, read_flag; { register unsigned *mask = (read_flag ? select_read_mask : select_write_mask); (*(WORD_FOR_BIT(mask, fd))) &= ~(BIT_FOR_INDEX(fd)); if (read_flag) select_read_count--; else select_write_count--; } void enqueueEvent(event) register struct body *event; { register struct body *q, *prev = 0; register fract time = event->body.event.time; #ifdef PERFORMANCE int count = 0; queue_length++; #endif #ifdef EVENT_DEBUG check_q(); #endif assert(event->type==event_type); assert(!event->body.event.is_queued); event->body.event.is_interest = 0; event->body.event.is_exclusive = 0; if (eventq == 0 || time < eventq->body.event.time) { event->body.event.next = eventq; eventq = event; } else { q = eventq; while (q->body.event.next && q->body.event.next->body.event.time <= time) { #ifdef PERFORMANCE count++; #endif prev = q; q = q->body.event.next; } #ifdef PERFORMANCE queue_compares += count; queue_event_count++; #endif event->body.event.next = q->body.event.next; if (collapse_motion && event->body.event.name.type == keyword_type && body_of(&event->body.event.name) == motion_keyword && q->body.event.name.type == keyword_type && body_of(&q->body.event.name) == motion_keyword) { if (prev) prev->body.event.next = event; else { eventq = event; event->body.event.time = q->body.event.time; } q->body.event.is_queued = 0; decref(q); #ifdef PERFORMANCE queue_length--; #endif } else { q->body.event.next = event; } } event->body.event.is_queued = 1; #ifdef EVENT_DEBUG check_q(); #endif } void dequeueEvent(event) register struct body *event; { register struct body *q; assert(event->type == event_type); if (eventq == 0 || !event->body.event.is_queued) return; #ifdef PERFORMANCE if (queue_length > max_queue_length) max_queue_length = queue_length; queue_length--; #endif if (eventq == event) { eventq = event->body.event.next; } else { for (q = eventq; ;) { if (q->body.event.next == event) { q->body.event.next = event->body.event.next; break; } if ((q = q->body.event.next) == 0) { assert(!event->body.event.is_queued); /* i. e. ERROR */ } } } event->body.event.is_queued = 0; decref(event); } static set_alarm(base) fract base; { fract delta; if (eventq == 0) { next_time.tv_sec = (1<<24)-1; next_time.tv_usec = 999999; return; } if (eventq->body.event.time <= base) { next_time.tv_sec = 0; next_time.tv_usec = 0; } else { delta = (eventq->body.event.time - base) * 60; next_time.tv_sec = cfloorfr(delta); next_time.tv_usec = (fractionalbits(delta) * 15625) >> 10; /* we want (fract * 1000000) >> 16, to get u_int microseconds. * but the multiplication can overflow. So use the fact that * 10^6 == 5^6 * 2^6: 15625 is 5^6, and save the 6 bits of shift. */ } } static distribute_damage() { register struct canvas *dam; for (; dam = cs_firstdamaged(); cs_nextdamaged()) if (cs_getdamageshape(dam)) if (!cs_retained(dam) && !cs_mapped(dam) || dam->pixrect.pr_width == 0) { /* Don't deliver damage messages on canvases that can't have any valid pixels */ sh_decref(cs_getdamageshape(dam)); cs_cleardamageshape(dam); } else { register struct body *b = createevent(); set_typed_bodied_object(&(b->body.event.name), keyword_type, damage_keyword); b->body.event.canvas = dam; cv_incref(dam); b->body.event.time = last_event_time; b->body.event.serial = ++event_serial_number; b->body.event.keystate = current_keystate; incref(current_keystate); distributeevent(b); decref(b); } } static run_process(ee) register struct execution_environment *ee; { #ifdef GPROF_HOOKS /* the silly static up there makes this procedure invisible!!! */ asm("gP_RunProcess:"); #endif ee->event = 0; if (ee->pos) PostScript(ee); #ifdef GPROF_HOOKS asm("gP_RunProcBottom:"); #endif if (ee->pos < ee->execution_stack) free_process(ee); else if (ee->event > 0) { /* blocked on a read */ if (ee->event > max_fds + 1) { abort(); } add_selectable_file(ee->event - 1, 1); process_readers[ee->event] = ee; /* Multiple readers on the same file lose */ } else if (ee->event == 0) enqueue_process(ee); } #define test_bits(mask, out, n) if((mask&((1<>=n; #define find_1st_one(mask, out) \ { out = 0; test_bits(mask, out, 16) \ test_bits(mask, out, 8) test_bits(mask, out, 4) \ test_bits(mask, out, 2) test_bits(mask, out, 1) \ } run_readers(read_mask) unsigned *read_mask; { register unsigned fd, save, word; register int index; register struct execution_environment *ee; for (index = maximum_fd / MASK_BITS; index >= 0; index--) { save = read_mask[index]; while (word = save) { find_1st_one(word, fd); /* macro sets fd */ save ^= 1 << fd; if (fd == vsdev) EventsHaveSelected(); #ifdef REF else if (fd == msdev) MsEventsHaveSelected(); else if (fd == kbdev) KbEventsHaveSelected(); #endif else { remove_selectable_file(fd, 1); if (ee = process_readers[fd + 1]) { process_readers[fd + 1] = 0; run_process(ee); } } } read_mask[index] = 0; } } #ifdef NOTDEF run_writers(write_mask) unsigned *write_mask; { register unsigned fd, save, word; register int index; register struct execution_environment *ee; for (index = maximum_fd / MASK_BITS; index >= 0; index--) { save = write_mask[index]; while (word = save) { find_1st_one(word, fd); /* macro sets fd */ save ^= 1 << fd; if (ee = process_writers[fd + 1]) { process_writers[fd + 1] = 0; } else remove_selectable_file(fd, 0); } write_mask[index] = 0; } } #endif NOTDEF static statusdict_jobtimeout(ee) register struct execution_environment *ee; { set_fixed_object(ee->optop, fracti(timeout_value)); ee->optop += 1; } static statusdict_setjobtimeout(ee) register struct execution_environment *ee; { register struct object *optop = ee->optop - 1; register int val; switch (optop->type) { case fixed_type: val = roundfr(optop->value.fixed); break; case real_type: val = roundfr(fractf(optop->value.real)); break; case null_type: val = DEFAULT_TIMEOUT; break; default: ee->error_code = typecheck_error_code; return; } if (val < 0) { ee->error_code = rangecheck_error_code; return; } timeout_value = val; ee->optop--; } initialize_scheduler() { collapse_motion = 1; io_ready = 1; skip_select_limit = SELECT_SKIPS; damage_keyword = get_name("Damaged", -1); max_fds = getdtablesize(); gettimeofday(&startup_time, 0); timeout_value = DEFAULT_TIMEOUT; define_operator("statusdict_jobtimeout", statusdict_jobtimeout, 0, 0, 1); define_operator("statusdict_setjobtimeout", statusdict_setjobtimeout, 0, 1, 0); }