kernel: Add thread setting to prevent page waits and use it in the file cache.

See inline comment: otherwise we could deadlock waiting for busy pages.

At the same time, make page_faults_allowed just an int16 and drop
atomics and extra checks, they aren't needed.

Fixes #19441.

Change-Id: I1b7cc06f66b44c3520fa36497c076ee5a6320706
Reviewed-on: https://review.haiku-os.org/c/haiku/+/9120
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Augustin Cavalier
2025-03-14 18:34:06 +00:00
committed by waddlesplash
parent bf0c6bfb90
commit 20a6449385
7 changed files with 81 additions and 22 deletions
+2 -2
View File
@@ -287,8 +287,8 @@ struct Thread : TeamThreadIteratorEntry<thread_id>, KernelReferenceable {
void (*fault_handler)(void);
jmp_buf fault_handler_state;
int32 page_faults_allowed;
/* this field may only stay in debug builds in the future */
int16 page_faults_allowed;
int16 page_fault_waits_allowed;
BKernel::Team *team; // protected by team lock, thread lock, scheduler
// lock, team_lock
+2 -2
View File
@@ -68,8 +68,8 @@ void slab_init_post_sem();
void slab_init_post_thread();
// to protect code regions with interrupts turned on
void permit_page_faults(void);
void forbid_page_faults(void);
void permit_page_faults();
void forbid_page_faults();
// private kernel only extension (should be moved somewhere else):
area_id create_area_etc(team_id team, const char *name, size_t size,
@@ -3,14 +3,16 @@
* Copyright 2019-2024, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT license.
*/
#include "DataContainer.h"
#include <StackOrHeapArray.h>
#include <util/AutoLock.h>
#include <util/BitUtils.h>
#include <slab/Slab.h>
#include <thread.h>
#include <vfs.h>
#include <vm/VMCache.h>
#include <vm/vm_page.h>
#include "VMAnonymousNoSwapCache.h"
@@ -321,8 +323,8 @@ DataContainer::_SwitchToCacheMode()
status_t
DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
size_t* bytesProcessed, bool isWrite)
DataContainer::_DoCacheIO(off_t offset, uint8* buffer, ssize_t length,
size_t* bytesProcessed, bool isWrite, bool retriesAllowed)
{
const size_t originalLength = length;
const bool user = IS_USER_ADDRESS(buffer);
@@ -335,6 +337,7 @@ DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
return B_NO_MEMORY;
cache_get_pages(fCache, rounded_offset, rounded_len, isWrite, pages);
thread_get_current_thread()->page_fault_waits_allowed--;
status_t error = B_OK;
size_t index = 0;
@@ -358,23 +361,39 @@ DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
if (page != NULL) {
error = vm_memcpy_from_physical(buffer, at, bytes, user);
} else {
if (user) {
if (user)
error = user_memset(buffer, 0, bytes);
} else {
else
memset(buffer, 0, bytes);
}
}
}
if (error != B_OK)
break;
offset += bytes;
buffer += bytes;
length -= bytes;
index++;
}
thread_get_current_thread()->page_fault_waits_allowed++;
cache_put_pages(fCache, rounded_offset, rounded_len, pages, error == B_OK);
if (error == B_BUSY && retriesAllowed) {
// See comment in the file_cache's cache_io() routine.
if (user) {
error = user_memset(buffer, 0, length);
} else {
memset(buffer, 0, length);
error = B_OK;
}
if (error == B_OK) {
size_t processed;
error = _DoCacheIO(offset, buffer, length, &processed, isWrite, false);
length -= processed;
}
}
if (bytesProcessed != NULL)
*bytesProcessed = length > 0 ? originalLength - length : originalLength;
@@ -42,8 +42,8 @@ private:
inline bool _RequiresCacheMode(size_t size);
inline bool _IsCacheMode() const;
status_t _SwitchToCacheMode();
status_t _DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
size_t* bytesProcessed, bool isWrite);
status_t _DoCacheIO(off_t offset, uint8* buffer, ssize_t length,
size_t* bytesProcessed, bool isWrite, bool retriesAllowed = true);
inline int32 _CountBlocks() const;
+42 -2
View File
@@ -450,7 +450,6 @@ read_into_cache(file_cache_ref* ref, void* cookie, off_t offset,
// make the pages accessible in the cache
for (int32 i = pageIndex; i-- > 0;) {
DEBUG_PAGE_ACCESS_END(pages[i]);
cache->MarkPageUnbusy(pages[i]);
}
@@ -729,7 +728,7 @@ satisfy_cache_io(file_cache_ref* ref, void* cookie, cache_func function,
static status_t
cache_io(void* _cacheRef, void* cookie, off_t offset, addr_t buffer,
do_cache_io(void* _cacheRef, void* cookie, off_t offset, addr_t buffer,
size_t* _size, bool doWrite)
{
if (_cacheRef == NULL)
@@ -911,6 +910,47 @@ cache_io(void* _cacheRef, void* cookie, off_t offset, addr_t buffer,
}
static status_t
cache_io(void* ref, void* cookie, off_t offset, addr_t buffer,
size_t* _size, bool doWrite)
{
size_t originalSize = *_size;
thread_get_current_thread()->page_fault_waits_allowed--;
status_t status = do_cache_io(ref, cookie, offset, buffer, _size, doWrite);
thread_get_current_thread()->page_fault_waits_allowed++;
if (status == B_BUSY) {
// This likely means that fault handler would've needed to wait for a page,
// but we can't allow that here because it could be one of our pages that
// it would've waited on, which would cause a deadlock.
// Call memset so that all pages are faulted in, and retry.
off_t retryOffset = offset;
addr_t retryBuffer = buffer;
size_t retrySize = originalSize;
if (*_size != originalSize) {
retryOffset += *_size;
retryBuffer += *_size;
retrySize -= *_size;
}
if (IS_USER_ADDRESS(buffer)) {
status = user_memset((void*)retryBuffer, 0, retrySize);
} else {
memset((void*)retryBuffer, 0, retrySize);
status = B_OK;
}
if (status == B_OK) {
thread_get_current_thread()->page_fault_waits_allowed--;
status = do_cache_io(ref, cookie, retryOffset, retryBuffer, &retrySize, doWrite);
*_size += retrySize;
thread_get_current_thread()->page_fault_waits_allowed++;
}
}
return status;
}
static status_t
file_cache_control(const char* subsystem, uint32 function, void* buffer,
size_t bufferSize)
+1
View File
@@ -281,6 +281,7 @@ Thread::Thread(const char* name, thread_id threadID, struct cpu_ent* cpu)
user_thread(NULL),
fault_handler(0),
page_faults_allowed(1),
page_fault_waits_allowed(1),
team(NULL),
select_infos(NULL),
kernel_stack_area(-1),
+7 -8
View File
@@ -4084,20 +4084,16 @@ vm_init_post_modules(kernel_args* args)
void
permit_page_faults(void)
permit_page_faults()
{
Thread* thread = thread_get_current_thread();
if (thread != NULL)
atomic_add(&thread->page_faults_allowed, 1);
thread_get_current_thread()->page_faults_allowed++;
}
void
forbid_page_faults(void)
forbid_page_faults()
{
Thread* thread = thread_get_current_thread();
if (thread != NULL)
atomic_add(&thread->page_faults_allowed, -1);
thread_get_current_thread()->page_faults_allowed--;
}
@@ -4289,6 +4285,9 @@ fault_get_page(PageFaultContext& context)
page = cache->LookupPage(context.cacheOffset);
if (page != NULL && page->busy) {
if (thread_get_current_thread()->page_fault_waits_allowed < 1)
return B_BUSY;
// page must be busy -- wait for it to become unbusy
context.UnlockAll(cache);
cache->ReleaseRefLocked();