操作系統分頁存儲管理詳解及Java實現

1. 分頁存儲管理概述

分頁存儲管理是操作系統內存管理的重要技術之一,它將進程的地址空間和物理內存都劃分為固定大小的塊,分別稱為頁面(Page)和頁框(Page Frame)。通過頁表(Page Table)實現邏輯地址到物理地址的轉換。

1.1 基本概念

  • 頁面:進程邏輯地址空間的固定大小塊
  • 頁框:物理內存的固定大小塊
  • 頁表:存儲頁面到頁框映射關係的數據結構
  • 頁表項:頁表中的每個條目,包含物理頁框號和其他控制信息

2. 分頁存儲管理原理

2.1 地址轉換機制

邏輯地址轉換為物理地址的過程:

邏輯地址 = 頁號(P) + 頁內偏移(D) 物理地址 = 頁框號(F) × 頁面大小 + 頁內偏移(D)

2.2 頁表結構

頁表通常包含以下信息:

  • 物理頁框號
  • 有效位(表示頁面是否在內存中)
  • 訪問權限位
  • 修改位(髒位)
  • 訪問時間戳等

3. Java實現分頁存儲管理

下面我們使用Java實現一個完整的分頁存儲管理系統。

3.1 內存管理單元(MMU)實現

/**

 * 內存管理單元 - 負責地址轉換

 */

public class MemoryManagementUnit {

    private PageTable pageTable;

    private PhysicalMemory physicalMemory;

    private final int pageSize;

    private int pageFaultCount;

    

    public MemoryManagementUnit(int pageSize, int physicalMemorySize) {

        this.pageSize = pageSize;

        this.physicalMemory = new PhysicalMemory(physicalMemorySize, pageSize);

        this.pageTable = new PageTable();

        this.pageFaultCount = 0;

    }

    

    /**

     * 將邏輯地址轉換為物理地址

     */

    public int translateAddress(int logicalAddress) {

        int pageNumber = logicalAddress / pageSize;

        int offset = logicalAddress % pageSize;

        

        PageTableEntry entry = pageTable.getEntry(pageNumber);

        

        if (entry == null || !entry.isValid()) {

            // 頁面錯誤處理

            handlePageFault(pageNumber);

            entry = pageTable.getEntry(pageNumber);

        }

        

        // 更新訪問信息

        entry.setAccessed(true);

        entry.setLastAccessTime(System.currentTimeMillis());

        

        int frameNumber = entry.getFrameNumber();

        return frameNumber * pageSize + offset;

    }

    

    /**

     * 處理頁面錯誤

     */

    private void handlePageFault(int pageNumber) {

        pageFaultCount++;

        System.out.println("頁面錯誤發生! 頁面號: " + pageNumber);

        

        // 分配新的頁框

        int frameNumber = allocateFrame();

        

        if (frameNumber == -1) {

            // 需要頁面置換

            frameNumber = performPageReplacement();

        }

        

        // 加載頁面到內存

        loadPageToMemory(pageNumber, frameNumber);

        

        // 更新頁表

        PageTableEntry newEntry = new PageTableEntry(frameNumber, true);

        pageTable.addEntry(pageNumber, newEntry);

    }

    

    /**

     * 分配頁框

     */

    private int allocateFrame() {

        return physicalMemory.allocateFrame();

    }

    

    /**

     * 執行頁面置換

     */

    private int performPageReplacement() {

        // 使用LRU算法選擇要置換的頁面

        int victimPage = selectVictimPage();

        PageTableEntry victimEntry = pageTable.getEntry(victimPage);

        

        // 如果頁面被修改過,需要寫回磁盤

        if (victimEntry.isDirty()) {

            writePageToDisk(victimPage, victimEntry.getFrameNumber());

        }

        

        int frameNumber = victimEntry.getFrameNumber();

        

        // 使頁表項無效

        victimEntry.setValid(false);

        

        return frameNumber;

    }

    

    /**

     * 使用LRU算法選擇被置換的頁面

     */

    private int selectVictimPage() {

        return pageTable.findLRUPage();

    }

    

    /**

     * 加載頁面到內存(模擬)

     */

    private void loadPageToMemory(int pageNumber, int frameNumber) {

        System.out.println("加載頁面 " + pageNumber + " 到頁框 " + frameNumber);

        // 在實際系統中,這裏會從磁盤讀取數據到物理內存

        physicalMemory.loadPage(frameNumber, pageNumber);

    }

    

    /**

     * 寫頁面到磁盤(模擬)

     */

    private void writePageToDisk(int pageNumber, int frameNumber) {

        System.out.println("將頁面 " + pageNumber + " 從頁框 " + frameNumber + " 寫回磁盤");

    }

    

    // 統計信息

    public void printStatistics() {

        System.out.println("總頁面錯誤次數: " + pageFaultCount);

    }

    

    public PageTable getPageTable() {

        return pageTable;

    }

}

3.2 頁表實現

import java.util.*;

import java.util.concurrent.ConcurrentHashMap;/**

 * 頁表實現

 */

public class PageTable {

    private Map<Integer, PageTableEntry> entries;

    private int size;

    

    public PageTable() {

        this.entries = new ConcurrentHashMap<>();

        this.size = 0;

    }

    

    public PageTableEntry getEntry(int pageNumber) {

        return entries.get(pageNumber);

    }

    

    public void addEntry(int pageNumber, PageTableEntry entry) {

        entries.put(pageNumber, entry);

        size++;

    }

    

    public void removeEntry(int pageNumber) {

        entries.remove(pageNumber);

        size--;

    }

    

    /**

     * 查找最近最少使用的頁面

     */

    public int findLRUPage() {

        long minAccessTime = Long.MAX_VALUE;

        int lruPage = -1;

        

        for (Map.Entry<Integer, PageTableEntry> entry : entries.entrySet()) {

            if (entry.getValue().isValid() && 

                entry.getValue().getLastAccessTime() < minAccessTime) {

                minAccessTime = entry.getValue().getLastAccessTime();

                lruPage = entry.getKey();

            }

        }

        

        return lruPage;

    }

    

    /**

     * 獲取頁表大小

     */

    public int getSize() {

        return size;

    }

    

    /**

     * 打印頁表內容(用於調試)

     */

    public void printPageTable() {

        System.out.println("=== 頁表內容 ===");

        System.out.println("頁號\t頁框號\t有效位\t修改位\t訪問時間");

        

        for (Map.Entry<Integer, PageTableEntry> entry : entries.entrySet()) {

            PageTableEntry pte = entry.getValue();

            if (pte.isValid()) {

                System.out.printf("%d\t%d\t%s\t%s\t%d%n",

                    entry.getKey(),

                    pte.getFrameNumber(),

                    pte.isValid() ? "1" : "0",

                    pte.isDirty() ? "1" : "0",

                    pte.getLastAccessTime());

            }

        }

    }

}3.3 頁表項實現
/**

 * 頁表項類

 */

public class PageTableEntry {

    private int frameNumber;      // 物理頁框號

    private boolean valid;        // 有效位

    private boolean dirty;        // 修改位(髒位)

    private boolean accessed;     // 訪問位

    private long lastAccessTime;  // 最後訪問時間

    

    public PageTableEntry(int frameNumber, boolean valid) {

        this.frameNumber = frameNumber;

        this.valid = valid;

        this.dirty = false;

        this.accessed = false;

        this.lastAccessTime = System.currentTimeMillis();

    }

    

    // Getter 和 Setter 方法

    public int getFrameNumber() {

        return frameNumber;

    }

    

    public void setFrameNumber(int frameNumber) {

        this.frameNumber = frameNumber;

    }

    

    public boolean isValid() {

        return valid;

    }

    

    public void setValid(boolean valid) {

        this.valid = valid;

    }

    

    public boolean isDirty() {

        return dirty;

    }

    

    public void setDirty(boolean dirty) {

        this.dirty = dirty;

    }

    

    public boolean isAccessed() {

        return accessed;

    }

    

    public void setAccessed(boolean accessed) {

        this.accessed = accessed;

        if (accessed) {

            this.lastAccessTime = System.currentTimeMillis();

        }

    }

    

    public long getLastAccessTime() {

        return lastAccessTime;

    }

    

    public void setLastAccessTime(long lastAccessTime) {

        this.lastAccessTime = lastAccessTime;

    }

}3.4 物理內存管理
import java.util.BitSet;
/**

 * 物理內存管理

 */

public class PhysicalMemory {

    private byte[] memory;           // 物理內存數組

    private BitSet frameAllocation;  // 頁框分配位圖

    private int frameCount;          // 總頁框數

    private int pageSize;            // 頁面大小

    private int allocatedFrames;     // 已分配頁框數

    

    public PhysicalMemory(int totalSize, int pageSize) {

        this.pageSize = pageSize;

        this.frameCount = totalSize / pageSize;

        this.memory = new byte[totalSize];

        this.frameAllocation = new BitSet(frameCount);

        this.allocatedFrames = 0;

        

        System.out.println("物理內存初始化: " + totalSize + " bytes, " + 

                          frameCount + " 個頁框, 頁面大小: " + pageSize + " bytes");

    }

    

    /**

     * 分配一個空閒頁框

     */

    public int allocateFrame() {

        if (allocatedFrames >= frameCount) {

            return -1; // 沒有空閒頁框

        }

        

        int freeFrame = frameAllocation.nextClearBit(0);

        if (freeFrame < frameCount) {

            frameAllocation.set(freeFrame);

            allocatedFrames++;

            System.out.println("分配頁框: " + freeFrame);

            return freeFrame;

        }

        

        return -1;

    }

    

    /**

     * 釋放頁框

     */

    public void freeFrame(int frameNumber) {

        if (frameNumber >= 0 && frameNumber < frameCount && frameAllocation.get(frameNumber)) {

            frameAllocation.clear(frameNumber);

            allocatedFrames--;

            System.out.println("釋放頁框: " + frameNumber);

        }

    }

    

    /**

     * 加載頁面到指定頁框(模擬)

     */

    public void loadPage(int frameNumber, int pageNumber) {

        int startAddress = frameNumber * pageSize;

        // 模擬從磁盤加載數據到內存

        // 在實際系統中,這裏會有實際的磁盤I/O操作

        

        System.out.println("模擬加載頁面 " + pageNumber + " 數據到頁框 " + frameNumber + 

                          " (地址: " + startAddress + ")");

    }

    

    /**

     * 從物理內存讀取數據

     */

    public byte readByte(int physicalAddress) {

        if (physicalAddress >= 0 && physicalAddress < memory.length) {

            return memory[physicalAddress];

        }

        throw new IllegalArgumentException("無效的物理地址: " + physicalAddress);

    }

    

    /**

     * 向物理內存寫入數據

     */

    public void writeByte(int physicalAddress, byte data) {

        if (physicalAddress >= 0 && physicalAddress < memory.length) {

            memory[physicalAddress] = data;

        } else {

            throw new IllegalArgumentException("無效的物理地址: " + physicalAddress);

        }

    }

    

    /**

     * 獲取內存使用統計

     */

    public void printMemoryUsage() {

        System.out.println("物理內存使用情況: " + allocatedFrames + "/" + frameCount + 

                          " 頁框已分配 (" + 

                          (allocatedFrames * 100 / frameCount) + "%)");

    }

}

3.5 進程模擬類

import java.util.Random;
/**

 * 模擬進程的內存訪問行為

 */

public class ProcessSimulator {

    private MemoryManagementUnit mmu;

    private Random random;

    private int processId;

    

    public ProcessSimulator(int processId, MemoryManagementUnit mmu) {

        this.processId = processId;

        this.mmu = mmu;

        this.random = new Random();

    }

    

    /**

     * 模擬進程執行,產生內存訪問序列

     */

    public void execute(int accessCount) {

        System.out.println("進程 " + processId + " 開始執行,將進行 " + accessCount + " 次內存訪問");

        

        for (int i = 0; i < accessCount; i++) {

            // 生成隨機邏輯地址(0-64KB範圍)

            int logicalAddress = random.nextInt(64 * 1024);

            

            // 隨機選擇讀或寫操作

            boolean isWrite = random.nextBoolean();

            

            try {

                int physicalAddress = mmu.translateAddress(logicalAddress);

                

                if (isWrite) {

                    // 模擬寫操作

                    mmu.getPageTable().getEntry(logicalAddress / 4096).setDirty(true);

                    System.out.println("進程 " + processId + ": 寫邏輯地址 " + logicalAddress + 

                                      " -> 物理地址 " + physicalAddress);

                } else {

                    // 模擬讀操作

                    System.out.println("進程 " + processId + ": 讀邏輯地址 " + logicalAddress + 

                                      " -> 物理地址 " + physicalAddress);

                }

                

                // 隨機休眠,模擬處理時間

                Thread.sleep(random.nextInt(10));

                

            } catch (Exception e) {

                System.err.println("內存訪問錯誤: " + e.getMessage());

            }

        }

        

        System.out.println("進程 " + processId + " 執行完成");

    }

}

3.6 測試主程序

/**

 * 分頁存儲管理系統測試程序

 */

public class PagingSystemDemo {

    public static void main(String[] args) {

        // 系統配置

        int pageSize = 4096;          // 4KB頁面大小

        int physicalMemorySize = 64 * 1024; // 64KB物理內存

        

        System.out.println("=== 分頁存儲管理系統啓動 ===");

        System.out.println("配置: 頁面大小=" + pageSize + "B, 物理內存=" + physicalMemorySize + "B");

        

        // 創建內存管理單元

        MemoryManagementUnit mmu = new MemoryManagementUnit(pageSize, physicalMemorySize);

        

        // 創建並啓動多個進程模擬內存訪問

        Thread[] processes = new Thread[3];

        

        for (int i = 0; i < processes.length; i++) {

            final int processId = i;

            processes[i] = new Thread(() -> {

                ProcessSimulator process = new ProcessSimulator(processId, mmu);

                process.execute(20); // 每個進程進行20次內存訪問

            });

            processes[i].start();

        }

        

        // 等待所有進程完成

        for (Thread process : processes) {

            try {

                process.join();

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        

        // 打印統計信息

        System.out.println("\n=== 系統運行統計 ===");

        mmu.printStatistics();

        mmu.getPageTable().printPageTable();

        

        // 打印內存使用情況

        System.out.println("\n=== 內存使用情況 ===");

        // 這裏需要訪問physicalMemory,在實際系統中應該有相應的方法

        

        System.out.println("=== 分頁存儲管理系統演示完成 ===");

    }

}

4. 分頁系統的優勢與挑戰

4.1 優勢

  1. 內存利用率高:消除了外部碎片
  2. 管理簡單:固定大小的頁面便於管理
  3. 支持虛擬內存:可以實現請求分頁
  4. 共享方便:多個進程可以共享相同的頁面

4.2 挑戰

  1. 內部碎片:頁面末尾未使用的空間
  2. 頁表開銷:頁表本身佔用內存空間
  3. 地址轉換開銷:需要額外的內存訪問
  4. 頁面置換算法:選擇合適的置換策略很關鍵

5. 頁面置換算法

除了上面實現的LRU算法,還有其他常用算法:

5.1 FIFO(先進先出)

public class FIFOPageReplacement {

    private Queue<Integer> pageQueue;

    

    public int selectVictimPage(PageTable pageTable) {

        return pageQueue.poll(); // 返回最早進入的頁面

    }

}5.2 時鐘算法
public class ClockPageReplacement {

    private int clockHand;

    private PageTable pageTable;

    

    public int selectVictimPage() {

        while (true) {

            PageTableEntry entry = pageTable.getEntry(clockHand);

            if (!entry.isAccessed()) {

                return clockHand;

            }

            entry.setAccessed(false);

            clockHand = (clockHand + 1) % pageTable.getSize();

        }

    }

}

6. 總結

分頁存儲管理是現代操作系統的核心內存管理技術,它通過頁面和頁框的映射機制,有效解決了內存碎片問題,支持虛擬內存實現。本文通過完整的Java實現展示了分頁系統的工作原理,包括地址轉換、頁面錯誤處理、頁面置換等關鍵機制。

這個實現雖然簡化,但包含了分頁系統的核心概念,可以作為學習和理解操作系統內存管理的良好起點。在實際操作系統中,這些機制會更加複雜,需要考慮多級頁表、TLB快表、寫時複製等高級特性。