2009-06-10 23:30:24 +02:00
|
|
|
#define _GNU_SOURCE
|
|
|
|
|
2009-06-10 09:58:50 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <string.h>
|
2009-06-19 13:27:08 +02:00
|
|
|
#include <dlfcn.h>
|
2009-06-23 13:10:04 +02:00
|
|
|
#include <sys/time.h>
|
2009-06-10 09:58:50 +02:00
|
|
|
|
|
|
|
/* Non standards includes */
|
|
|
|
#include <papihighlevel.h>
|
2009-06-18 14:48:07 +02:00
|
|
|
#include <commtech.h>
|
2009-06-10 09:58:50 +02:00
|
|
|
#include <specific_comm.h>
|
|
|
|
|
2009-06-18 22:10:15 +02:00
|
|
|
|
|
|
|
#define toString(x) doStringification(x)
|
|
|
|
#define doStringification(x) #x
|
2009-06-24 18:18:05 +02:00
|
|
|
#define WORDS_PER_BUF (BUF_SIZE / sizeof(uintptr_t))
|
|
|
|
#define DIV_SEC(secs, div) ((unsigned ) (((unsigned) secs) / (unsigned long) (div)))
|
|
|
|
#define DIV_USEC(nsecs, nusecs, div) ((unsigned) (((unsigned) (nusecs) + 1000000 * \
|
|
|
|
((unsigned ) (nsecs) % (div))) / (unsigned long) (div)))
|
2009-06-18 22:10:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
static long nb_bufs_sent = 0;
|
2009-06-17 18:15:16 +02:00
|
|
|
long nb_prod = 0;
|
2009-06-24 22:25:28 +02:00
|
|
|
static int (*init_calc)(int) = NULL;
|
2009-06-30 22:35:11 +02:00
|
|
|
static void **(*do_calc)(void) = NULL;
|
2009-06-24 22:25:28 +02:00
|
|
|
static int (*end_calc)(void) = NULL;
|
2009-07-01 03:10:38 +02:00
|
|
|
static int shared = 0; /* We are not shared by default */
|
2009-06-12 00:34:33 +02:00
|
|
|
pthread_cond_t cond_cons_has_finished = PTHREAD_COND_INITIALIZER;
|
|
|
|
pthread_mutex_t mutex_cons_has_finished = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static int consumer_has_finished = 0;
|
|
|
|
static int producers_ended = 0;
|
2009-07-01 02:36:11 +02:00
|
|
|
static int init_calc_arg = 0;
|
2009-06-10 09:58:50 +02:00
|
|
|
|
|
|
|
void usage(char *argv[])
|
|
|
|
{
|
|
|
|
char format[] = "-n [options]";
|
|
|
|
char options[] = "Required options :\n"
|
2009-07-01 03:10:38 +02:00
|
|
|
"-n nb_buffer_sent\t\tNumber of buffer to send to another core\n"
|
|
|
|
"\t\t\t\tBuffer size is " toString(BUF_SIZE) " bytes\n"
|
|
|
|
"-p nb_producers\t\t\tNumber of producers which send data to another core\n"
|
2009-06-10 09:58:50 +02:00
|
|
|
"Facultative options :\n"
|
2009-07-01 03:10:38 +02:00
|
|
|
"-h\t\t\t\tPrint this help\n"
|
|
|
|
"-s <level>\t\t\tShare the same L<level> cache or not\n"
|
|
|
|
"\t\t\t\tIf level is:\n"
|
|
|
|
"\t\t\t\t\t> 0, then the same L<level> must be shared\n"
|
|
|
|
"\t\t\t\t\t< 0, then different L<level> must be used\n"
|
|
|
|
"\t\t\t\t\t= 0, then no constraint is given, only main memory (RAM) is guaranteed to be shared\n"
|
2009-07-01 02:45:28 +02:00
|
|
|
"-c calculation_libname arg\tLibrary to use for calculation with its argument\n"
|
2009-07-01 03:10:38 +02:00
|
|
|
"\t\t\t\tThis library must implement functions in calc.h\n";
|
2009-06-10 09:58:50 +02:00
|
|
|
printf("Usage : %s %s\n", argv[0], format);
|
|
|
|
printf("Options :\n");
|
|
|
|
printf("%s\n", options);
|
|
|
|
}
|
|
|
|
|
2009-06-24 22:25:28 +02:00
|
|
|
int do_noinit(int unused)
|
2009-06-19 13:27:08 +02:00
|
|
|
{
|
2009-06-24 22:25:28 +02:00
|
|
|
return 0;
|
2009-06-19 13:27:08 +02:00
|
|
|
}
|
|
|
|
|
2009-06-30 22:35:11 +02:00
|
|
|
void **do_nocalc(void)
|
2009-06-19 13:27:08 +02:00
|
|
|
{
|
2009-06-30 22:35:11 +02:00
|
|
|
static int an_int, *an_int_ptr = &an_int;
|
2009-06-19 13:27:08 +02:00
|
|
|
|
2009-06-30 22:35:11 +02:00
|
|
|
return (void **) &an_int_ptr;
|
2009-06-19 13:27:08 +02:00
|
|
|
}
|
|
|
|
|
2009-06-24 22:25:28 +02:00
|
|
|
int do_noend(void)
|
2009-06-19 21:15:23 +02:00
|
|
|
{
|
2009-06-24 22:25:28 +02:00
|
|
|
return 0;
|
2009-06-19 21:15:23 +02:00
|
|
|
}
|
|
|
|
|
2009-06-10 09:58:50 +02:00
|
|
|
int analyse_options(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int opt;
|
|
|
|
|
|
|
|
opterr = 0;
|
2009-07-01 03:10:38 +02:00
|
|
|
while ((opt = getopt(argc, argv, ":hs::c:n:p:")) != -1)
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
|
|
|
switch (opt)
|
|
|
|
{
|
|
|
|
case 'c' :
|
|
|
|
{
|
|
|
|
struct stat file_stat;
|
2009-06-19 13:27:08 +02:00
|
|
|
void *dl_descriptor;
|
|
|
|
|
|
|
|
if (stat(optarg, &file_stat))
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
2009-06-19 13:27:08 +02:00
|
|
|
fprintf(stderr, "%s: %s\n", optarg, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
dl_descriptor = dlopen(optarg, RTLD_LAZY | RTLD_LOCAL);
|
|
|
|
if (dl_descriptor == NULL)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "dlopen error: %s\n", dlerror());
|
|
|
|
return -1;
|
|
|
|
}
|
2009-06-30 22:35:11 +02:00
|
|
|
init_calc = (int (*)(int)) dlsym(dl_descriptor, "init_calc");
|
|
|
|
do_calc = (void ** (*)(void)) dlsym(dl_descriptor, "do_calc");
|
|
|
|
end_calc = (int (*)(void)) dlsym(dl_descriptor, "end_calc");
|
2009-06-19 13:27:08 +02:00
|
|
|
if ((init_calc == NULL) || (do_calc == NULL) || (end_calc == NULL))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "A symbol cannot be loaded: %s\n", dlerror());
|
2009-06-10 09:58:50 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2009-07-01 02:36:11 +02:00
|
|
|
if ((optind == argc) || (*argv[optind] == '-'))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Missing argument for -c option\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
char *inval;
|
|
|
|
init_calc_arg = strtol(argv[optind], &inval, 10);
|
|
|
|
if ((*argv[optind] == '\0') || (*inval != '\0'))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Option '-c' needs also an integer argument\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if ((init_calc_arg <= 0) || ((init_calc_arg == LONG_MAX) && errno == ERANGE))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Number of useless loop to be done between 2 send must be"
|
|
|
|
" between 1 and %ld, both inclusive\n", LONG_MAX);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
optind++;
|
2009-06-10 09:58:50 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'h' :
|
|
|
|
usage(argv);
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
case 'n' :
|
|
|
|
{
|
|
|
|
char *inval;
|
2009-06-18 22:10:15 +02:00
|
|
|
nb_bufs_sent = strtol(optarg, &inval, 10);
|
2009-06-10 09:58:50 +02:00
|
|
|
if ((*optarg == '\0') || (*inval != '\0'))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Option '-n' needs an integer argument\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2009-06-18 22:10:15 +02:00
|
|
|
if ((nb_bufs_sent <= 0) || ((nb_bufs_sent == LONG_MAX) && errno == ERANGE))
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "Number of cache lines to be sent must be between 1 and %ld, both inclusive\n", LONG_MAX);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'p' :
|
|
|
|
{
|
|
|
|
char *inval;
|
|
|
|
nb_prod = strtol(optarg, &inval, 10);
|
|
|
|
if ((*optarg == '\0') || (*inval != '\0'))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Option '-p' needs an integer argument\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2009-06-18 22:10:15 +02:00
|
|
|
if ((nb_prod <= 0) || ((nb_prod == LONG_MAX) && errno == ERANGE))
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "Number of producers must be between 1 and %ld, both inclusive\n", LONG_MAX);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 's' :
|
2009-07-01 03:10:38 +02:00
|
|
|
if ((optind != argc) && (*argv[optind] != '-'))
|
|
|
|
{
|
|
|
|
int share_level;
|
|
|
|
char *inval;
|
|
|
|
share_level = strtol(argv[optind], &inval, 10);
|
|
|
|
if ((*argv[optind] == '\0') || (*inval != '\0'))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Option '-p' needs an integer argument\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if ((share_level == LONG_MIN) || ((share_level == LONG_MAX) && errno == ERANGE))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Shared memory level must be between %ld and %ld, both inclusive\n", LONG_MIN, LONG_MAX);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* TODO: Real management of shared memory level */
|
|
|
|
if (share_level <= 0) /* -x: We wan't level x not to be shared; 0 do as we want, only memory is guaranteed to be shared */
|
|
|
|
shared = 0;
|
|
|
|
else
|
|
|
|
shared = 1;
|
|
|
|
optind++;
|
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
break;
|
|
|
|
case '?' :
|
|
|
|
fprintf(stderr, "Option inconnue\n");
|
|
|
|
return -1;
|
|
|
|
case ':' :
|
|
|
|
fprintf(stderr, "Option %s needs an argument\n", argv[optind]);
|
|
|
|
return -1;
|
|
|
|
default :
|
|
|
|
fprintf(stderr, "Error while analysing command line options\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2009-06-18 22:10:15 +02:00
|
|
|
if (!nb_bufs_sent)
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "You must give the number of cache lines to be sent\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!nb_prod)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "You must give the number of producers\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2009-06-10 23:30:24 +02:00
|
|
|
if (shared && (nb_prod > 1))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Too many producers to fit with the consumer in processors which share a same cache\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2009-06-19 13:27:08 +02:00
|
|
|
if (do_calc == NULL)
|
|
|
|
{
|
2009-06-19 21:15:23 +02:00
|
|
|
init_calc = do_noinit;
|
2009-06-19 13:27:08 +02:00
|
|
|
do_calc = do_nocalc;
|
2009-06-19 21:15:23 +02:00
|
|
|
end_calc = do_noend;
|
2009-06-19 13:27:08 +02:00
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-24 22:25:28 +02:00
|
|
|
void wait_consumer(void)
|
|
|
|
{
|
|
|
|
pthread_mutex_lock(&mutex_cons_has_finished);
|
|
|
|
if (++producers_ended == nb_prod)
|
|
|
|
cont = 0;
|
|
|
|
if (!consumer_has_finished)
|
|
|
|
pthread_cond_wait(&cond_cons_has_finished, &mutex_cons_has_finished);
|
|
|
|
pthread_mutex_unlock(&mutex_cons_has_finished);
|
|
|
|
}
|
|
|
|
|
2009-06-17 18:15:16 +02:00
|
|
|
void *producer(void *unused)
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
2009-06-16 12:58:30 +02:00
|
|
|
int i, j;
|
2009-06-23 13:10:04 +02:00
|
|
|
struct timeval tv1, tv2, tv_result;
|
2009-06-10 09:58:50 +02:00
|
|
|
|
2009-06-24 22:25:28 +02:00
|
|
|
if (init_producer_thread())
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Initialization of thread has failed\n");
|
|
|
|
wait_consumer();
|
|
|
|
return &nb_prod; /* &nb_prod can't be NULL, whatever NULL is bound to */
|
|
|
|
}
|
2009-06-10 23:30:24 +02:00
|
|
|
if (shared)
|
|
|
|
{
|
|
|
|
pthread_t tid;
|
|
|
|
cpu_set_t cpuset;
|
|
|
|
|
|
|
|
tid = pthread_self();
|
|
|
|
CPU_ZERO(&cpuset);
|
|
|
|
CPU_SET(1, &cpuset);
|
|
|
|
if (pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset))
|
|
|
|
{
|
|
|
|
perror("pthread_setaffinity_np");
|
2009-06-24 22:25:28 +02:00
|
|
|
wait_consumer();
|
|
|
|
return &nb_prod; /* &nb_prod can't be NULL, whatever NULL is bound to */
|
2009-06-10 23:30:24 +02:00
|
|
|
}
|
|
|
|
}
|
2009-06-17 19:12:22 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
pthread_t tid;
|
|
|
|
cpu_set_t cpuset;
|
|
|
|
|
|
|
|
tid = pthread_self();
|
|
|
|
CPU_ZERO(&cpuset);
|
|
|
|
CPU_SET(2, &cpuset);
|
|
|
|
if (pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset))
|
|
|
|
{
|
|
|
|
perror("pthread_setaffinity_np");
|
2009-06-24 22:25:28 +02:00
|
|
|
wait_consumer();
|
|
|
|
return &nb_prod; /* &nb_prod can't be NULL, whatever NULL is bound to */
|
2009-06-17 19:12:22 +02:00
|
|
|
}
|
|
|
|
}
|
2009-07-01 02:36:11 +02:00
|
|
|
if (init_calc(init_calc_arg))
|
2009-06-24 22:25:28 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "Initialization of calculation has failed\n");
|
|
|
|
wait_consumer();
|
|
|
|
return &nb_prod; /* nb_prod can't be NULL, whatever NULL is bound to */
|
|
|
|
}
|
2009-06-23 13:10:04 +02:00
|
|
|
gettimeofday(&tv1, NULL);
|
2009-06-10 09:58:50 +02:00
|
|
|
if (initialize_papi() != -1)
|
|
|
|
{
|
2009-06-18 22:10:15 +02:00
|
|
|
for(i = 0; i < nb_bufs_sent; i++) {
|
2009-06-18 14:48:07 +02:00
|
|
|
//printf("[%p] Send %d new CACHE_LINE\n", (void *) pthread_self(), BUF_SIZE / CACHE_LINE_SIZE);
|
2009-06-24 18:18:05 +02:00
|
|
|
for(j = 0; j < WORDS_PER_BUF; j++)
|
2009-06-19 13:27:08 +02:00
|
|
|
send(do_calc());
|
2009-06-10 09:58:50 +02:00
|
|
|
}
|
2009-06-24 18:18:05 +02:00
|
|
|
print_results(WORDS_PER_BUF, nb_bufs_sent);
|
2009-06-10 09:58:50 +02:00
|
|
|
}
|
2009-06-23 13:10:04 +02:00
|
|
|
gettimeofday(&tv2, NULL);
|
|
|
|
tv_result.tv_sec = tv2.tv_sec - tv1.tv_sec;
|
|
|
|
if (tv2.tv_usec < tv1.tv_usec)
|
|
|
|
{
|
|
|
|
tv_result.tv_usec = tv1.tv_usec - tv2.tv_usec;
|
|
|
|
tv_result.tv_sec--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
tv_result.tv_usec = tv2.tv_usec - tv1.tv_usec;
|
2009-06-24 18:18:05 +02:00
|
|
|
printf("total_time: %u.%06u / %u.%06u / %u.%06u\n", (unsigned) tv_result.tv_sec,
|
2009-06-24 22:25:28 +02:00
|
|
|
(unsigned) tv_result.tv_usec,
|
|
|
|
DIV_SEC(tv_result.tv_sec, nb_bufs_sent),
|
|
|
|
DIV_USEC(tv_result.tv_sec, tv_result.tv_usec, nb_bufs_sent),
|
|
|
|
DIV_SEC(tv_result.tv_sec, nb_bufs_sent * WORDS_PER_BUF),
|
|
|
|
DIV_USEC(tv_result.tv_sec, tv_result.tv_usec, nb_bufs_sent * WORDS_PER_BUF));
|
|
|
|
if (end_calc())
|
|
|
|
{
|
|
|
|
fprintf(stderr, "uninitialization of calculation has failed\n");
|
|
|
|
wait_consumer();
|
|
|
|
return &nb_prod; /* &nb_prod can't be NULL, whatever NULL is bound to */
|
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
printf("[%p] Producer finished !\n", (void*) pthread_self());
|
2009-06-12 00:34:33 +02:00
|
|
|
/*
|
|
|
|
* When a producer end its thread-local storage vanished. Thus,
|
|
|
|
* producers must finish only after consumer has stopped using them
|
|
|
|
*/
|
2009-06-24 22:25:28 +02:00
|
|
|
wait_consumer();
|
|
|
|
if (end_producer_thread())
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Uninitialization of thread has failed\n");
|
|
|
|
return &nb_prod; /* &nb_prod can't be NULL, whatever NULL is bound to */
|
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2009-06-18 14:48:07 +02:00
|
|
|
void onMessage(void *val)
|
2009-06-10 09:58:50 +02:00
|
|
|
{
|
|
|
|
//printf("Receive value: %p\n", (void *) val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void *receptor(void *a)
|
|
|
|
{
|
2009-06-10 23:30:24 +02:00
|
|
|
if (shared)
|
|
|
|
{
|
|
|
|
pthread_t tid;
|
|
|
|
cpu_set_t cpuset;
|
|
|
|
|
|
|
|
tid = pthread_self();
|
|
|
|
CPU_ZERO(&cpuset);
|
2009-06-17 19:12:22 +02:00
|
|
|
CPU_SET(0, &cpuset);
|
2009-06-10 23:30:24 +02:00
|
|
|
if (pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset))
|
|
|
|
{
|
|
|
|
perror("pthread_setaffinity_np");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
reception(onMessage);
|
2009-06-12 00:34:33 +02:00
|
|
|
pthread_mutex_lock(&mutex_cons_has_finished);
|
|
|
|
consumer_has_finished = 1;
|
|
|
|
pthread_cond_broadcast(&cond_cons_has_finished);
|
|
|
|
pthread_mutex_unlock(&mutex_cons_has_finished);
|
2009-06-10 09:58:50 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2009-06-24 22:25:28 +02:00
|
|
|
int i, global_return_value = EXIT_SUCCESS;
|
2009-06-10 09:58:50 +02:00
|
|
|
void *return_value;
|
|
|
|
pthread_t *tid;
|
|
|
|
|
|
|
|
if (analyse_options(argc, argv))
|
|
|
|
return EXIT_FAILURE;
|
2009-06-17 18:15:16 +02:00
|
|
|
if (init_library())
|
|
|
|
return EXIT_FAILURE;
|
2009-06-10 09:58:50 +02:00
|
|
|
tid = (pthread_t *) malloc((nb_prod + 1) * sizeof(pthread_t));
|
2009-06-17 18:15:16 +02:00
|
|
|
if (tid == NULL)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Failed to allocate %lu bytes needed for thread creation\n", (nb_prod + 1) * sizeof(pthread_t));
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
for(i = 0; i < nb_prod; i++)
|
2009-06-17 18:15:16 +02:00
|
|
|
pthread_create(&tid[i], NULL, producer, NULL);
|
2009-06-10 09:58:50 +02:00
|
|
|
pthread_create(&tid[i], NULL, receptor, NULL);
|
|
|
|
for(i = 0; i < nb_prod; i++)
|
2009-06-24 22:25:28 +02:00
|
|
|
{
|
2009-06-10 09:58:50 +02:00
|
|
|
pthread_join(tid[i], &return_value);
|
2009-06-24 22:25:28 +02:00
|
|
|
if (return_value != NULL)
|
|
|
|
global_return_value = EXIT_FAILURE;
|
|
|
|
}
|
2009-06-10 09:58:50 +02:00
|
|
|
pthread_join(tid[i], &return_value);
|
2009-06-24 22:25:28 +02:00
|
|
|
if (return_value != NULL)
|
|
|
|
global_return_value = EXIT_FAILURE;
|
2009-06-10 09:58:50 +02:00
|
|
|
free(tid);
|
2009-06-24 22:25:28 +02:00
|
|
|
if (end_library())
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
return global_return_value;
|
2009-06-10 09:58:50 +02:00
|
|
|
}
|