[Kos-dev] "Race condition" 1

Thomas Petazzoni kos-dev@enix.org
Tue, 27 May 2003 20:28:51 +0200


This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
--------------enigFBB0811E98ABCA6258800B1D
Content-Type: multipart/mixed;
 boundary="------------040802060708090104010601"

This is a multi-part message in MIME format.
--------------040802060708090104010601
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

Hello,

> A mon avis il faut privilegier la premiere explication (ie
> tester...)..., puis faire :
>   - modif eflags dans init_kernel_thread_context pour positionner
>     le bit IE a 0
>   - modif execute_thread pour ajouter :

J'ai fait ces modifications :
 * initialisation à 2 de Eflags (donc sans interruptions)
 * modification de execute_thread() pour appeler
cpl0_delete_pending_thread() avant de lancer le thread.
 * modification de cpl0_switch_with_return pour supprimer un thread en
attente de suppression.

Normalement, ça devrait fonctionner : lorsqu'un thread est marqué comme
étant à supprimer dans cpl0_switch_no_return, on a deux choix :
 * soit le prochain thread à éxécuter est nouveau -> on se retrouve donc
dans execute_thread() et on supprime le thread.
 * soit le prochain thread à éxécuter avait déjà été éxécuté au moins
une fois -> on se retrouve (théoriquement ?) au niveau de get_there, et
donc ensuite on se retrouve juste après le
cpl0_switch_with_return_internal. Et normalement, il y a la suppression
effective du thread à ce moment.

Malheureusement, il y a encore une race condition quelque part, parce
que quand j'ai 2 suppressions de threads très très rapprochées :

[cpl0_switch_no_return@_cpl0_switch.c:15] **** System Halted **** :
Assertion thread_to_be_destroyed == NULL failed

Warning: This handler may be overloaded
----------- Backtrace --------------
Warning: current thread not identified
bt[0] = debug.ro:__dbg_backtrace+0x8b (0x0, 0x0, 0x40092fc4, 0x3008e2f4)
bt[1] = debug.ro:__dbg_halt_handler+0x30 (0x0, 0x0, 0x40092fe4, 0x300937c6)
bt[2] = arch-task.ro:cpl0_switch_no_return+0x139 (0x4003bf8c,
0x300c2000, 0x8, 0x212)
bt[3] = sched.ro:reschedule_after_termination+0x1a2 (0x3009b27c, 0x5f,
0x0, 0x0)
bt[4] = debug.ro:__dbg_backtrace+0x8b (0x0, 0x0, 0x40092fc4, 0x3008e2f4)
bt[5] = debug.ro:__dbg_halt_handler+0x30 (0x0, 0x0, 0x40092fe4, 0x300937c6)
------------------------------------

Ceci signifie qu'on est rentré dans cpl0_switch_no_return (destruction
d'un thread), alors que le précédent thread marqué comme étant à
détruire n'a pas été supprimé.

Pourtant, j'ai bien mis des ASSERTions de partout pour vérifier que les
interruptions sont désactivés, mais j'ai l'impression que ça marche
quand même pas.

A noter que pour reproduire le bug, il faut utiliser le test du fichier
joint, et que je n'ai pas réussi à reproduire le bug si je mets moins de
100 threads. Ca doit être possible, mais bon ;-)

J'inclus en attaché le code de _thread_cpu_context.c et _cpl0_switch.c.
A noter qu'il faut faire une modif dans kos/spinlock.h, car la macro
test_interrupt_enabled() déclare une variable "flags", ce qui est pas
très malin.

Thomas
-- 
PETAZZONI Thomas - thomas.petazzoni@enix.org - UIN : 34937744
http://www.enix.org/~thomas/
KOS: http://kos.enix.org/ - Lolut: http://lolut.utbm.info
Fingerprint : 0BE1 4CF3 CEA4 AC9D CC6E  1624 F653 CB30 98D3 F7A7

--------------040802060708090104010601
Content-Type: text/x-csrc;
 name="_cpl0_switch.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="_cpl0_switch.c"


#include <kos/spinlock.h>
#include <task/task.h>
#include <arch/task/_task.h>
#include <idt/idt.h>

/* FIXME for SMP : one per processor */
static thread_t *thread_to_be_destroyed = NULL;

void cpl0_switch_no_return (cpu_state_t *cpu_state,
			    thread_t *myself)
{
  ASSERT_FATAL(test_interrupt_enabled() == FALSE);
  ASSERT_FATAL(get_hw_isr_nested_level() == 0);
  ASSERT_FATAL(thread_to_be_destroyed == NULL);
  thread_to_be_destroyed = myself;
  cpl0_switch_no_return_internal(cpu_state);
  ASSERT(FALSE);
}

void cpl0_delete_pending_thread()
{
  ASSERT_FATAL(test_interrupt_enabled() == FALSE);
  if (thread_to_be_destroyed != NULL)
    {
      thread_t *thread_to_destroy = thread_to_be_destroyed;
      thread_to_be_destroyed = NULL;
      delete_kernel_thread(thread_to_destroy);
    }
  ASSERT_FATAL(test_interrupt_enabled() == FALSE);
}

void cpl0_switch_with_return (cpu_state_t **from_context, 
			      cpu_state_t *to_context)
{
  ASSERT_FATAL(test_interrupt_enabled() == FALSE);
  ASSERT_FATAL(get_hw_isr_nested_level() == 0);

  cpl0_switch_with_return_internal(from_context, to_context);

  __dbg_printk("Restarting thread\n");

  if (thread_to_be_destroyed != NULL)
    {
      thread_t *thread_to_destroy = thread_to_be_destroyed;
      thread_to_be_destroyed = NULL;
      delete_kernel_thread(thread_to_destroy);
    }

  ASSERT_FATAL(test_interrupt_enabled() == FALSE);
}

--------------040802060708090104010601
Content-Type: text/x-csrc;
 name="_thread_cpu_context.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="_thread_cpu_context.c"

/*
 * Copyright (C) 2000, Thomas PETAZZONI
 * http://kos.enix.org
 *
 * Functions needed to create and delete user threads.
 *
 * @(#) $Id: _thread_cpu_context.c,v 1.9 2003/05/25 14:32:13 thomas Exp $
 */

#include <kos/assert.h>
#include <kos/system.h>
#include <lib/string.h>
#include <pmm/pmm.h>
#include <kos/macros.h>
#include <kos/spinlock.h>

#include "_task.h"

static void execute_thread(kernel_thread_fct_t *kt, void *data)
{
  k_ui32_t flags;

  __dbg_printk("kt=0x%x, data=0x%x\n", kt, data);

  ASSERT_FATAL(test_interrupt_enabled() == FALSE);
  cpl0_delete_pending_thread();
  ASSERT_FATAL(test_interrupt_enabled() == FALSE);

  save_flags(flags);
  flags |= (1 << 9);
  restore_flags(flags);

  kt(data);
  reschedule_after_termination();
}

void init_kernel_thread_context (thread_t *thread,
				 kernel_thread_fct_t *kt, 
				 void *data)
{
  cpu_state_t *new_cpu_state;

  /* we reserve 4 bytes to store the kernel thread parameter */
  thread->cpu_context.cpl0 = (cpu_state_t*)
    (thread->cpl0_stack_base_addr + 
     CPL0_KERNEL_STACK_VIRTUAL_SIZE - 
     (sizeof(cpu_state_t) + 
      sizeof(struct cpl0_call_parameters)));

  new_cpu_state = thread->cpu_context.cpl0;

  memset(new_cpu_state, 0x0, sizeof(cpu_state_t));
  new_cpu_state->eip    = (addr_t) execute_thread;
  new_cpu_state->cs     = SEG_CODE_KERNEL_ID << 3;
  new_cpu_state->eflags = 2;
  new_cpu_state->ss     = SEG_DATA_KERNEL_ID << 3;
  new_cpu_state->ds     = SEG_DATA_KERNEL_ID << 3;
  new_cpu_state->es     = SEG_DATA_KERNEL_ID << 3;
  new_cpu_state->fs     = SEG_DATA_KERNEL_ID << 3;
  new_cpu_state->gs     = SEG_DATA_KERNEL_ID << 3;

  new_cpu_state->u.cpl0.return_addr   = (unsigned) 0;
  new_cpu_state->u.cpl0.thread_fct    = (unsigned) kt;
  new_cpu_state->u.cpl0.thread_arg    = (unsigned) data;
}

void init_user_thread_context (thread_t *thread, 
			       addr_t eip,
			       void *data)
{
  cpu_state_t *new_cpu_state;
  addr_t ppage;

  UNUSED(data);
  
  thread->cpu_context.cpl0 = (cpu_state_t*)
    ( thread->cpl0_stack_base_addr +
      CPL0_USER_STACK_VIRTUAL_SIZE - (sizeof(cpu_state_t) + 8));
  new_cpu_state = thread->cpu_context.cpl0;

  __dbg_printk(_B_RED "Addr of CPL0 Stack is 0x%x (base=0x%x)\n" _B_NORM, 
	       thread->cpu_context.cpl0,
	       thread->cpl0_stack_base_addr);
  
  memset(new_cpu_state, 0x0, sizeof(cpu_state_t));
  new_cpu_state->eip = eip;
  new_cpu_state->cs  = (SEG_CODE_USER_ID << 3) | 3;
  new_cpu_state->eflags = 514 | (1<<12) | (1<<13);

  /* Even if we have a CPL3 thread, this 'ss' must point to the data
     kernel segment. The real ss which is going to be used is defined
     below */
  new_cpu_state->ss     = SEG_DATA_KERNEL_ID << 3;
  new_cpu_state->ds     = (SEG_DATA_USER_ID << 3) | 3;
  new_cpu_state->es     = (SEG_DATA_USER_ID << 3) | 3;
  new_cpu_state->fs     = (SEG_DATA_USER_ID << 3) | 3;
  new_cpu_state->gs     = (SEG_DATA_USER_ID << 3) | 3;

  
  /* Address of the CPL3 stack */
  new_cpu_state->u.cpl3.esp3 = 
    (unsigned) (CPL3_HEAP_ADDR + CPL3_HEAP_SIZE - PAGE_SIZE - 4);

  /* Segment selector for the CPL3 stack (used when going to lower
     privilege) */
  new_cpu_state->u.cpl3.ss3 =
    (SEG_DATA_USER_ID << 3) | 3;
  /*
  new_cpu_state->u.cpl3.return_addr =
    (unsigned) user_thread_end_handler;

  new_cpu_state->u.cpl3.return_cs = 
    (SEG_CODE_KERNEL_ID << 3);
  */

  /*
  __dbg_printk(_B_RED "ESP3 = 0x%x (0x%x)\n" _B_NORM,
	       (unsigned)new_cpu_state->u.cpl3.esp3,
	       &new_cpu_state->u.cpl3.return_addr);
  */
  ppage = get_physical_page(PHYS_PAGE_USER, PHYS_PAGE_SWAPPABLE);
  CONCEPTION_ASSERT(ppage != 0);

  __dbg_printk(_B_RED "Mapping 0x%x=>0x%x (%d)\n" _B_NORM,
	       PAGE_ALIGN_INF(new_cpu_state->u.cpl3.esp3),
	       ppage, thread->mother_team->mm_context.pd_index_in_pd_table);
  
  arch_map_virtual_page(& thread->mother_team->mm_context,
			PAGE_ALIGN_INF(new_cpu_state->u.cpl3.esp3),
			ppage,
			VM_ACCESS_READ | VM_ACCESS_WRITE);

}

--------------040802060708090104010601
Content-Type: text/x-csrc;
 name="sleep_test.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="sleep_test.c"

/*
 * Copyright (C) 2000, Thomas Petazzoni
 * http://kos.enix.org
 *
 * Sleep function test.
 *
 * @(#) $Id$
 */

#include <scheduler/scheduler.h>
#include <debug/debug.h>
#include <task/task.h>
#include <test/_test.h>

#define TEST_NB_THREADS 100
#define TEST_DURATION   100

static uint thread_count = 0;

static void sleep_test_thread(void *data)
{
  int i;
  int delay = (int) 1;

  for (i = 0; i < TEST_DURATION; i++)
    {
      //      __dbg_printk("%c", ('A'-1+delay));
      usleep(delay * 10000);
    }

  __dbg_printk("Ended : %d\n", delay);
  thread_count--;
}

result_t sleep_test()
{
  int i;

  for (i = 0; i < TEST_NB_THREADS; i++)
    {
      create_kernel_thread(NULL, sleep_test_thread, (void *) (i+1));
      thread_count ++;
    }

  while(thread_count != 0) 
    {
      usleep(1000000);
    }

  __dbg_printk("Test done\n");

  return 0;
}

--------------040802060708090104010601--

--------------enigFBB0811E98ABCA6258800B1D
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (GNU/Linux)
Comment: Using GnuPG with Debian - http://enigmail.mozdev.org

iD8DBQE+065n9lPLMJjT96cRAt8zAJ93RSYUKpTig5eWwmPphDi5rHEJOgCgqxEC
3i8Hie2rKO6ektthW2kR0r0=
=yJ9b
-----END PGP SIGNATURE-----

--------------enigFBB0811E98ABCA6258800B1D--