diff --git a/src/add-ons/kernel/file_cache/launch_speedup.cpp b/src/add-ons/kernel/file_cache/launch_speedup.cpp index d74262c415..84219b2934 100644 --- a/src/add-ons/kernel/file_cache/launch_speedup.cpp +++ b/src/add-ons/kernel/file_cache/launch_speedup.cpp @@ -7,6 +7,9 @@ * can be the start of an application or the boot process. * When a session is started, it will prefetch all files from an earlier * session in order to speed up the launching or booting process. + * + * Note: this module is using private kernel API and is definitely not + * meant to be an example on how to write modules. */ @@ -20,8 +23,8 @@ #include #include #include -#include -#include +//#include +//#include #include #include @@ -30,6 +33,9 @@ #include #include #include +#include + +extern dev_t gBootDevice; // ToDo: combine the last 3-5 sessions to their intersection @@ -42,6 +48,8 @@ # define TRACE(x) ; #endif +#define VNODE_HASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) \ + + (uint32)(vnodeid)) ^ (uint32)(mountid)) struct data_part { off_t offset; @@ -60,13 +68,18 @@ struct node { class Session { public: Session(team_id team, const char *name, mount_id device, - vnode_id node, int32 seconds = 60); + vnode_id node, int32 seconds); + Session(const char *name); ~Session(); status_t InitCheck(); team_id Team() const { return fTeam; } const char *Name() const { return fName; } + const node_ref &NodeRef() const { return fNodeRef; } bool IsActive() const { return fActiveUntil >= system_time(); } + bool IsClosing() const { return fClosing; } + bool IsMainSession() const; + bool IsWorthSaving() const; void AddNode(mount_id device, vnode_id node); void RemoveNode(mount_id device, vnode_id node); @@ -74,6 +87,10 @@ class Session { void Lock() { mutex_lock(&fLock); } void Unlock() { mutex_unlock(&fLock); } + status_t LoadFromDirectory(int fd); + status_t Save(); + + Session *&Next() { return fNext; } static uint32 NextOffset() { return offsetof(Session, fNext); } private: @@ -83,9 +100,13 @@ class Session { char fName[B_OS_NAME_LENGTH]; mutex fLock; hash_table *fNodeHash; + struct node *fNodes; + int32 fNodeCount; team_id fTeam; node_ref fNodeRef; bigtime_t fActiveUntil; + bigtime_t fTimestamp; + bool fClosing; }; class SessionGetter { @@ -95,6 +116,7 @@ class SessionGetter { status_t New(const char *name, mount_id device, vnode_id node, Session **_session); + void Stop(); private: Session *fSession; @@ -102,7 +124,10 @@ class SessionGetter { static Session *sMainSession; static hash_table *sTeamHash; -static mutex sLock; +static hash_table *sPrefetchHash; +static Session *sMainPrefetchSessions; + // singly-linked list +static recursive_lock sLock; node_ref::node_ref() @@ -130,14 +155,37 @@ node_hash(void *_node, const void *_key, uint32 range) struct node *node = (struct node *)_node; const struct node_ref *key = (node_ref *)_key; -#define VHASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) + (uint32)(vnodeid)) ^ (uint32)(mountid)) - if (node != NULL) - return (VHASH(node->ref.device, node->ref.node) % range); + return VNODE_HASH(node->ref.device, node->ref.node) % range; - return (VHASH(key->device, key->node) % range); + return VNODE_HASH(key->device, key->node) % range; +} -#undef VHASH + +static int +prefetch_compare(void *_session, const void *_key) +{ + Session *session = (Session *)_session; + const struct node_ref *key = (node_ref *)_key; + + if (session->NodeRef().device == key->device + && session->NodeRef().node == key->node) + return 0; + + return -1; +} + + +static uint32 +prefetch_hash(void *_session, const void *_key, uint32 range) +{ + Session *session = (Session *)_session; + const struct node_ref *key = (node_ref *)_key; + + if (session != NULL) + return VNODE_HASH(session->NodeRef().device, session->NodeRef().node) % range; + + return VNODE_HASH(key->device, key->node) % range; } @@ -175,23 +223,30 @@ stop_session(Session *session) TRACE(("stop_session(%s)\n", session->Name())); - session->Lock(); + if (session->IsWorthSaving()) + session->Save(); - MutexLocker locker(&sLock); + { + RecursiveLocker locker(&sLock); - if (session->Team() >= B_OK) - hash_remove(sTeamHash, session); + if (session->Team() >= B_OK) + hash_remove(sTeamHash, session); + + if (session == sMainSession) + sMainSession = NULL; + } delete session; } static Session * -start_session(team_id team, mount_id device, vnode_id node, const char *name) +start_session(team_id team, mount_id device, vnode_id node, const char *name, + int32 seconds = 30) { - MutexLocker locker(&sLock); + RecursiveLocker locker(&sLock); - Session *session = new Session(team, name, device, node); + Session *session = new Session(team, name, device, node, seconds); if (session == NULL) return NULL; @@ -212,17 +267,54 @@ static void team_gone(team_id team, void *_session) { Session *session = (Session *)_session; + + session->Lock(); stop_session(session); } +static void +load_prefetch_data() +{ + DIR *dir = opendir("/etc/launch_cache"); + if (dir == NULL) + return; + + struct dirent *dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + Session *session = new Session(dirent->d_name); + dprintf("%s\n", dirent->d_name); + + if (session->LoadFromDirectory(dir->fd) != B_OK) { + delete session; + continue; + } + + if (session->IsMainSession()) { + session->Next() = sMainPrefetchSessions; + sMainPrefetchSessions = session; + } else { + hash_insert(sPrefetchHash, session); + } + } + + closedir(dir); +} + + // #pragma mark - Session::Session(team_id team, const char *name, mount_id device, vnode_id node, int32 seconds) : - fTeam(team) + fNodes(NULL), + fNodeCount(0), + fTeam(team), + fClosing(false) { if (name != NULL) { size_t length = strlen(name) + 1; @@ -236,6 +328,7 @@ Session::Session(team_id team, const char *name, mount_id device, mutex_init(&fLock, "launch speedup session"); fNodeHash = hash_init(64, 0, &node_compare, &node_hash); fActiveUntil = system_time() + seconds * 1000000LL; + fTimestamp = system_time(); fNodeRef.device = device; fNodeRef.node = node; @@ -245,19 +338,53 @@ Session::Session(team_id team, const char *name, mount_id device, } +Session::Session(const char *name) + : + fNodeHash(NULL), + fNodes(NULL), + fClosing(false) +{ + fTeam = -1; + fNodeRef.device = -1; + fNodeRef.node = -1; + + if (isdigit(name[0])) { + // parse node ref + char *end; + fNodeRef.device = strtol(name, &end, 0); + if (end != NULL) + fNodeRef.node = strtoull(end + 1, NULL, 0); + } + + strlcpy(fName, name, B_OS_NAME_LENGTH); +} + + Session::~Session() { mutex_destroy(&fLock); // free all nodes - uint32 cookie = 0; - struct node *node; - while ((node = (struct node *)hash_remove_first(fNodeHash, &cookie)) != NULL) { - TRACE((" node %ld:%Ld\n", node->ref.device, node->ref.node)); - free(node); - } - hash_uninit(fNodeHash); + if (fNodeHash) { + // ... from the hash + uint32 cookie = 0; + struct node *node; + while ((node = (struct node *)hash_remove_first(fNodeHash, &cookie)) != NULL) { + //TRACE((" node %ld:%Ld\n", node->ref.device, node->ref.node)); + free(node); + } + + hash_uninit(fNodeHash); + } else { + // ... from the list + struct node *node = fNodes, *next = NULL; + + for (; node != NULL; node = next) { + next = node->next; + free(node); + } + } } @@ -306,6 +433,7 @@ Session::AddNode(mount_id device, vnode_id id) node->timestamp = system_time(); hash_insert(fNodeHash, node); + fNodeCount++; } @@ -313,8 +441,88 @@ void Session::RemoveNode(mount_id device, vnode_id id) { struct node *node = _FindNode(device, id); - if (node != NULL && --node->ref_count <= 0) + if (node != NULL && --node->ref_count <= 0) { hash_remove(fNodeHash, node); + fNodeCount--; + } +} + + +status_t +Session::LoadFromDirectory(int fd) +{ + return B_ERROR; +} + + +status_t +Session::Save() +{ + fClosing = true; + + char name[B_OS_NAME_LENGTH + 25]; + if (!IsMainSession()) { + snprintf(name, sizeof(name), "/etc/launch_cache/%ld:%Ld %s", + fNodeRef.device, fNodeRef.node, Name()); + } else + snprintf(name, sizeof(name), "/etc/launch_cache/%s", Name()); + + int fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fd < B_OK) + return errno; + + status_t status = B_OK; + off_t fileSize = 0; + + // ToDo: order nodes by timestamp... (should improve launch speed) + // ToDo: test which parts of a file have been read (and save that as well) + + // enlarge file, so that it can be written faster + ftruncate(fd, 512 * 1024); + + struct hash_iterator iterator; + struct node *node; + + hash_open(fNodeHash, &iterator); + while ((node = (struct node *)hash_next(fNodeHash, &iterator)) != NULL) { + snprintf(name, sizeof(name), "%ld:%Ld\n", node->ref.device, node->ref.node); + + ssize_t bytesWritten = write(fd, name, strlen(name)); + if (bytesWritten < B_OK) { + status = bytesWritten; + break; + } + + fileSize += bytesWritten; + } + + hash_close(fNodeHash, &iterator, false); + + ftruncate(fd, fileSize); + close(fd); + + return status; +} + + +bool +Session::IsWorthSaving() const +{ + // ToDo: sort out entries with only very few nodes, and those that load + // instantly, anyway + if (fNodeCount < 5 || system_time() - fTimestamp < 400000) { + // sort anything out that opens less than 5 files, or needs less + // than 0.4 seconds to load an run + return false; + } + return true; +} + + +bool +Session::IsMainSession() const +{ + return fNodeRef.node == -1; } @@ -323,15 +531,19 @@ Session::RemoveNode(mount_id device, vnode_id id) SessionGetter::SessionGetter(team_id team, Session **_session) { - MutexLocker locker(&sLock); + RecursiveLocker locker(&sLock); if (sMainSession != NULL) fSession = sMainSession; else fSession = (Session *)hash_lookup(sTeamHash, &team); - if (fSession != NULL) - fSession->Lock(); + if (fSession != NULL) { + if (!fSession->IsClosing()) + fSession->Lock(); + else + fSession = NULL; + } *_session = fSession; } @@ -360,6 +572,16 @@ SessionGetter::New(const char *name, mount_id device, vnode_id node, } +void +SessionGetter::Stop() +{ + if (fSession == sMainSession) + sMainSession = NULL; + + stop_session(fSession); + fSession = NULL; +} + // #pragma mark - @@ -367,6 +589,12 @@ static void node_opened(void *vnode, int32 fdType, mount_id device, vnode_id parent, vnode_id node, const char *name, off_t size) { + if (device < gBootDevice) { + // we ignore any access to rootfs, pipefs, and devfs + // ToDo: if we can ever move the boot device on the fly, this will break + return; + } + Session *session; SessionGetter getter(team_get_current_team_id(), &session); @@ -381,10 +609,9 @@ node_opened(void *vnode, int32 fdType, mount_id device, vnode_id parent, } if (session == NULL || !session->IsActive()) { - if (session != NULL && sMainSession != NULL) { + if (sMainSession != NULL) { // ToDo: this opens a race condition with the "stop session" syscall - stop_session(sMainSession); - sMainSession = NULL; + getter.Stop(); } return; } @@ -419,7 +646,10 @@ launch_speedup_control(const char *subsystem, uint32 function, || user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK) return B_BAD_ADDRESS; - sMainSession = start_session(-1, -1, -1, name); + if (isdigit(name[0]) || name[0] == '.') + return B_BAD_VALUE; + + sMainSession = start_session(-1, -1, -1, name, 60); sMainSession->Unlock(); return B_OK; } @@ -435,6 +665,7 @@ launch_speedup_control(const char *subsystem, uint32 function, if (sMainSession == NULL || strcmp(sMainSession->Name(), name)) return B_BAD_VALUE; + sMainSession->Lock(); stop_session(sMainSession); sMainSession = NULL; return B_OK; @@ -449,8 +680,13 @@ static void uninit() { unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 1); + + recursive_lock_lock(&sLock); + + // ToDo: free sessions from hash tables hash_uninit(sTeamHash); - mutex_destroy(&sLock); + hash_uninit(sPrefetchHash); + recursive_lock_destroy(&sLock); } @@ -463,26 +699,41 @@ init() status_t status; - if (mutex_init(&sLock, "launch speedup") < B_OK) { - status = sLock.sem; + sPrefetchHash = hash_init(64, Session::NextOffset(), &prefetch_compare, &prefetch_hash); + if (sPrefetchHash == NULL) { + status = B_NO_MEMORY; goto err1; } + if (recursive_lock_init(&sLock, "launch speedup") < B_OK) { + status = sLock.sem; + goto err2; + } + // register kernel syscalls if (register_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, launch_speedup_control, 1, 0) != B_OK) { status = B_ERROR; - goto err2; + goto err3; } + // read in prefetch knowledge base + + mkdir("/etc/launch_cache", 0755); + load_prefetch_data(); + + // start boot session + sMainSession = start_session(-1, -1, -1, "system boot"); sMainSession->Unlock(); return B_OK; +err3: + recursive_lock_destroy(&sLock); +err2: + hash_uninit(sPrefetchHash); err1: hash_uninit(sTeamHash); -err2: - mutex_destroy(&sLock); return status; }