Tutorial >> More Plugins | TOC |
The pdelay plugin shows how a plugin can queue a packet for a fixed period of time (its delay) and then forward the packet when its delay has expired. The forwarding decision is made when a new packet arrives (and pdelay_handle_packet is called), and when a scheduled callback is made (and pdelay_callback is called). The periodic callback is registered with the kernel by the plugin as part of its initialization and is called after the SPC's hardclock routine runs when its period has expired.
Several library functions and macros are used to deal with the queue, time, interrupts, callbacks and packet forwarding:
Although the delay behavior is relatively simple, the developer must deal with several details of kernel programming. First, the plugin must interact with the SPC's hardclock routine. Second, it must ensure that the use of kernel resources are synchronized properly.
The SPC's hardclock routine is called at a fixed frequency to perform periodic services. The default period is 500 microseconds. The pdelay plugin registers the pdelay_callback function with the hardclock routine during plugin initialization.
The plugin works with two queues: 1) the queue of incoming packets (usually one packet), and 2) the queue holding delayed packets. Packets are placed on a queue by the pdelay_handle_packet function and are dequeued by both the pdelay_handle_packet and the pdelay_callback functions. In order to ensure consistent, uninterrupted behavior, these two routines disable all interrupts using the PLUGIN_SPLCLOCK_FCT function while they work and then reenable the interrupts using the PLUGIN_SPLX_FCT function right before they return.
The queue processing logic looks like this (pseudo-code form):
pdelay_init_class( ): // called when plugin is loaded Set the callback period to cbPeriod; global_pdelay_inst_ptr = NULL; Register pdelay_callback with kernel; pdelay_create_instance( ): Disable interrupts; Initialize instance variables; global_pdelay_inst_ptr = myinst; Enable interrupts; pdelay_fwdpkts(cur_time, myinst, caller): // fwd expired delays while (first queued buffer has an expired delay) { Remove first buffer from delay queue and forward packet; } pdelay_handle_packet(this, bufferList): // handle incoming pkts Disable interrupts; Remove buffer from bufferList and insert into delay queue; Forward packets with expired delays; Enable interrupts; pdelay_callback( ): // run every cbPeriod/500 clock ticks if (global_pdelay_inst_ptr != NULL) { if (there is no plugin instance) return; Disable interrupts; Forward packets with expired delays; Enable interrupts; }In the above pseudo-code, I have removed all logic having to do with packet accounting and debugging.
The queue processing logic has the following features:
The pdelay.h code below shows the key constants and the instance state structure and omits the boiler plate code which includes function prototypes:
#define DELAY_TIME 50 // Initial delay in msec static int cbPeriod = 500; // callback period in usec (500 usec) struct pdelay_instance { struct rp_instance rootinstance; HDRQ_t qhead; // addr of 1st pkt buffer in delay queue int qlen; // #pkts in delay queue int pkt_count; // #pkts handled by instance int drop_count; // #pkts dropped by instance int fwd_count; // #pkts forwarded by instance int max_qlen; // maximum queue length int earliest_depart_time; // earliest time nxt pkt should be sent (msec) int delay_time; // amount to delay (msec) };The default delay (DELAY_TIME) is 50 msec, and the default callback period (cbPeriod) is 500 usec. Most of the instance variables are for packet accounting and will not be discussed. qhead is the address of the first packet buffer and is NULL when the queue is empty. When the queue is non-empty, earliest_depart_time is the expiration time of the first packet buffer; i.e., when that packet should be forwarded. The time is derived by adding the delay to the arrival time of the packet; i.e., the time when pdelay_handle_packet gets the packet on its packet list. delay_time is the current plugin delay and is initialized to DELAY_TIME. A user can change the delay by sending the plugin a message of type 2 through the RLI.
1 void pdelay_handle_packet( 2 struct rp_instance *this, // points to instance structure 3 void *bufferList) // points to list of pkt buffers 4 { 5 struct pdelay_instance *inst = (struct pdelay_instance *)this; 6 u_int32_t curr_time; // Current time in ms 7 struct msr_bufhdr_t *buffer; // incoming pkt buffer 8 int s; 9 10 s = PLUGIN_SPLCLOCK_FCT(); 11 buffer = msr_firstBuffer(bufferList); 12 msr_removeBuffer(bufferList, buffer); // rm pkt from pkt list 13 inst->pkt_count++; 14 15 curr_time = PLUGIN_CPU_CLOCK_1MSEC_FCT(); 16 buffer->time_fwd = curr_time + inst->delay_time; // when pkt should go out 17 18 msr_addBuffer(&inst->qhead, buffer); // queue pkt 19 inst->qlen++; 20 if (inst->max_qlen < inst->qlen) inst->max_qlen = inst->qlen; 21 if (inst->qlen == 1) { 22 inst->earliest_depart_time = curr_time + inst->delay_time; 23 } 24 25 pdelay_fwdpkts (curr_time, inst, 0); 26 PLUGIN_SPLX_FCT(s); 27 return; 28 }The pdelay_handle_packet code tour:
The pdelay_fwdpkt function is called from pdelay_handle_packet and pdelay_callback to forward packets whose delay has expired. It should run only when interrupts have been disabled to prevent either an arriving packet or callback from modifying the delay queue while it is running.
1 void pdelay_fwdpkts ( 2 u_int32_t curr_time, // 3 struct pdelay_instance *myinst, // instance addr 4 int caller // 0: called by pdelay_handle_packet 5 // 1: called by pdelay_callback 6 ) { 7 struct msr_bufhdr_t *buffer; // pkt buffer 8 9 while ( (myinst->qlen > 0) && (curr_time >= myinst->earliest_depart_time) ) { 10 buffer = msr_firstBuffer(&myinst->qhead); 11 msr_removeBuffer(&myinst->qhead, buffer); 12 if (PLUGIN_IP_FWD_FCT(buffer) == 0) myinst->fwd_count++; 13 myinst->qlen--; 14 if (myinst->qlen > 0) { // check next packet 15 buffer = msr_firstBuffer(&myinst->qhead); 16 myinst->earliest_depart_time = buffer->time_fwd; 17 } 18 } 19 }The pdelay_fwdpkt code tour:
The pdelay_callback function is similar to the pdelay_handle_packet function in that it will forward packets with expired delays. But it is different in that it is called periodically rather than as a result of an arriving packet.
1 void pdelay_callback(void) { 2 struct pdelay_instance *myinst; 3 u_int32_t curr_time; 4 int s; 5 6 if (global_pdelay_inst_ptr == NULL) return; 7 myinst = (struct pdelay_instance *) global_pdelay_inst_ptr; 8 if (myinst == NULL) return; 9 10 if (myinst->qlen > 0) { // Get current time; check for expired delays 11 s = PLUGIN_SPLCLOCK_FCT(); // Block clock interrupts 12 curr_time = PLUGIN_CPU_CLOCK_1MSEC_FCT(); 13 pdelay_fwdpkts (1, curr_time, myinst); 14 PLUGIN_SPLX_FCT(s); 15 } 16 }The pdelay_callback code tour:
The pdelay_init_class function registers the pdelay_callback function with the kernel to be called every cbPeriod usec. cbPeriod should be an integer multiple of the hardclock period (500 usec). Alternatively, this initialization could have been done in pdelay_create_instance since the callback structure can only support one instance in each class at the moment.
1 void pdelay_init_class (void) 2 { 3 int poll_freq = cbPeriod; // callback period 4 int ticks = PLUGIN_MSR_USEC2TICKS_FCT(poll_freq); 5 6 pdelay_class.classid = PDELAY_ID; 7 pdelay_class.itype = RP_INTERFACE_TYPE_HLIST; 8 pdelay_class.create_instance = pdelay_create_instance; 9 10 global_pdelay_inst_ptr = NULL; 11 12 PLUGIN_MSR_CLOCK_HANDLER_FCT(pdelay_callback, 13 MSR_CLOCK_HANDLER_PCU_ID, ticks); 14 }The pdelay_init_class code tour:
The pdelay_create_instance function is straightforward except that global_pdelay_inst_ptr (Line 23) must not be set until the instance variables have been initialized since a NULL value prevents the callback function from taking affect.
1 struct rp_instance * pdelay_create_instance ( 2 struct rp_class *myclass, 3 u_int32_t instanceid 4 ) { 5 struct pdelay_instance *myinst; 6 int s; 7 8 MSR_PLUGIN_MALLOC(myinst, struct pdelay_instance *, 9 sizeof(struct pdelay_instance), M_MSR, M_WAITOK); 10 if (myinst == NULL) return NULL; 11 12 ... standard instance initialization ... 13 14 msr_initBuffer(&myinst->qhead); // Instance specific processing 15 myinst->qlen = 0; 16 myinst->pkt_count = 0; 17 myinst->drop_count = 0; 18 myinst->fwd_count = 0; 19 myinst->max_qlen = 0; 20 myinst->delay_time = DELAY_TIME; 21 myinst->earliest_depart_time = 0; 22 23 global_pdelay_inst_ptr = (struct rp_instance *) myinst; 24 25 return (struct rp_instance *)myinst; 26 }
Tutorial >> More Plugins | TOC |