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 ;
|
||||
|
||||
UseHeaders [ FDirName $(SUBDIR) $(DOTDOT) device_manager ] ;
|
||||
UsePrivateHeaders [ FDirName kernel disk_device_manager ] ;
|
||||
UsePrivateHeaders [ FDirName kernel util ] ;
|
||||
|
||||
KernelMergeObject kernel_vm.o :
|
||||
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-2011, Ingo Weinhold, [email protected].
|
||||
* Copyright 2002-2009, Axel Dörfler, [email protected].
|
||||
@@ -17,13 +19,20 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <FindDirectory.h>
|
||||
#include <KernelExport.h>
|
||||
#include <NodeMonitor.h>
|
||||
|
||||
#include <arch_config.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 <fs/fd.h>
|
||||
#include <fs/KPath.h>
|
||||
#include <fs_info.h>
|
||||
#include <fs_interface.h>
|
||||
#include <heap.h>
|
||||
#include <kernel_daemon.h>
|
||||
@@ -69,6 +78,8 @@
|
||||
#define SWAP_BLOCK_MASK (SWAP_BLOCK_PAGES - 1)
|
||||
|
||||
|
||||
static const char* const kDefaultSwapPath = "/var/swap";
|
||||
|
||||
struct swap_file : DoublyLinkedListLinkImpl<swap_file> {
|
||||
int fd;
|
||||
struct vnode* vnode;
|
||||
@@ -1167,6 +1178,93 @@ VMAnonymousCache::_MergeSwapPages(VMAnonymousCache* source)
|
||||
// #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
|
||||
swap_file_add(const char* path)
|
||||
{
|
||||
@@ -1340,47 +1438,161 @@ swap_init_post_modules()
|
||||
if (gReadOnlyBootDevice)
|
||||
return;
|
||||
|
||||
off_t size = 0;
|
||||
bool swapEnabled = true;
|
||||
bool swapAutomatic = true;
|
||||
off_t swapSize = 0;
|
||||
VolumeInfo selectedVolume = {};
|
||||
|
||||
void* settings = load_driver_settings("virtual_memory");
|
||||
|
||||
if (settings != NULL) {
|
||||
if (!get_driver_boolean_parameter(settings, "vm", false, false)) {
|
||||
unload_driver_settings(settings);
|
||||
return;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
const char* string = get_driver_parameter(settings, "swap_size", NULL,
|
||||
NULL);
|
||||
size = string ? atoll(string) : 0;
|
||||
|
||||
unload_driver_settings(settings);
|
||||
} else {
|
||||
size = (off_t)vm_page_num_pages() * B_PAGE_SIZE * 2;
|
||||
}
|
||||
|
||||
if (size < B_PAGE_SIZE)
|
||||
if (swapAutomatic) {
|
||||
swapEnabled = true;
|
||||
swapSize = (off_t)vm_page_num_pages() * B_PAGE_SIZE * 2;
|
||||
}
|
||||
|
||||
if (!swapEnabled || swapSize < B_PAGE_SIZE)
|
||||
return;
|
||||
|
||||
int fd = open("/var/swap", O_RDWR | O_CREAT | O_NOCACHE, S_IRUSR | S_IWUSR);
|
||||
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 {
|
||||
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 (dev < 0)
|
||||
dev = gBootDevice;
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
struct stat stat;
|
||||
stat.st_size = size;
|
||||
stat.st_size = swapSize;
|
||||
status_t error = _kern_write_stat(fd, NULL, false, &stat,
|
||||
sizeof(struct stat), B_STAT_SIZE | B_STAT_SIZE_INSECURE);
|
||||
if (error != B_OK) {
|
||||
dprintf("Failed to resize /var/swap to %lld bytes: %s\n", size,
|
||||
strerror(error));
|
||||
dprintf("%s: Failed to resize %s to %lld bytes: %s\n", __func__,
|
||||
swapPath, swapSize, strerror(error));
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
error = swap_file_add("/var/swap");
|
||||
if (error != B_OK)
|
||||
dprintf("Failed to add swap file /var/swap: %s\n", strerror(error));
|
||||
error = swap_file_add(swapPath);
|
||||
if (error != B_OK) {
|
||||
dprintf("%s: Failed to add swap file %s: %s\n", __func__, swapPath,
|
||||
strerror(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user