#include "pub_core_basics.h"
#include "pub_core_vki.h"
-#include "pub_core_vkiscnums.h"
+#include "pub_core_vkiscnums.h" // __NR_sched_yield
#include "pub_core_threadstate.h"
#include "pub_core_aspacemgr.h"
#include "pub_core_clreq.h" // for VG_USERREQ__*
#include "pub_core_mallocfree.h"
#include "pub_core_options.h"
#include "pub_core_replacemalloc.h"
-#include "pub_core_scheduler.h"
#include "pub_core_signals.h"
#include "pub_core_stacks.h"
#include "pub_core_stacktrace.h" // For VG_(get_and_pp_StackTrace)()
#include "pub_core_translate.h" // For VG_(translate)()
#include "pub_core_transtab.h"
#include "priv_sema.h"
+#include "pub_core_scheduler.h" // self
/* #include "pub_core_debuginfo.h" */ // DEBUGGING HACK ONLY
When this returns, we'll actually be running.
*/
-void VG_(set_running)(ThreadId tid)
+void VG_(set_running)(ThreadId tid, HChar* who)
{
- ThreadState *tst = VG_(get_ThreadState)(tid);
+ ThreadState *tst;
+
+#if 0
+ if (VG_(clo_trace_sched)) {
+ HChar buf[100];
+ vg_assert(VG_(strlen)(who) <= 100-50);
+ VG_(sprintf)(buf, "waiting for lock (%s)", who);
+ print_sched_event(tid, buf);
+ }
+#endif
+
+ /* First, acquire the lock. We can't do anything else safely prior
+ to this point. Even doing debug printing prior to this point
+ is, technically, wrong. */
+ ML_(sema_down)(&run_sema);
+
+ tst = VG_(get_ThreadState)(tid);
vg_assert(tst->status != VgTs_Runnable);
tst->status = VgTs_Runnable;
-
- ML_(sema_down)(&run_sema);
+
if (VG_(running_tid) != VG_INVALID_THREADID)
VG_(printf)("tid %d found %d running\n", tid, VG_(running_tid));
vg_assert(VG_(running_tid) == VG_INVALID_THREADID);
VG_(unknown_SP_update)(VG_(get_SP(tid)), VG_(get_SP(tid)));
- if (VG_(clo_trace_sched))
- print_sched_event(tid, "now running");
+ if (VG_(clo_trace_sched)) {
+ HChar buf[150];
+ vg_assert(VG_(strlen)(who) <= 150-50);
+ VG_(sprintf)(buf, " acquired lock (%s)", who);
+ print_sched_event(tid, buf);
+ }
// While thre modeling is disable, issue thread_run events here
// VG_(tm_thread_switchto)(tid);
but it may mean that we remain in a Runnable state and we're just
yielding the CPU to another thread).
*/
-void VG_(set_sleeping)(ThreadId tid, ThreadStatus sleepstate)
+void VG_(set_sleeping)(ThreadId tid, ThreadStatus sleepstate, HChar* who)
{
ThreadState *tst = VG_(get_ThreadState)(tid);
vg_assert(VG_(running_tid) == tid);
VG_(running_tid) = VG_INVALID_THREADID;
- /* Release the run_sema; this will reschedule any runnable
- thread. */
- ML_(sema_up)(&run_sema);
-
if (VG_(clo_trace_sched)) {
- Char buf[50];
- VG_(sprintf)(buf, "now sleeping in state %s",
- VG_(name_of_ThreadStatus)(sleepstate));
+ Char buf[200];
+ vg_assert(VG_(strlen)(who) <= 200-100);
+ VG_(sprintf)(buf, "releasing lock (%s) -> %s",
+ who, VG_(name_of_ThreadStatus)(sleepstate));
print_sched_event(tid, buf);
}
+
+ /* Release the run_sema; this will reschedule any runnable
+ thread. */
+ ML_(sema_up)(&run_sema);
}
/* Clear out the ThreadState and release the semaphore. Leaves the
/* There should still be a valid exitreason for this thread */
vg_assert(VG_(threads)[tid].exitreason != VgSrc_None);
+ if (VG_(clo_trace_sched))
+ print_sched_event(tid, "release lock in VG_(exit_thread)");
+
ML_(sema_up)(&run_sema);
}
-/* Kill a thread. This interrupts whatever a thread is doing, and
- makes it exit ASAP. This does not set the exitreason or
- exitcode. */
-void VG_(kill_thread)(ThreadId tid)
+/* If 'tid' is blocked in a syscall, send it SIGVGKILL so as to get it
+ out of the syscall and onto doing the next thing, whatever that is.
+ If it isn't blocked in a syscall, has no effect on the thread. */
+void VG_(get_thread_out_of_syscall)(ThreadId tid)
{
vg_assert(VG_(is_valid_tid)(tid));
vg_assert(!VG_(is_running_thread)(tid));
- vg_assert(VG_(is_exiting)(tid));
if (VG_(threads)[tid].status == VgTs_WaitSys) {
if (VG_(clo_trace_signals))
- VG_(message)(Vg_DebugMsg, "kill_thread zaps tid %d lwp %d",
+ VG_(message)(Vg_DebugMsg,
+ "get_thread_out_of_syscall zaps tid %d lwp %d",
tid, VG_(threads)[tid].os_state.lwpid);
VG_(tkill)(VG_(threads)[tid].os_state.lwpid, VG_SIGVGKILL);
}
*/
void VG_(vg_yield)(void)
{
- struct vki_timespec ts = { 0, 1 };
ThreadId tid = VG_(running_tid);
vg_assert(tid != VG_INVALID_THREADID);
vg_assert(VG_(threads)[tid].os_state.lwpid == VG_(gettid)());
- VG_(set_sleeping)(tid, VgTs_Yielding);
-
- //VG_(printf)("tid %d yielding EIP=%p\n", tid, VG_(threads)[tid].arch.m_eip);
+ VG_(set_sleeping)(tid, VgTs_Yielding, "VG_(vg_yield)");
/*
Tell the kernel we're yielding.
*/
- if (1)
- VG_(do_syscall0)(__NR_sched_yield);
- else
- VG_(nanosleep)(&ts);
+ VG_(do_syscall0)(__NR_sched_yield);
- VG_(set_running)(tid);
+ VG_(set_running)(tid, "VG_(vg_yield)");
}
{
tst->os_state.lwpid = 0;
tst->os_state.threadgroup = 0;
+# if defined(VGO_aix5)
+ tst->os_state.cancel_async = False;
+ tst->os_state.cancel_disabled = False;
+ tst->os_state.cancel_progress = Canc_NoRequest;
+# endif
}
static void os_state_init(ThreadState *tst)
ML_(sema_init)(&run_sema);
for (i = 0 /* NB; not 1 */; i < VG_N_THREADS; i++) {
-
/* Paranoia .. completely zero it out. */
VG_(memset)( & VG_(threads)[i], 0, sizeof( VG_(threads)[i] ) );
do_pre_run_checks(tst);
/* end Paranoia */
- //if (0) {
- // Char buf[100];
- // Bool ok = VG_(get_fnname_if_entry) ( tst->arch.vex.guest_CIA,
- // buf, 100 );
- // if (ok) {
- // Addr r2actual = tst->arch.vex.guest_GPR2;
- // Addr r2tocptr = VG_(get_tocptr)( tst->arch.vex.guest_CIA );
- // if (1) VG_(printf)("R2 act 0x%016llx toc 0x%016llx %s\n",
- // r2actual, r2tocptr, buf);
- // if (r2tocptr != 0) vg_assert(r2actual == r2tocptr);
- // }
- //}
-
trc = 0;
dispatch_ctr_SAVED = VG_(dispatch_ctr);
VG_(threads)[tid].arch.vex.guest_RESVN = 0;
# endif
+# if defined(VGP_ppc32_aix5) || defined(VGP_ppc64_aix5)
+ /* On AIX, we need to get a plausible value for SPRG3 for this
+ thread, since it's used I think as a thread-state pointer. It
+ is presumably set by the kernel for each dispatched thread and
+ cannot be changed by user space. It therefore seems safe enough
+ to copy the host's value of it into the guest state at the point
+ the thread is dispatched.
+ (Later): Hmm, looks like SPRG3 is only used in 32-bit mode.
+ Oh well. */
+ { UWord host_sprg3;
+ __asm__ __volatile__( "mfspr %0,259\n" : "=b"(host_sprg3) );
+ VG_(threads)[tid].arch.vex.guest_SPRG3_RO = host_sprg3;
+ vg_assert(sizeof(VG_(threads)[tid].arch.vex.guest_SPRG3_RO) == sizeof(void*));
+ }
+# endif
+
/* there should be no undealt-with signals */
//vg_assert(VG_(threads)[tid].siginfo.si_signo == 0);
+ if (0) {
+ vki_sigset_t m;
+ Int i, err = VG_(sigprocmask)(VKI_SIG_SETMASK, NULL, &m);
+ vg_assert(err == 0);
+ VG_(printf)("tid %d: entering code with unblocked signals: ", tid);
+ for (i = 1; i <= _VKI_NSIG; i++)
+ if (!VG_(sigismember)(&m, i))
+ VG_(printf)("%d ", i);
+ VG_(printf)("\n");
+ }
+
vg_assert(VG_(in_generated_code) == False);
VG_(in_generated_code) = True;
VG_(dispatch_ctr) = SCHEDULING_QUANTUM + 1;
- while(!VG_(is_exiting)(tid)) {
+ while (!VG_(is_exiting)(tid)) {
+
if (VG_(dispatch_ctr) == 1) {
- /* Our slice is done, so yield the CPU to another thread. This
- doesn't sleep between sleeping and running, since that would
- take too much time. */
- VG_(set_sleeping)(tid, VgTs_Yielding);
- /* nothing */
- VG_(set_running)(tid);
+
+# if defined(VGP_ppc32_aix5) || defined(VGP_ppc64_aix5)
+ /* Note: count runnable threads before dropping The Lock. */
+ Int rt = VG_(count_runnable_threads)();
+# endif
+
+ /* Our slice is done, so yield the CPU to another thread. On
+ Linux, this doesn't sleep between sleeping and running,
+ since that would take too much time. On AIX, we have to
+ prod the scheduler to get it consider other threads; not
+ doing so appears to cause very long delays before other
+ runnable threads get rescheduled. */
+
+ /* 4 July 06: it seems that a zero-length nsleep is needed to
+ cause async thread cancellation (canceller.c) to terminate
+ in finite time; else it is in some kind of race/starvation
+ situation and completion is arbitrarily delayed (although
+ this is not a deadlock).
+
+ Unfortunately these sleeps cause MPI jobs not to terminate
+ sometimes (some kind of livelock). So sleeping once
+ every N opportunities appears to work. */
+
+ /* 3 Aug 06: doing sys__nsleep works but crashes some apps.
+ sys_yield also helps the problem, whilst not crashing apps. */
+
+ VG_(set_sleeping)(tid, VgTs_Yielding,
+ "VG_(scheduler):timeslice");
+ /* ------------ now we don't have The Lock ------------ */
+
+# if defined(VGP_ppc32_aix5) || defined(VGP_ppc64_aix5)
+ { static Int ctr=0;
+ vg_assert(__NR_AIX5__nsleep != __NR_AIX5_UNKNOWN);
+ vg_assert(__NR_AIX5_yield != __NR_AIX5_UNKNOWN);
+ if (1 && rt > 0 && ((++ctr % 3) == 0)) {
+ //struct vki_timespec ts;
+ //ts.tv_sec = 0;
+ //ts.tv_nsec = 0*1000*1000;
+ //VG_(do_syscall2)(__NR_AIX5__nsleep, (UWord)&ts, (UWord)NULL);
+ VG_(do_syscall0)(__NR_AIX5_yield);
+ }
+ }
+# endif
+
+ VG_(set_running)(tid, "VG_(scheduler):timeslice");
+ /* ------------ now we do have The Lock ------------ */
/* OK, do some relatively expensive housekeeping stuff */
scheduler_sanity(tid);
VG_(threads)[tid].exitreason = src;
if (src == VgSrc_FatalSig)
VG_(threads)[tid].os_state.fatalsig = VKI_SIGKILL;
- VG_(kill_thread)(tid);
+ VG_(get_thread_out_of_syscall)(tid);
}
}
if (0 || VG_(clo_trace_syscalls) || VG_(clo_trace_sched))
VG_(message)(Vg_DebugMsg,
"__libc_freeres() done; really quitting!");
- VG_(threads)[tid].exitreason = VgSrc_ExitSyscall;
+ VG_(threads)[tid].exitreason = VgSrc_ExitThread;
break;
default:
void scheduler_sanity ( ThreadId tid )
{
Bool bad = False;
+ static UInt lasttime = 0;
+ UInt now;
+ Int lwpid = VG_(gettid)();
if (!VG_(is_running_thread)(tid)) {
VG_(message)(Vg_DebugMsg,
- "Thread %d is supposed to be running, but doesn't own run_sema (owned by %d)\n",
+ "Thread %d is supposed to be running, "
+ "but doesn't own run_sema (owned by %d)\n",
tid, VG_(running_tid));
bad = True;
}
- if (VG_(gettid)() != VG_(threads)[tid].os_state.lwpid) {
+ if (lwpid != VG_(threads)[tid].os_state.lwpid) {
VG_(message)(Vg_DebugMsg,
"Thread %d supposed to be in LWP %d, but we're actually %d\n",
tid, VG_(threads)[tid].os_state.lwpid, VG_(gettid)());
bad = True;
}
+
+ if (lwpid != run_sema.owner_thread) {
+ VG_(message)(Vg_DebugMsg,
+ "Thread %d doesn't own the run_sema\n",
+ tid);
+ bad = True;
+ }
+
+ /* Periodically show the state of all threads, for debugging
+ purposes. */
+ now = VG_(read_millisecond_timer)();
+ if (0 && (!bad) && (lasttime + 4000/*ms*/ <= now)) {
+ lasttime = now;
+ VG_(printf)("\n------------ Sched State at %d ms ------------\n",
+ (Int)now);
+ VG_(show_sched_status)();
+ }
+
+ /* core_panic also shows the sched status, which is why we don't
+ show it above if bad==True. */
+ if (bad)
+ VG_(core_panic)("scheduler_sanity: failed");
}
void VG_(sanity_check_general) ( Bool force_expensive )
{
ThreadId tid;
+ static UInt next_slow_check_at = 1;
+ static UInt slow_check_interval = 25;
+
if (VG_(clo_sanity_level) < 1) return;
/* --- First do all the tests that we can do quickly. ---*/
/* --- Now some more expensive checks. ---*/
- /* Once every 25 times, check some more expensive stuff. */
+ /* Once every now and again, check some more expensive stuff.
+ Gradually increase the interval between such checks so as not to
+ burden long-running programs too much. */
if ( force_expensive
- || VG_(clo_sanity_level) > 1
- || (VG_(clo_sanity_level) == 1 && (sanity_fast_count % 25) == 0)) {
+ || VG_(clo_sanity_level) > 1
+ || (VG_(clo_sanity_level) == 1
+ && sanity_fast_count == next_slow_check_at)) {
+
+ if (0) VG_(printf)("SLOW at %d\n", sanity_fast_count-1);
+ next_slow_check_at = sanity_fast_count - 1 + slow_check_interval;
+ slow_check_interval++;
sanity_slow_count++;
if (VG_(needs).sanity_checks) {
*/
#include "pub_core_basics.h"
+#include "pub_core_debuglog.h"
#include "pub_core_vki.h"
#include "pub_core_libcassert.h"
#include "pub_core_libcfile.h"
pipe-based token passing scheme.
*/
+/* Cycle the char passed through the pipe through 'A' .. 'Z' to make
+ it easier to make sense of strace/truss output - makes it possible
+ to see more clearly the change of ownership of the lock. Need to
+ be careful to reinitialise it at fork() time. */
+static Char sema_char = '!'; /* will cause assertion failures if used
+ before sema_init */
+
void ML_(sema_init)(vg_sema_t *sema)
{
- Int res;
- VG_(pipe)(sema->pipe);
+ Char buf[2];
+ Int res, r;
+ r = VG_(pipe)(sema->pipe);
+ vg_assert(r == 0);
+
+ vg_assert(sema->pipe[0] != sema->pipe[1]);
+
sema->pipe[0] = VG_(safe_fd)(sema->pipe[0]);
sema->pipe[1] = VG_(safe_fd)(sema->pipe[1]);
+ if (0)
+ VG_(debugLog)(0,"zz","sema_init: %d %d\n", sema->pipe[0],
+ sema->pipe[1]);
+ vg_assert(sema->pipe[0] != sema->pipe[1]);
+
sema->owner_thread = -1;
/* create initial token */
- res = VG_(write)(sema->pipe[1], "T", 1);
+ sema_char = 'A';
+ buf[0] = sema_char;
+ buf[1] = 0;
+ sema_char++;
+ res = VG_(write)(sema->pipe[1], buf, 1);
vg_assert(res == 1);
}
void ML_(sema_deinit)(vg_sema_t *sema)
{
+ vg_assert(sema->owner_thread != -1); /* must be initialised */
+ vg_assert(sema->pipe[0] != sema->pipe[1]);
VG_(close)(sema->pipe[0]);
VG_(close)(sema->pipe[1]);
sema->pipe[0] = sema->pipe[1] = -1;
+ sema->owner_thread = -1;
}
/* get a token */
void ML_(sema_down)(vg_sema_t *sema)
{
- Char buf[2] = { 'x' };
+ Char buf[2];
Int ret;
Int lwpid = VG_(gettid)();
vg_assert(sema->owner_thread != lwpid); /* can't have it already */
+ vg_assert(sema->pipe[0] != sema->pipe[1]);
again:
- ret = VG_(read)(sema->pipe[0], buf, 2);
+ buf[0] = buf[1] = 0;
+ ret = VG_(read)(sema->pipe[0], buf, 1);
+
+ if (ret != 1)
+ VG_(debugLog)(0, "scheduler",
+ "VG_(sema_down): read returned %d\n", ret);
if (ret == -VKI_EINTR)
goto again;
vg_assert(ret == 1); /* should get exactly 1 token */
- vg_assert(buf[0] == 'T');
+ vg_assert(buf[0] >= 'A' && buf[0] <= 'Z');
+ vg_assert(buf[1] == 0);
+
+ if (sema_char == 'Z') sema_char = 'A'; else sema_char++;
sema->owner_thread = lwpid;
}
void ML_(sema_up)(vg_sema_t *sema)
{
Int ret;
-
+ Char buf[2];
+ buf[0] = sema_char;
+ buf[1] = 0;
+ vg_assert(sema->owner_thread != -1); /* must be initialised */
+ vg_assert(sema->pipe[0] != sema->pipe[1]);
vg_assert(sema->owner_thread == VG_(gettid)()); /* must have it */
sema->owner_thread = 0;
- ret = VG_(write)(sema->pipe[1], "T", 1);
+ ret = VG_(write)(sema->pipe[1], buf, 1);
+
+ if (ret != 1)
+ VG_(debugLog)(0, "scheduler",
+ "VG_(sema_up):write returned %d\n", ret);
+
vg_assert(ret == 1);
}