2 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 /** This module memorizes all opened files for a certain session. A session
7 * can be the start of an application or the boot process.
8 * When a session is started, it will prefetch all files from an earlier
9 * session in order to speed up the launching or booting process.
11 * Note: this module is using private kernel API and is definitely not
12 * meant to be an example on how to write modules.
16 #include "launch_speedup.h"
18 #include <KernelExport.h>
21 #include <util/kernel_cpp.h>
22 #include <util/AutoLock.h>
25 #include <file_cache.h>
26 #include <generic_syscall.h>
36 extern dev_t gBootDevice
;
39 // ToDo: combine the last 3-5 sessions to their intersection
40 // ToDo: maybe ignore sessions if the node count is < 3 (without system libs)
42 #define TRACE_CACHE_MODULE
43 #ifdef TRACE_CACHE_MODULE
44 # define TRACE(x) dprintf x
49 #define VNODE_HASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) \
50 + (uint32)(vnodeid)) ^ (uint32)(mountid))
67 typedef node_ref KeyType
;
68 typedef node ValueType
;
70 size_t HashKey(KeyType key
) const
72 return VNODE_HASH(key
.device
, key
.node
);
75 size_t Hash(ValueType
* value
) const
77 return HashKey(value
->ref
);
80 bool Compare(KeyType key
, ValueType
* node
) const
82 return (node
->ref
.device
== key
.device
&& node
->ref
.node
== key
.node
);
85 ValueType
*& GetLink(ValueType
* value
) const
91 typedef BOpenHashTable
<NodeHash
> NodeTable
;
95 Session(team_id team
, const char *name
, dev_t device
,
96 ino_t node
, int32 seconds
);
97 Session(const char *name
);
100 status_t
InitCheck();
101 team_id
Team() const { return fTeam
; }
102 const char *Name() const { return fName
; }
103 const node_ref
&NodeRef() const { return fNodeRef
; }
104 bool IsActive() const { return fActiveUntil
>= system_time(); }
105 bool IsClosing() const { return fClosing
; }
106 bool IsMainSession() const;
107 bool IsWorthSaving() const;
109 void AddNode(dev_t device
, ino_t node
);
110 void RemoveNode(dev_t device
, ino_t node
);
112 void Lock() { mutex_lock(&fLock
); }
113 void Unlock() { mutex_unlock(&fLock
); }
115 status_t
StartWatchingTeam();
116 void StopWatchingTeam();
118 status_t
LoadFromDirectory(int fd
);
122 Session
*&Next() { return fNext
; }
125 struct node
*_FindNode(dev_t device
, ino_t node
);
128 char fName
[B_OS_NAME_LENGTH
];
130 NodeTable
*fNodeHash
;
135 bigtime_t fActiveUntil
;
136 bigtime_t fTimestamp
;
138 bool fIsWatchingTeam
;
141 class SessionGetter
{
143 SessionGetter(team_id team
, Session
**_session
);
146 status_t
New(const char *name
, dev_t device
, ino_t node
,
154 static Session
*sMainSession
;
155 static SessionTable
*sTeamHash
;
156 static PrefetchTable
*sPrefetchHash
;
157 static Session
*sMainPrefetchSessions
;
158 // singly-linked list
159 static recursive_lock sLock
;
168 struct PrefetchHash
{
169 typedef node_ref KeyType
;
170 typedef Session ValueType
;
172 size_t HashKey(KeyType key
) const
174 return VNODE_HASH(key
.device
, key
.node
);
177 size_t Hash(ValueType
* value
) const
179 return HashKey(value
->NodeRef());
182 bool Compare(KeyType key
, ValueType
* session
) const
184 return (session
->NodeRef().device
== key
.device
185 && session
->NodeRef().node
== key
.node
);
188 ValueType
*& GetLink(ValueType
* value
) const
190 return value
->Next();
194 typedef BOpenHashTable
<PrefetchHash
> PrefetchTable
;
198 typedef team_id KeyType
;
199 typedef Session ValueType
;
201 size_t HashKey(KeyType key
) const
206 size_t Hash(ValueType
* value
) const
208 return HashKey(value
->Team());
211 bool Compare(KeyType key
, ValueType
* session
) const
213 return session
->Team
== key
;
216 ValueType
*& GetLink(ValueType
* value
) const
218 return value
->Next();
222 typedef BOpenHashTable
<SessionHash
> SessionTable
;
226 stop_session(Session
*session
)
231 TRACE(("stop_session(%s)\n", session
->Name()));
233 if (session
->IsWorthSaving())
237 RecursiveLocker
locker(&sLock
);
239 if (session
->Team() >= B_OK
)
240 sTeamHash
->Remove(session
);
242 if (session
== sMainSession
)
251 start_session(team_id team
, dev_t device
, ino_t node
, const char *name
,
254 RecursiveLocker
locker(&sLock
);
256 Session
*session
= new Session(team
, name
, device
, node
, seconds
);
260 if (session
->InitCheck() != B_OK
|| session
->StartWatchingTeam() != B_OK
) {
265 // let's see if there is a prefetch session for this session
267 Session
*prefetchSession
;
268 if (session
->IsMainSession()) {
269 // search for session by name
270 for (prefetchSession
= sMainPrefetchSessions
;
271 prefetchSession
!= NULL
;
272 prefetchSession
= prefetchSession
->Next()) {
273 if (!strcmp(prefetchSession
->Name(), name
)) {
279 // ToDo: search for session by device/node ID
280 prefetchSession
= NULL
;
282 if (prefetchSession
!= NULL
) {
283 TRACE(("found prefetch session %s\n", prefetchSession
->Name()));
284 prefetchSession
->Prefetch();
288 sTeamHash
->Insert(session
);
296 team_gone(team_id team
, void *_session
)
298 Session
*session
= (Session
*)_session
;
301 stop_session(session
);
306 parse_node_ref(const char *string
, node_ref
&ref
, const char **_end
= NULL
)
310 ref
.device
= strtol(string
, &end
, 0);
311 if (end
== NULL
|| ref
.device
== 0)
314 ref
.node
= strtoull(end
+ 1, &end
, 0);
323 new_node(dev_t device
, ino_t id
)
325 struct node
*node
= new ::node
;
329 node
->ref
.device
= device
;
331 node
->timestamp
= system_time();
340 DIR *dir
= opendir("/etc/launch_cache");
344 struct dirent
*dirent
;
345 while ((dirent
= readdir(dir
)) != NULL
) {
346 if (dirent
->d_name
[0] == '.')
349 Session
*session
= new Session(dirent
->d_name
);
351 if (session
->LoadFromDirectory(dirfd(dir
)) != B_OK
) {
356 if (session
->IsMainSession()) {
357 session
->Next() = sMainPrefetchSessions
;
358 sMainPrefetchSessions
= session
;
360 sPrefetchHash
->Insert(session
);
371 Session::Session(team_id team
, const char *name
, dev_t device
,
372 ino_t node
, int32 seconds
)
378 fIsWatchingTeam(false)
381 size_t length
= strlen(name
) + 1;
382 if (length
> B_OS_NAME_LENGTH
)
383 name
+= length
- B_OS_NAME_LENGTH
;
385 strlcpy(fName
, name
, B_OS_NAME_LENGTH
);
389 mutex_init(&fLock
, "launch speedup session");
390 fNodeHash
= new(std::nothrow
) NodeTable();
391 if (fNodeHash
&& fNodeHash
->Init(64) != B_OK
) {
395 fActiveUntil
= system_time() + seconds
* 1000000LL;
396 fTimestamp
= system_time();
398 fNodeRef
.device
= device
;
399 fNodeRef
.node
= node
;
401 TRACE(("start session %ld:%Ld \"%s\", system_time: %Ld, active until: %Ld\n",
402 device
, node
, Name(), system_time(), fActiveUntil
));
406 Session::Session(const char *name
)
411 fIsWatchingTeam(false)
414 fNodeRef
.device
= -1;
417 if (isdigit(name
[0]))
418 parse_node_ref(name
, fNodeRef
);
420 strlcpy(fName
, name
, B_OS_NAME_LENGTH
);
426 mutex_destroy(&fLock
);
429 struct node
*node
, *next
= NULL
;
433 node
= fNodeHash
->Clear(true);
439 for (; node
!= NULL
; node
= next
) {
452 if (fNodeHash
== NULL
)
460 Session::_FindNode(dev_t device
, ino_t node
)
466 return fNodeHash
->Lookup(key
);
471 Session::AddNode(dev_t device
, ino_t id
)
473 struct node
*node
= _FindNode(device
, id
);
479 node
= new_node(device
, id
);
483 fNodeHash
->Insert(node
);
489 Session::RemoveNode(dev_t device
, ino_t id
)
491 struct node
*node
= _FindNode(device
, id
);
492 if (node
!= NULL
&& --node
->ref_count
<= 0) {
493 fNodeHash
->Remove(node
);
500 Session::StartWatchingTeam()
505 status_t status
= start_watching_team(Team(), team_gone
, this);
507 fIsWatchingTeam
= true;
514 Session::StopWatchingTeam()
517 stop_watching_team(Team(), team_gone
, this);
524 if (fNodes
== NULL
|| fNodeHash
!= NULL
)
527 for (struct node
*node
= fNodes
; node
!= NULL
; node
= node
->next
) {
528 cache_prefetch(node
->ref
.device
, node
->ref
.node
, 0, ~0UL);
534 Session::LoadFromDirectory(int directoryFD
)
536 TRACE(("load session %s\n", Name()));
538 int fd
= _kern_open(directoryFD
, Name(), O_RDONLY
, 0);
543 if (fstat(fd
, &stat
) != 0) {
548 if (stat
.st_size
> 32768) {
549 // for safety reasons
550 // ToDo: make a bit larger later
555 char *buffer
= (char *)malloc(stat
.st_size
);
556 if (buffer
== NULL
) {
561 if (read(fd
, buffer
, stat
.st_size
) < stat
.st_size
) {
567 const char *line
= buffer
;
569 while (parse_node_ref(line
, nodeRef
, &line
)) {
570 struct node
*node
= new_node(nodeRef
.device
, nodeRef
.node
);
572 // note: this reverses the order of the nodes in the file
590 char name
[B_OS_NAME_LENGTH
+ 25];
591 if (!IsMainSession()) {
592 snprintf(name
, sizeof(name
), "/etc/launch_cache/%ld:%Ld %s",
593 fNodeRef
.device
, fNodeRef
.node
, Name());
595 snprintf(name
, sizeof(name
), "/etc/launch_cache/%s", Name());
597 int fd
= open(name
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0644);
601 status_t status
= B_OK
;
604 // ToDo: order nodes by timestamp... (should improve launch speed)
605 // ToDo: test which parts of a file have been read (and save that as well)
607 // enlarge file, so that it can be written faster
608 ftruncate(fd
, 512 * 1024);
610 NodeTable::Iterator
iterator(fNodeHash
);
611 while (iterator
.HasNext()) {
612 struct node
*node
= iterator
.Next();
613 snprintf(name
, sizeof(name
), "%ld:%Ld\n", node
->ref
.device
, node
->ref
.node
);
615 ssize_t bytesWritten
= write(fd
, name
, strlen(name
));
616 if (bytesWritten
< B_OK
) {
617 status
= bytesWritten
;
621 fileSize
+= bytesWritten
;
624 ftruncate(fd
, fileSize
);
632 Session::IsWorthSaving() const
634 // ToDo: sort out entries with only very few nodes, and those that load
636 if (fNodeCount
< 5 || system_time() - fTimestamp
< 400000) {
637 // sort anything out that opens less than 5 files, or needs less
638 // than 0.4 seconds to load an run
646 Session::IsMainSession() const
648 return fNodeRef
.node
== -1;
655 SessionGetter::SessionGetter(team_id team
, Session
**_session
)
657 RecursiveLocker
locker(&sLock
);
659 if (sMainSession
!= NULL
)
660 fSession
= sMainSession
;
662 fSession
= sTeamHash
->Lookup(team
);
664 if (fSession
!= NULL
) {
665 if (!fSession
->IsClosing())
671 *_session
= fSession
;
675 SessionGetter::~SessionGetter()
677 if (fSession
!= NULL
)
683 SessionGetter::New(const char *name
, dev_t device
, ino_t node
,
686 Thread
*thread
= thread_get_current_thread();
687 fSession
= start_session(thread
->team
->id
, device
, node
, name
);
689 if (fSession
!= NULL
) {
690 *_session
= fSession
;
699 SessionGetter::Stop()
701 if (fSession
== sMainSession
)
704 stop_session(fSession
);
712 node_opened(struct vnode
*vnode
, int32 fdType
, dev_t device
, ino_t parent
,
713 ino_t node
, const char *name
, off_t size
)
715 if (device
< gBootDevice
) {
716 // we ignore any access to rootfs, pipefs, and devfs
717 // ToDo: if we can ever move the boot device on the fly, this will break
722 SessionGetter
getter(team_get_current_team_id(), &session
);
724 if (session
== NULL
) {
725 char buffer
[B_FILE_NAME_LENGTH
];
727 && vfs_get_vnode_name(vnode
, buffer
, sizeof(buffer
)) == B_OK
)
730 // create new session for this team
731 getter
.New(name
, device
, node
, &session
);
734 if (session
== NULL
|| !session
->IsActive()) {
735 if (sMainSession
!= NULL
) {
736 // ToDo: this opens a race condition with the "stop session" syscall
742 session
->AddNode(device
, node
);
747 node_closed(struct vnode
*vnode
, int32 fdType
, dev_t device
, ino_t node
,
751 SessionGetter
getter(team_get_current_team_id(), &session
);
756 if (accessType
== FILE_CACHE_NO_IO
)
757 session
->RemoveNode(device
, node
);
762 launch_speedup_control(const char *subsystem
, uint32 function
,
763 void *buffer
, size_t bufferSize
)
766 case LAUNCH_SPEEDUP_START_SESSION
:
768 char name
[B_OS_NAME_LENGTH
];
769 if (!IS_USER_ADDRESS(buffer
)
770 || user_strlcpy(name
, (const char *)buffer
, B_OS_NAME_LENGTH
) < B_OK
)
771 return B_BAD_ADDRESS
;
773 if (isdigit(name
[0]) || name
[0] == '.')
776 sMainSession
= start_session(-1, -1, -1, name
, 60);
777 sMainSession
->Unlock();
781 case LAUNCH_SPEEDUP_STOP_SESSION
:
783 char name
[B_OS_NAME_LENGTH
];
784 if (!IS_USER_ADDRESS(buffer
)
785 || user_strlcpy(name
, (const char *)buffer
, B_OS_NAME_LENGTH
) < B_OK
)
786 return B_BAD_ADDRESS
;
788 // ToDo: this check is not thread-safe
789 if (sMainSession
== NULL
|| strcmp(sMainSession
->Name(), name
))
792 if (!strcmp(name
, "system boot"))
793 dprintf("STOP BOOT %Ld\n", system_time());
795 sMainSession
->Lock();
796 stop_session(sMainSession
);
809 unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS
, 1);
811 recursive_lock_lock(&sLock
);
813 // free all sessions from the hashes
815 Session
*session
= sTeamHash
->Clear(true);
816 while (session
!= NULL
) {
817 Session
*next
= session
->next
;
821 session
= sPrefetchHash
->Clear(true);
822 while (session
!= NULL
) {
823 Session
*next
= session
->next
;
828 // free all sessions from the main prefetch list
830 for (session
= sMainPrefetchSessions
; session
!= NULL
; ) {
831 sMainPrefetchSessions
= session
->Next();
833 session
= sMainPrefetchSessions
;
837 delete sPrefetchHash
;
838 recursive_lock_destroy(&sLock
);
845 sTeamHash
= new(std::nothrow
) SessionTable();
846 if (sTeamHash
== NULL
|| sTeamHash
->Init(64) != B_OK
)
851 sPrefetchHash
= new(std::nothrow
) PrefetchTable();
852 if (sPrefetchHash
== NULL
|| sPrefetchHash
->Init(64) != B_OK
) {
853 status
= B_NO_MEMORY
;
857 recursive_lock_init(&sLock
, "launch speedup");
859 // register kernel syscalls
860 if (register_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS
,
861 launch_speedup_control
, 1, 0) != B_OK
) {
866 // read in prefetch knowledge base
868 mkdir("/etc/launch_cache", 0755);
869 load_prefetch_data();
871 // start boot session
873 sMainSession
= start_session(-1, -1, -1, "system boot");
874 sMainSession
->Unlock();
875 dprintf("START BOOT %Ld\n", system_time());
879 recursive_lock_destroy(&sLock
);
880 delete sPrefetchHash
;
888 std_ops(int32 op
, ...)
894 case B_MODULE_UNINIT
:
904 static struct cache_module_info sLaunchSpeedupModule
= {
906 CACHE_MODULES_NAME
"/launch_speedup/v1",
916 module_info
*modules
[] = {
917 (module_info
*)&sLaunchSpeedupModule
,