外观
os/test_models.py
配套源码,运行方法见同目录 README。返回实验总览。
python
import unittest
from models import MAXVA, PAGE_SIZE, MemoryFault, OutOfMemory, PhysicalMemory, Process, Sv39, split_va
class AddressTranslationTests(unittest.TestCase):
def test_indexes_are_nine_bit_fields(self):
va = (3 << 30) | (5 << 21) | (7 << 12) | 0xABC
self.assertEqual(split_va(va), (3, 5, 7, 0xABC))
def test_four_kib_page_offset_is_preserved(self):
table = Sv39()
table.map(0x4000, 0x80002000, write=True)
self.assertEqual(table.translate(0x4123, "write"), 0x80002123)
def test_two_mib_leaf_preserves_twenty_one_bit_offset(self):
table = Sv39()
table.map(0x200000, 0x80400000, level=1)
self.assertEqual(table.translate(0x3FFFFF), 0x805FFFFF)
def test_one_gib_leaf(self):
table = Sv39()
table.map(0x40000000, 0x80000000, level=2)
self.assertEqual(table.translate(0x456789AB), 0x856789AB)
def test_permissions_and_user_bit(self):
table = Sv39()
table.map(0, 0x80000000, read=False, execute=True)
table.map(PAGE_SIZE, 0x80001000, user=False)
self.assertEqual(table.translate(0, "execute"), 0x80000000)
with self.assertRaises(MemoryFault):
table.translate(0, "read")
with self.assertRaises(MemoryFault):
table.translate(PAGE_SIZE)
self.assertEqual(table.translate(PAGE_SIZE, from_user=False), 0x80001000)
def test_missing_and_out_of_range_addresses(self):
table = Sv39()
for address in (0, -1, MAXVA):
with self.assertRaises(MemoryFault):
table.translate(address)
def test_alignment_and_illegal_pte_permissions(self):
table = Sv39()
with self.assertRaises(MemoryFault):
table.map(0, PAGE_SIZE, level=1)
with self.assertRaises(MemoryFault):
table.map(0, 0, read=False, write=True)
def test_huge_page_overlap_cannot_replace_mapping(self):
table = Sv39()
table.map(0, 0x80000000, level=1)
with self.assertRaises(MemoryFault):
table.map(PAGE_SIZE, 0x90000000)
self.assertEqual(table.translate(PAGE_SIZE), 0x80001000)
class CopyOnWriteTests(unittest.TestCase):
def setUp(self):
self.memory = PhysicalMemory(capacity=8)
self.parent = Process(self.memory)
self.parent.map_zero(0)
self.parent.write(0, b"initial")
def test_fork_does_not_copy_data_pages(self):
child = self.parent.fork()
self.assertEqual(len(self.memory.pages), 1)
self.assertEqual(self.memory.copied_bytes, 0)
self.assertEqual(child.read(0, 7), b"initial")
self.assertEqual(list(self.memory.refs.values()), [2])
self.assertFalse(self.parent.mappings[0].writable)
def test_child_write_preserves_parent_and_untouched_bytes(self):
child = self.parent.fork()
child.write(0, b"new")
self.assertEqual(child.read(0, 7), b"newtial")
self.assertEqual(self.parent.read(0, 7), b"initial")
self.assertEqual(self.memory.copied_bytes, PAGE_SIZE)
child.write(0, b"abc")
self.assertEqual(self.memory.copied_bytes, PAGE_SIZE)
def test_parent_write_preserves_child(self):
child = self.parent.fork()
self.parent.write(0, b"P")
self.assertEqual(child.read(0, 1), b"i")
def test_fork_of_already_cow_mapping_preserves_cow_bit(self):
child = self.parent.fork()
grandchild = child.fork()
self.assertEqual(list(self.memory.refs.values()), [3])
grandchild.write(0, b"G")
self.assertEqual(self.parent.read(0, 1), b"i")
self.assertEqual(child.read(0, 1), b"i")
def test_original_readonly_page_remains_readonly_after_fork(self):
self.parent.map_zero(PAGE_SIZE, writable=False)
child = self.parent.fork()
self.assertFalse(child.mappings[1].cow)
with self.assertRaises(MemoryFault):
child.write(PAGE_SIZE, b"x")
def test_last_reference_can_become_writable_without_copy(self):
child = self.parent.fork()
child.close()
self.parent.write(0, b"P")
self.assertEqual(self.memory.copied_bytes, 0)
self.assertEqual(len(self.memory.pages), 1)
def test_exit_order_never_frees_shared_live_page(self):
child = self.parent.fork()
self.parent.close()
self.assertEqual(child.read(0, 7), b"initial")
child.close()
self.assertEqual(self.memory.pages, {})
self.assertEqual(self.memory.refs, {})
child.close() # Idempotent teardown.
def test_oom_during_cow_preserves_mapping_and_data(self):
self.memory.capacity = 1
child = self.parent.fork()
old_page = child.mappings[0].page
with self.assertRaises(OutOfMemory):
child.write(0, b"X")
self.assertEqual(child.mappings[0].page, old_page)
self.assertEqual(child.read(0, 7), b"initial")
self.assertEqual(self.memory.refs[old_page], 2)
def test_kernel_copyout_resolves_cow_on_each_crossed_page(self):
self.parent.map_zero(PAGE_SIZE)
child = self.parent.fork()
child.copyout(PAGE_SIZE - 2, b"ABCD")
self.assertEqual(child.read(PAGE_SIZE - 2, 4), b"ABCD")
self.assertEqual(self.parent.read(PAGE_SIZE - 2, 4), bytes(4))
self.assertEqual(self.memory.copied_bytes, 2 * PAGE_SIZE)
def test_unmapped_write_does_not_allocate(self):
before = self.memory.allocations
with self.assertRaises(MemoryFault):
self.parent.write(3 * PAGE_SIZE, b"x")
self.assertEqual(self.memory.allocations, before)
def test_repeated_forks_writes_and_exits_reclaim_every_page(self):
children = [self.parent.fork() for _ in range(4)]
for index, child in enumerate(children):
child.write(0, bytes([index]))
self.assertEqual(self.parent.read(0, 7), b"initial")
for child in children:
child.close()
self.parent.close()
self.assertEqual(len(self.memory.pages), 0)
self.assertEqual(self.memory.refs, {})
if __name__ == "__main__":
unittest.main()