Added support for faulty pagers and their threads to become zombies

Added support for pagers that fault to suspend and become zombies
along with all the threads that they manage. Zombie killing is to
be done at a later time, from this special zombie queue.

The implementation works same as a suspension, with the added action
that the thread is moved to a queue in kernel container.
This commit is contained in:
Bahadir Balban
2009-10-19 18:48:55 +03:00
parent 9177166817
commit cfa35e4a66
8 changed files with 110 additions and 78 deletions

View File

@@ -8,8 +8,6 @@
#define __RESOURCES_H__
/* Number of containers defined at compile-time */
#include <l4/config.h>
#include <l4/generic/capability.h>
#include <l4/lib/idpool.h>
#include INC_SUBARCH(mm.h)
@@ -41,6 +39,13 @@ container_head_init(struct container_head *chead)
link_init(&chead->list);
}
/* Hash table for all existing tasks */
struct ktcb_list {
struct link list;
struct spinlock list_lock;
int count;
};
/*
* Everything on the platform is described and stored
* in the structure below.
@@ -81,6 +86,9 @@ struct kernel_container {
struct mem_cache *mutex_cache;
struct mem_cache *cap_cache;
struct mem_cache *cont_cache;
/* Zombie thread list */
struct ktcb_list zombie_list;
};
extern struct kernel_container kernel_container;

View File

@@ -133,13 +133,6 @@ union ktcb_union {
char kstack[PAGE_SIZE];
};
/* Hash table for all existing tasks */
struct ktcb_list {
struct link list;
struct spinlock list_lock;
int count;
};
/*
* Each task is allocated a unique global id. A thread group can only belong to
* a single leader, and every thread can only belong to a single thread group.
@@ -161,6 +154,8 @@ void tcb_init(struct ktcb *tcb);
struct ktcb *tcb_alloc_init(void);
void tcb_delete(struct ktcb *tcb);
void ktcb_list_add(struct ktcb *new, struct ktcb_list *ktcb_list);
void init_ktcb_list(struct ktcb_list *ktcb_list);
void task_update_utcb(struct ktcb *task);
int tcb_check_and_lazy_map_utcb(struct ktcb *task);

View File

@@ -11,7 +11,8 @@ void thread_id_pool_init(void);
int thread_id_new(void);
int thread_id_del(int tid);
void thread_destroy_self(void);
void thread_destroy_current(void);
int thread_destroy(struct task_ids *ids);
void thread_make_zombie(struct ktcb *task);
#endif /* __THREAD_H__ */

View File

@@ -24,9 +24,24 @@ int sys_thread_switch(void)
/*
* This suspends a thread which is in either suspended,
* sleeping or runnable state.
* sleeping or runnable state. `flags' field specifies an additional
* status for the thread, that implies an additional action as well
* as suspending. For example, a TASK_EXITING flag ensures the task
* is moved to a zombie queue during suspension.
*
* Why no race?
*
* There is no race between the pager setting TASK_SUSPENDING,
* and waiting for TASK_INACTIVE non-atomically because the target
* task starts suspending only when it sees TASK_SUSPENDING set and
* it only wakes up the pager after it has switched state to
* TASK_INACTIVE.
*
* If the pager hasn't come to wait_event() and the wake up call is
* already gone, the state is already TASK_INACTIVE so the pager
* won't sleep at all.
*/
int thread_suspend(l4id_t tid)
int thread_suspend(l4id_t tid, unsigned int flags)
{
struct ktcb *task;
int ret = 0;
@@ -38,7 +53,7 @@ int thread_suspend(l4id_t tid)
return 0;
/* Signify we want to suspend the thread */
task->flags |= TASK_SUSPENDING;
task->flags |= TASK_SUSPENDING | flags;
/* Wake it up if it's sleeping */
wake_up_task(task, WAKEUP_INTERRUPT | WAKEUP_SYNC);
@@ -91,7 +106,7 @@ int thread_recycle(struct task_ids *ids)
if (!(task = tcb_find(ids->tid)))
return -ESRCH;
if ((ret = thread_suspend(ids->tid)) < 0)
if ((ret = thread_suspend(ids->tid, 0)) < 0)
return ret;
/*
@@ -113,7 +128,7 @@ int thread_recycle(struct task_ids *ids)
return 0;
}
void thread_destroy_self();
void thread_destroy_current();
int thread_destroy(l4id_t tid)
{
@@ -126,14 +141,14 @@ int thread_destroy(l4id_t tid)
* Pager destroying itself
*/
if (tid == current->tid) {
thread_destroy_self();
thread_destroy_current();
BUG();
}
if (!(task = tcb_find(tid)))
return -ESRCH;
if ((ret = thread_suspend(tid)) < 0)
if ((ret = thread_suspend(tid, 0)) < 0)
return ret;
/* Remove tcb from global list so any callers will get -ESRCH */
@@ -152,16 +167,36 @@ int thread_destroy(l4id_t tid)
return 0;
}
void thread_make_zombie(struct ktcb *task)
{
/* Remove from its list, callers get -ESRCH */
tcb_remove(task);
/*
* If there are any sleepers on any of the task's
* waitqueues, we need to wake those tasks up.
*/
wake_up_all(&task->wqh_send, 0);
wake_up_all(&task->wqh_recv, 0);
printk("Thread (%d) becoming zombie. (Current: (%d))\n", task->tid, current->tid);
BUG_ON(!(task->flags & TASK_EXITING));
/* Add to zombie list, to be destroyed later */
ktcb_list_add(task, &kernel_container.zombie_list);
}
/*
* Pagers destroy themselves either by accessing an illegal
* address or voluntarily. All threads managed also get
* destroyed.
*/
void thread_destroy_self(void)
void thread_destroy_current(void)
{
struct ktcb *task, *n;
/* Destroy all threads under control of this pager */
/* Suspend all threads under control of this pager */
spin_lock(&curcont->ktcb_list.list_lock);
list_foreach_removable_struct(task, n,
&curcont->ktcb_list.list,
@@ -169,41 +204,15 @@ void thread_destroy_self(void)
if (task->tid == current->tid)
continue;
spin_unlock(&curcont->ktcb_list.list_lock);
thread_destroy(task->tid);
thread_suspend(task->tid, TASK_EXITING);
spin_lock(&curcont->ktcb_list.list_lock);
}
spin_unlock(&curcont->ktcb_list.list_lock);
/*
* Indicate intention to destroy to any
* destroyer code we will add later on
*/
/* Indicate we want to become zombie on suspend */
current->flags |= TASK_EXITING;
tcb_remove(current);
/*
* If there are any sleepers on any of the task's
* waitqueues, we need to wake those tasks up.
*
* These could be tasks that have called us from
* other containers.
*/
wake_up_all(&current->wqh_send, 0);
wake_up_all(&current->wqh_recv, 0);
printk("%s: Suspending self (%d)\n", __FUNCTION__, current->tid);
/* Remain as a zombie for now */
sched_suspend_sync();
/* NOTE:
* If we deleted ourself here, probably that would be
* the end of what we need to do and could get away
* with that because ktcb's are cached and always mapped.
* It wouldn't hurt to delete ourself inside a
* non-preemptable point and schedule never to return.
*/
}
int thread_resume(struct task_ids *ids)
@@ -432,7 +441,7 @@ int sys_thread_control(unsigned int flags, struct task_ids *ids)
ret = thread_start(ids);
break;
case THREAD_SUSPEND:
ret = thread_suspend(ids->tid);
ret = thread_suspend(ids->tid, 0);
break;
case THREAD_RESUME:
ret = thread_resume(ids);

View File

@@ -79,21 +79,29 @@ void fault_ipc_to_pager(u32 faulty_pc, u32 fsr, u32 far)
fault->fsr = fsr;
fault->far = far;
/* Write pte of the abort address, which is different on pabt/dabt */
/*
* Write pte of the abort address,
* which is different on pabt/dabt
*/
if (is_prefetch_abort(fsr))
fault->pte = virt_to_pte(faulty_pc);
else
fault->pte = virt_to_pte(far);
/*
* System calls save arguments (and message registers) on the kernel
* stack. They are then referenced from the caller's ktcb. Here, we
* forge a fault structure as if an ipc syscall has occured. Then
* the reference to the fault structure is set in the ktcb such that
* it lies on the mr0 offset when referred as the syscall context.
* System calls save arguments (and message registers)
* on the kernel stack. They are then referenced from
* the caller's ktcb. Here, we forge a fault structure
* as if an ipc syscall has occured. Then the reference
* to the fault structure is set in the ktcb such that
* it lies on the mr0 offset when referred as the syscall
* context.
*/
/* Assign fault such that it overlaps as the MR0 reference in ktcb. */
/*
* Assign fault such that it overlaps
* as the MR0 reference in ktcb.
*/
current->syscall_regs = (syscall_context_t *)
((unsigned long)&mr[0] -
offsetof(syscall_context_t, r3));
@@ -105,25 +113,28 @@ void fault_ipc_to_pager(u32 faulty_pc, u32 fsr, u32 far)
if (current->tid == current->pagerid) {
printk("Pager (%d) self-faulting. Exiting.\n",
current->tid);
thread_destroy_self();
thread_destroy_current();
}
/* Send ipc to the task's pager */
if ((err = ipc_sendrecv(current->pagerid,
current->pagerid, 0)) < 0) {
//printk("Thread (%d) faulted in kernel and its pager "
// "returned error (%d). Suspending.\n",
// current->tid, err);
printk("Thread (%d) faulted in kernel and its pager "
"returned error (%d). Suspend and exiting thread.\n",
current->tid, err);
BUG_ON(current->nlocks);
current->flags |= TASK_EXITING;
sched_suspend_sync();
}
}
/*
* When a task calls the kernel and the supplied user buffer is not mapped, the kernel
* generates a page fault to the task's pager so that the pager can make the decision
* on mapping the buffer. Remember that if a task maps its own user buffer to itself
* this way, the kernel can access it, since it shares that task's page table.
* When a task calls the kernel and the supplied user buffer is
* not mapped, the kernel generates a page fault to the task's
* pager so that the pager can make the decision on mapping the
* buffer. Remember that if a task maps its own user buffer to
* itself this way, the kernel can access it, since it shares
* that task's page table.
*/
int pager_pagein_request(unsigned long addr, unsigned long size,
unsigned int flags)
@@ -251,12 +262,6 @@ void data_abort_handler(u32 faulted_pc, u32 fsr, u32 far)
/* This notifies the pager */
fault_ipc_to_pager(faulted_pc, fsr, far);
if (current->flags & TASK_SUSPENDING) {
BUG_ON(current->nlocks);
sched_suspend_sync();
}
return;
error:
@@ -269,6 +274,7 @@ error:
while (1)
;
}
void prefetch_abort_handler(u32 faulted_pc, u32 fsr, u32 far, u32 lr)
{
set_abort_type(fsr, ARM_PABT);
@@ -280,11 +286,6 @@ void prefetch_abort_handler(u32 faulted_pc, u32 fsr, u32 far, u32 lr)
if (KERN_ADDR(lr))
goto error;
fault_ipc_to_pager(faulted_pc, fsr, far);
if (current->flags & TASK_SUSPENDING) {
BUG_ON(current->nlocks);
sched_suspend_sync();
}
return;
error:
@@ -306,14 +307,17 @@ void dump_undef_abort(u32 undef_addr)
}
extern int current_irq_nest_count;
/*
* This is called right where the nest count is increased in case the nesting
* is beyond the predefined max limit. It is another matter whether this
* limit is enough to guarantee the kernel stack is not overflown.
* This is called right where the nest count is increased
* in case the nesting is beyond the predefined max limit.
* It is another matter whether this limit is enough to
* guarantee the kernel stack is not overflown.
*/
void irq_overnest_error(void)
{
dprintk("Irqs nested beyond limit. Current count: ", current_irq_nest_count);
dprintk("Irqs nested beyond limit. Current count: ",
current_irq_nest_count);
printascii("Halting system...\n");
while(1)
;

View File

@@ -399,6 +399,8 @@ void init_kernel_container(struct kernel_container *kcont)
memcap_unmap(&kcont->physmem_free, kernel_area->start,
kernel_area->end);
init_ktcb_list(&kcont->zombie_list);
/* TODO:
* Add all virtual memory areas used by the kernel
* e.g. kernel virtual area, syscall page, kip page,

View File

@@ -14,6 +14,7 @@
#include <l4/generic/resource.h>
#include <l4/generic/container.h>
#include <l4/generic/preempt.h>
#include <l4/generic/thread.h>
#include <l4/generic/irq.h>
#include <l4/generic/tcb.h>
#include <l4/api/errno.h>
@@ -239,6 +240,9 @@ void sched_suspend_sync(void)
BUG_ON(scheduler.prio_total < 0);
preempt_enable();
if (current->flags & TASK_EXITING)
thread_make_zombie(current);
/*
* Async wake up any waiting pagers
*

View File

@@ -127,6 +127,15 @@ struct ktcb *tcb_find(l4id_t tid)
return 0;
}
void ktcb_list_add(struct ktcb *new, struct ktcb_list *ktcb_list)
{
spin_lock(&ktcb_list->list_lock);
BUG_ON(!list_empty(&new->task_list));
BUG_ON(!++ktcb_list->count);
list_insert(&new->task_list, &ktcb_list->list);
spin_unlock(&ktcb_list->list_lock);
}
void tcb_add(struct ktcb *new)
{
struct container *c = new->container;