Skip to content

Commit b56724b

Browse files
committed
Use address space reference counting to fix leak
Previously, thread_destroy() never released address space references, causing AS structures to leak when threads were destroyed. The shared counter was incremented when threads shared an AS but never decremented. Address space is now freed when the last thread holding a reference is destroyed, rather than leaking indefinitely.
1 parent ce791fa commit b56724b

6 files changed

Lines changed: 113 additions & 19 deletions

File tree

include/memory.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,20 @@ typedef struct {
2323

2424
struct fpage *mpu_first; /*! head of MPU fpage list */
2525
struct fpage *mpu_stack_first; /*! head of MPU stack fpage list */
26-
uint32_t shared; /*! shared user number */
26+
uint32_t shared; /*! Reference count: number of threads using this AS */
2727
} as_t;
2828

29+
/*
30+
* Address space reference counting.
31+
* as_get(): Increment reference count (thread sharing this AS)
32+
* as_put(): Decrement reference count; frees AS when it reaches 0
33+
*
34+
* Thread must be unlinked from AS before calling as_put().
35+
* IRQ protection ensures atomicity of refcount operations.
36+
*/
37+
void as_get(as_t *as);
38+
void as_put(as_t *as);
39+
2940
/**
3041
* Memory pool represents area of physical address space
3142
* We set flags to it (kernel & user permissions),

include/platform/irq.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ static inline void irq_enable(void)
2525
__asm__ __volatile__ ("cpsie i" ::: "memory");
2626
}
2727

28+
/*
29+
* Save and restore interrupt state (PRIMASK).
30+
* Used for critical sections that may be nested or called from
31+
* contexts where IRQ state is unknown.
32+
*/
33+
static inline uint32_t irq_save_flags(void)
34+
{
35+
uint32_t flags;
36+
__asm__ __volatile__ (
37+
"mrs %0, primask\n\t"
38+
"cpsid i"
39+
: "=r" (flags) :: "memory");
40+
return flags;
41+
}
42+
43+
static inline void irq_restore_flags(uint32_t flags)
44+
{
45+
__asm__ __volatile__ (
46+
"msr primask, %0"
47+
:: "r" (flags) : "memory");
48+
}
49+
2850
static inline void irq_svc(void)
2951
{
3052
__asm__ __volatile__ ("svc #0");

include/thread.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ tcb_t *thread_init(l4_thread_t globalid, utcb_t *utcb);
133133
tcb_t *thread_create(l4_thread_t globalid, utcb_t *utcb);
134134
void thread_destroy(tcb_t *thr);
135135
void thread_space(tcb_t *thr, l4_thread_t spaceid, utcb_t *utcb);
136-
void thread_free_space(tcb_t *thr);
137136
void thread_init_ctx(void *sp, void *pc, void *rx, tcb_t *thr);
138137
void thread_init_kernel_ctx(void *sp, tcb_t *thr);
139138
void thread_switch(tcb_t *thr);

kernel/memory.c

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <init_hook.h>
1313
#include <kip.h>
1414
#include <platform/cortex_m.h>
15+
#include <platform/irq.h>
1516

1617
/**
1718
* @file memory.c
@@ -378,31 +379,79 @@ as_t *as_create(uint32_t as_spaceid)
378379

379380
as->as_spaceid = as_spaceid;
380381
as->first = NULL;
381-
as->shared = 0;
382+
as->mpu_first = NULL;
383+
as->mpu_stack_first = NULL;
384+
as->shared = 1; /* Creator holds initial reference */
382385

383386
return as;
384387
}
385388

386-
void as_destroy(as_t *as)
389+
/*
390+
* Increment address space reference count.
391+
* Called when a thread shares an existing address space.
392+
*/
393+
void as_get(as_t *as)
387394
{
388-
fpage_t *fp, *fpnext;
389-
fp = as->first;
395+
uint32_t flags;
390396

391-
if (as->shared > 0) {
392-
--as->shared;
397+
if (!as)
393398
return;
399+
400+
flags = irq_save_flags();
401+
as->shared++;
402+
irq_restore_flags(flags);
403+
}
404+
405+
/*
406+
* Decrement address space reference count.
407+
* Frees the address space when refcount reaches 0.
408+
* IRQs remain disabled through as_destroy() to prevent reentrancy.
409+
*/
410+
void as_put(as_t *as)
411+
{
412+
uint32_t flags;
413+
414+
if (!as)
415+
return;
416+
417+
flags = irq_save_flags();
418+
419+
/* Guard against refcount underflow (double-put bug) */
420+
if (as->shared == 0) {
421+
irq_restore_flags(flags);
422+
dbg_printf(DL_KDB, "AS: refcount underflow for %p\n",
423+
as->as_spaceid);
424+
return;
425+
}
426+
427+
if (--as->shared == 0) {
428+
/* Keep IRQs disabled through destroy */
429+
as_destroy(as);
394430
}
431+
irq_restore_flags(flags);
432+
}
433+
434+
/*
435+
* Free all fpages and the address space structure.
436+
* Called by as_put() when refcount reaches 0.
437+
* Caller must hold IRQs disabled.
438+
*/
439+
void as_destroy(as_t *as)
440+
{
441+
fpage_t *fp, *fpnext;
442+
fp = as->first;
395443

396444
/*
397445
* FIXME: What if a CLONED fpage which is MAPPED is to be deleted
398446
*/
399447
while (fp) {
448+
fpnext = fp->as_next;
449+
400450
if (fp->fpage.flags & FPAGE_CLONE)
401451
unmap_fpage(as, fp);
402452
else
403453
destroy_fpage(fp);
404454

405-
fpnext = fp->as_next;
406455
fp = fpnext;
407456
}
408457

kernel/syscall.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ static void sys_thread_control(uint32_t *param1, uint32_t *param2)
8181
param1[REG_R0] = 0;
8282
return;
8383
}
84-
thread_free_space(thr);
84+
/* thread_destroy() handles AS refcount via as_put() */
8585
thread_destroy(thr);
8686
param1[REG_R0] = 1;
8787
}

kernel/thread.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ void thread_destroy(tcb_t *thr)
277277
caller->t_child = thr->t_child;
278278
}
279279

280+
/* Release address space reference.
281+
* This may free the AS if this was the last reference.
282+
*/
283+
if (thr->as)
284+
as_put(thr->as);
285+
280286
thread_deinit(thr);
281287
}
282288

@@ -310,10 +316,23 @@ void thread_space(tcb_t *thr, l4_thread_t spaceid, utcb_t *utcb)
310316
dbg_printf(DL_THREAD,
311317
"\tNew space: as: %p, utcb: %p \n", thr->as, utcb);
312318
} else {
313-
tcb_t *space = thread_by_globalid(spaceid);
319+
tcb_t *space;
320+
as_t *shared_as;
314321

315-
thr->as = space->as;
316-
++(space->as->shared);
322+
irq_disable();
323+
space = thread_by_globalid(spaceid);
324+
if (!space || !space->as) {
325+
irq_enable();
326+
dbg_printf(DL_KDB,
327+
"THREAD: space thread %t not found\n",
328+
spaceid);
329+
return;
330+
}
331+
shared_as = space->as;
332+
as_get(shared_as);
333+
irq_enable();
334+
335+
thr->as = shared_as;
317336
}
318337

319338
/* If no caller, than it is mapping from kernel to root thread
@@ -333,12 +352,6 @@ void thread_space(tcb_t *thr, l4_thread_t spaceid, utcb_t *utcb)
333352
utcb, thr->t_globalid);
334353
}
335354

336-
void thread_free_space(tcb_t *thr)
337-
{
338-
/* free address space */
339-
as_destroy(thr->as);
340-
}
341-
342355
void thread_init_ctx(void *sp, void *pc, void *regs, tcb_t *thr)
343356
{
344357
int i;

0 commit comments

Comments
 (0)