// 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);
sprintf(logfile, logfilename, tid);
strcpy(path, logdir);
strcat(path, separator);
strcat(path, logfile);
if (nr_anamolies == 0 ) {
remove(path);
return ;
}
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 ;
}
Messung V0.5 in Prozent C=96 H=88 G=91
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-10-12)
¤
*© Formatika GbR, Deutschland