# HG changeset patch # User Edouard Tisserant # Date 1598951748 -7200 # Node ID f9d494d113394c6247ffcf8d400bfbd8b08de1fc # Parent 4ac68ec9786f0c0e8c06f092f47ca4f274f8dce6# Parent 4dd67aa45855de05717c8065021a70c1b87acd63 Merge diff -r 4ac68ec9786f -r f9d494d11339 modbus/mb_runtime.c --- a/modbus/mb_runtime.c Mon Aug 31 13:54:08 2020 +0200 +++ b/modbus/mb_runtime.c Tue Sep 01 11:15:48 2020 +0200 @@ -28,6 +28,7 @@ #include #include #include +#include /* required for pause() */ #include "mb_slave_and_master.h" #include "MB_%(locstr)s.h" @@ -328,7 +329,7 @@ /* struct timespec cur_time; clock_gettime(CLOCK_MONOTONIC, &cur_time); - fprintf(stderr, "Modbus client thread - new cycle (%%ld:%%ld)!\n", cur_time.tv_sec, cur_time.tv_nsec); + fprintf(stderr, "Modbus client thread (%%d) - new cycle (%%ld:%%ld)!\n", client_node_id, cur_time.tv_sec, cur_time.tv_nsec); */ int req; for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){ @@ -345,8 +346,10 @@ if ((client_requests[req].flag_exec_req == 0) && (client_nodes[client_requests[req].client_node_id].periodic_act == 0)) continue; - //fprintf(stderr, "Modbus plugin: RUNNING<###> of Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n", - // req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req ); + /* + fprintf(stderr, "Modbus client thread (%%d): RUNNING Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n", + client_node_id, req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req ); + */ int res_tmp = __execute_mb_request(req); switch (res_tmp) { @@ -433,102 +436,76 @@ -/* Function to activate a client node's thread */ -/* returns -1 if it could not send the signal */ -static int __signal_client_thread(int client_node_id) { - /* We TRY to signal the client thread. - * We do this because this function can be called at the end of the PLC scan cycle - * and we don't want it to block at that time. - */ - if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) != 0) - return -1; - client_nodes[client_node_id].execute_req = 1; // tell the thread to execute - pthread_cond_signal (&(client_nodes[client_node_id].condv)); - pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); - return 0; -} - - - -/* Function that will be called whenever a client node's periodic timer expires. */ -/* The client node's thread will be waiting on a condition variable, so this function simply signals that - * condition variable. + + +/* Thread that simply implements a periodic 'timer', + * i.e. periodically sends signal to the thread running __mb_client_thread() * - * The same callback function is called by the timers of all client nodes. The id of the client node - * in question will be passed as a parameter to the call back function. + * Note that we do not use a posix timer (timer_create() ) because there doesn't seem to be a way + * of having the timer notify the thread that is portable across Xenomai and POSIX. + * - SIGEV_THREAD : not supported by Xenomai + * - SIGEV_THREAD_ID : Linux specific (i.e. non POSIX) + * Even so, I did not get it to work under Linux (issues with the header files) + * - SIGEV_SIGNAL : Will not work, as signal is sent to random thread in process! */ -void __client_node_timer_callback_function(int client_node_id) { - /* signal the client node's condition variable on which the client node's thread should be waiting... */ - /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */ - if (pthread_mutex_trylock (&(client_nodes[client_node_id].mutex)) != 0) - /* we never get to signal the thread for activation. But that is OK. - * If it still in the communication cycle (during which the mutex is kept locked) - * then that means that the communication cycle is falling behing in the periodic - * communication cycle, and we therefore need to skip a period. +static void *__mb_client_timer_thread(void *_index) { + int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast) + struct timespec next_cycle; + + int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */ + int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */ + + // Enable thread cancelation. Enabled is default, but set it anyway to be safe. + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + if (client_nodes[client_node_id].comm_period <= 0) { + // No periodic activation required => nothing to do! + while (1) pause(); // wait to be canceled when program terminates (shutdown() is called) + return NULL; // not really necessary, just makes it easier to understand the code. + } + + // get the current time + clock_gettime(CLOCK_MONOTONIC, &next_cycle); + + while(1) { + // Determine absolute time instant for starting the next cycle + struct timespec prev_cycle, now; + prev_cycle = next_cycle; + timespec_add(next_cycle, period_sec, period_nsec); + + /* NOTE: + * It is probably un-necessary to check for overflow of timer! + * Even in 32 bit systems this will take at least 68 years since the computer booted + * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0 + * every time the system boots). On 64 bit systems, it will take over + * 10^11 years to overflow. */ - return; - client_nodes[client_node_id].execute_req = 1; // tell the thread to execute - client_nodes[client_node_id].periodic_act = 1; // tell the thread the activation was done by periodic timer - pthread_cond_signal (&(client_nodes[client_node_id].condv)); - pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); -} - - - -static int stop_mb_client_timer_thread; -static void *__mb_client_timer_thread(void *_index) { - sigset_t set; - int signum; - sigemptyset(&set); - sigaddset(&set, SIGALRM); - - int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast) - /* initialize the timer that will be used to periodically activate the client node */ - { - // start off by reseting the flag that will be set whenever the timer expires - client_nodes[client_node_id].periodic_act = 0; - - if (timer_create(CLOCK_REALTIME, NULL, &(client_nodes[client_node_id].timer_id)) < 0) { - fprintf(stderr, "Modbus plugin: Error (%%s) creating timer for modbus client node %%s\n", strerror(errno), client_nodes[client_node_id].location); - return NULL; + clock_gettime(CLOCK_MONOTONIC, &now); + if (next_cycle.tv_sec < prev_cycle.tv_sec) { + /* Timer overflow. See NOTE B above */ + next_cycle = now; + timespec_add(next_cycle, period_sec, period_nsec); + } + + while (0 != clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL)); + + /* signal the client node's condition variable on which the client node's thread should be waiting... */ + /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */ + if (pthread_mutex_trylock (&(client_nodes[client_node_id].mutex)) == 0) { + client_nodes[client_node_id].execute_req = 1; // tell the thread to execute + client_nodes[client_node_id].periodic_act = 1; // tell the thread the activation was done by periodic timer + pthread_cond_signal (&(client_nodes[client_node_id].condv)); + pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); + } else { + /* We never get to signal the thread for activation. But that is OK. + * If it still in the communication cycle (during which the mutex is kept locked) + * then that means that the communication cycle is falling behing in the periodic + * communication cycle, and we therefore need to skip a period. + */ } } - int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */ - int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */ - - // configure the timer for periodic activation - { - struct itimerspec timerspec; - timerspec.it_interval.tv_sec = period_sec; - timerspec.it_interval.tv_nsec = period_nsec; - timerspec.it_value = timerspec.it_interval; - - if (timer_settime(client_nodes[client_node_id].timer_id, 0 /* flags */, &timerspec, NULL) < 0) - fprintf(stderr, "Modbus plugin: Error configuring periodic activation timer for Modbus client %%s.\n", client_nodes[client_node_id].location); - } - - stop_mb_client_timer_thread = 0; - while(!stop_mb_client_timer_thread) { - if(sigwait (&set, &signum) == -1) - perror ("sigwait"); - - if(stop_mb_client_timer_thread) - break; - - if(signum == SIGALRM) - __client_node_timer_callback_function(client_node_id); - else - fprintf(stderr, "Modbus plugin: spurious wakeup of timer thread for Modbus client %%s.\n", client_nodes[client_node_id].location); - - } - - // timer was created, so we try to destroy it! - int res = timer_delete(client_nodes[client_node_id].timer_id); - if (res < 0) - fprintf(stderr, "Modbus plugin: Error destroying timer for modbus client node %%s\n", client_nodes[client_node_id].location); - - return NULL; + return NULL; // humour the compiler -> will never be executed! } @@ -740,13 +717,26 @@ */ if ((client_requests[index].flag_exec_req != 0) && (0 == client_requests[index].flag_exec_started)) { int client_node_id = client_requests[index].client_node_id; - if (__signal_client_thread(client_node_id) >= 0) { - /* - upon success, set flag_exec_started - * - both flags (flag_exec_req and flag_exec_started) will be reset - * once the transaction has completed. - */ - client_requests[index].flag_exec_started = 1; - } + + /* We TRY to signal the client thread. + * We do this because this function can be called at the end of the PLC scan cycle + * and we don't want it to block at that time. + */ + if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) == 0) { + client_nodes[client_node_id].execute_req = 1; // tell the thread to execute + pthread_cond_signal (&(client_nodes[client_node_id].condv)); + pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); + /* - upon success, set flag_exec_started + * - both flags (flag_exec_req and flag_exec_started) will be reset + * once the transaction has completed. + */ + client_requests[index].flag_exec_started = 1; + } else { + /* The mutex is locked => the client thread is currently executing MB transactions. + * We will try to activate it in the next PLC cycle... + * For now, do nothing. + */ + } } } } @@ -794,8 +784,8 @@ close = 0; if (client_nodes[index].init_state >= 4) { - stop_mb_client_timer_thread = 1; - pthread_kill(client_nodes[index].timer_thread_id, SIGALRM); + // timer thread was launched, so we try to cancel it! + close = pthread_cancel(client_nodes[index].timer_thread_id); close |= pthread_join (client_nodes[index].timer_thread_id, NULL); if (close < 0) fprintf(stderr, "Modbus plugin: Error closing timer thread for modbus client node %%s\n", client_nodes[index].location); @@ -835,6 +825,7 @@ client_nodes[index].init_state = 0; } +//fprintf(stderr, "Modbus plugin: __cleanup_%%s() 5 close=%%d res=%%d\n", client_nodes[index].location, close, res); /* kill thread and close connections of each modbus server node */ for (index=0; index < NUMBER_OF_SERVER_NODES; index++) { close = 0; diff -r 4ac68ec9786f -r f9d494d11339 modbus/mb_runtime.h --- a/modbus/mb_runtime.h Mon Aug 31 13:54:08 2020 +0200 +++ b/modbus/mb_runtime.h Tue Sep 01 11:15:48 2020 +0200 @@ -107,7 +107,6 @@ int prev_error; // error code of the last printed error message (0 when no error) pthread_t thread_id; // thread handling all communication for this client node pthread_t timer_thread_id; // thread handling periodical timer for this client node - timer_t timer_id; // timer used to periodically activate this client node's thread pthread_mutex_t mutex; // mutex to be used with the following condition variable pthread_cond_t condv; // used to signal the client thread when to start new modbus transactions int execute_req; /* used, in association with condition variable,