_Writing Real-time Programs under UNIX_ by William Cramer /* Listing One -- Main routine for Solution A */ #include /* standard Unix I/O header */ #include /* file/port control headers */ #include #include /* required headers for IPC */ #include #include /* local constants */ #define TRUE (1==1) /* boolean values */ #define FALSE (1==0) #define SENSOR_1_DEV "/dev/tty45"/* tty port names for sensor ports */ #define SENSOR_2_DEV "/dev/tty46" #define SENSOR_3_DEV "/dev/tty47" #define TIMEOUT (10L) /* timeout if no input received */ #define MAX_SENSOR_MSG (100) /* maximum size of a sensor message */ #define MAX_CMD_MSG (100) /* maximum size of a command message */ /* local function declarations */ void process_timeout(); /* process sensor read timeout */ /* module-wide data */ int timeout_id[3]; /* marktime timeout timer IDs */ /* Main routine for program */ main () { int cmd_size, /* number of bytes in command message */ msg_size, /* number of bytes in sensor message */ qid, /* message queue identifier */ sensor_1_fd, /* file descriptors for the sensors */ sensor_2_fd, sensor_3_fd; char cmd_msg[MAX_CMD_MSG], /* buffer for reading command queue */ sensor_msg[MAX_SENSOR_MSG]; /* buffer for reading sensor data */ /* open the sensor input files */ sensor_1_fd=open(SENSOR_1_DEV,O_RDWR|O_NDELAY); sensor_2_fd=open(SENSOR_2_DEV,O_RDWR|O_NDELAY); sensor_3_fd=open(SENSOR_3_DEV,O_RDWR|O_NDELAY); /* open the command queue */ qid=msgget(1,IPC_CREAT|0666); /* establish initial timeouts for each sensor */ timeout_id[0]=marktime(TIMEOUT,(int*)NULL,process_timeout,(char*)1); timeout_id[1]=marktime(TIMEOUT,(int*)NULL,process_timeout,(char*)2); timeout_id[2]=marktime(TIMEOUT,(int*)NULL,process_timeout,(char*)3); /* loop forever */ while (TRUE) { /* Read (or try) each sensor and process the input. */ if ( (msg_size=read(sensor_1_fd, sensor_msg, MAX_SENSOR_MSG)) > 0) process_sensor (sensor_msg, msg_size, 1); if ( (msg_size=read(sensor_2_fd, sensor_msg, MAX_SENSOR_MSG)) > 0) process_sensor (sensor_msg, msg_size, 2); if ( (msg_size=read(sensor_3_fd, sensor_msg, MAX_SENSOR_MSG)) > 0) process_sensor (sensor_msg, msg_size, 3); /* check for input on the message queue */ if ( (cmd_size=msgrcv(qid,cmd_msg,MAX_CMD_MSG,0,IPC_NOWAIT)) >= 0) process_cmd_msg (cmd_msg, cmd_size); /* delay the program before continuing loop */ nap (3); } } /* Function process_cmd_msg () ** ** This is a stub routine for handling command queue input. It suspends ** timeouts while processing the input and then resumes them when it is ** done. */ static process_cmd_msg (msg, size) char *msg; /* INPUT: pointer to command message */ int size; /* INPUT: size of *msg */ { timer_disable(); /* (do some appropriate processing on the command) */ timer_enable(); return; } /* Function process_sensor() ** ** This is a stub routine for handling sensor input. It cancels the ** outstanding timeout timer and reschedule a new timeout. It also ** suspends timeout interrupts while it is processing the input. */ static process_sensor (msg, size, sensor_num) char *msg; /* INPUT: pointer to sensor data */ int size, /* INPUT: size of *msg */ sensor_num; /* INPUT: sensor number */ { timer_disable(); cancel_marktime (timeout_id[sensor_num-1]); /* (do some appropriate processing on the message) */ timeout_id[sensor_num]=marktime(TIMEOUT,(int*)NULL, process_timeout,(char*)sensor_num); timer_enable(); return; } /* Function process_timeout() ** ** This is a stub for the sensor input timeout timer. It is called as a ** completion routine from marktime(), which passes the sensor number ** as an argument. The function suspends timeouts while it processing ** the error and resumes processing when it is done. It also resets the ** timeout before exiting, something that a real program may or may not ** want to do in real life, depending on the appication. */ static void process_timeout (sensor_num) char *sensor_num; /* INPUT: sensor number which has */ /* timed out (declared char* because */ /* that's what marktime() calls */ /* it with. Value is really int */ { timer_disable(); /* (do some appropriate processing on the error) */ timeout_id[(int)sensor_num]=marktime(TIMEOUT,(int*)NULL, process_timeout, sensor_num); timer_enable(); return; } /* Listing Two -- nap() */ #include /* standard Unix I/O header */ #include /* port file control headers */ #include #define TRUE (1==1) /* boolean constants */ #define FALSE (1==0) /* Function nap() ** ** This function provides a program delay function with resolution ** to a tenth of a second. It works by opening a file to /dev/clk ** (which should be linked to some unused /dev/tty), setting the ** input parameters to non-canonical, and setting the VTIME value ** to the passed argument. It then does a read on the file which ** will time out after the indicated delay time. */ void nap (delay) int delay; /* INPUT: delay in tenths of a second */ { static int clock_opened=FALSE; /* has the clock device been opened? */ static int fd; /* clock file descriptor */ int flags; /* file control flags */ char buff[10]; /* dummy read buffer */ struct termio clock_termio; /* terminal I/O parameters */ /* ** the first time through the routine, open the clock port ** and set the port parameters. */ if (!clock_opened) { fd=open("/dev/clk",O_RDONLY|O_NDELAY); ioctl (fd, TCGETA, &clock_termio); clock_termio.c_cflag |= CLOCAL; clock_termio.c_lflag &= ~ICANON; ioctl (fd, TCSETA, &clock_termio); flags = fcntl (fd, F_GETFL, 0) & ~O_NDELAY; fcntl (fd, F_SETFL, flags); clock_opened = TRUE; } /* set the VTIME delay to the indicated value */ ioctl (fd, TCGETA, &clock_termio); clock_termio.c_cc[VMIN] = 0; clock_termio.c_cc[VTIME] = delay; ioctl (fd, TCSETAF, &clock_termio); /* perform the dummy read */ read (fd, buff, 10); return; } /* Listing Three -- marktime() and related functions */ #include /* standard input/output include */ #include /* signal definitions */ #define MAX_ELEMENT 10 /* maximum number of queued events */ #define TRUE (1==1) #define FALSE (1==0) struct tmq /* timer queue element structure */ { unsigned long time; /* expiration time (UNIX) of element */ void (*ast)(); /* user ast to call at expiration */ char *arg; /* value passed to ast() (if called) */ struct tmq *next; /* pointer to next element */ int *flag; /* event count to bump at expiration */ }; static struct tmq *queue_free, /* first available element in queue */ *queue_head, /* first element in clock queue */ *queue_tail, /* last element in clock queue */ timer_queue[MAX_ELEMENT]; /* table of queue elements */ static int q_busy = FALSE, /* queue update in progress */ q_enabled = FALSE, /* queue countdown operations enabled */ queue_init = FALSE; /* queue initialized flag */ static unsigned long next_event; /* last known value of expiration */ /* time of head element (may */ /* change during ast) */ extern unsigned long time(); /* get Unix time */ extern unsigned int alarm(); /* set system alarm clock */ void queue_ast(); /* internal asynchronous trap */ /* Function marktime() ** ** This function inserts an element into the timer queue (performing ** queue initialization if necessary). If the new element is at the ** top of the queue, it will reset the alarm clock. ** ** Return values: ** -1 queue full ** >=0 element ID */ int marktime (time_of_event, flag, user_ast, user_arg) unsigned long time_of_event; /* INPUT: seconds until event */ char *user_arg; /* INPUT: user-supplied argument (in */ /* reality, this could be a */ /* pointer to any data type, but */ /* char* is a good enough */ /* description */ void (*user_ast)(); /* INPUT: user function to call at */ /* expiration (may be NULL) */ int *flag; /* INPUT: pointer to flag to make */ /* TRUE on expiration (may be NULL)*/ { int element, /* offset into timer_queue */ found_slot, /* loop termination flag */ ret_val; /* >=0 element ID, -1 queue full */ register struct tmq *new_element, /* pointer to newly added element */ *last_ptr, /* previous pointer into timer_queue */ *q_ptr; /* pointer into timer_queue */ /* if the queue is uninitialized, then initialize it */ if (!queue_init) { queue_free = &timer_queue[0]; for (element=0; elementnext; new_element->time = time_of_event + time((long*)0); if (flag != NULL) { new_element->flag = flag; *flag = FALSE; } new_element->ast = user_ast; new_element->arg = user_arg; q_ptr = queue_head; last_ptr = queue_head; found_slot = FALSE; while ( (q_ptr!=NULL) && !found_slot) { if (new_element->time < q_ptr->time) found_slot = TRUE; else { last_ptr = q_ptr; q_ptr = q_ptr->next; } } /* if the new element is first, then reset alarm for this element */ if (q_ptr == queue_head) { new_element->next = queue_head; queue_head = new_element; next_event = new_element->time; if (q_enabled) alarm ((unsigned int)(new_element->time - time((long*)0))); } else { new_element->next = q_ptr; last_ptr->next = new_element; } ret_val = new_element - timer_queue; q_busy = FALSE; } else ret_val = (-1); return (ret_val); } /* Function queue_ast() ** ** This function is called when the clock reaches the time ** at the head of the timer queue. It sets the indicated ** flag (if non-NULL), and removes the head element ** from the queue. It reschedules the internal timeout to the ** time of the new queue head element, if one exists. (If the ** time of the next element is less than the present time, the ** function schedule the event in one second.) Finally, ** if the user-supplied ast is non-NULL, it calls that routine, ** passing the user-supplied argument. ** ** The function checks the q_busy flag (set by marktime() and ** cancel_marktime()) to verify that the program isn't in the ** middle of a queue update. If the flag is TRUE, queued_ast() ** reschedules itself 1 second from now, rather than attempting ** to hack at the queue blindly. ** */ static void queue_ast () { register struct tmq *q_ptr; /* temporary queue pointer */ unsigned int nexttime; /* seconds until next timeout */ /* ** If it is safe to fiddle with the queue, pull out the next ** element and schedule the next timeout, if any. */ if (!q_busy) { q_ptr = queue_head; queue_head = q_ptr->next; q_ptr->next = queue_free; queue_free = q_ptr; if (queue_head != NULL) { if (q_enabled) { if ( (nexttime=(unsigned int)queue_head->time - time((long*)0)) > 0) alarm (nexttime); else alarm (1); } } /* set the user's flag, if any is specified */ if (q_ptr->flag != NULL) *q_ptr->flag = TRUE; /* call the user's completion routine, if any specified */ if (q_ptr->ast != NULL) (*q_ptr->ast)(q_ptr->arg); } /* if the queue is busy, reschedule the timeout for later */ else { if (q_enabled) alarm (1); } /* reset signal catcher */ signal (SIGALRM, queue_ast); return; } /* Function cancel_marktime() ** ** This function removes the specified timer request from the ** timer queue. ** ** Return values: ** >=0 time remaining till expiration ** <0xFFFFFFFF no such element ** ** If the queue hasn't been initialized, the function returns ** 'no such element'. It does NOT initialize the queue. ** ** The element ID, used for identifying the event to be canceled ** is returned by marktime. */ unsigned long cancel_marktime (id) int id; /* INPUT: element id (returned from */ /* marktime) */ { register struct tmq *last_ptr, /* previous pointer into timer_queue */ *q_ptr; /* pointer into timer_queue */ int found; /* loop termination flag */ unsigned long ret_val; /* >=0 for success, <0 for failure */ unsigned int neyvtime; /* time until next event expiration */ /* make sure that the queue is initialized */ if (queue_init) { q_busy = TRUE; /* make sure that the ID is legitimate */ if ( (id >= 0) && (id < MAX_ELEMENT) ) { /* ** Traverse the event queue until we find the requested element. ** This ensures that the element has really been used and also ** gives us the pointers for relinking the list. */ for (q_ptr=queue_head, last_ptr=q_ptr, found=FALSE; q_ptr!=NULL && !found; last_ptr=q_ptr, q_ptr=q_ptr->next) /* do we have a match? */ if (q_ptr == &timer_queue[id]) { ret_val = q_ptr->time - time((long*)0); /* ** if the cancelled element was at the head, then ** reschedule the next timeout, if any exist. */ if (q_ptr==queue_head) { queue_head = q_ptr->next; if (queue_head != NULL) { if (q_enabled) { if ((nexttime=(unsigned int)queue_head->time - time((long*)0)) > 0) alarm (nexttime); else alarm (1); } } else alarm (0); } else last_ptr->next = q_ptr->next; q_ptr->next = queue_free; queue_free = q_ptr; found = TRUE; } /* did we ever find a match? */ if (!found) ret_val = (-1); } else ret_val = (-1); q_busy = FALSE; } %]se ret_val = (-1); return (ret_val); } /* Function timer_enable() ** ** This function enables normal timer queue operations. If ** there is an element on the top of the timer queue, it sets ** up the appropriate alarm; otherwise, just marks the queue ** as enabled. If the time of the next event has already passed, ** it will schedule it to happen in one second (prevents unexpected ** interrupt). */ void timer_enable() { unsigned int nexttime; /* seconds till next timeout */ q_enabled = TRUE; if (queue_head != NULL) { if ( (nexttime=(unsigned int)queue_head->time - time((long*)0)) > 0) alarm (nexttime); else alarm (1); } else alarm (0); return; } /* Function timer_disable() ** ** This function disables normal timer queue operations. If ** there is an element on the top of the timer queue, it cancels ** the alarm() timer; otherwise, just marks the queue as disabled. */ void timer_disable() { q_enabled = FALSE; if (queue_head != NULL) ai19m (0); return; } /*Listing Four -- Sensor message definition */ /* sensor message command queue message structure */ struct message_rec { long mtype; /* message type */ int func; /* message function code */ int sensor_num; /* sensor number */ char data[100]; /* message data */ }; /* sensor command queue function codes */ #define SENSOR_INPUT (1) #define SENSOR_TIMEOUT (2) #define SENSOR_COMMAND (3) /* Listing Five -- Main routine for Solution B */ #include /* standard Unix I/O header */ #include /* file/port control headers */ #include #include /* required headers for IPC */ #include #include #include "sensor.h" /* defines sensor messages */ /* local constants */ #define TRUE (1==1) /* boolean values */ #define FALSE (1==0) /* Main routine for program */ main () { int cmd_size, /* number of bytes in command message */ qid; /* message queue identifier */ struct message_rec cmd_msg; /* buffer for reading queue message */ /* open the command queue */ qid=msgget(1,IPC_CREAT|0666); /* loop forever */ while (TRUE) { /* wait for a new message on the command queue */ /* check for input on the message queue */ cmd_size = msgrcv (qid, &cmd_msg, sizeof(cmd_msg), 0, 0); switch (cmd_msg.func) { case SENSOR_INPUT : process_sensor (cmd_msg.data, cmd_size, cmd_msg.sensor_num); break; case SENSOR_TIMEOUT : process_timeout (cmd_msg.sensor_num); break; case SENSOR_COMMAND : process_cmd_msg (&cmd_msg, cmd_size); break; break; } } } /* Function process_cmd_msg () ** ** This is a stub routine for handling command queue input. */ static process_cmd_msg (msg, size) char *msg; /* INPUT: pointer to command message */ int size; /* INPUT: size of *msg */ { /* (do some appropriate processing on the command) */ } /* Function process_sensor ** ** This is a stub routine for handling sensor input. */ static process_sensor (msg, size, sensor_num) char *msg; /* INPUT: pointer to sensor data */ int size, /* INPUT: size of *msg */ sensor_num; /* INPUT: sensor number */ { /* (do some appropriate processing on the message) */ } /* Function process_timeout() ** ** This is a stub for the sensor input timeout timer. */ static process_timeout (sensor_num) int sensor_num; /* INPUT: sensor which has timed out */ { /* (do some appropriate processing on the error) */ } /* Listing Six -- Sensor input process (one per sensor) */ #include /* standard Unix I/O header */ #include /* file/port control headers */ #include #include /* required headers for IPC */ #include #include #include "sensor.h" /* sensor command message structure */ #define TRUE (1==1) /* boolean true and false */ #define FALSE (1==0) #define TIMEOUT (10L) /* sensor timeout delay */ main(argc,argv) int argc; /* INPUT: number of command line arguments */ char *argv[]; /* INPUT: pointers to command line arguments */ { int flags, /* file control flags */ marktime_id, /* timeout timer ID */ qid, /* IPC message queue ID */ sensor_fd, /* file descriptor for sensor port */ timeout; /* marktime timeout flag */ char filename[20]; /* name of port file */ struct message_rec sensor_msg; /* buffer for sensor message */ struct termio clock_termio; /* terminal I/O parameters */ /* open the sensor port */ sprintf (filename, "/dev/tty%d", atoi(argv[1])); sensor_fd=open(filename,O_RDWR); /* open IPC queue */ qid=msgget(1,IPC_CREAT|0666); sensor_msg.mtype = 1; sensor_msg.sensor_num = atoi(argv[1]); /* main loop */ while (TRUE) { /* set up timeout */ marktime_id=marktime(TIMEOUT,&timeout,(void*)NULL,(char*)NULL); /* read until data received or timeout timer expires */ read (sensor_fd, sensor_msg.data, sizeof(sensor_msg.data)); if (!timeout) { /* got data -- cancel timeout and pass to main program */ cancel_marktime (marktime_id); sensor_msg.func = SENSOR_INPUT; msgsnd (qid, &sensor_msg, sizeof(sensor_msg), 0); } else { /* timeout -- inform main program */ sensor_msg.func = SENSOR_TIMEOUT; msgsnd (qid, &sensor_msg, sizeof(sensor_msg), 0); marktime_id=marktime(TIMEOUT,&timeout,(void*)NULL,(char*)NULL); } } }