You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
8.9 KiB
307 lines
8.9 KiB
/*
|
|
* Copyright 2002-2019 Intel Corporation.
|
|
*
|
|
* This software is provided to you as Sample Source Code as defined in the accompanying
|
|
* End User License Agreement for the Intel(R) Software Development Products ("Agreement")
|
|
* section 1.L.
|
|
*
|
|
* This software and the related documents are provided as is, with no express or implied
|
|
* warranties, other than those that are expressly stated in the License.
|
|
*/
|
|
|
|
/*
|
|
* This test verifies that PIN_LOCK's are safe to use in an application that
|
|
* uses real-time scheduling on Linux. Previously, PIN_LOCK was a spin lock,
|
|
* which didn't work with real-time scheduling. If a low priority thread held
|
|
* a lock, a high-priority thread would just spin forever trying to acquire
|
|
* it.
|
|
*
|
|
* This test tries to reproduce the failing scenario. A "worker" thread runs
|
|
* in a loop where Pin has instrumented the code, and the instrumentation
|
|
* acquires and releases a PIN_LOCK. Meanwhile, a "scheduler" thread tries to
|
|
* lower the priority of the worker and then executes some code where Pin has also
|
|
* added instrumentation to acquire the same PIN_LOCK. The hope is that the
|
|
* worker's priority will be lowered while it holds the PIN_LOCK. Since both
|
|
* threads are constrained to run on the same CPU, the low priority worker
|
|
* should never give up the CPU to the high priority scheduler unless the
|
|
* scheduler actually blocks while attempting to get the lock.
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/syscall.h>
|
|
|
|
// The number of times that the scheduler thread tries to lower the priority
|
|
// of the worker thread. The test exits with success if we can complete this
|
|
// many iterations without deadlock.
|
|
//
|
|
const unsigned long NUM_SCHEDULES = 100;
|
|
|
|
#ifndef SYS_gettid
|
|
#define SYS_gettid __NR_gettid
|
|
#endif
|
|
|
|
enum PRIORITY
|
|
{
|
|
PRIORITY_LOW,
|
|
PRIORITY_HIGH
|
|
};
|
|
|
|
typedef void (*VOIDFUNPTR)();
|
|
|
|
|
|
// Synchronization used to wait for the worker thread to initialize itself
|
|
// before the scheduler thread starts the test.
|
|
//
|
|
pthread_mutex_t Lock;
|
|
pthread_cond_t WorkerInitialized;
|
|
bool WorkerIsInitialized = false;
|
|
pid_t WorkerTid; // O/S thread ID of the worker.
|
|
|
|
// Ensures that error messages are not printed simultaneously by two threads.
|
|
//
|
|
pthread_mutex_t ErrorLock;
|
|
|
|
// Priority levels used for "high" and "low" priority.
|
|
//
|
|
int PriorityLow;
|
|
int PriorityHigh;
|
|
|
|
bool IsError = false; // Set true if a system call fails in worker / scheduler thread.
|
|
volatile bool Done = false; // Asynchronously set by scheduler to terminate worker loop.
|
|
|
|
|
|
static void *Worker(void *);
|
|
static void *Scheduler(void *);
|
|
extern "C" void DoWorkInstrumentedWithPin();
|
|
extern "C" void DoGetLockWithPin();
|
|
extern "C" void TellPinNotSupported();
|
|
static bool SetPriority(pid_t, PRIORITY);
|
|
static bool SetAffinity(pid_t);
|
|
static bool CheckSupported();
|
|
static pid_t GetTid();
|
|
|
|
|
|
int main()
|
|
{
|
|
pthread_mutex_init(&Lock, 0);
|
|
pthread_mutex_init(&ErrorLock, 0);
|
|
pthread_cond_init(&WorkerInitialized, 0);
|
|
|
|
// Make sure that the system supports real-time scheduling. The test
|
|
// passes if it is not supported, which avoids failures during automated
|
|
// testing.
|
|
//
|
|
if (!CheckSupported())
|
|
{
|
|
std::cout << "No RT support\n";
|
|
TellPinNotSupported();
|
|
return 1;
|
|
}
|
|
|
|
// Figure out the priority levels to use for "high" and "low".
|
|
//
|
|
PriorityLow = sched_get_priority_min(SCHED_RR);
|
|
if (PriorityLow < 1)
|
|
PriorityLow = 1;
|
|
PriorityHigh = PriorityLow + 1;
|
|
if (PriorityHigh > sched_get_priority_max(SCHED_RR))
|
|
{
|
|
std::cerr << "Not enough priority levels" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// The main thread runs at high priority to ensure that it can wait for
|
|
// the other threads to complete.
|
|
//
|
|
if (!SetPriority(GetTid(), PRIORITY_HIGH))
|
|
return 1;
|
|
|
|
// Create the worker and scheduler threads.
|
|
//
|
|
pthread_t workerThread;
|
|
if (pthread_create(&workerThread, 0, Worker, 0) != 0)
|
|
{
|
|
std::cerr << "Unable to create worker thread" << std::endl;
|
|
return 1;
|
|
}
|
|
pthread_t schedulerThread;
|
|
if (pthread_create(&schedulerThread, 0, Scheduler, 0) != 0)
|
|
{
|
|
std::cerr << "Unable to create scheduler thread" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
pthread_join(workerThread, 0);
|
|
pthread_join(schedulerThread, 0);
|
|
|
|
return (IsError) ? 1 : 0;
|
|
}
|
|
|
|
|
|
static void *Worker(void *)
|
|
{
|
|
WorkerTid = GetTid();
|
|
if (!SetPriority(WorkerTid, PRIORITY_HIGH) || !SetAffinity(WorkerTid))
|
|
IsError = true;
|
|
|
|
pthread_mutex_lock(&Lock);
|
|
WorkerIsInitialized = true;
|
|
pthread_cond_signal(&WorkerInitialized);
|
|
pthread_mutex_unlock(&Lock);
|
|
|
|
// Execute the work loop via a volatile pointer to prevent the compiler
|
|
// from inlining. The Pin tool instruments DoWorkInstrumentedWithPin(),
|
|
// so we don't want its body to be inlined.
|
|
//
|
|
volatile VOIDFUNPTR doWork = DoWorkInstrumentedWithPin;
|
|
doWork();
|
|
return 0;
|
|
}
|
|
|
|
static void *Scheduler(void *)
|
|
{
|
|
if (!SetPriority(GetTid(), PRIORITY_HIGH) || !SetAffinity(GetTid()))
|
|
IsError = true;
|
|
|
|
// Wait for the worker to initialize itself.
|
|
//
|
|
pthread_mutex_lock(&Lock);
|
|
while (!WorkerIsInitialized)
|
|
pthread_cond_wait(&WorkerInitialized, &Lock);
|
|
pthread_mutex_unlock(&Lock);
|
|
|
|
// This loop tries to lower the priority of the worker while it holds
|
|
// the PIN_LOCK.
|
|
//
|
|
volatile VOIDFUNPTR doGetLockWithPin = DoGetLockWithPin;
|
|
for (unsigned long i = 0; i < NUM_SCHEDULES && !IsError; i++)
|
|
{
|
|
// Lower the priority, then try to acquire the PIN_LOCK. We want
|
|
// to attempt to acuire the lock here while the worker has the
|
|
// lock and is running at low priority.
|
|
//
|
|
if (!SetPriority(WorkerTid, PRIORITY_LOW))
|
|
IsError = true;
|
|
doGetLockWithPin();
|
|
|
|
if ((i % (NUM_SCHEDULES / 10)) == 0)
|
|
std::cout << "Iterations: " << std::dec << i << std::endl;
|
|
|
|
// Raise the worker priority and yield the processor to it. Let the
|
|
// worker start running again before the next attempt.
|
|
//
|
|
if (!SetPriority(WorkerTid, PRIORITY_HIGH))
|
|
IsError = true;
|
|
sched_yield();
|
|
}
|
|
|
|
Done = true;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" void DoWorkInstrumentedWithPin()
|
|
{
|
|
// This is the worker loop that is instrumented by Pin. This loop
|
|
// continually acquires and releases a PIN_LOCK.
|
|
//
|
|
while (!Done);
|
|
}
|
|
|
|
extern "C" void DoGetLockWithPin()
|
|
{
|
|
// This is executed by the scheduler after it has lowered the priority of
|
|
// the worker. Pin also tries to acquire the PIN_LOCK here.
|
|
}
|
|
|
|
extern "C" void TellPinNotSupported()
|
|
{
|
|
// The Pin tool instruments this function, so that it is informed if the
|
|
// test is not run because the O/S doesn't support real-time scheduling.
|
|
}
|
|
|
|
// Change the priority of a thread to "high" or "low".
|
|
//
|
|
static bool SetPriority(pid_t tid, PRIORITY priority)
|
|
{
|
|
struct sched_param param;
|
|
param.sched_priority = (priority == PRIORITY_HIGH) ? PriorityHigh : PriorityLow;
|
|
|
|
if (sched_setscheduler(tid, SCHED_RR, ¶m) != 0)
|
|
{
|
|
pthread_mutex_lock(&ErrorLock);
|
|
std::cerr << "Error from sched_setscheduler(0x" << std::hex << tid << "), errno=" <<
|
|
std::dec << errno << std::endl;
|
|
pthread_mutex_unlock(&ErrorLock);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Constrain a thread so that it only runs on CPU #0.
|
|
//
|
|
static bool SetAffinity(pid_t tid)
|
|
{
|
|
cpu_set_t cpus;
|
|
CPU_ZERO(&cpus);
|
|
CPU_SET(0, &cpus);
|
|
|
|
if (sched_setaffinity(tid, sizeof(cpus), &cpus) != 0)
|
|
{
|
|
pthread_mutex_lock(&ErrorLock);
|
|
std::cerr << "Error from sched_setaffinity(0x" << std::hex << tid << "), errno=" <<
|
|
std::dec << errno << std::endl;
|
|
pthread_mutex_unlock(&ErrorLock);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if the O/S supports real-time scheduling.
|
|
//
|
|
static bool CheckSupported()
|
|
{
|
|
pid_t tid = GetTid();
|
|
if (tid == -1)
|
|
{
|
|
std::cout << "Platform doesn't support SYS_gettid.\n";
|
|
return false;
|
|
}
|
|
|
|
struct sched_param param;
|
|
param.sched_priority = 2;
|
|
if (sched_setscheduler(tid, SCHED_RR, ¶m) != 0)
|
|
{
|
|
std::cout << "Cannot set SCHED_RR priority level 2. Do one of the following to fix:\n";
|
|
std::cout << " 1) Run this test manually as root.\n";
|
|
std::cout << " 2) Edit the \"/etc/security/limits.conf\" file and raise the \"rtprio\"\n";
|
|
std::cout << " limit to at least 2.\n";
|
|
return false;
|
|
}
|
|
|
|
cpu_set_t cpus;
|
|
CPU_ZERO(&cpus);
|
|
CPU_SET(0, &cpus);
|
|
|
|
if (sched_setaffinity(tid, sizeof(cpus), &cpus) != 0)
|
|
{
|
|
std::cout << "Cannot set CPU affinity to CPU 0.\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get the O/S thread ID of the caller.
|
|
//
|
|
static pid_t GetTid()
|
|
{
|
|
pid_t tid = syscall(SYS_gettid);
|
|
return tid;
|
|
}
|