Skip to content

Commit 901a6dc

Browse files
committed
Add a vnode cache to the kext to improve the performance of finding a vnode's virtualization root.
1 parent 8f7e3c1 commit 901a6dc

22 files changed

+1662
-30
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,6 @@ ModelManifest.xml
226226

227227
# VS Code private directory
228228
.vscode/
229+
230+
# ProjFS Kext Unit Test coverage results
231+
ProjFS.Mac/CoverageResult.txt

ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj

+23-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
/* End PBXAggregateTarget section */
2525

2626
/* Begin PBXBuildFile section */
27+
264758C921EFBA8B0095B9F8 /* VnodeCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 264758C721EFBA8B0095B9F8 /* VnodeCache.cpp */; };
28+
264758CA21EFBA8B0095B9F8 /* VnodeCache.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 264758C821EFBA8B0095B9F8 /* VnodeCache.hpp */; };
29+
264758CC21FA709B0095B9F8 /* VnodeCacheTestable.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 264758CB21FA709B0095B9F8 /* VnodeCacheTestable.hpp */; };
30+
264758CE21FA71140095B9F8 /* VnodeCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 264758CD21FA71140095B9F8 /* VnodeCacheTests.mm */; };
31+
264758CF21FA71E10095B9F8 /* VnodeCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 264758C721EFBA8B0095B9F8 /* VnodeCache.cpp */; };
2732
265504D0224ADE11005FAD74 /* MockPerfTracing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 265504CE224ADE11005FAD74 /* MockPerfTracing.cpp */; };
2833
43057C5E21E439C700487681 /* prjfs-log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 43057C5B21E439C700487681 /* prjfs-log.cpp */; };
2934
43057C5F21E439C700487681 /* kext-perf-tracing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 43057C5C21E439C700487681 /* kext-perf-tracing.cpp */; };
@@ -145,6 +150,12 @@
145150
/* End PBXCopyFilesBuildPhase section */
146151

147152
/* Begin PBXFileReference section */
153+
262DFF982230798E005CC5DD /* VnodeCachePrivate.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = VnodeCachePrivate.hpp; sourceTree = "<group>"; };
154+
263DD5AD225D44C2005FEE9C /* VnodeCacheEntriesWrapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VnodeCacheEntriesWrapper.hpp; sourceTree = "<group>"; };
155+
264758C721EFBA8B0095B9F8 /* VnodeCache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VnodeCache.cpp; sourceTree = "<group>"; };
156+
264758C821EFBA8B0095B9F8 /* VnodeCache.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VnodeCache.hpp; sourceTree = "<group>"; };
157+
264758CB21FA709B0095B9F8 /* VnodeCacheTestable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VnodeCacheTestable.hpp; sourceTree = "<group>"; };
158+
264758CD21FA71140095B9F8 /* VnodeCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = VnodeCacheTests.mm; sourceTree = "<group>"; };
148159
265504CE224ADE11005FAD74 /* MockPerfTracing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MockPerfTracing.cpp; sourceTree = "<group>"; };
149160
43057C5B21E439C700487681 /* prjfs-log.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "prjfs-log.cpp"; sourceTree = "<group>"; };
150161
43057C5C21E439C700487681 /* kext-perf-tracing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "kext-perf-tracing.cpp"; sourceTree = "<group>"; };
@@ -339,6 +350,10 @@
339350
4391F89421E42AC40008103C /* VirtualizationRoots.hpp */,
340351
4A781DB722299C5300DB7733 /* VirtualizationRootsTestable.hpp */,
341352
4A781DA6222094C300DB7733 /* VirtualizationRootsPrivate.hpp */,
353+
264758C721EFBA8B0095B9F8 /* VnodeCache.cpp */,
354+
264758C821EFBA8B0095B9F8 /* VnodeCache.hpp */,
355+
262DFF982230798E005CC5DD /* VnodeCachePrivate.hpp */,
356+
264758CB21FA709B0095B9F8 /* VnodeCacheTestable.hpp */,
342357
4391F8A121E42AC50008103C /* VnodeUtilities.cpp */,
343358
4391F8A221E42AC50008103C /* VnodeUtilities.hpp */,
344359
);
@@ -392,6 +407,8 @@
392407
4A781DAD2223374200DB7733 /* MockVnodeAndMount.hpp */,
393408
4A781DB122233A1E00DB7733 /* TestLocks.cpp */,
394409
4A781DB322233A4500DB7733 /* TestMemory.cpp */,
410+
263DD5AD225D44C2005FEE9C /* VnodeCacheEntriesWrapper.hpp */,
411+
264758CD21FA71140095B9F8 /* VnodeCacheTests.mm */,
395412
4A781DA42220946000DB7733 /* VirtualizationRootsTests.mm */,
396413
4A8C13AA21F268FE00002878 /* PrjFSKextTests.exp */,
397414
F5554F402239881800B31D19 /* MockProc.hpp */,
@@ -417,6 +434,8 @@
417434
4A558DBC22357AB000AFDE07 /* ProviderMessaging.hpp in Headers */,
418435
4391F8B321E42AC50008103C /* PrjFSService.hpp in Headers */,
419436
4391F8B721E42AC50008103C /* PrjFSClasses.hpp in Headers */,
437+
264758CC21FA709B0095B9F8 /* VnodeCacheTestable.hpp in Headers */,
438+
264758CA21EFBA8B0095B9F8 /* VnodeCache.hpp in Headers */,
420439
4391F8BC21E42AC50008103C /* VnodeUtilities.hpp in Headers */,
421440
4391F8BD21E42AC50008103C /* PerformanceTracing.hpp in Headers */,
422441
4391F8B921E42AC50008103C /* PrjFSProviderUserClientPrivate.hpp in Headers */,
@@ -656,6 +675,7 @@
656675
4A558DBA22357AB000AFDE07 /* ProviderMessaging.cpp in Sources */,
657676
4391F8B621E42AC50008103C /* PrjFSKext.cpp in Sources */,
658677
4391F8AB21E42AC50008103C /* KextLog.cpp in Sources */,
678+
264758C921EFBA8B0095B9F8 /* VnodeCache.cpp in Sources */,
659679
4391F8B421E42AC50008103C /* PrjFSService.cpp in Sources */,
660680
);
661681
runOnlyForDeploymentPostprocessing = 0;
@@ -694,6 +714,7 @@
694714
files = (
695715
4A781DA72220971E00DB7733 /* VirtualizationRoots.cpp in Sources */,
696716
4A8C13A521F23F0200002878 /* KauthHandler.cpp in Sources */,
717+
264758CF21FA71E10095B9F8 /* VnodeCache.cpp in Sources */,
697718
);
698719
runOnlyForDeploymentPostprocessing = 0;
699720
};
@@ -707,12 +728,13 @@
707728
4A781DAE2223374200DB7733 /* MockVnodeAndMount.cpp in Sources */,
708729
F5554F422239883B00B31D19 /* MockProc.cpp in Sources */,
709730
4A781DB422233A4500DB7733 /* TestMemory.cpp in Sources */,
710-
D9C087F222384670009C1110 /* MemoryTests.mm in Sources */,
711731
F5EACDC22242AB2D00EEA70E /* HandleOperationTests.mm in Sources */,
712732
F5E39C7A21F1118D006D65C2 /* KauthHandlerTests.mm in Sources */,
733+
D9C087F222384670009C1110 /* MemoryTests.mm in Sources */,
713734
4A781DB222233A1E00DB7733 /* TestLocks.cpp in Sources */,
714735
4A781DB02223391A00DB7733 /* KextLogMock.cpp in Sources */,
715736
F554202D224BB6E5008BAE95 /* ProviderMessagingMock.cpp in Sources */,
737+
264758CE21FA71140095B9F8 /* VnodeCacheTests.mm in Sources */,
716738
);
717739
runOnlyForDeploymentPostprocessing = 0;
718740
};

ProjFS.Mac/PrjFSKext/KauthHandler.cpp

+95-14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "public/PrjFSCommon.h"
77
#include "public/PrjFSPerfCounter.h"
8+
#include "public/PrjFSXattrs.h"
89
#include "VirtualizationRoots.hpp"
910
#include "VnodeUtilities.hpp"
1011
#include "KauthHandler.hpp"
@@ -16,7 +17,7 @@
1617
#include "kernel-header-wrappers/stdatomic.h"
1718
#include "KextLog.hpp"
1819
#include "ProviderMessaging.hpp"
19-
#include "public/PrjFSXattrs.h"
20+
#include "VnodeCache.hpp"
2021

2122
#ifdef KEXT_UNIT_TESTING
2223
#include "KauthHandlerTestable.hpp"
@@ -88,12 +89,13 @@ static bool TryGetVirtualizationRoot(
8889
int* kauthResult,
8990
int* kauthError);
9091

91-
static bool ShouldHandleFileOpEvent(
92+
KEXT_STATIC bool ShouldHandleFileOpEvent(
9293
// In params:
9394
PerfTracer* perfTracer,
9495
vfs_context_t _Nonnull context,
9596
const vnode_t vnode,
9697
kauth_action_t action,
98+
bool isDirectory,
9799

98100
// Out params:
99101
VirtualizationRootHandle* root,
@@ -124,6 +126,11 @@ kern_return_t KauthHandler_Init()
124126
goto CleanupAndFail;
125127
}
126128

129+
if (VnodeCache_Init())
130+
{
131+
goto CleanupAndFail;
132+
}
133+
127134
s_vnodeListener = kauth_listen_scope(KAUTH_SCOPE_VNODE, HandleVnodeOperation, nullptr);
128135
if (nullptr == s_vnodeListener)
129136
{
@@ -172,6 +179,11 @@ kern_return_t KauthHandler_Cleanup()
172179

173180
WaitForListenerCompletion();
174181

182+
if (VnodeCache_Cleanup())
183+
{
184+
result = KERN_FAILURE;
185+
}
186+
175187
if (VirtualizationRoots_Cleanup())
176188
{
177189
result = KERN_FAILURE;
@@ -468,6 +480,8 @@ KEXT_STATIC int HandleFileOpOperation(
468480

469481
putCurrentVnode = true;
470482

483+
bool isDirectory = (0 != vnode_isdir(currentVnode));
484+
471485
VirtualizationRootHandle root = RootHandle_None;
472486
FsidInode vnodeFsidInode = {};
473487
int pid;
@@ -476,6 +490,7 @@ KEXT_STATIC int HandleFileOpOperation(
476490
context,
477491
currentVnode,
478492
action,
493+
isDirectory,
479494
&root,
480495
&vnodeFsidInode,
481496
&pid))
@@ -491,7 +506,7 @@ KEXT_STATIC int HandleFileOpOperation(
491506
PerfSample renameSample(&perfTracer, PrjFSPerfCounter_FileOp_Renamed);
492507

493508
MessageType messageType =
494-
vnode_isdir(currentVnode)
509+
isDirectory
495510
? MessageType_KtoU_NotifyDirectoryRenamed
496511
: MessageType_KtoU_NotifyFileRenamed;
497512

@@ -566,6 +581,7 @@ KEXT_STATIC int HandleFileOpOperation(
566581
context,
567582
currentVnode,
568583
action,
584+
false /* isDirectory */,
569585
&root,
570586
&vnodeFsidInode,
571587
&pid))
@@ -619,6 +635,7 @@ KEXT_STATIC int HandleFileOpOperation(
619635
context,
620636
currentVnode,
621637
action,
638+
false /* isDirectory */,
622639
&root,
623640
&vnodeFsidInode,
624641
&pid))
@@ -780,10 +797,11 @@ static bool TryGetVirtualizationRoot(
780797
int* kauthError)
781798
{
782799
PerfSample findRootSample(perfTracer, PrjFSPerfCounter_VnodeOp_GetVirtualizationRoot);
783-
784-
*vnodeFsidInode = Vnode_GetFsidAndInode(vnode, context, true /* the inode is used for getting the path in the provider, so use linkid */);
785-
*root = VirtualizationRoot_FindForVnode(
800+
801+
*root = VnodeCache_FindRootForVnode(
786802
perfTracer,
803+
PrjFSPerfCounter_VnodeOp_Vnode_Cache_Hit,
804+
PrjFSPerfCounter_VnodeOp_Vnode_Cache_Miss,
787805
PrjFSPerfCounter_VnodeOp_FindRoot,
788806
PrjFSPerfCounter_VnodeOp_FindRoot_Iteration,
789807
vnode,
@@ -836,6 +854,8 @@ static bool TryGetVirtualizationRoot(
836854
return false;
837855
}
838856

857+
*vnodeFsidInode = Vnode_GetFsidAndInode(vnode, context, true /* the inode is used for getting the path in the provider, so use linkid */);
858+
839859
return true;
840860
}
841861

@@ -884,12 +904,13 @@ static bool CurrentProcessWasSpawnedByRegularUser()
884904
}
885905

886906

887-
static bool ShouldHandleFileOpEvent(
907+
KEXT_STATIC bool ShouldHandleFileOpEvent(
888908
// In params:
889909
PerfTracer* perfTracer,
890-
vfs_context_t context,
910+
vfs_context_t _Nonnull context,
891911
const vnode_t vnode,
892912
kauth_action_t action,
913+
bool isDirectory,
893914

894915
// Out params:
895916
VirtualizationRootHandle* root,
@@ -914,12 +935,72 @@ static bool ShouldHandleFileOpEvent(
914935
{
915936
PerfSample findRootSample(perfTracer, PrjFSPerfCounter_FileOp_ShouldHandle_FindVirtualizationRoot);
916937

917-
*root = VirtualizationRoot_FindForVnode(
918-
perfTracer,
919-
PrjFSPerfCounter_FileOp_FindRoot,
920-
PrjFSPerfCounter_FileOp_FindRoot_Iteration,
921-
vnode,
922-
context);
938+
if (isDirectory)
939+
{
940+
if (KAUTH_FILEOP_RENAME == action)
941+
{
942+
// Directory renames into (or out) of virtualization roots require invalidating all of the entries
943+
// within the directory being renamed (because all of those entires now have a new virtualzation root).
944+
// Rather than trying to find all vnodes in the cache that are children of the directory being renamed
945+
// we simply invalidate the entire cache.
946+
//
947+
// Potential future optimizations include:
948+
// - Only invalidate the cache if the rename moves a directory in or out of a directory
949+
// - Keeping a per-root generation ID in the cache to allow invalidating a subset of the cache
950+
VnodeCache_InvalidateCache(perfTracer);
951+
}
952+
953+
*root = VnodeCache_FindRootForVnode(
954+
perfTracer,
955+
PrjFSPerfCounter_FileOp_Vnode_Cache_Hit,
956+
PrjFSPerfCounter_FileOp_Vnode_Cache_Miss,
957+
PrjFSPerfCounter_FileOp_FindRoot,
958+
PrjFSPerfCounter_FileOp_FindRoot_Iteration,
959+
vnode,
960+
context);
961+
}
962+
else
963+
{
964+
// TODO(Mac): Once all hardlink paths are delivered to the appropriate root(s)
965+
// check if the KAUTH_FILEOP_LINK case can be removed. For now the check is required to make
966+
// sure we're looking up the most up-to-date parent information for the vnode on the next
967+
// access to the vnode cache
968+
switch(action)
969+
{
970+
case KAUTH_FILEOP_LINK:
971+
*root = VnodeCache_InvalidateVnodeRootAndGetLatestRoot(
972+
perfTracer,
973+
PrjFSPerfCounter_FileOp_Vnode_Cache_Hit,
974+
PrjFSPerfCounter_FileOp_Vnode_Cache_Miss,
975+
PrjFSPerfCounter_FileOp_FindRoot,
976+
PrjFSPerfCounter_FileOp_FindRoot_Iteration,
977+
vnode,
978+
context);
979+
break;
980+
981+
case KAUTH_FILEOP_RENAME:
982+
*root = VnodeCache_RefreshRootForVnode(
983+
perfTracer,
984+
PrjFSPerfCounter_FileOp_Vnode_Cache_Hit,
985+
PrjFSPerfCounter_FileOp_Vnode_Cache_Miss,
986+
PrjFSPerfCounter_FileOp_FindRoot,
987+
PrjFSPerfCounter_FileOp_FindRoot_Iteration,
988+
vnode,
989+
context);
990+
break;
991+
992+
default:
993+
*root = VnodeCache_FindRootForVnode(
994+
perfTracer,
995+
PrjFSPerfCounter_FileOp_Vnode_Cache_Hit,
996+
PrjFSPerfCounter_FileOp_Vnode_Cache_Miss,
997+
PrjFSPerfCounter_FileOp_FindRoot,
998+
PrjFSPerfCounter_FileOp_FindRoot_Iteration,
999+
vnode,
1000+
context);
1001+
break;
1002+
}
1003+
}
9231004

9241005
if (!VirtualizationRoot_IsValidRootHandle(*root))
9251006
{

ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "../PrjFSKext/kernel-header-wrappers/kauth.h"
44
#include "../PrjFSKext/kernel-header-wrappers/vnode.h"
55
#include "../PrjFSKext/PerformanceTracing.hpp"
6+
#include "../PrjFSKext/VirtualizationRoots.hpp"
67

78
#ifndef __cplusplus
89
#error None of the kext code is set up for being called from C or Objective-C; change the including file to C++ or Objective-C++
@@ -12,6 +13,8 @@
1213
#error This class should only be called for unit tests
1314
#endif
1415

16+
struct FsidInode;
17+
1518
KEXT_STATIC_INLINE bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit);
1619
KEXT_STATIC_INLINE bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask);
1720
KEXT_STATIC_INLINE bool TryGetFileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context, bool* flaggedInRoot);
@@ -47,4 +50,16 @@ KEXT_STATIC bool ShouldHandleVnodeOpEvent(
4750
char procname[MAXCOMLEN + 1],
4851
int* kauthResult,
4952
int* kauthError);
53+
KEXT_STATIC bool ShouldHandleFileOpEvent(
54+
// In params:
55+
PerfTracer* perfTracer,
56+
vfs_context_t context,
57+
const vnode_t vnode,
58+
kauth_action_t action,
59+
bool isDirectory,
60+
61+
// Out params:
62+
VirtualizationRootHandle* root,
63+
FsidInode* vnodeFsidInode,
64+
int* pid);
5065
KEXT_STATIC void UseMainForkIfNamedStream(vnode_t& vnode, bool& putVnodeWhenDone);

ProjFS.Mac/PrjFSKext/PerformanceTracing.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class PerfTracer
2323
public:
2424
inline PerfTracer();
2525
inline bool IsEnabled();
26-
inline void IncrementCount(PrjFSPerfCounter counter);
26+
inline void IncrementCount(PrjFSPerfCounter counter, bool ignoreSampling = false);
2727
};
2828

2929
inline PerfTracer::PerfTracer()
@@ -51,10 +51,10 @@ inline bool PerfTracer::IsEnabled()
5151
#endif
5252
}
5353

54-
inline void PerfTracer::IncrementCount(PrjFSPerfCounter counter)
54+
inline void PerfTracer::IncrementCount(PrjFSPerfCounter counter, bool ignoreSampling /* = false */)
5555
{
5656
#if PRJFS_PERFORMANCE_TRACING_ENABLE
57-
if (this->IsEnabled())
57+
if (ignoreSampling || this->IsEnabled())
5858
{
5959
PerfTracing_RecordSample(counter, 0, 0);
6060
}

ProjFS.Mac/PrjFSKext/VirtualizationRoots.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ VirtualizationRootHandle VirtualizationRoot_FindForVnode(
126126
PrjFSPerfCounter functionCounter,
127127
PrjFSPerfCounter innerLoopCounter,
128128
vnode_t _Nonnull initialVnode,
129-
vfs_context_t context)
129+
vfs_context_t _Nonnull context)
130130
{
131131
PerfSample findForVnodeSample(perfTracer, functionCounter);
132132

ProjFS.Mac/PrjFSKext/VirtualizationRootsTestable.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#error Don't #include this file in non-testing builds
55
#endif
66

7+
#include "VirtualizationRoots.hpp"
78
#include "VirtualizationRootsPrivate.hpp"
89

910
extern uint16_t s_maxVirtualizationRoots;

0 commit comments

Comments
 (0)