memsem.cc
1 /******************************************************************** 2 * Description: memsem.cc 3 * Uses a block of memory to implement a mutual exclusion semaphore. 4 * With LynxOs and SunOs using semop is very inefficient if the semaphore 5 * will usually be available. Other platforms may give you no semaphore 6 * operations. 7 * 8 * Example: You are communicating with another process via shared memory. 9 * You need a mutual exclusion semaphore because you don't want the reader 10 * to occasionally read the buffer while the writer has written only part 11 * of the message, but most of the time when the writer won't be using 12 * buffer and the reader should get access immediately. semop will take 13 * 100 to 150 microseconds to return access, while mem_get_access should 14 * take less than a microsecond with less than 10 total_connections. 15 * 16 * Derived from a work by Fred Proctor & Will Shackleford 17 * 18 * Author: 19 * License: LGPL Version 2 20 * System: Linux 21 * 22 * Copyright (c) 2004 All rights reserved. 23 * 24 * Last change: 25 ********************************************************************/ 26 27 #ifdef __cplusplus 28 extern "C" { 29 #endif 30 31 #include <stddef.h> /* NULL */ 32 33 #ifdef __cplusplus 34 }; 35 #endif 36 37 #include "timer.hh" /* etime(), esleep() */ 38 #include "memsem.hh" /* struct mem_access_object */ 39 #include "rcs_print.hh" // rcs_print_error() 40 41 #define TIMEOUT_MIN ((double) 1.0E-6) 42 43 /****************************************************************** 44 * Take the mutual exclusion semaphore. 45 * 46 * Parameters: 47 * data should point to an area of memory accesable to all processes that 48 * is at least "total_connections" bytes long, 49 * connection_number is a unique identifier for this process it should 50 * be between 0 and ("total_connections"-1) 51 * total_connections is the maximum number of processes that can use this 52 * block of memory as a semaphore. 53 * timeout - is the time allowed for getting the semaphore. 54 * (If timeout is negative then wait forever.) 55 * semdelay - is the time to wait between tries for the semaphore. 56 * read_only - Will the access to the buffer only to read the memory. 57 * split_buffer - The buffer will be split so that one area may be read 58 * while the other area is written to. 59 * toggle_bit - If the buffer is split the toggle bit determines which area 60 * will be read from and which may be written to. (O.K. its really a byte but 61 * think of it as 1 bit since it should only be 0 or 1) 62 * 63 * Returns: 0 for success: -1 for invalid parameters: or -2 if it timed out. 64 ***********************************************************************/ 65 /*! \todo Another #if 0 */ 66 #if 0 67 int mem_get_access(void *data, long connection_number, 68 long total_connections, double timeout, double semdelay, 69 int read_only, int split_buffer, char &toggle_bit) 70 #endif 71 int mem_get_access(struct mem_access_object *mo) 72 { 73 char *mylock; 74 char current_lock; 75 char *plock; 76 char *lastlock; 77 int semaphores_clear; 78 double start_time, time; 79 int split_buffer = 0; 80 int read_only; 81 int total_connections; 82 int connection_number; 83 double timeout; 84 #ifdef DEBUG 85 rcs_print("mem_get_access: - Time = %lf\n", etime()); 86 static int count = 0; 87 count++; 88 #endif 89 90 /* Check Parameters. */ 91 if ((total_connections = mo->total_connections) <= 92 (connection_number = mo->connection_number) || connection_number < 0) 93 { 94 return -1; 95 } 96 if (NULL == mo->data) { 97 return -1; 98 } 99 100 /* Check for a request for me to sleep */ 101 int wait_requested = 1; 102 lastlock = ((char *) mo->data) + total_connections; 103 mylock = ((char *) mo->data) + connection_number; 104 time = start_time = etime(); /* get start time */ 105 while (wait_requested 106 && (time - start_time < mo->timeout / 2 || mo->timeout < 0)) { 107 wait_requested = 0; 108 for (plock = (char *) mo->data; plock < lastlock; plock++) { 109 if (5 == (current_lock = *plock) && plock != mylock) { 110 wait_requested = 1; 111 } 112 } 113 if (wait_requested) { 114 esleep(mo->sem_delay); 115 } 116 } 117 118 /* Try the locks one time before checking time because checking the locks 119 generally takes much less time than checking the time. */ 120 *mylock = 4; 121 mo->toggle_bit = ((char *) mo->data)[total_connections]; 122 read_only = mo->read_only; 123 #ifdef DEBUG 124 if (read_only) { 125 rcs_print("added sleep: %d - Time = %lf\n", __LINE__, etime()); 126 esleep(0.02); 127 } 128 #endif 129 if (read_only) { 130 split_buffer = mo->split_buffer; 131 if (split_buffer) { 132 *mylock = 2 + mo->toggle_bit; 133 return 0; 134 } 135 *mylock = 2; 136 } else { 137 *mylock = 1; 138 } 139 140 #ifdef debug 141 if (read_only) { 142 rcs_print("added sleep: %d - time = %lf\n", __line__, etime()); 143 esleep(0.01); 144 } 145 #endif 146 semaphores_clear = 1; 147 lastlock = ((char *) mo->data) + total_connections; 148 mo->toggle_bit = ((char *) mo->data)[total_connections]; 149 for (plock = (char *) mo->data; plock < lastlock; plock++) { 150 if (0 != (current_lock = *plock)) { 151 if (plock != mylock) { 152 if (!(read_only && current_lock > 1) && 153 !(split_buffer && current_lock == 2 + mo->toggle_bit) 154 && (current_lock != 5)) { 155 semaphores_clear = 0; 156 } 157 } 158 } 159 } 160 #ifdef debug 161 if (0) // read_only && 0 == count % 2) 162 { 163 rcs_print("added sleep: %d - time = %lf\n", __line__, etime()); 164 esleep(0.01); 165 } 166 #endif 167 if (semaphores_clear) { 168 return 0; 169 } 170 timeout = mo->timeout; 171 if (timeout < TIMEOUT_MIN && timeout > 0) { 172 *mylock = 0; 173 return (-2); 174 } 175 /* release the lock before going to sleep. */ 176 *mylock = 5; 177 178 if (NULL != mo->sem) { 179 if (-1 == mo->sem->wait()) { 180 *mylock = 0; 181 return -1; 182 } 183 } else { 184 esleep(mo->sem_delay); /* sleep for 100 microseconds */ 185 } 186 if (read_only) { 187 *mylock = 2; 188 } else { 189 *mylock = 1; 190 } 191 192 #ifdef debug 193 if (0) // read_only && 0 == count % 7) 194 { 195 rcs_print("added sleep: %d - time = %lf\n", __line__, etime()); 196 esleep(0.01); 197 } 198 #endif 199 while ((timeout >= 0) ? ((time - start_time) < timeout) : 1) { 200 if (split_buffer) { 201 mo->toggle_bit = ((char *) mo->data)[total_connections]; 202 } 203 semaphores_clear = 1; 204 plock = (char *) mo->data; 205 mo->toggle_bit = ((char *) mo->data)[total_connections]; 206 for (; plock < lastlock; plock++) { 207 current_lock = *plock; 208 if (0 != current_lock) { 209 if (plock != mylock && 210 !(read_only && current_lock > 1) && 211 !(split_buffer && current_lock == 2 + mo->toggle_bit) 212 && (current_lock != 5)) { 213 semaphores_clear = 0; 214 } 215 } 216 } 217 if (semaphores_clear) { 218 return 0; 219 } 220 if (NULL != mo->sem) { 221 *mylock = 5; 222 mo->sem->wait(); 223 } else { 224 *mylock = 5; 225 esleep(mo->sem_delay); /* sleep for 100 microseconds */ 226 } 227 if (read_only) { 228 *mylock = 2; 229 } else { 230 *mylock = 1; 231 } 232 #ifdef DEBUG 233 if (0) // read_only && 0 == count % 4) 234 { 235 rcs_print("added sleep: %d - Time = %lf\n", __LINE__, etime()); 236 esleep(0.01); 237 } 238 #endif 239 time = etime(); 240 } 241 *mylock = 0; 242 return (-2); 243 } 244 245 /****************************************************************** 246 * Give up the mutual exclusion semaphore. 247 * 248 * Parameters: 249 * data should point to an area of memory accesable to all processes that 250 * is at least "total_connections" bytes long, 251 * connection_number is a unique identifier for this process it should 252 * be between 0 and ("total_connections"-1) 253 * 254 * Returns: 0 for success: -1 for invalid parameters 255 ***********************************************************************/ 256 int mem_release_access(struct mem_access_object *mo) 257 { 258 int i; 259 int process_waiting = 0; 260 #ifdef DEBUG 261 rcs_print("mem_release_access: - Time = %lf\n", etime()); 262 static int count = 0; 263 count++; 264 #endif 265 if (NULL == mo) { 266 rcs_print_error("mem_release_access: Invalid memory object.\n"); 267 return -1; 268 } 269 if (NULL == mo->data || mo->connection_number < 0) { 270 rcs_print_error("mem_release_access: Invalid memory object.\n"); 271 return -1; 272 } 273 274 if (mo->sem != NULL) { 275 process_waiting = 0; 276 for (i = 0; i < mo->total_connections; i++) { 277 if (((char *) mo->data)[i] == 5) { 278 process_waiting = 1; 279 break; 280 } 281 } 282 } 283 #ifdef DEBUG 284 if (0) // (0 == count % 5) 285 { 286 rcs_print("added sleep: %d - Time = %lf\n", __LINE__, etime()); 287 esleep(0.01); 288 } 289 #endif 290 291 /* If were using a split buffer and this is the end of a write toggle the 292 toggle bit. */ 293 if (mo->split_buffer && ((char *) mo->data)[mo->connection_number] == 1) { 294 ((char *) mo->data)[mo->total_connections] = !(mo->toggle_bit); 295 } 296 #ifdef DEBUG 297 if (0) // 0 == count % 7) 298 { 299 rcs_print("added sleep: %d - Time = %lf\n", __LINE__, etime()); 300 esleep(0.01); 301 } 302 #endif 303 304 ((char *) mo->data)[mo->connection_number] = 0; 305 #ifdef DEBUG 306 if (0) // 0 == count % 11) 307 { 308 rcs_print("added sleep: %d - Time = %lf\n", __LINE__, etime()); 309 esleep(0.01); 310 } 311 #endif 312 313 if (mo->sem != NULL) { 314 if (process_waiting) { 315 mo->sem->post(); 316 } 317 } 318 return (0); 319 }