| // SPDX-License-Identifier: GPL-2.0 |
| |
| /* |
| * Copyright 2019, Nick Piggin, Gautham R. Shenoy, Aneesh Kumar K.V, IBM Corp. |
| */ |
| |
| /* |
| * |
| * Test tlbie/mtpidr race. We have 4 threads doing flush/load/compare/store |
| * sequence in a loop. The same threads also rung a context switch task |
| * that does sched_yield() in loop. |
| * |
| * The snapshot thread mark the mmap area PROT_READ in between, make a copy |
| * and copy it back to the original area. This helps us to detect if any |
| * store continued to happen after we marked the memory PROT_READ. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/ipc.h> |
| #include <sys/shm.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <linux/futex.h> |
| #include <unistd.h> |
| #include <asm/unistd.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <sched.h> |
| #include <time.h> |
| #include <stdarg.h> |
| #include <pthread.h> |
| #include <signal.h> |
| #include <sys/prctl.h> |
| |
| static inline void dcbf(volatile unsigned int *addr) |
| { |
| __asm__ __volatile__ ("dcbf %y0; sync" : : "Z"(*(unsigned char *)addr) : "memory"); |
| } |
| |
| static void err_msg(char *msg) |
| { |
| |
| time_t now; |
| time(&now); |
| printf("=================================\n"); |
| printf(" Error: %s\n", msg); |
| printf(" %s", ctime(&now)); |
| printf("=================================\n"); |
| exit(1); |
| } |
| |
| static char *map1; |
| static char *map2; |
| static pid_t rim_process_pid; |
| |
| /* |
| * A "rim-sequence" is defined to be the sequence of the following |
| * operations performed on a memory word: |
| * 1) FLUSH the contents of that word. |
| * 2) LOAD the contents of that word. |
| * 3) COMPARE the contents of that word with the content that was |
| * previously stored at that word |
| * 4) STORE new content into that word. |
| * |
| * The threads in this test that perform the rim-sequence are termed |
| * as rim_threads. |
| */ |
| |
| /* |
| * A "corruption" is defined to be the failed COMPARE operation in a |
| * rim-sequence. |
| * |
| * A rim_thread that detects a corruption informs about it to all the |
| * other rim_threads, and the mem_snapshot thread. |
| */ |
| static volatile unsigned int corruption_found; |
| |
| /* |
| * This defines the maximum number of rim_threads in this test. |
| * |
| * The THREAD_ID_BITS denote the number of bits required |
| * to represent the thread_ids [0..MAX_THREADS - 1]. |
| * We are being a bit paranoid here and set it to 8 bits, |
| * though 6 bits suffice. |
| * |
| */ |
| #define MAX_THREADS 64 |
| #define THREAD_ID_BITS 8 |
| #define THREAD_ID_MASK ((1 << THREAD_ID_BITS) - 1) |
| static unsigned int rim_thread_ids[MAX_THREADS]; |
| static pthread_t rim_threads[MAX_THREADS]; |
| |
| |
| /* |
| * Each rim_thread works on an exclusive "chunk" of size |
| * RIM_CHUNK_SIZE. |
| * |
| * The ith rim_thread works on the ith chunk. |
| * |
| * The ith chunk begins at |
| * map1 + (i * RIM_CHUNK_SIZE) |
| */ |
| #define RIM_CHUNK_SIZE 1024 |
| #define BITS_PER_BYTE 8 |
| #define WORD_SIZE (sizeof(unsigned int)) |
| #define WORD_BITS (WORD_SIZE * BITS_PER_BYTE) |
| #define WORDS_PER_CHUNK (RIM_CHUNK_SIZE/WORD_SIZE) |
| |
| static inline char *compute_chunk_start_addr(unsigned int thread_id) |
| { |
| char *chunk_start; |
| |
| chunk_start = (char *)((unsigned long)map1 + |
| (thread_id * RIM_CHUNK_SIZE)); |
| |
| return chunk_start; |
| } |
| |
| /* |
| * The "word-offset" of a word-aligned address inside a chunk, is |
| * defined to be the number of words that precede the address in that |
| * chunk. |
| * |
| * WORD_OFFSET_BITS denote the number of bits required to represent |
| * the word-offsets of all the word-aligned addresses of a chunk. |
| */ |
| #define WORD_OFFSET_BITS (__builtin_ctz(WORDS_PER_CHUNK)) |
| #define WORD_OFFSET_MASK ((1 << WORD_OFFSET_BITS) - 1) |
| |
| static inline unsigned int compute_word_offset(char *start, unsigned int *addr) |
| { |
| unsigned int delta_bytes, ret; |
| delta_bytes = (unsigned long)addr - (unsigned long)start; |
| |
| ret = delta_bytes/WORD_SIZE; |
| |
| return ret; |
| } |
| |
| /* |
| * A "sweep" is defined to be the sequential execution of the |
| * rim-sequence by a rim_thread on its chunk one word at a time, |
| * starting from the first word of its chunk and ending with the last |
| * word of its chunk. |
| * |
| * Each sweep of a rim_thread is uniquely identified by a sweep_id. |
| * SWEEP_ID_BITS denote the number of bits required to represent |
| * the sweep_ids of rim_threads. |
| * |
| * As to why SWEEP_ID_BITS are computed as a function of THREAD_ID_BITS, |
| * WORD_OFFSET_BITS, and WORD_BITS, see the "store-pattern" below. |
| */ |
| #define SWEEP_ID_BITS (WORD_BITS - (THREAD_ID_BITS + WORD_OFFSET_BITS)) |
| #define SWEEP_ID_MASK ((1 << SWEEP_ID_BITS) - 1) |
| |
| /* |
| * A "store-pattern" is the word-pattern that is stored into a word |
| * location in the 4)STORE step of the rim-sequence. |
| * |
| * In the store-pattern, we shall encode: |
| * |
| * - The thread-id of the rim_thread performing the store |
| * (The most significant THREAD_ID_BITS) |
| * |
| * - The word-offset of the address into which the store is being |
| * performed (The next WORD_OFFSET_BITS) |
| * |
| * - The sweep_id of the current sweep in which the store is |
| * being performed. (The lower SWEEP_ID_BITS) |
| * |
| * Store Pattern: 32 bits |
| * |------------------|--------------------|---------------------------------| |
| * | Thread id | Word offset | sweep_id | |
| * |------------------|--------------------|---------------------------------| |
| * THREAD_ID_BITS WORD_OFFSET_BITS SWEEP_ID_BITS |
| * |
| * In the store pattern, the (Thread-id + Word-offset) uniquely identify the |
| * address to which the store is being performed i.e, |
| * address == map1 + |
| * (Thread-id * RIM_CHUNK_SIZE) + (Word-offset * WORD_SIZE) |
| * |
| * And the sweep_id in the store pattern identifies the time when the |
| * store was performed by the rim_thread. |
| * |
| * We shall use this property in the 3)COMPARE step of the |
| * rim-sequence. |
| */ |
| #define SWEEP_ID_SHIFT 0 |
| #define WORD_OFFSET_SHIFT (SWEEP_ID_BITS) |
| #define THREAD_ID_SHIFT (WORD_OFFSET_BITS + SWEEP_ID_BITS) |
| |
| /* |
| * Compute the store pattern for a given thread with id @tid, at |
| * location @addr in the sweep identified by @sweep_id |
| */ |
| static inline unsigned int compute_store_pattern(unsigned int tid, |
| unsigned int *addr, |
| unsigned int sweep_id) |
| { |
| unsigned int ret = 0; |
| char *start = compute_chunk_start_addr(tid); |
| unsigned int word_offset = compute_word_offset(start, addr); |
| |
| ret += (tid & THREAD_ID_MASK) << THREAD_ID_SHIFT; |
| ret += (word_offset & WORD_OFFSET_MASK) << WORD_OFFSET_SHIFT; |
| ret += (sweep_id & SWEEP_ID_MASK) << SWEEP_ID_SHIFT; |
| return ret; |
| } |
| |
| /* Extract the thread-id from the given store-pattern */ |
| static inline unsigned int extract_tid(unsigned int pattern) |
| { |
| unsigned int ret; |
| |
| ret = (pattern >> THREAD_ID_SHIFT) & THREAD_ID_MASK; |
| return ret; |
| } |
| |
| /* Extract the word-offset from the given store-pattern */ |
| static inline unsigned int extract_word_offset(unsigned int pattern) |
| { |
| unsigned int ret; |
| |
| ret = (pattern >> WORD_OFFSET_SHIFT) & WORD_OFFSET_MASK; |
| |
| return ret; |
| } |
| |
| /* Extract the sweep-id from the given store-pattern */ |
| static inline unsigned int extract_sweep_id(unsigned int pattern) |
| |
| { |
| unsigned int ret; |
| |
| ret = (pattern >> SWEEP_ID_SHIFT) & SWEEP_ID_MASK; |
| |
| return ret; |
| } |
| |
| /************************************************************ |
| * * |
| * Logging the output of the verification * |
| * * |
| ************************************************************/ |
| #define LOGDIR_NAME_SIZE 100 |
| static char logdir[LOGDIR_NAME_SIZE]; |
| |
| static FILE *fp[MAX_THREADS]; |
| static const char logfilename[] ="Thread-%02d-Chunk"; |
| |
| static inline void start_verification_log(unsigned int tid, |
| unsigned int *addr, |
| unsigned int cur_sweep_id, |
| unsigned int prev_sweep_id) |
| { |
| FILE *f; |
| char logfile[30]; |
| char path[LOGDIR_NAME_SIZE + 30]; |
| char separator[2] = "/"; |
| char *chunk_start = compute_chunk_start_addr(tid); |
| unsigned int size = RIM_CHUNK_SIZE; |
| |
| sprintf(logfile, logfilename, tid); |
| strcpy(path, logdir); |
| strcat(path, separator); |
| strcat(path, logfile); |
| f = fopen(path, "w"); |
| |
| if (!f) { |
| err_msg("Unable to create logfile\n"); |
| } |
| |
| fp[tid] = f; |
| |
| fprintf(f, "----------------------------------------------------------\n"); |
| fprintf(f, "PID = %d\n", rim_process_pid); |
| fprintf(f, "Thread id = %02d\n", tid); |
| fprintf(f, "Chunk Start Addr = 0x%016lx\n", (unsigned long)chunk_start); |
| fprintf(f, "Chunk Size = %d\n", size); |
| fprintf(f, "Next Store Addr = 0x%016lx\n", (unsigned long)addr); |
| fprintf(f, "Current sweep-id = 0x%08x\n", cur_sweep_id); |
| fprintf(f, "Previous sweep-id = 0x%08x\n", prev_sweep_id); |
| fprintf(f, "----------------------------------------------------------\n"); |
| } |
| |
| static inline void log_anamoly(unsigned int tid, unsigned int *addr, |
| unsigned int expected, unsigned int observed) |
| { |
| FILE *f = fp[tid]; |
| |
| fprintf(f, "Thread %02d: Addr 0x%lx: Expected 0x%x, Observed 0x%x\n", |
| tid, (unsigned long)addr, expected, observed); |
| fprintf(f, "Thread %02d: Expected Thread id = %02d\n", tid, extract_tid(expected)); |
| fprintf(f, "Thread %02d: Observed Thread id = %02d\n", tid, extract_tid(observed)); |
| fprintf(f, "Thread %02d: Expected Word offset = %03d\n", tid, extract_word_offset(expected)); |
| fprintf(f, "Thread %02d: Observed Word offset = %03d\n", tid, extract_word_offset(observed)); |
| fprintf(f, "Thread %02d: Expected sweep-id = 0x%x\n", tid, extract_sweep_id(expected)); |
| fprintf(f, "Thread %02d: Observed sweep-id = 0x%x\n", tid, extract_sweep_id(observed)); |
| fprintf(f, "----------------------------------------------------------\n"); |
| } |
| |
| static inline void end_verification_log(unsigned int tid, unsigned nr_anamolies) |
| { |
| FILE *f = fp[tid]; |
| char logfile[30]; |
| char path[LOGDIR_NAME_SIZE + 30]; |
| char separator[] = "/"; |
| |
| fclose(f); |
| |
| if (nr_anamolies == 0) { |
| remove(path); |
| return; |
| } |
| |
| sprintf(logfile, logfilename, tid); |
| strcpy(path, logdir); |
| strcat(path, separator); |
| strcat(path, logfile); |
| |
| printf("Thread %02d chunk has %d corrupted words. For details check %s\n", |
| tid, nr_anamolies, path); |
| } |
| |
| /* |
| * When a COMPARE step of a rim-sequence fails, the rim_thread informs |
| * everyone else via the shared_memory pointed to by |
| * corruption_found variable. On seeing this, every thread verifies the |
| * content of its chunk as follows. |
| * |
| * Suppose a thread identified with @tid was about to store (but not |
| * yet stored) to @next_store_addr in its current sweep identified |
| * @cur_sweep_id. Let @prev_sweep_id indicate the previous sweep_id. |
| * |
| * This implies that for all the addresses @addr < @next_store_addr, |
| * Thread @tid has already performed a store as part of its current |
| * sweep. Hence we expect the content of such @addr to be: |
| * |-------------------------------------------------| |
| * | tid | word_offset(addr) | cur_sweep_id | |
| * |-------------------------------------------------| |
| * |
| * Since Thread @tid is yet to perform stores on address |
| * @next_store_addr and above, we expect the content of such an |
| * address @addr to be: |
| * |-------------------------------------------------| |
| * | tid | word_offset(addr) | prev_sweep_id | |
| * |-------------------------------------------------| |
| * |
| * The verifier function @verify_chunk does this verification and logs |
| * any anamolies that it finds. |
| */ |
| static void verify_chunk(unsigned int tid, unsigned int *next_store_addr, |
| unsigned int cur_sweep_id, |
| unsigned int prev_sweep_id) |
| { |
| unsigned int *iter_ptr; |
| unsigned int size = RIM_CHUNK_SIZE; |
| unsigned int expected; |
| unsigned int observed; |
| char *chunk_start = compute_chunk_start_addr(tid); |
| |
| int nr_anamolies = 0; |
| |
| start_verification_log(tid, next_store_addr, |
| cur_sweep_id, prev_sweep_id); |
| |
| for (iter_ptr = (unsigned int *)chunk_start; |
| (unsigned long)iter_ptr < (unsigned long)chunk_start + size; |
| iter_ptr++) { |
| unsigned int expected_sweep_id; |
| |
| if (iter_ptr < next_store_addr) { |
| expected_sweep_id = cur_sweep_id; |
| } else { |
| expected_sweep_id = prev_sweep_id; |
| } |
| |
| expected = compute_store_pattern(tid, iter_ptr, expected_sweep_id); |
| |
| dcbf((volatile unsigned int*)iter_ptr); //Flush before reading |
| observed = *iter_ptr; |
| |
| if (observed != expected) { |
| nr_anamolies++; |
| log_anamoly(tid, iter_ptr, expected, observed); |
| } |
| } |
| |
| end_verification_log(tid, nr_anamolies); |
| } |
| |
| static void set_pthread_cpu(pthread_t th, int cpu) |
| { |
| cpu_set_t run_cpu_mask; |
| struct sched_param param; |
| |
| CPU_ZERO(&run_cpu_mask); |
| CPU_SET(cpu, &run_cpu_mask); |
| pthread_setaffinity_np(th, sizeof(cpu_set_t), &run_cpu_mask); |
| |
| param.sched_priority = 1; |
| if (0 && sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) { |
| /* haven't reproduced with this setting, it kills random preemption which may be a factor */ |
| fprintf(stderr, "could not set SCHED_FIFO, run as root?\n"); |
| } |
| } |
| |
| static void set_mycpu(int cpu) |
| { |
| cpu_set_t run_cpu_mask; |
| struct sched_param param; |
| |
| CPU_ZERO(&run_cpu_mask); |
| CPU_SET(cpu, &run_cpu_mask); |
| sched_setaffinity(0, sizeof(cpu_set_t), &run_cpu_mask); |
| |
| param.sched_priority = 1; |
| if (0 && sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) { |
| fprintf(stderr, "could not set SCHED_FIFO, run as root?\n"); |
| } |
| } |
| |
| static volatile int segv_wait; |
| |
| static void segv_handler(int signo, siginfo_t *info, void *extra) |
| { |
| while (segv_wait) { |
| sched_yield(); |
| } |
| |
| } |
| |
| static void set_segv_handler(void) |
| { |
| struct sigaction sa; |
| |
| sa.sa_flags = SA_SIGINFO; |
| sa.sa_sigaction = segv_handler; |
| |
| if (sigaction(SIGSEGV, &sa, NULL) == -1) { |
| perror("sigaction"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| int timeout = 0; |
| /* |
| * This function is executed by every rim_thread. |
| * |
| * This function performs sweeps over the exclusive chunks of the |
| * rim_threads executing the rim-sequence one word at a time. |
| */ |
| static void *rim_fn(void *arg) |
| { |
| unsigned int tid = *((unsigned int *)arg); |
| |
| int size = RIM_CHUNK_SIZE; |
| char *chunk_start = compute_chunk_start_addr(tid); |
| |
| unsigned int prev_sweep_id; |
| unsigned int cur_sweep_id = 0; |
| |
| /* word access */ |
| unsigned int pattern = cur_sweep_id; |
| unsigned int *pattern_ptr = &pattern; |
| unsigned int *w_ptr, read_data; |
| |
| set_segv_handler(); |
| |
| /* |
| * Let us initialize the chunk: |
| * |
| * Each word-aligned address addr in the chunk, |
| * is initialized to : |
| * |-------------------------------------------------| |
| * | tid | word_offset(addr) | 0 | |
| * |-------------------------------------------------| |
| */ |
| for (w_ptr = (unsigned int *)chunk_start; |
| (unsigned long)w_ptr < (unsigned long)(chunk_start) + size; |
| w_ptr++) { |
| |
| *pattern_ptr = compute_store_pattern(tid, w_ptr, cur_sweep_id); |
| *w_ptr = *pattern_ptr; |
| } |
| |
| while (!corruption_found && !timeout) { |
| prev_sweep_id = cur_sweep_id; |
| cur_sweep_id = cur_sweep_id + 1; |
| |
| for (w_ptr = (unsigned int *)chunk_start; |
| (unsigned long)w_ptr < (unsigned long)(chunk_start) + size; |
| w_ptr++) { |
| unsigned int old_pattern; |
| |
| /* |
| * Compute the pattern that we would have |
| * stored at this location in the previous |
| * sweep. |
| */ |
| old_pattern = compute_store_pattern(tid, w_ptr, prev_sweep_id); |
| |
| /* |
| * FLUSH:Ensure that we flush the contents of |
| * the cache before loading |
| */ |
| dcbf((volatile unsigned int*)w_ptr); //Flush |
| |
| /* LOAD: Read the value */ |
| read_data = *w_ptr; //Load |
| |
| /* |
| * COMPARE: Is it the same as what we had stored |
| * in the previous sweep ? It better be! |
| */ |
| if (read_data != old_pattern) { |
| /* No it isn't! Tell everyone */ |
| corruption_found = 1; |
| } |
| |
| /* |
| * Before performing a store, let us check if |
| * any rim_thread has found a corruption. |
| */ |
| if (corruption_found || timeout) { |
| /* |
| * Yes. Someone (including us!) has found |
| * a corruption :( |
| * |
| * Let us verify that our chunk is |
| * correct. |
| */ |
| /* But first, let us allow the dust to settle down! */ |
| verify_chunk(tid, w_ptr, cur_sweep_id, prev_sweep_id); |
| |
| return 0; |
| } |
| |
| /* |
| * Compute the new pattern that we are going |
| * to write to this location |
| */ |
| *pattern_ptr = compute_store_pattern(tid, w_ptr, cur_sweep_id); |
| |
| /* |
| * STORE: Now let us write this pattern into |
| * the location |
| */ |
| *w_ptr = *pattern_ptr; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| static unsigned long start_cpu = 0; |
| static unsigned long nrthreads = 4; |
| |
| static pthread_t mem_snapshot_thread; |
| |
| static void *mem_snapshot_fn(void *arg) |
| { |
| int page_size = getpagesize(); |
| size_t size = page_size; |
| void *tmp = malloc(size); |
| |
| while (!corruption_found && !timeout) { |
| /* Stop memory migration once corruption is found */ |
| segv_wait = 1; |
| |
| mprotect(map1, size, PROT_READ); |
| |
| /* |
| * Load from the working alias (map1). Loading from map2 |
| * also fails. |
| */ |
| memcpy(tmp, map1, size); |
| |
| /* |
| * Stores must go via map2 which has write permissions, but |
| * the corrupted data tends to be seen in the snapshot buffer, |
| * so corruption does not appear to be introduced at the |
| * copy-back via map2 alias here. |
| */ |
| memcpy(map2, tmp, size); |
| /* |
| * Before releasing other threads, must ensure the copy |
| * back to |
| */ |
| asm volatile("sync" ::: "memory"); |
| mprotect(map1, size, PROT_READ|PROT_WRITE); |
| asm volatile("sync" ::: "memory"); |
| segv_wait = 0; |
| |
| usleep(1); /* This value makes a big difference */ |
| } |
| |
| return 0; |
| } |
| |
| void alrm_sighandler(int sig) |
| { |
| timeout = 1; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int c; |
| int page_size = getpagesize(); |
| time_t now; |
| int i, dir_error; |
| pthread_attr_t attr; |
| key_t shm_key = (key_t) getpid(); |
| int shmid, run_time = 20 * 60; |
| struct sigaction sa_alrm; |
| |
| snprintf(logdir, LOGDIR_NAME_SIZE, |
| "/tmp/logdir-%u", (unsigned int)getpid()); |
| while ((c = getopt(argc, argv, "r:hn:l:t:")) != -1) { |
| switch(c) { |
| case 'r': |
| start_cpu = strtoul(optarg, NULL, 10); |
| break; |
| case 'h': |
| printf("%s [-r <start_cpu>] [-n <nrthreads>] [-l <logdir>] [-t <timeout>]\n", argv[0]); |
| exit(0); |
| break; |
| case 'n': |
| nrthreads = strtoul(optarg, NULL, 10); |
| break; |
| case 'l': |
| strncpy(logdir, optarg, LOGDIR_NAME_SIZE - 1); |
| break; |
| case 't': |
| run_time = strtoul(optarg, NULL, 10); |
| break; |
| default: |
| printf("invalid option\n"); |
| exit(0); |
| break; |
| } |
| } |
| |
| if (nrthreads > MAX_THREADS) |
| nrthreads = MAX_THREADS; |
| |
| shmid = shmget(shm_key, page_size, IPC_CREAT|0666); |
| if (shmid < 0) { |
| err_msg("Failed shmget\n"); |
| } |
| |
| map1 = shmat(shmid, NULL, 0); |
| if (map1 == (void *) -1) { |
| err_msg("Failed shmat"); |
| } |
| |
| map2 = shmat(shmid, NULL, 0); |
| if (map2 == (void *) -1) { |
| err_msg("Failed shmat"); |
| } |
| |
| dir_error = mkdir(logdir, 0755); |
| |
| if (dir_error) { |
| err_msg("Failed mkdir"); |
| } |
| |
| printf("start_cpu list:%lu\n", start_cpu); |
| printf("number of worker threads:%lu + 1 snapshot thread\n", nrthreads); |
| printf("Allocated address:0x%016lx + secondary map:0x%016lx\n", (unsigned long)map1, (unsigned long)map2); |
| printf("logdir at : %s\n", logdir); |
| printf("Timeout: %d seconds\n", run_time); |
| |
| time(&now); |
| printf("=================================\n"); |
| printf(" Starting Test\n"); |
| printf(" %s", ctime(&now)); |
| printf("=================================\n"); |
| |
| for (i = 0; i < nrthreads; i++) { |
| if (1 && !fork()) { |
| prctl(PR_SET_PDEATHSIG, SIGKILL); |
| set_mycpu(start_cpu + i); |
| for (;;) |
| sched_yield(); |
| exit(0); |
| } |
| } |
| |
| |
| sa_alrm.sa_handler = &alrm_sighandler; |
| sigemptyset(&sa_alrm.sa_mask); |
| sa_alrm.sa_flags = 0; |
| |
| if (sigaction(SIGALRM, &sa_alrm, 0) == -1) { |
| err_msg("Failed signal handler registration\n"); |
| } |
| |
| alarm(run_time); |
| |
| pthread_attr_init(&attr); |
| for (i = 0; i < nrthreads; i++) { |
| rim_thread_ids[i] = i; |
| pthread_create(&rim_threads[i], &attr, rim_fn, &rim_thread_ids[i]); |
| set_pthread_cpu(rim_threads[i], start_cpu + i); |
| } |
| |
| pthread_create(&mem_snapshot_thread, &attr, mem_snapshot_fn, map1); |
| set_pthread_cpu(mem_snapshot_thread, start_cpu + i); |
| |
| |
| pthread_join(mem_snapshot_thread, NULL); |
| for (i = 0; i < nrthreads; i++) { |
| pthread_join(rim_threads[i], NULL); |
| } |
| |
| if (!timeout) { |
| time(&now); |
| printf("=================================\n"); |
| printf(" Data Corruption Detected\n"); |
| printf(" %s", ctime(&now)); |
| printf(" See logfiles in %s\n", logdir); |
| printf("=================================\n"); |
| return 1; |
| } |
| return 0; |
| } |