Kernel VM: Improve swap file selection
* Heavily based on Hamish Morrison's GCI work with some modified logic and cleanup. #3723 * Adds automatic swap as well as user specified swap * Limits: Automatic: (ram * 2) up to 25% of the disk User: user specified up to 90% of the disk * Supports changing the swap disk location
This commit is contained in:
@@ -3,6 +3,8 @@ SubDir HAIKU_TOP src system kernel vm ;
|
|||||||
UsePrivateHeaders shared ;
|
UsePrivateHeaders shared ;
|
||||||
|
|
||||||
UseHeaders [ FDirName $(SUBDIR) $(DOTDOT) device_manager ] ;
|
UseHeaders [ FDirName $(SUBDIR) $(DOTDOT) device_manager ] ;
|
||||||
|
UsePrivateHeaders [ FDirName kernel disk_device_manager ] ;
|
||||||
|
UsePrivateHeaders [ FDirName kernel util ] ;
|
||||||
|
|
||||||
KernelMergeObject kernel_vm.o :
|
KernelMergeObject kernel_vm.o :
|
||||||
PageCacheLocker.cpp
|
PageCacheLocker.cpp
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright 2012, Alexander von Gluck IV, [email protected].
|
||||||
|
* Copyright 2011, Hamish Morrison, [email protected].
|
||||||
* Copyright 2008, Zhao Shuai, [email protected].
|
* Copyright 2008, Zhao Shuai, [email protected].
|
||||||
* Copyright 2008-2011, Ingo Weinhold, [email protected].
|
* Copyright 2008-2011, Ingo Weinhold, [email protected].
|
||||||
* Copyright 2002-2009, Axel Dörfler, [email protected].
|
* Copyright 2002-2009, Axel Dörfler, [email protected].
|
||||||
@@ -17,13 +19,20 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <FindDirectory.h>
|
||||||
#include <KernelExport.h>
|
#include <KernelExport.h>
|
||||||
#include <NodeMonitor.h>
|
#include <NodeMonitor.h>
|
||||||
|
|
||||||
#include <arch_config.h>
|
#include <arch_config.h>
|
||||||
#include <boot_device.h>
|
#include <boot_device.h>
|
||||||
|
#include <disk_device_manager/KDiskDevice.h>
|
||||||
|
#include <disk_device_manager/KDiskDeviceManager.h>
|
||||||
|
#include <disk_device_manager/KDiskSystem.h>
|
||||||
|
#include <disk_device_manager/KPartitionVisitor.h>
|
||||||
#include <driver_settings.h>
|
#include <driver_settings.h>
|
||||||
#include <fs/fd.h>
|
#include <fs/fd.h>
|
||||||
|
#include <fs/KPath.h>
|
||||||
|
#include <fs_info.h>
|
||||||
#include <fs_interface.h>
|
#include <fs_interface.h>
|
||||||
#include <heap.h>
|
#include <heap.h>
|
||||||
#include <kernel_daemon.h>
|
#include <kernel_daemon.h>
|
||||||
@@ -69,6 +78,8 @@
|
|||||||
#define SWAP_BLOCK_MASK (SWAP_BLOCK_PAGES - 1)
|
#define SWAP_BLOCK_MASK (SWAP_BLOCK_PAGES - 1)
|
||||||
|
|
||||||
|
|
||||||
|
static const char* const kDefaultSwapPath = "/var/swap";
|
||||||
|
|
||||||
struct swap_file : DoublyLinkedListLinkImpl<swap_file> {
|
struct swap_file : DoublyLinkedListLinkImpl<swap_file> {
|
||||||
int fd;
|
int fd;
|
||||||
struct vnode* vnode;
|
struct vnode* vnode;
|
||||||
@@ -1167,6 +1178,93 @@ VMAnonymousCache::_MergeSwapPages(VMAnonymousCache* source)
|
|||||||
// #pragma mark -
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: This can be removed if we get BFS uuid's
|
||||||
|
struct VolumeInfo {
|
||||||
|
char name[B_FILE_NAME_LENGTH];
|
||||||
|
char device[B_FILE_NAME_LENGTH];
|
||||||
|
char filesystem[B_OS_NAME_LENGTH];
|
||||||
|
off_t capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class PartitionScorer : public KPartitionVisitor {
|
||||||
|
public:
|
||||||
|
PartitionScorer(VolumeInfo& volumeInfo)
|
||||||
|
:
|
||||||
|
fBestPartition(NULL),
|
||||||
|
fBestScore(-1),
|
||||||
|
fVolumeInfo(volumeInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool VisitPre(KPartition* partition)
|
||||||
|
{
|
||||||
|
if (!partition->ContainsFileSystem())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
KPath path;
|
||||||
|
partition->GetPath(&path);
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
if (strcmp(fVolumeInfo.name, partition->ContentName()) == 0)
|
||||||
|
score += 4;
|
||||||
|
if (strcmp(fVolumeInfo.device, path.Path()) == 0)
|
||||||
|
score += 3;
|
||||||
|
if (fVolumeInfo.capacity == partition->Size())
|
||||||
|
score += 2;
|
||||||
|
if (strcmp(fVolumeInfo.filesystem,
|
||||||
|
partition->DiskSystem()->ShortName()) == 0) {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
if (score >= 4 && score > fBestScore) {
|
||||||
|
fBestPartition = partition;
|
||||||
|
fBestScore = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KPartition* fBestPartition;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int32 fBestScore;
|
||||||
|
VolumeInfo fVolumeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
get_mount_point(KPartition* partition, KPath* mountPoint)
|
||||||
|
{
|
||||||
|
if (!mountPoint || !partition->ContainsFileSystem())
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
|
||||||
|
const char* volumeName = partition->ContentName();
|
||||||
|
if (!volumeName || strlen(volumeName) == 0)
|
||||||
|
volumeName = partition->Name();
|
||||||
|
if (!volumeName || strlen(volumeName) == 0)
|
||||||
|
volumeName = "unnamed volume";
|
||||||
|
|
||||||
|
char basePath[B_PATH_NAME_LENGTH];
|
||||||
|
int32 len = snprintf(basePath, sizeof(basePath), "/%s", volumeName);
|
||||||
|
for (int32 i = 1; i < len; i++)
|
||||||
|
if (basePath[i] == '/')
|
||||||
|
basePath[i] = '-';
|
||||||
|
char* path = mountPoint->LockBuffer();
|
||||||
|
int32 pathLen = mountPoint->BufferSize();
|
||||||
|
strncpy(path, basePath, pathLen);
|
||||||
|
|
||||||
|
struct stat dummy;
|
||||||
|
for (int i = 1; ; i++) {
|
||||||
|
if (stat(path, &dummy) != 0)
|
||||||
|
break;
|
||||||
|
snprintf(path, pathLen, "%s%d", basePath, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPoint->UnlockBuffer();
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
swap_file_add(const char* path)
|
swap_file_add(const char* path)
|
||||||
{
|
{
|
||||||
@@ -1340,47 +1438,161 @@ swap_init_post_modules()
|
|||||||
if (gReadOnlyBootDevice)
|
if (gReadOnlyBootDevice)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
off_t size = 0;
|
bool swapEnabled = true;
|
||||||
|
bool swapAutomatic = true;
|
||||||
|
off_t swapSize = 0;
|
||||||
|
VolumeInfo selectedVolume = {};
|
||||||
|
|
||||||
void* settings = load_driver_settings("virtual_memory");
|
void* settings = load_driver_settings("virtual_memory");
|
||||||
|
|
||||||
if (settings != NULL) {
|
if (settings != NULL) {
|
||||||
if (!get_driver_boolean_parameter(settings, "vm", false, false)) {
|
// We pass a lot of information on the swap device, this is mostly to
|
||||||
|
// ensure that we are dealing with the same device that was configured.
|
||||||
|
|
||||||
|
// TODO: Some kind of BFS uuid would be great here :)
|
||||||
|
const char* enabled = get_driver_parameter(settings, "vm", NULL, NULL);
|
||||||
|
|
||||||
|
if (enabled != NULL) {
|
||||||
|
swapEnabled = get_driver_boolean_parameter(settings, "vm",
|
||||||
|
false, false);
|
||||||
|
|
||||||
|
const char* size = get_driver_parameter(settings, "swap_size",
|
||||||
|
NULL, NULL);
|
||||||
|
const char* volume = get_driver_parameter(settings,
|
||||||
|
"swap_volume_name", NULL, NULL);
|
||||||
|
const char* device = get_driver_parameter(settings,
|
||||||
|
"swap_volume_device", NULL, NULL);
|
||||||
|
const char* filesystem = get_driver_parameter(settings,
|
||||||
|
"swap_volume_filesystem", NULL, NULL);
|
||||||
|
const char* capacity = get_driver_parameter(settings,
|
||||||
|
"swap_volume_capacity", NULL, NULL);
|
||||||
|
|
||||||
|
if (size != NULL && device != NULL && volume != NULL
|
||||||
|
&& filesystem != NULL && capacity != NULL) {
|
||||||
|
// User specified a size / volume
|
||||||
|
swapAutomatic = false;
|
||||||
|
swapSize = atoll(size);
|
||||||
|
strncpy(selectedVolume.name, volume,
|
||||||
|
sizeof(selectedVolume.name));
|
||||||
|
strncpy(selectedVolume.device, device,
|
||||||
|
sizeof(selectedVolume.device));
|
||||||
|
strncpy(selectedVolume.filesystem, filesystem,
|
||||||
|
sizeof(selectedVolume.filesystem));
|
||||||
|
selectedVolume.capacity = atoll(capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
unload_driver_settings(settings);
|
unload_driver_settings(settings);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* string = get_driver_parameter(settings, "swap_size", NULL,
|
if (swapAutomatic) {
|
||||||
NULL);
|
swapEnabled = true;
|
||||||
size = string ? atoll(string) : 0;
|
swapSize = (off_t)vm_page_num_pages() * B_PAGE_SIZE * 2;
|
||||||
|
}
|
||||||
|
|
||||||
unload_driver_settings(settings);
|
if (!swapEnabled || swapSize < B_PAGE_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dev_t dev = -1;
|
||||||
|
|
||||||
|
if (!swapAutomatic) {
|
||||||
|
KDiskDeviceManager::CreateDefault();
|
||||||
|
KDiskDeviceManager* manager = KDiskDeviceManager::Default();
|
||||||
|
PartitionScorer visitor(selectedVolume);
|
||||||
|
|
||||||
|
KDiskDevice* device;
|
||||||
|
int32 cookie = 0;
|
||||||
|
while ((device = manager->NextDevice(&cookie)) != NULL) {
|
||||||
|
if (device->IsReadOnlyMedia() || device->IsWriteOnce()
|
||||||
|
|| device->IsRemovable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
device->VisitEachDescendant(&visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visitor.fBestPartition) {
|
||||||
|
dprintf("%s: Can't find configured swap partition '%s'\n",
|
||||||
|
__func__, selectedVolume.name);
|
||||||
} else {
|
} else {
|
||||||
size = (off_t)vm_page_num_pages() * B_PAGE_SIZE * 2;
|
if (visitor.fBestPartition->IsMounted())
|
||||||
|
dev = visitor.fBestPartition->VolumeID();
|
||||||
|
else {
|
||||||
|
KPath devPath, mountPoint;
|
||||||
|
visitor.fBestPartition->GetPath(&devPath);
|
||||||
|
get_mount_point(visitor.fBestPartition, &mountPoint);
|
||||||
|
const char* mountPath = mountPoint.Path();
|
||||||
|
mkdir(mountPath, S_IRWXU | S_IRWXG | S_IRWXO);
|
||||||
|
dev = _kern_mount(mountPath, devPath.Path(),
|
||||||
|
NULL, 0, NULL, 0);
|
||||||
|
if (dev < 0) {
|
||||||
|
dprintf("%s: Can't mount configured swap partition '%s'\n",
|
||||||
|
__func__, selectedVolume.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size < B_PAGE_SIZE)
|
if (dev < 0)
|
||||||
return;
|
dev = gBootDevice;
|
||||||
|
|
||||||
int fd = open("/var/swap", O_RDWR | O_CREAT | O_NOCACHE, S_IRUSR | S_IWUSR);
|
KPath path;
|
||||||
|
struct fs_info info;
|
||||||
|
_kern_read_fs_info(dev, &info);
|
||||||
|
if (dev == gBootDevice)
|
||||||
|
path = kDefaultSwapPath;
|
||||||
|
else {
|
||||||
|
vfs_entry_ref_to_path(info.dev, info.root,
|
||||||
|
".", path.LockBuffer(), path.BufferSize());
|
||||||
|
path.UnlockBuffer();
|
||||||
|
path.Append("swap");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* swapPath = path.Path();
|
||||||
|
|
||||||
|
// Swap size limits prevent oversized swap files
|
||||||
|
off_t existingSwapSize = 0;
|
||||||
|
struct stat existingSwapStat;
|
||||||
|
if (stat(swapPath, &existingSwapStat) == 0)
|
||||||
|
existingSwapSize = existingSwapStat.st_size;
|
||||||
|
|
||||||
|
off_t freeSpace = info.free_blocks * info.block_size + existingSwapSize;
|
||||||
|
off_t maxSwap = freeSpace;
|
||||||
|
if (swapAutomatic) {
|
||||||
|
// Adjust automatic swap to a maximum of 25% of the free space
|
||||||
|
maxSwap = (off_t)(0.25 * freeSpace);
|
||||||
|
} else {
|
||||||
|
// If user specified, leave 10% of the disk free
|
||||||
|
maxSwap = freeSpace - (off_t)(0.10 * freeSpace);
|
||||||
|
dprintf("%s: Warning: User specified swap file consumes over 90%% of "
|
||||||
|
"the available free space, limiting to 90%%\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapSize > maxSwap)
|
||||||
|
swapSize = maxSwap;
|
||||||
|
|
||||||
|
// Create swap file
|
||||||
|
int fd = open(swapPath, O_RDWR | O_CREAT | O_NOCACHE, S_IRUSR | S_IWUSR);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
dprintf("Can't open/create /var/swap: %s\n", strerror(errno));
|
dprintf("%s: Can't open/create %s: %s\n", __func__,
|
||||||
|
swapPath, strerror(errno));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stat stat;
|
struct stat stat;
|
||||||
stat.st_size = size;
|
stat.st_size = swapSize;
|
||||||
status_t error = _kern_write_stat(fd, NULL, false, &stat,
|
status_t error = _kern_write_stat(fd, NULL, false, &stat,
|
||||||
sizeof(struct stat), B_STAT_SIZE | B_STAT_SIZE_INSECURE);
|
sizeof(struct stat), B_STAT_SIZE | B_STAT_SIZE_INSECURE);
|
||||||
if (error != B_OK) {
|
if (error != B_OK) {
|
||||||
dprintf("Failed to resize /var/swap to %lld bytes: %s\n", size,
|
dprintf("%s: Failed to resize %s to %lld bytes: %s\n", __func__,
|
||||||
strerror(error));
|
swapPath, swapSize, strerror(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
error = swap_file_add("/var/swap");
|
error = swap_file_add(swapPath);
|
||||||
if (error != B_OK)
|
if (error != B_OK) {
|
||||||
dprintf("Failed to add swap file /var/swap: %s\n", strerror(error));
|
dprintf("%s: Failed to add swap file %s: %s\n", __func__, swapPath,
|
||||||
|
strerror(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user