reactos/reactos/ntoskrnl/cc/fs.c

250 lines
5.5 KiB
C
Raw Normal View History

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS kernel
* FILE: ntoskrnl/cc/fs.c
* PURPOSE: Implements cache managers functions useful for File Systems
*
* PROGRAMMERS: Alex Ionescu
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <internal/debug.h>
#ifndef VACB_MAPPING_GRANULARITY
#define VACB_MAPPING_GRANULARITY (256 * 1024)
#endif
/* GLOBALS *****************************************************************/
- Okay so...listen up. First off: When you acquire a lock such as a fast mutex, you should never acquire it recursively. For example, when you handle a page fault in a section, then page fault while handling that page fault (which is perfectly okay), you shouldn't be trying to re-acquire the address space lock that you're already holding. After this fix, this scenario works and countless others. Apps like QTInfo now work and load, and PictureViewer doesn't BSOD the system anymore. I've fixed this by changing the lock to a pushlock. It not only increases speed inside the memory manager significantly (such as during page fault handling), but does allow recursive acquisition without any problems. - Now if that wasn't bad enough, here's a couple more tips. Fast Mutexes actually require APC_LEVEL to be effective. If you're going to be using a Fast Mutex and calling it with the "Unsafe" version, then don't expect anything to work. Also, using functions like "CcTryToAcquireBrokenMutex" where correct code is duplicated then hacked to work isn't a big help either. And that's not all. Fast Mutex disables kernel APCs by setting the KernelApcDisable flag on, and it's expected that the count inside the fast mutex will match the count inside the thread. In other words, LOCK ACQUISITION AND RELEASE MUST BE ORDERED. You can't acquire LOCK A and B, and then release lock A and B, because that leads to deadlocks and other issues. So of course, the Cache Manager acquired a view lock, then acquired a segment lock, then released the view lock, then released the segment lock, then re-acquired the view lock. Uh, no, that won't work. You know what else doesn't work so well? Disabling APCs about 6-9 times to acquire a single lock, and using spinlocks in the same code path as well. Just how paranoid are you about thread safety, but still manage to get it wrong? Okay, so we've got recursion, out-of-order lock acquision and release, made-up "broken" acquire functions, and using a lock that depends on APC_LEVEL at PASSIVE_LEVEL. The best part is when Cc builds an array of cache segments, and locks each of them... then during release, the list gets parsed head-first, so the first acquired locks get released first. So locks a, b, c, d get acquired, then a, b, c, d get released. Great! Sounds about right for ReactOS's Cache Manager design. I've changed the view lock to a guarded mutex -- which actually properly disables APCs and works at PASSIVE_LEVEL, and changed the segment locks to be push locks. First it'll be 10 times faster then acquiring a bazillion fast mutexes, especially since APCs have already been disabled at this point, and it also allows you to do most of the stupid things the Cache Manager does. Out-of-order release is still not going to work well, so eventually on a multi-processor machine the code will completely die -- but at least it'll work on UP for now. In the end, this makes things like the Inkscape installer and Quicktime Installer to work, and probably countless other things that generated ASSERTS in the fast mutex code. -- Alex Ionescu svn path=/trunk/; revision=30401
2007-11-12 19:00:26 +00:00
extern KGUARDED_MUTEX ViewLock;
extern ULONG DirtyPageCount;
NTSTATUS CcRosInternalFreeCacheSegment(PCACHE_SEGMENT CacheSeg);
/* FUNCTIONS *****************************************************************/
/*
* @unimplemented
*/
LARGE_INTEGER
STDCALL
CcGetDirtyPages (
IN PVOID LogHandle,
IN PDIRTY_PAGE_ROUTINE DirtyPageRoutine,
IN PVOID Context1,
IN PVOID Context2
)
{
LARGE_INTEGER i;
UNIMPLEMENTED;
i.QuadPart = 0;
return i;
}
/*
* @implemented
*/
PFILE_OBJECT
STDCALL
CcGetFileObjectFromBcb (
IN PVOID Bcb
)
{
PINTERNAL_BCB iBcb = (PINTERNAL_BCB)Bcb;
return iBcb->CacheSegment->Bcb->FileObject;
}
/*
* @unimplemented
*/
LARGE_INTEGER
STDCALL
CcGetLsnForFileObject (
IN PFILE_OBJECT FileObject,
OUT PLARGE_INTEGER OldestLsn OPTIONAL
)
{
LARGE_INTEGER i;
UNIMPLEMENTED;
i.QuadPart = 0;
return i;
}
/*
* @unimplemented
*/
VOID
STDCALL
CcInitializeCacheMap (
IN PFILE_OBJECT FileObject,
IN PCC_FILE_SIZES FileSizes,
IN BOOLEAN PinAccess,
IN PCACHE_MANAGER_CALLBACKS CallBacks,
IN PVOID LazyWriterContext
)
{
ASSERT(FileObject);
ASSERT(FileSizes);
/* Call old ROS cache init function */
CcRosInitializeFileCache(FileObject,
/*PAGE_SIZE*/ VACB_MAPPING_GRANULARITY, CallBacks,
LazyWriterContext);
}
/*
* @unimplemented
*/
BOOLEAN
STDCALL
CcIsThereDirtyData (
IN PVPB Vpb
)
{
UNIMPLEMENTED;
return FALSE;
}
/*
* @unimplemented
*/
BOOLEAN
STDCALL
CcPurgeCacheSection (
IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
IN PLARGE_INTEGER FileOffset OPTIONAL,
IN ULONG Length,
IN BOOLEAN UninitializeCacheMaps
)
{
UNIMPLEMENTED;
return FALSE;
}
/*
* @implemented
*/
VOID STDCALL
CcSetFileSizes (IN PFILE_OBJECT FileObject,
IN PCC_FILE_SIZES FileSizes)
{
KIRQL oldirql;
PBCB Bcb;
PLIST_ENTRY current_entry;
PCACHE_SEGMENT current;
LIST_ENTRY FreeListHead;
NTSTATUS Status;
DPRINT("CcSetFileSizes(FileObject 0x%p, FileSizes 0x%p)\n",
FileObject, FileSizes);
DPRINT("AllocationSize %d, FileSize %d, ValidDataLength %d\n",
(ULONG)FileSizes->AllocationSize.QuadPart,
(ULONG)FileSizes->FileSize.QuadPart,
(ULONG)FileSizes->ValidDataLength.QuadPart);
Bcb = FileObject->SectionObjectPointer->SharedCacheMap;
/*
* It is valid to call this function on file objects that weren't
* initialized for caching. In this case it's simple no-op.
*/
if (Bcb == NULL)
return;
if (FileSizes->AllocationSize.QuadPart < Bcb->AllocationSize.QuadPart)
{
InitializeListHead(&FreeListHead);
- Okay so...listen up. First off: When you acquire a lock such as a fast mutex, you should never acquire it recursively. For example, when you handle a page fault in a section, then page fault while handling that page fault (which is perfectly okay), you shouldn't be trying to re-acquire the address space lock that you're already holding. After this fix, this scenario works and countless others. Apps like QTInfo now work and load, and PictureViewer doesn't BSOD the system anymore. I've fixed this by changing the lock to a pushlock. It not only increases speed inside the memory manager significantly (such as during page fault handling), but does allow recursive acquisition without any problems. - Now if that wasn't bad enough, here's a couple more tips. Fast Mutexes actually require APC_LEVEL to be effective. If you're going to be using a Fast Mutex and calling it with the "Unsafe" version, then don't expect anything to work. Also, using functions like "CcTryToAcquireBrokenMutex" where correct code is duplicated then hacked to work isn't a big help either. And that's not all. Fast Mutex disables kernel APCs by setting the KernelApcDisable flag on, and it's expected that the count inside the fast mutex will match the count inside the thread. In other words, LOCK ACQUISITION AND RELEASE MUST BE ORDERED. You can't acquire LOCK A and B, and then release lock A and B, because that leads to deadlocks and other issues. So of course, the Cache Manager acquired a view lock, then acquired a segment lock, then released the view lock, then released the segment lock, then re-acquired the view lock. Uh, no, that won't work. You know what else doesn't work so well? Disabling APCs about 6-9 times to acquire a single lock, and using spinlocks in the same code path as well. Just how paranoid are you about thread safety, but still manage to get it wrong? Okay, so we've got recursion, out-of-order lock acquision and release, made-up "broken" acquire functions, and using a lock that depends on APC_LEVEL at PASSIVE_LEVEL. The best part is when Cc builds an array of cache segments, and locks each of them... then during release, the list gets parsed head-first, so the first acquired locks get released first. So locks a, b, c, d get acquired, then a, b, c, d get released. Great! Sounds about right for ReactOS's Cache Manager design. I've changed the view lock to a guarded mutex -- which actually properly disables APCs and works at PASSIVE_LEVEL, and changed the segment locks to be push locks. First it'll be 10 times faster then acquiring a bazillion fast mutexes, especially since APCs have already been disabled at this point, and it also allows you to do most of the stupid things the Cache Manager does. Out-of-order release is still not going to work well, so eventually on a multi-processor machine the code will completely die -- but at least it'll work on UP for now. In the end, this makes things like the Inkscape installer and Quicktime Installer to work, and probably countless other things that generated ASSERTS in the fast mutex code. -- Alex Ionescu svn path=/trunk/; revision=30401
2007-11-12 19:00:26 +00:00
KeAcquireGuardedMutex(&ViewLock);
KeAcquireSpinLock(&Bcb->BcbLock, &oldirql);
current_entry = Bcb->BcbSegmentListHead.Flink;
while (current_entry != &Bcb->BcbSegmentListHead)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT, BcbSegmentListEntry);
current_entry = current_entry->Flink;
if (current->FileOffset > FileSizes->AllocationSize.QuadPart)
{
if (current->ReferenceCount == 0 || (current->ReferenceCount == 1 && current->Dirty))
{
RemoveEntryList(&current->BcbSegmentListEntry);
RemoveEntryList(&current->CacheSegmentListEntry);
RemoveEntryList(&current->CacheSegmentLRUListEntry);
if (current->Dirty)
{
RemoveEntryList(&current->DirtySegmentListEntry);
DirtyPageCount -= Bcb->CacheSegmentSize / PAGE_SIZE;
}
InsertHeadList(&FreeListHead, &current->BcbSegmentListEntry);
}
else
{
DPRINT1("Anyone has referenced a cache segment behind the new size.\n");
KeBugCheck(CACHE_MANAGER);
}
}
}
Bcb->AllocationSize = FileSizes->AllocationSize;
Bcb->FileSize = FileSizes->FileSize;
KeReleaseSpinLock(&Bcb->BcbLock, oldirql);
- Okay so...listen up. First off: When you acquire a lock such as a fast mutex, you should never acquire it recursively. For example, when you handle a page fault in a section, then page fault while handling that page fault (which is perfectly okay), you shouldn't be trying to re-acquire the address space lock that you're already holding. After this fix, this scenario works and countless others. Apps like QTInfo now work and load, and PictureViewer doesn't BSOD the system anymore. I've fixed this by changing the lock to a pushlock. It not only increases speed inside the memory manager significantly (such as during page fault handling), but does allow recursive acquisition without any problems. - Now if that wasn't bad enough, here's a couple more tips. Fast Mutexes actually require APC_LEVEL to be effective. If you're going to be using a Fast Mutex and calling it with the "Unsafe" version, then don't expect anything to work. Also, using functions like "CcTryToAcquireBrokenMutex" where correct code is duplicated then hacked to work isn't a big help either. And that's not all. Fast Mutex disables kernel APCs by setting the KernelApcDisable flag on, and it's expected that the count inside the fast mutex will match the count inside the thread. In other words, LOCK ACQUISITION AND RELEASE MUST BE ORDERED. You can't acquire LOCK A and B, and then release lock A and B, because that leads to deadlocks and other issues. So of course, the Cache Manager acquired a view lock, then acquired a segment lock, then released the view lock, then released the segment lock, then re-acquired the view lock. Uh, no, that won't work. You know what else doesn't work so well? Disabling APCs about 6-9 times to acquire a single lock, and using spinlocks in the same code path as well. Just how paranoid are you about thread safety, but still manage to get it wrong? Okay, so we've got recursion, out-of-order lock acquision and release, made-up "broken" acquire functions, and using a lock that depends on APC_LEVEL at PASSIVE_LEVEL. The best part is when Cc builds an array of cache segments, and locks each of them... then during release, the list gets parsed head-first, so the first acquired locks get released first. So locks a, b, c, d get acquired, then a, b, c, d get released. Great! Sounds about right for ReactOS's Cache Manager design. I've changed the view lock to a guarded mutex -- which actually properly disables APCs and works at PASSIVE_LEVEL, and changed the segment locks to be push locks. First it'll be 10 times faster then acquiring a bazillion fast mutexes, especially since APCs have already been disabled at this point, and it also allows you to do most of the stupid things the Cache Manager does. Out-of-order release is still not going to work well, so eventually on a multi-processor machine the code will completely die -- but at least it'll work on UP for now. In the end, this makes things like the Inkscape installer and Quicktime Installer to work, and probably countless other things that generated ASSERTS in the fast mutex code. -- Alex Ionescu svn path=/trunk/; revision=30401
2007-11-12 19:00:26 +00:00
KeReleaseGuardedMutex(&ViewLock);
current_entry = FreeListHead.Flink;
while(current_entry != &FreeListHead)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT, BcbSegmentListEntry);
current_entry = current_entry->Flink;
Status = CcRosInternalFreeCacheSegment(current);
if (!NT_SUCCESS(Status))
{
DPRINT1("CcRosInternalFreeCacheSegment failed, status = %x\n", Status);
KeBugCheck(CACHE_MANAGER);
}
}
}
else
{
KeAcquireSpinLock(&Bcb->BcbLock, &oldirql);
Bcb->AllocationSize = FileSizes->AllocationSize;
Bcb->FileSize = FileSizes->FileSize;
KeReleaseSpinLock(&Bcb->BcbLock, oldirql);
}
}
/*
* @unimplemented
*/
VOID
STDCALL
CcSetLogHandleForFile (
IN PFILE_OBJECT FileObject,
IN PVOID LogHandle,
IN PFLUSH_TO_LSN FlushToLsnRoutine
)
{
UNIMPLEMENTED;
}
/*
* @unimplemented
*/
BOOLEAN
STDCALL
CcUninitializeCacheMap (
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER TruncateSize OPTIONAL,
IN PCACHE_UNINITIALIZE_EVENT UninitializeCompleteEvent OPTIONAL
)
{
#if 0
UNIMPLEMENTED;
return FALSE;
#else
return NT_SUCCESS(CcRosReleaseFileCache(FileObject));
#endif
}