mirror of
https://github.com/copy/v86.git
synced 2026-10-12 02:12:23 +00:00
Virtio balloon
This commit is contained in:
@@ -81,7 +81,7 @@ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature
|
||||
CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \
|
||||
memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \
|
||||
acpi.js apic.js ioapic.js \
|
||||
state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js \
|
||||
state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \
|
||||
bus.js log.js cpu.js debug.js \
|
||||
elf.js kernel.js
|
||||
LIB_FILES=9p.js filesystem.js jor1k.js marshall.js
|
||||
@@ -327,6 +327,7 @@ devices-test: all-debug
|
||||
./tests/devices/fetch_network.js
|
||||
USE_VIRTIO=1 ./tests/devices/fetch_network.js
|
||||
./tests/devices/wisp_network.js
|
||||
./tests/devices/virtio_balloon.js
|
||||
|
||||
rust-test: $(RUST_FILES)
|
||||
env RUSTFLAGS="-D warnings" RUST_BACKTRACE=full RUST_TEST_THREADS=1 cargo test -- --nocapture
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<script src="src/ne2k.js"></script>
|
||||
<script src="src/state.js"></script>
|
||||
<script src="src/virtio.js"></script>
|
||||
<script src="src/virtio_balloon.js"></script>
|
||||
<script src="src/virtio_console.js"></script>
|
||||
<script src="src/virtio_net.js"></script>
|
||||
<script src="src/bus.js"></script>
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ let files = [
|
||||
"src/virtio.js",
|
||||
"src/virtio_console.js",
|
||||
"src/virtio_net.js",
|
||||
//"src/virtio_balloon.js",
|
||||
"src/virtio_balloon.js",
|
||||
"src/bus.js",
|
||||
|
||||
"src/debug.js",
|
||||
|
||||
@@ -304,6 +304,7 @@ V86.prototype.continue_init = async function(emulator, options)
|
||||
settings.preserve_mac_from_state_image = options.preserve_mac_from_state_image;
|
||||
settings.mac_address_translation = options.mac_address_translation;
|
||||
settings.cpuid_level = options.cpuid_level;
|
||||
settings.virtio_balloon = options.virtio_balloon;
|
||||
settings.virtio_console = options.virtio_console;
|
||||
settings.virtio_net = options.virtio_net;
|
||||
settings.screen_options = options.screen_options;
|
||||
|
||||
+6
-1
@@ -420,6 +420,7 @@ CPU.prototype.get_state = function()
|
||||
state[81] = this.devices.uart3;
|
||||
state[82] = this.devices.virtio_console;
|
||||
state[83] = this.devices.virtio_net;
|
||||
state[84] = this.devices.virtio_balloon;
|
||||
|
||||
return state;
|
||||
};
|
||||
@@ -552,6 +553,7 @@ CPU.prototype.set_state = function(state)
|
||||
this.devices.uart3 && this.devices.uart3.set_state(state[81]);
|
||||
this.devices.virtio_console && this.devices.virtio_console.set_state(state[82]);
|
||||
this.devices.virtio_net && this.devices.virtio_net.set_state(state[83]);
|
||||
this.devices.virtio_balloon && this.devices.virtio_balloon.set_state(state[84]);
|
||||
|
||||
this.fw_value = state[62];
|
||||
|
||||
@@ -651,7 +653,7 @@ CPU.prototype.pack_memory = function()
|
||||
|
||||
CPU.prototype.unpack_memory = function(bitmap, packed_memory)
|
||||
{
|
||||
this.zero_memory(this.memory_size[0]);
|
||||
this.zero_memory(0, this.memory_size[0]);
|
||||
|
||||
const page_count = this.memory_size[0] >> 12;
|
||||
let packed_page = 0;
|
||||
@@ -979,6 +981,9 @@ CPU.prototype.init = function(settings, device_bus)
|
||||
{
|
||||
this.devices.virtio_console = new VirtioConsole(this, device_bus);
|
||||
}
|
||||
if(settings.virtio_balloon) {
|
||||
this.devices.virtio_balloon = new VirtioBalloon(this, device_bus);
|
||||
}
|
||||
|
||||
if(true)
|
||||
{
|
||||
|
||||
@@ -39,7 +39,9 @@ pub fn allocate_memory(size: u32) -> u32 {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn zero_memory(size: u32) { ptr::write_bytes(mem8, 0, size as usize); }
|
||||
pub unsafe fn zero_memory(addr: u32, size: u32) {
|
||||
ptr::write_bytes(mem8.offset(addr as isize), 0, size as usize);
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static mut vga_mem8: *mut u8 = ptr::null_mut();
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
"use strict";
|
||||
|
||||
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003
|
||||
|
||||
const VIRTIO_BALLOON_F_MUST_TELL_HOST = 0;
|
||||
const VIRTIO_BALLOON_F_STATS_VQ = 1;
|
||||
const VIRTIO_BALLOON_F_DEFLATE_ON_OOM = 2;
|
||||
const VIRTIO_BALLOON_F_FREE_PAGE_HINT = 3;
|
||||
|
||||
const STAT_NAMES = [
|
||||
"SWAP_IN",
|
||||
"SWAP_OUT",
|
||||
"MAJFLT",
|
||||
"MINFLT",
|
||||
"MEMFREE",
|
||||
"MEMTOT",
|
||||
"AVAIL",
|
||||
"CACHES",
|
||||
"HTLB_PGALLOC",
|
||||
"HTLB_PGFAIL",
|
||||
];
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {CPU} cpu
|
||||
* @param {BusConnector} bus
|
||||
*/
|
||||
function VirtioBalloon(cpu, bus)
|
||||
{
|
||||
/** @const @type {BusConnector} */
|
||||
this.bus = bus;
|
||||
this.num_pages = 0;
|
||||
this.actual = 0;
|
||||
this.fp_cmd = 0;
|
||||
this.zeroed = 0;
|
||||
|
||||
const queues = [
|
||||
{size_supported: 32, notify_offset: 0},
|
||||
{size_supported: 32, notify_offset: 0},
|
||||
{size_supported: 2, notify_offset: 1},
|
||||
{size_supported: 64, notify_offset: 2},
|
||||
];
|
||||
|
||||
//setInterval(() => this.GetStats(console.log.bind(console, "STATS")), 10000);
|
||||
|
||||
/** @type {VirtIO} */
|
||||
this.virtio = new VirtIO(cpu,
|
||||
{
|
||||
name: "virtio-balloon",
|
||||
pci_id: 0x0B << 3,
|
||||
device_id: 0x1045,
|
||||
subsystem_device_id: 5,
|
||||
common:
|
||||
{
|
||||
initial_port: 0xD800,
|
||||
queues: queues,
|
||||
features:
|
||||
[
|
||||
VIRTIO_BALLOON_F_STATS_VQ,
|
||||
VIRTIO_BALLOON_F_FREE_PAGE_HINT,
|
||||
VIRTIO_F_VERSION_1,
|
||||
],
|
||||
on_driver_ok: () => {
|
||||
dbg_log("Balloon setup", LOG_PCI);
|
||||
},
|
||||
},
|
||||
notification:
|
||||
{
|
||||
initial_port: 0xD900,
|
||||
single_handler: false,
|
||||
handlers:
|
||||
[
|
||||
(queue_id) =>
|
||||
{
|
||||
const queue = this.virtio.queues[queue_id];
|
||||
while(queue.has_request())
|
||||
{
|
||||
const bufchain = queue.pop_request();
|
||||
const buffer = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(buffer);
|
||||
this.virtio.queues[queue_id].push_reply(bufchain);
|
||||
let n = buffer.byteLength / 4;
|
||||
this.actual += (queue_id === 0 ? n : -n);
|
||||
//console.log(queue_id === 0 ? "Inflate" : "Deflate", this.num_pages, this.actual, bufchain.read_buffers);
|
||||
}
|
||||
this.virtio.queues[queue_id].flush_replies();
|
||||
},
|
||||
(queue_id) =>
|
||||
{
|
||||
const queue = this.virtio.queues[queue_id];
|
||||
if(queue.has_request())
|
||||
{
|
||||
const bufchain = queue.pop_request();
|
||||
const buffer = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(buffer);
|
||||
let result = {};
|
||||
for(let i = 0; i < bufchain.length_readable; i += 10) {
|
||||
let [cat, value] = marshall.Unmarshall(["h", "d"], buffer, { offset : i });
|
||||
result[STAT_NAMES[cat]] = value;
|
||||
}
|
||||
this.virtio.queues[queue_id].push_reply(bufchain);
|
||||
if(this.stats_cb) this.stats_cb(result);
|
||||
}
|
||||
},
|
||||
(queue_id) =>
|
||||
{
|
||||
const queue = this.virtio.queues[queue_id];
|
||||
while(queue.has_request())
|
||||
{
|
||||
const bufchain = queue.pop_request();
|
||||
if(bufchain.length_readable > 0) {
|
||||
const buffer = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(buffer);
|
||||
let [cmd] = marshall.Unmarshall(["w"], buffer, { offset : 0 });
|
||||
if(cmd === 0) {
|
||||
if(this.free_cb) this.free_cb(this.zeroed);
|
||||
if(this.fp_cmd > 1) this.fp_cmd = 1; // Signal done
|
||||
this.virtio.notify_config_changes();
|
||||
}
|
||||
}
|
||||
if(bufchain.length_writable > 0) {
|
||||
// console.log("Free pages hinted", bufchain.read_buffers, bufchain.write_buffers);
|
||||
let zeros = new Uint8Array(0);
|
||||
for(let i = 0; i < bufchain.write_buffers.length; ++i) {
|
||||
let b = bufchain.write_buffers[i];
|
||||
this.zeroed += b.len;
|
||||
this.virtio.cpu.zero_memory(b.addr_low, b.len);
|
||||
}
|
||||
}
|
||||
this.virtio.queues[queue_id].push_reply(bufchain);
|
||||
}
|
||||
this.virtio.queues[queue_id].flush_replies();
|
||||
},
|
||||
],
|
||||
},
|
||||
isr_status:
|
||||
{
|
||||
initial_port: 0xD700,
|
||||
},
|
||||
device_specific:
|
||||
{
|
||||
initial_port: 0xD600,
|
||||
struct:
|
||||
[
|
||||
{
|
||||
bytes: 4,
|
||||
name: "num_pages",
|
||||
read: () => this.num_pages,
|
||||
write: data => { /* read only */ },
|
||||
},
|
||||
{
|
||||
bytes: 4,
|
||||
name: "actual",
|
||||
read: () => {
|
||||
return this.actual;
|
||||
},
|
||||
write: data => { /* read only */ },
|
||||
},
|
||||
{
|
||||
bytes: 4,
|
||||
name: "free_page_hint_cmd_id",
|
||||
read: () => this.fp_cmd,
|
||||
write: data => { /* read only */ },
|
||||
}
|
||||
]
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
VirtioBalloon.prototype.Inflate = function(amount) {
|
||||
this.num_pages += amount;
|
||||
this.virtio.notify_config_changes();
|
||||
};
|
||||
|
||||
VirtioBalloon.prototype.Deflate = function(amount) {
|
||||
this.num_pages -= amount;
|
||||
this.virtio.notify_config_changes();
|
||||
};
|
||||
|
||||
VirtioBalloon.prototype.Cleanup = function(cb) {
|
||||
this.fp_cmd = 2;
|
||||
this.free_cb = cb;
|
||||
this.zeroed = 0;
|
||||
this.virtio.notify_config_changes();
|
||||
};
|
||||
|
||||
|
||||
VirtioBalloon.prototype.get_state = function()
|
||||
{
|
||||
const state = [];
|
||||
state[0] = this.virtio;
|
||||
state[1] = this.num_pages;
|
||||
state[2] = this.actual;
|
||||
return state;
|
||||
};
|
||||
|
||||
VirtioBalloon.prototype.set_state = function(state)
|
||||
{
|
||||
this.virtio.set_state(state[0]);
|
||||
this.num_pages = state[1];
|
||||
this.actual = state[2];
|
||||
};
|
||||
|
||||
VirtioBalloon.prototype.GetStats = function(data)
|
||||
{
|
||||
this.stats_cb = data;
|
||||
const queue = this.virtio.queues[2];
|
||||
while(queue.has_request())
|
||||
{
|
||||
const bufchain = queue.pop_request();
|
||||
this.virtio.queues[2].push_reply(bufchain);
|
||||
}
|
||||
this.virtio.queues[2].flush_replies();
|
||||
};
|
||||
|
||||
VirtioBalloon.prototype.Reset = function() {
|
||||
|
||||
};
|
||||
Executable
+213
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
process.on("unhandledRejection", exn => { throw exn; });
|
||||
|
||||
const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
|
||||
|
||||
const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
|
||||
|
||||
const assert = require("assert").strict;
|
||||
const SHOW_LOGS = false;
|
||||
|
||||
function wait(time) {
|
||||
return new Promise((res) => setTimeout(res, time));
|
||||
}
|
||||
|
||||
let s1;
|
||||
const tests =
|
||||
[
|
||||
{
|
||||
name: "Use space",
|
||||
start: async () =>
|
||||
{
|
||||
emulator.serial0_send("udhcpc\n");
|
||||
emulator.serial0_send("free -h\n");
|
||||
emulator.serial0_send("lspci -k\n");
|
||||
emulator.serial0_send("dd if=/dev/urandom of=/tmp/file bs=1M count=64\n");
|
||||
emulator.serial0_send("du -h /tmp/file\n");
|
||||
emulator.serial0_send("free -h\n");
|
||||
emulator.serial0_send("rm /tmp/file\n");
|
||||
emulator.serial0_send("free -h\n");
|
||||
emulator.serial0_send("echo done\n");
|
||||
},
|
||||
end_trigger: "done",
|
||||
end: (capture) =>
|
||||
{
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Delete space",
|
||||
start: async () =>
|
||||
{
|
||||
emulator.serial0_send("echo 3 > /proc/sys/vm/drop_caches\n");
|
||||
emulator.serial0_send("df -h; echo done\n");
|
||||
},
|
||||
end_trigger: "done",
|
||||
end: (capture) =>
|
||||
{
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Reclaim space",
|
||||
start: async () =>
|
||||
{
|
||||
s1 = await emulator.save_state();
|
||||
emulator.v86.cpu.devices.virtio_balloon.GetStats(s => { console.log(s); });
|
||||
emulator.v86.cpu.devices.virtio_balloon.Cleanup(async (zeroed) => {
|
||||
console.log("Zeroed ", zeroed / 1024 / 1024, "MB of pages");
|
||||
await wait(2000);
|
||||
emulator.serial0_send("echo done\n");
|
||||
});
|
||||
},
|
||||
end_trigger: "done",
|
||||
end: async (capture) =>
|
||||
{
|
||||
let s2 = await emulator.save_state();
|
||||
let saved = s1.byteLength - s2.byteLength;
|
||||
console.log(`Saved ${saved>>20}MB of ${s1.byteLength>>20}MB (${saved/s1.byteLength*100}%)`);
|
||||
assert(saved > 1000000, "not enough space saved");
|
||||
emulator.serial0_send("echo done\n");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Wait",
|
||||
start: async () =>
|
||||
{
|
||||
},
|
||||
end_trigger: "done",
|
||||
end: async (capture) =>
|
||||
{
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get Memory",
|
||||
start: async () =>
|
||||
{
|
||||
emulator.serial0_send("free -m; echo done\n");
|
||||
},
|
||||
end_trigger: "done",
|
||||
end: async (capture) =>
|
||||
{
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Inflate Balloon",
|
||||
start: async () =>
|
||||
{
|
||||
emulator.v86.cpu.devices.virtio_balloon.Inflate(30000);
|
||||
await wait(1000);
|
||||
emulator.serial0_send("free -m; echo done\n");
|
||||
},
|
||||
end_trigger: "done",
|
||||
end: async (capture) =>
|
||||
{
|
||||
assert(emulator.v86.cpu.devices.virtio_balloon.actual === 30000, "Got 30000 pages from vm");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const emulator = new V86({
|
||||
bios: { url: __dirname + "/../../bios/seabios.bin" },
|
||||
vga_bios: { url: __dirname + "/../../bios/vgabios.bin" },
|
||||
bzimage: { url: __dirname + "/../../images/buildroot-bzimage68.bin" },
|
||||
autostart: true,
|
||||
memory_size: 200 * 1024 * 1024,
|
||||
disable_jit: +process.env.DISABLE_JIT,
|
||||
virtio_console: true,
|
||||
virtio_balloon: true,
|
||||
uart0: true,
|
||||
net_device: {
|
||||
relay_url: "fetch",
|
||||
type: "virtio",
|
||||
},
|
||||
log_level: SHOW_LOGS ? 0x400000 : 0,
|
||||
cmdline: "console=/dev/ttyS0"
|
||||
});
|
||||
|
||||
let test_num = 0;
|
||||
let booted = false;
|
||||
let line = "";
|
||||
let capture = "";
|
||||
let end_trigger;
|
||||
|
||||
emulator.bus.register("emulator-started", function()
|
||||
{
|
||||
console.log("Booting now, please stand by");
|
||||
});
|
||||
|
||||
emulator.add_listener("serial0-output-byte", function(byte)
|
||||
{
|
||||
const chr = String.fromCharCode(byte);
|
||||
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let new_line = "";
|
||||
if(chr === "\n")
|
||||
{
|
||||
console.log(" Captured: %s", line);
|
||||
new_line = line;
|
||||
capture += line + "\n";
|
||||
line = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
line += chr;
|
||||
}
|
||||
|
||||
if(new_line === end_trigger)
|
||||
{
|
||||
let test_has_failed = false;
|
||||
|
||||
try {
|
||||
tests[test_num].end(capture);
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
test_has_failed = true;
|
||||
}
|
||||
|
||||
if(!test_has_failed)
|
||||
{
|
||||
console.log("[+] Test #%d passed: %s", test_num, tests[test_num].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(tests[test_num].allow_failure)
|
||||
{
|
||||
console.warn("[!] Test #%d failed: %s (failure allowed)", test_num, tests[test_num].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error("[-] Test #%d failed: %s", test_num, tests[test_num].name);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
test_num++;
|
||||
|
||||
}
|
||||
|
||||
if(!booted && line.endsWith("~% ") || new_line === end_trigger)
|
||||
{
|
||||
booted = true;
|
||||
|
||||
if(test_num >= tests.length)
|
||||
{
|
||||
emulator.destroy();
|
||||
|
||||
console.log("Tests finished.");
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("Starting test #%d: %s", test_num, tests[test_num].name);
|
||||
|
||||
capture = "";
|
||||
end_trigger = tests[test_num].end_trigger;
|
||||
|
||||
tests[test_num].start();
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user