kernel/thread: Cache kernel stacks.

Instead of creating and destroying kernel stack areas on every thread
creation and destruction, use the object_cache's constructor/destructor
hooks to create areas when cached objects are created, and reuse them.

This avoids the last major cause of kernel map flushing during compile
jobs, however it's not as much of a performance improvement as I'd hoped,
seeming to be within the realm of noise (within a VM anyway.) On systems
like VirtualBox where ICIs are much more expensive, it may be a bigger
performance boost.
This commit is contained in:
Augustin Cavalier
2025-01-27 16:39:23 -05:00
parent 9744f981a5
commit d836d5447a
+99 -15
View File
@@ -154,6 +154,101 @@ static ThreadNotificationService sNotificationService;
static object_cache* sThreadCache;
// #pragma mark - kernel stack cache
struct CachedKernelStack : public DoublyLinkedListLinkImpl<CachedKernelStack> {
VMArea* area;
addr_t base;
addr_t top;
};
static spinlock sCachedKernelStacksLock = B_SPINLOCK_INITIALIZER;
static DoublyLinkedList<CachedKernelStack> sCachedKernelStacks;
static area_id
allocate_kernel_stack(const char* name, addr_t* base, addr_t* top)
{
InterruptsSpinLocker locker(sCachedKernelStacksLock);
CachedKernelStack* cachedStack = sCachedKernelStacks.RemoveHead();
locker.Unlock();
if (cachedStack == NULL) {
panic("out of cached stacks!");
return B_NO_MEMORY;
}
memcpy(cachedStack->area->name, name, B_OS_NAME_LENGTH);
*base = cachedStack->base;
*top = cachedStack->top;
return cachedStack->area->id;
}
static void
free_kernel_stack(area_id areaID, addr_t base, addr_t top)
{
#if defined(STACK_GROWS_DOWNWARDS)
const addr_t stackStart = base + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE;
#else
const addr_t stackStart = base;
#endif
CachedKernelStack* cachedStack = (CachedKernelStack*)stackStart;
cachedStack->area = VMAreas::Lookup(areaID);
cachedStack->base = base;
cachedStack->top = top;
strcpy(cachedStack->area->name, "cached kstack");
InterruptsSpinLocker locker(sCachedKernelStacksLock);
sCachedKernelStacks.Add(cachedStack);
}
static status_t
create_kernel_stack(void* cookie, void* object)
{
// We could theoretically cast the passed object to Thread* and assign
// the kstack to it directly, but this would be incompatible with the
// debug filling of blocks on allocate/free. It also is probably good
// to not reuse kstacks quite so rapidly, anyway.
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address_specification = B_ANY_KERNEL_ADDRESS;
physical_address_restrictions physicalRestrictions = {};
addr_t base;
area_id area = create_area_etc(B_SYSTEM_TEAM, "",
KERNEL_STACK_SIZE + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE,
B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA
| B_KERNEL_STACK_AREA, 0, KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE,
&virtualRestrictions, &physicalRestrictions, (void**)&base);
if (area < 0)
return area;
addr_t top = base + KERNEL_STACK_SIZE
+ KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE;
free_kernel_stack(area, base, top);
return B_OK;
}
static void
destroy_kernel_stack(void* cookie, void* object)
{
InterruptsSpinLocker locker(sCachedKernelStacksLock);
CachedKernelStack* cachedStack = sCachedKernelStacks.RemoveHead();
locker.Unlock();
delete_area(cachedStack->area->id);
}
// #pragma mark - Thread
@@ -247,7 +342,7 @@ Thread::~Thread()
// delete the resources, that may remain in either case
if (kernel_stack_area >= 0)
delete_area(kernel_stack_area);
free_kernel_stack(kernel_stack_area, kernel_stack_base, kernel_stack_top);
fPendingSignals.Clear();
@@ -943,17 +1038,9 @@ thread_create_thread(const ThreadCreationAttributes& attributes, bool kernel)
char stackName[B_OS_NAME_LENGTH];
snprintf(stackName, B_OS_NAME_LENGTH, "%s_%" B_PRId32 "_kstack",
thread->name, thread->id);
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address_specification = B_ANY_KERNEL_ADDRESS;
physical_address_restrictions physicalRestrictions = {};
thread->kernel_stack_area = create_area_etc(B_SYSTEM_TEAM, stackName,
KERNEL_STACK_SIZE + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE,
B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA
| B_KERNEL_STACK_AREA, 0, KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE,
&virtualRestrictions, &physicalRestrictions,
(void**)&thread->kernel_stack_base);
thread->kernel_stack_area = allocate_kernel_stack(stackName,
&thread->kernel_stack_base, &thread->kernel_stack_top);
if (thread->kernel_stack_area < 0) {
// we're not yet part of a team, so we can just bail out
status = thread->kernel_stack_area;
@@ -964,9 +1051,6 @@ thread_create_thread(const ThreadCreationAttributes& attributes, bool kernel)
return status;
}
thread->kernel_stack_top = thread->kernel_stack_base + KERNEL_STACK_SIZE
+ KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE;
if (kernel) {
// Init the thread's kernel stack. It will start executing
// common_thread_entry() with the arguments we prepare here.
@@ -2744,7 +2828,7 @@ thread_init(kernel_args *args)
// create the thread structure object cache
sThreadCache = create_object_cache_etc("threads", sizeof(Thread), 64,
0, 0, 0, 0, NULL, NULL, NULL, NULL);
0, 0, 0, 0, NULL, create_kernel_stack, destroy_kernel_stack, NULL);
// Note: The x86 port requires 64 byte alignment of thread structures.
if (sThreadCache == NULL)
panic("thread_init(): failed to allocate thread object cache!");