[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nptl] Re: Patch for SCHED_RR test with LinuxThreads



Hello, 

> > [
> > though I don't know if I am really sorry: nobody else found those 
> > issues
> > :-D
> > ]
> 
> Do you mean that we're the firsts, or that this is probably not a real
> issue?

No... Excuse my selfish inference, but I was thinking that nobody 
found the issue in _MY_ code.


> > I shall publish the new version by Monday. I want to give me a few 
> > days in order to review the program. 
> 
> No problem. I can run the tests with a minimal effort on a 2x and a 8x
> IA32 xeon, and with SuSe 9.0; SLES 9; FC2. (and standard kernel or
> vanilla; standard glibc or recent one). Is this enough to get some
> conclusions?

Oh, well... fine!

In attachement, pls. find the version 2 of the test program. 
Since it changed quite a lot, I posted the entire version instead 
of the corresponding patch. 

I am a bit busy right now, but I hope I shall come back this week 
and post the "new results". 



Regards,
Loic. 

-- 
--
// Sender address goes to /dev/null (!!) 
// Use my 32/64 bits, ANSI C89, compliant email-address instead:   

unsigned y[]=
{0,34432,26811,16721,41866,63119,61007,48155,26147,10986};
void x(z){putchar(z);}; unsigned t; 
main(i){if(i<10){t=(y[i]*47560)%65521;x(t>>8);x(t&255);main(++i);}}

+++ GMX DSL Premiumtarife 3 Monate gratis* + WLAN-Router 0,- EUR* +++
Clevere DSL-Nutzer wechseln jetzt zu GMX: http://www.gmx.net/de/go/dsl
/*
 *----------------------------------------------------------------------------
 * Copyright (c) 2004, Loic Domaigne. All rights reserved.
 * Created by: Loic Domaigne.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 *---------------------------------------------------------------------------
 *
 * #------------------------#
 * #  To compile            #
 * #------------------------#
 * $ gcc -W -Wall -pthread -lrt test_schedrr.c -o test_schedrr
 *
 *
 * #------------------------#
 * #  To call the program:  #
 * #------------------------#
 * $ schedrr nthreads prio
 *
 * where nthreads is the number of SCHED_RR threads you want to start, and 
 * prio their priority. On some OS, you might need to run this process
 * as root. 
 *
 * Example: 
 * # ./test_schedrr 32 20  ## start 32 SCHED_RR threads at prio 20 
 * 
 *
 * #------------------------#
 * #  Program Tunings:      #
 * #------------------------#
 * You might tune
 *  o  the number of max threads supported by the application (default: 1024) 
 *     by setting NR_MAX_THREADS to another value to the compilation
 *
 *  o  the timeout used for the test (default: 30sec) by setting 
 *     TEST_TIMEOUT 
 *
 * Example: 
 * $ gcc -W -Wall -pthread -DTEST_TIMEOUT=100 -DNR_MAX_THREADS=4096 schedrr.c
 *  
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>

#ifndef _U_
#define _U_ __attribute__((unused))
#endif 

#define FOREVER for(;;) 
#define FIRE_TIMEOUT(tsec)			\
  do {						\
    pthread_mutex_lock (&mutex);		\
    alarm_state=ALARM_FIRED_UP;			\
    pthread_cond_signal (&cv);			\
    while ( alarm_state!=ALARM_RUNNING ) {      \
       pthread_cond_wait (&cv, &mutex);         \
    }                                           \
    pthread_mutex_unlock (&mutex);		\
  } while (0);

#ifndef NR_MAX_THREADS
#define NR_MAX_THREADS 1024
#endif 

#ifndef TEST_TIMEOUT
#define TEST_TIMEOUT 30
#endif

#define ONE_BILLION 1000000000


typedef struct {
  int             created;    /* ture if thread has been created */
  int             started;    /* true if thread has started */
  struct timespec tpstart;    /* time at which thread has been started */
  int             stop;       /* true if thread must stop   */
  unsigned long   counter;    /* kind of cpu usage */
  int             cancelled;  /* true if thread has been cancelled */
} thread_info_t; 

sigset_t          alarm_sigmask; 
int               nthreads;
int               prio;
thread_info_t     info[NR_MAX_THREADS]; 
struct timespec   tpstop;

/* ----------------------------
 * Workaround for LinuxThreads. 
 * ----------------------------
 *
 * alarm() must be called in the alarm_thread to work on LinuxThreads.
 * That's the classical LT limitation that signals are delivered to 
 * particular thread instead to the process. 
 *
 * The "go-mechanism" has been implemented in a very classical 
 * fashion with a pair of cv/mutex. 
 *
 */
enum {
  ALARM_OFF,
  ALARM_FIRED_UP,
  ALARM_RUNNING
} alarm_state = ALARM_OFF; 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cv    = PTHREAD_COND_INITIALIZER;


/*****************************************************************************
 * spawn_thread- starts a thread with a given scheduling policy/priority   
 *****************************************************************************
 *
 * This function is a helper function around pthread_create() to ease the 
 * creation of thread with other scheduling parameters than the default 
 * one.
 *
 * @tid@          : thread's id
 * @policy@       : thread's policy (SCHED_RR, SCHED_FIFO or SCHED_OTHER)
 * @prio@         : thread's priority 
 * @start_routine@: thread's start routine
 * @arg@          : thread's argument 
 *
 *****************************************************************************/
int
spawn_thread(pthread_t* tid,
	     int        policy,
	     int        prio,
	     void*      (*start_routine)(void*),
	     void*      arg
	     )
{
  pthread_attr_t      ta;     /* thread's attribute   */
  struct sched_param  sp;     /* scheduling parameter */
  int                 status; /* returned status      */

  /*
   *  set explicit scheduling to the wanted policy/prio
   */
  (void) pthread_attr_init(&ta);
  status = pthread_attr_setinheritsched(&ta, PTHREAD_EXPLICIT_SCHED);
  assert (status==0);

  status = pthread_attr_setschedpolicy(&ta, policy);
  assert (status==0);

  sp.sched_priority = prio;
  status = pthread_attr_setschedparam(&ta, &sp);
  assert (status==0);

  /*
   *  start corresponding thread
   */
  status = pthread_create(tid, &ta, start_routine, arg);
  return status;
}



/*****************************************************************************
 * dump_thread_info- dump thread's info
 *****************************************************************************
 *
 * This helper function dumps the field for the array @info@ of size @n@ 
 * 
 ****************************************************************************/
void
dump_thread_info(thread_info_t*   info,
		 int              n
		 )
{
  int i;
  
  for (i=0; i<n; i++) {
    /*
     * the idea behind those inc is force eventually cache coherency
     * (a memory barrier would be definitively better)
     */
    info[i].created++;
    info[i].started++;
    info[i].counter++;
    info[i].tpstart.tv_sec++;
    info[i].tpstart.tv_nsec++;

    printf ("Thread %5d: ",i+1);
    if ( info[i].created==1 ) {        // 1 because of +1 
      printf ("NOT CREATED\n");
    } else {
      if (info[i].started==1 ) {       // 1 because of +1 
	printf ("created but NOT STARTED\n");
      } else {
	struct timespec diff;
	diff.tv_sec  = tpstop.tv_sec  - info[i].tpstart.tv_sec + 1 ;
	diff.tv_nsec = tpstop.tv_nsec - info[i].tpstart.tv_nsec + 1 ;
	if ( diff.tv_nsec < 0 ) {
	  diff.tv_nsec += ONE_BILLION;
	  diff.tv_sec--;
	}
	printf ("started for %ld.%09ld sec, sched. %lu times\n",
		(long) diff.tv_sec,
		(long) diff.tv_nsec,
		info[i].counter-1
		);
      }
    }
  }
}


/*****************************************************************************
 * alarm_thread- caught eventual SIGALRM
 *****************************************************************************
 *
 * This thread wait synchronously on eventual receipt of the SIGARLM, and
 * set @alarm_raised@ to 1. 
 *
 * NOTE: 
 * YOU MUST INSURE THAT POLICY/PRIORITY OF TIMEOUT_THREAD IS COMPATIBLE WITH
 * THE OTHER THREADS IN THE PROCESS! IOW, when using RT-scheduling, make sure
 * that this thread has the highest prio.
 *
 *****************************************************************************/
void*
alarm_thread(void* unused _U_)
{
  int             sig_caught;
  int             status;

  /*
   * wait the order to fire the alarm up
   */
  printf ("alarm>   Waiting that alarm is fired up\n");
  pthread_mutex_lock(&mutex);
  while ( alarm_state!=ALARM_FIRED_UP ) {
    status = pthread_cond_wait (&cv, &mutex);
    assert (status==0);
  }
  alarm (TEST_TIMEOUT);
  
  /*
   * signal testdrv_thread that the alarm is running 
   */
  alarm_state = ALARM_RUNNING;
  printf ("alarm>   Alarm activated\n");
  pthread_cond_signal (&cv);
  pthread_mutex_unlock (&mutex);   

  /* 
   * wait synchronously on SIGALRM now 
   */
  sig_caught = 0;
  do {
    (void) sigwait (&alarm_sigmask, &sig_caught);
    if ( sig_caught == SIGALRM) {
      /*
       * something went really wrong: prints out thread info and exits
       */      
      assert ( clock_gettime(CLOCK_REALTIME, &tpstop)==0 );
      dump_thread_info(info, nthreads);
      printf ("TIMED OUT\n");
      _exit (1);
    }
  }
  while ( sig_caught!=SIGALRM);

  return NULL; // keep compiler happy 
}


/*****************************************************************************
 * cpu_thread- a pure compute bound thread.
 *****************************************************************************
 *
 * The following thread is a trivial pure compute bound thread. It does 
 * basically nothing but increments a (thread local) counter before yielding
 * the CPU. 
 *
 ****************************************************************************/
void*
cpu_thread (void* arg)
{
  thread_info_t* info = (thread_info_t*) arg;

  info->started = 1;
  assert ( clock_gettime(CLOCK_REALTIME, &info->tpstart)==0 );

  /*
   * busy loop until stop order received
   */
  while (info->stop==0) {
    info->counter++;
    sched_yield();
  }
  return NULL;
}


/*****************************************************************************
 * testdrv_thread- main test driver thread
 *****************************************************************************
 *
 * This is the thread that does the real work. It starts @nthreads@ SCHED_RR
 * threads running at priority @prio@. It waits then (using the thread info)
 * that all the threads have been started, before terminating the process.
 *
 * In order to avoid that the process hangs indefinitively, a global timeout
 * of TEST_TIMEOUT seconds is set right at the beginning. Each steps where 
 * a hang can occur is protected by testing if the alarm has been raised 
 * with the @alarm_raised@ flag.
 *
 ****************************************************************************/
void*
testdrv_thread(void* unused _U_)
{
  pthread_t      thrid[NR_MAX_THREADS];
  int            status;
  int            nrunning;
  int            i;

  /*
   *  set-up global timeout
   */
  FIRE_TIMEOUT(TEST_TIMEOUT);

  /*
   * start the @nthreads@ SCHED_RR threads at priority @prio@
   */
  printf ("testdrv> Starting %d threads SCHED_RR with prio %d\n",
	  nthreads,
	  prio
	  );
  for (i=0; i<nthreads; i++) {
    info[i].started = 0;
    info[i].stop    = 0;
    info[i].counter = 0;
    info[i].tpstart.tv_sec  =  0;
    info[i].tpstart.tv_nsec = -1;
    status = spawn_thread (&thrid[i], SCHED_RR, prio, cpu_thread, &info[i]);
    info[i].created = 1;
    if (status!=0) fprintf(stderr,"%s\n",strerror(status));
    assert (status==0);
  }
  printf ("testdrv> The %d SCHED_RR threads running at prio %d "
	  "have been created\n",
	  nthreads,
	  prio
	  );

  /*
   * now wait that all threads run
   */
  printf ("testdrv> Wait that all threads run... \n");
  do {
    nrunning = 0;
    for (i=0; i<nthreads; i++) {
      if (info[i].started==1) nrunning++;
    }
  }
  while ( nrunning!=nthreads); 

  /*
   * succeed!
   */
  assert ( clock_gettime(CLOCK_REALTIME, &tpstop)==0 );
  printf ("PASSED\n");
  exit (0);
}


/*###########################################################################*/
/*## MAIN PROCESS ENTRY POINT                                              ##*/
/*###########################################################################*/
int
main(int    argc,
     char*  argv[]
     )
{
  pthread_t  alarm_thrid;
  pthread_t  testdrv_thrid; 
  int        status; 
  int        minprio, maxprio;

  /*
   * process main args
   */
  assert (argc!=2);
  nthreads = atoi(argv[1]);
  prio     = atoi(argv[2]);
  assert (1 <= nthreads && nthreads <= NR_MAX_THREADS );
  maxprio = sched_get_priority_max (SCHED_RR);
  minprio = sched_get_priority_min (SCHED_RR);
  assert ( minprio <= prio && prio <= (maxprio-2) ); 

  /*
   * setup signal mask that shall be inherited by all threads
   */
  (void) sigemptyset (&alarm_sigmask);
  status = sigaddset (&alarm_sigmask, SIGALRM);
  assert (status==0);
  status = pthread_sigmask (SIG_BLOCK, &alarm_sigmask, NULL);
  assert (status==0);
  
  /*
   * start timeout handler thread
   */
  printf ("main>    Spawn alarm thread\n");
  status = spawn_thread (&alarm_thrid, SCHED_RR, prio+2, alarm_thread, NULL);
  assert (status==0);
  
  /*
   * start thread conducting the experiment
   */
  printf ("main>    Spawn test driver thread\n");
  status = spawn_thread (&testdrv_thrid, SCHED_RR, prio+1, testdrv_thread, NULL);
  assert (status==0);
  
  /*
   * shall be never joined 
   */
  status = pthread_join (testdrv_thrid, NULL);
  return 0;
}