[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--