CPU 虛擬化機制——受限直接執行 (LDE)

1. 引言:CPU虛擬化的核心問題

讓多個進程看似同時運行在一個物理CPU上。核心思想是時分共享 (time sharing) CPU。為了實現高效且可控的時分共享,本章介紹了一種關鍵機制,稱為受限直接執行 (Limited Direct Execution, LDE)

1.1 LDE的基本思想

  1. 直接執行 (Direct Execution): 為了性能,讓用戶程序盡可能直接在硬件CPU上運行。
  2. 受限 (Limited): 操作系統必須施加限制,以確保它能保持對系統的控制權,并且用戶程序不能執行危險或非授權的操作。

LDE基本思想

1.2 問題推導

問題推導

LDE協議有兩個階段:

  • 第一階段(在系統引導時):內核初始化陷阱表,并且CPU記住它的位置以供隨后使用。內核通過特權指令來執行此操作。
  • 第二階段(運行進程時):在使用從陷阱返回指令開始執行進程之前,內核設置了一些內容(例如,在進程列表中分配一個節點,分配內存)。這會將CPU切換到用戶模式并開始運行該進程。

2. 問題1:如何執行受限制的操作?

2.1 背景

直接執行時,如果進程需要執行I/O等特權操作怎么辦?不能讓用戶進程隨意操作硬件。

2.2 解決方案:引入特權級別 (Processor Modes)

  • 用戶模式 (User Mode): 運行用戶程序,權限受限,不能執行特權指令(如I/O)。
  • 內核模式 (Kernel Mode): 運行操作系統內核,擁有最高權限,可以執行任何指令,訪問任何硬件。

2.3 解決方案:受保護的控制權轉移 (Protected Control Transfer)

受保護的控制權轉移

  • 系統調用 (System Calls): 用戶程序通過執行特殊的trap指令,陷入(trap)到內核。
  • 陷入過程: trap指令會提升CPU權限到內核模式,并跳轉到操作系統預設的代碼地址。
  • 陷阱表 (Trap Table): 操作系統在啟動時設置一個陷阱表,告訴硬件在發生特定事件時應該跳轉到內核中的哪個處理程序。
  • 返回: 內核完成工作后,執行return-from-trap指令,降低CPU權限回用戶模式。

3. 問題2:如何在進程之間切換?

3.1 背景

實現了LDE后,當一個進程在CPU上運行時,操作系統本身并沒有運行。那么操作系統如何奪回控制權以切換到另一個進程,實現時分共享呢?

3.2 子問題:如何重獲CPU的控制權?

方案1 (協作式 Cooperative):

  • 依賴進程"自覺"
  • 進程通過發起系統調用或產生錯誤將控制權交還給OS
  • 缺陷: 如果進程陷入死循環且不進行系統調用或出錯,OS將失去控制權

方案2 (非協作式/搶占式 Preemptive):

  • 硬件支持——時鐘中斷 (Timer Interrupt)

時鐘中斷機制

  • OS在啟動時設置并啟動一個硬件時鐘
  • 時鐘會定期產生中斷信號,強制打斷當前運行的進程
  • CPU控制權轉移給OS預設的中斷處理程序

3.3 機制:上下文切換 (Context Switch)

當OS決定切換進程時,執行上下文切換:

  1. 保存當前進程的狀態到其進程結構(如PCB)或內核棧中
  2. 加載下一個要運行進程的狀態
  3. 切換內核棧指針
  4. 通過return-from-trap指令返回,CPU開始執行新加載的進程

4. 潛在問題:并發問題

提出擔憂: 如果在處理系統調用或中斷時,又發生了一個中斷怎么辦?

初步解答: 這是并發問題,將在后續章節詳細討論。常用方法包括在處理中斷時禁止中斷 (disable interrupts),或使用復雜的**鎖 (locking)**機制來保護內核數據結構。

5. 深入理解Trap和Trap Table

5.1 Trap (陷阱)

Trap機制說明

在操作系統和計算機體系結構的語境下,"陷阱"是一種機制,它允許將處理器的控制權從用戶模式安全地轉移到內核模式。

目的:

  • 執行受限操作: 通過系統調用實現
  • 處理異常和錯誤: 如除零、訪問無效內存
  • 響應硬件中斷: 如時鐘中斷、I/O完成信號

過程:

  1. 觸發:trap指令、CPU錯誤/異常或硬件中斷觸發
  2. 硬件動作: 保存狀態、提升權限、查詢陷阱表
  3. 內核執行: 跳轉到陷阱處理程序
  4. 返回: 通過return-from-trap指令返回用戶模式

5.2 Trap Table (陷阱表)

陷阱表是一個由操作系統內核創建和維護的數據結構,通常是一個數組。

內容: 每一項包含內核中特定處理程序代碼的內存地址

設置: 操作系統在啟動過程中初始化陷阱表

作用: 指導硬件在發生事件時應該跳轉到哪里

重要性: 確保控制權轉移的安全性和可控性

6. 陷阱表的技術實現

6.1 陷阱表概述

陷阱表結構

  • 陷阱表本質上是一個數組結構
  • 數組的索引對應中斷/異常/陷阱編號
  • 數組的每個元素包含處理該事件的內核代碼地址控制信息

6.2 x86架構的實現:中斷描述符表(IDT)

在x86架構中,陷阱表被稱為中斷描述符表(IDT),最多包含256個條目。

門描述符結構:

struct idt_entry {uint16_t offset_low;    // 處理程序地址低16位uint16_t selector;      // 段選擇子uint8_t  ist;           // IST索引和保留位uint8_t  type_attr;     // 類型和屬性uint16_t offset_mid;    // 處理程序地址中16位uint32_t offset_high;   // 處理程序地址高32位uint32_t zero;          // 保留位
} __attribute__((packed));

6.3 IDT初始化代碼示例

void initialize_idt() {// 設置除零錯誤處理程序set_idt_gate(0, (uint64_t)&divide_by_zero_handler, KERNEL_CS, 0x8E, 0);// 設置缺頁錯誤處理程序set_idt_gate(14, (uint64_t)&page_fault_handler, KERNEL_CS, 0x8E, 0);// 設置時鐘中斷處理程序set_idt_gate(32, (uint64_t)&timer_interrupt_handler, KERNEL_CS, 0x8E, 0);// 設置系統調用處理程序(注意DPL=3)set_idt_gate(128, (uint64_t)&system_call_handler, KERNEL_CS, 0xEE, 0);// 設置IDT指針idt_pointer.limit = sizeof(idt_table) - 1;idt_pointer.base  = (uint64_t)&idt_table;// 加載IDT寄存器load_idt(&idt_pointer);
}

6.4 處理程序實現

C語言處理函數:

void timer_interrupt_handler_c() { /* 時鐘中斷邏輯 */ }
uint64_t system_call_handler_c(uint64_t syscall_num, uint64_t arg1, ...) { /* 處理系統調用 */ }

匯編包裝器:

timer_interrupt_handler_asm:pushaq                      ; 保存所有通用寄存器call timer_interrupt_handler_c  ; 調用C函數popaq                       ; 恢復所有通用寄存器iretq                       ; 中斷返回

7. 總結

通過LDE機制解釋了如何虛擬化CPU的核心思想是讓程序直接運行,但預先設置好硬件限制(用戶/內核模式、陷阱處理、時鐘中斷),就像給房間做"寶寶防護 (baby proofing)"一樣。這樣既保證了效率,又維持了OS的控制權。

關鍵機制包括:

  • 特權級別分離
  • 受保護的控制權轉移
  • 時鐘中斷
  • 上下文切換
  • 陷阱表

實驗:測量上下文調度時間

創建兩個進程。

創建兩個管道 (pipe) 用于這兩個進程間雙向通信。

管道 1:進程 A -> 進程 B

管道 2:進程 B -> 進程 A

關鍵: 將這兩個進程綁定到同一個 CPU 核心上運行。這可以使用 sched_setaffinity() (Linux) 或類似系統調用。這是為了確保測量的是單個 CPU 上的上下文切換,而不是因為進程在不同 CPU 間遷移。

進行乒乓通信:

進程 A 向管道 1 寫入少量數據(例如 1 字節)。

進程 A 嘗試從管道 2 讀取數據。由于管道 2 是空的,進程 A 會阻塞 (block)。

操作系統檢測到進程 A 阻塞,執行上下文切換,調度進程 B 運行。

進程 B 嘗試從管道 1 讀取數據(讀取 A 寫入的數據)。

進程 B 向管道 2 寫入少量數據。

進程 B 嘗試從管道 1 讀取數據。由于管道 1 現在是空的,進程 B 會阻塞。

操作系統檢測到進程 B 阻塞,執行上下文切換,調度進程 A 運行(此時 A 可以讀到 B 寫入的數據,解除阻塞)。

這樣完成了一個來回 (round trip)。

重復這個來回很多次。

記錄總時間,除以總的來回次數,得到平均每次來回的時間。

注意: 一個完整的來回包含兩次上下文切換(A->B 和 B->A)。所以,用平均來回時間除以 2,得到單次上下文切換的估算成本。

2.2 工具
pipe(): 創建 UNIX 管道。

fork(): 創建子進程。

write(), read(): 通過管道讀寫數據。

sched_setaffinity(): (Linux) 將進程綁定到指定 CPU 核心。需要包含 <sched.h> 并可能需要定義 _GNU_SOURCE。

wait() 或 waitpid(): 父進程等待子進程結束。

計時函數: 使用 clock_gettime() 。

#define _GNU_SOURCE // 需要這個宏才能使用 sched_setaffinity
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sched.h>
#include <time.h>
#include <string.h> // For memset#define ITERATIONS 100000 // 上下文切換測量的迭代次數
#define CPU_TO_BIND 0      // 綁定到 CPU 核心 0,確保你的系統有這個核心// Helper function to calculate time difference in nanoseconds
long long timespec_diff_ns(struct timespec start, struct timespec end) {return (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);
}int main() {int pipe1[2]; // Pipe: Parent -> Childint pipe2[2]; // Pipe: Child -> Parentpid_t child_pid;struct timespec start_time, end_time;long long total_elapsed_ns;double avg_round_trip_ns, avg_context_switch_ns;char buffer = 'x'; // 用于在管道中傳輸的單個字節// 創建兩個管道if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {perror("pipe creation failed");return 1;}// 創建子進程child_pid = fork();if (child_pid == -1) {perror("fork failed");return 1;}// 設置 CPU 親和性 (Affinity)cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(CPU_TO_BIND, &cpu_set);if (sched_setaffinity(0, sizeof(cpu_set_t), &cpu_set) == -1) {perror("sched_setaffinity failed");// 不一定是致命錯誤,但測量結果可能不準}if (child_pid == 0) {// --- 子進程代碼 ---close(pipe1[1]); // 關閉 pipe1 的寫端close(pipe2[0]); // 關閉 pipe2 的讀端for (long i = 0; i < ITERATIONS; ++i) {// 從父進程讀if (read(pipe1[0], &buffer, 1) != 1) {perror("Child read failed");exit(1);}// 寫回給父進程if (write(pipe2[1], &buffer, 1) != 1) {perror("Child write failed");exit(1);}}close(pipe1[0]);close(pipe2[1]);exit(0);} else {// --- 父進程代碼 ---close(pipe1[0]); // 關閉 pipe1 的讀端close(pipe2[1]); // 關閉 pipe2 的寫端// 獲取開始時間 (在循環開始前)if (clock_gettime(CLOCK_MONOTONIC, &start_time) == -1) {perror("clock_gettime start failed");return 1;}// 執行乒乓通信循環for (long i = 0; i < ITERATIONS; ++i) {// 寫給子進程if (write(pipe1[1], &buffer, 1) != 1) {perror("Parent write failed");exit(1);}// 從子進程讀 (會阻塞,觸發 A->B 切換)if (read(pipe2[0], &buffer, 1) != 1) {perror("Parent read failed");exit(1);}// 讀完后,子進程會阻塞,觸發 B->A 切換}// 獲取結束時間if (clock_gettime(CLOCK_MONOTONIC, &end_time) == -1) {perror("clock_gettime end failed");return 1;}// 等待子進程結束wait(NULL);close(pipe1[1]);close(pipe2[0]);// 計算總耗時total_elapsed_ns = timespec_diff_ns(start_time, end_time);// 計算平均每次來回耗時avg_round_trip_ns = (double)total_elapsed_ns / ITERATIONS;// 計算平均每次上下文切換耗時 (來回時間 / 2)avg_context_switch_ns = avg_round_trip_ns / 2.0;printf("Measured %ld round trips between two processes on CPU %d.\n", (long)ITERATIONS, CPU_TO_BIND);printf("Total time: %lld ns\n", total_elapsed_ns);printf("Average round trip time: %.2f ns\n", avg_round_trip_ns);printf("Average context switch cost: %.2f ns\n", avg_context_switch_ns);}return 0;
}// 編譯: gcc -o measure_ctxsw measure_ctxsw.c -lrt -D_GNU_SOURCE
// 運行: ./measure_ctxsw

在這里插入圖片描述

8. 參考資料

  1. 陷阱、中斷、異常、信號
  2. OSTEP

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/76980.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/76980.shtml
英文地址,請注明出處:http://en.pswp.cn/web/76980.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux 中斷子系統鏈式中斷編程

直接貼代碼了&#xff1a; 虛擬中斷控制器代碼&#xff0c;chained_virt.c #include<linux/kernel.h> #include<linux/module.h> #include<linux/clk.h> #include<linux/err.h> #include<linux/init.h> #include<linux/interrupt.h> #inc…

容器修仙傳 我的靈根是Pod 第10章 心魔大劫(RBAC與SecurityContext)

第四卷&#xff1a;飛升之劫化神篇 第10章 心魔大劫&#xff08;RBAC與SecurityContext&#xff09; 血月當空&#xff0c;林衍的混沌靈根正在異變。 每道經脈都爬滿黑色紋路&#xff0c;神識海中回蕩著蠱惑之音&#xff1a;"破開藏經閣第九層禁制…奪取《太古弒仙訣》……

基于c#,wpf,ef框架,sql server數據庫,音樂播放器

詳細視頻: 【基于c#,wpf,ef框架,sql server數據庫&#xff0c;音樂播放器。-嗶哩嗶哩】 https://b23.tv/ZqmOKJ5

精益數據分析(21/126):剖析創業增長引擎與精益畫布指標

精益數據分析&#xff08;21/126&#xff09;&#xff1a;剖析創業增長引擎與精益畫布指標 大家好&#xff01;在創業和數據分析的探索道路上&#xff0c;我一直希望能和大家攜手共進&#xff0c;共同學習。今天&#xff0c;我們繼續深入研讀《精益數據分析》&#xff0c;剖析…

Spark-streaming核心編程

1.導入依賴?&#xff1a; <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-10_2.12</artifactId> <version>3.0.0</version> </dependency> 2.編寫代碼?&#xff1a; 創建Sp…

Kafka的ISR機制是什么?如何保證數據一致性?

一、Kafka ISR機制深度解析 1. ISR機制定義 ISR&#xff08;In-Sync Replicas&#xff09;是Kafka保證數據一致性的核心機制&#xff0c;由Leader副本&#xff08;復雜讀寫&#xff09;和Follower副本(負責備份)組成。當Follower副本的延遲超過replica.lag.time.max.ms&#…

Docker 基本概念與安裝指南

Docker 基本概念與安裝指南 一、Docker 核心概念 1. 容器&#xff08;Container&#xff09; 容器是 Docker 的核心運行單元&#xff0c;本質是一個輕量級的沙盒環境。它基于鏡像創建&#xff0c;包含應用程序及其運行所需的依賴&#xff08;如代碼、庫、環境變量等&#xf…

數據庫監控 | MongoDB監控全解析

PART 01 MongoDB&#xff1a;靈活、可擴展的文檔數據庫 MongoDB作為一款開源的NoSQL數據庫&#xff0c;憑借其靈活的數據模型&#xff08;基于BSON的文檔存儲&#xff09;、水平擴展能力&#xff08;分片集群&#xff09;和高可用性&#xff08;副本集架構&#xff09;&#x…

OpenFeign和Gateway

OpenFeign和Gateway 一.OpenFeign介紹二.快速上手1.引入依賴2.開啟openfeign的功能3.編寫客戶端4.修改遠程調用代碼5.測試 三.OpenFeign參數傳遞1.傳遞單個參數2.多個參數、傳遞對象和傳遞JSON字符串3.最佳方式寫代碼繼承的方式抽取的方式 四.部署OpenFeign五.統一服務入口-Gat…

spark-streaming(二)

DStream創建&#xff08;kafka數據源&#xff09; 1.在idea中的 pom.xml 中添加依賴 <dependency><groupId>org.apache.spark</groupId><artifactId>spark-streaming-kafka-0-10_2.12</artifactId><version>3.0.0</version> </…

JAVA聚焦OutOfMemoryError 異常

個人主頁 文章專欄 在正文開始前&#xff0c;我想多說幾句&#xff0c;也就是吐苦水吧…最近這段時間一直想寫點東西&#xff0c;停下來反思思考一下。 心中萬言&#xff0c;真正執筆時又不知先寫些什么。通常這個時候&#xff0c;我都會隨便寫寫&#xff0c;文風極像散文&…

如何在Spring Boot中配置自定義端口運行應用程序

Spring Boot 應用程序默認在端口 8080 上運行嵌入式 Web 服務器&#xff08;如 Tomcat、Jetty 或 Undertow&#xff09;。然而&#xff0c;在開發、測試或生產環境中&#xff0c;開發者可能需要將應用程序配置為在自定義端口上運行&#xff0c;例如避免端口沖突、適配微服務架構…

linux嵌入式(進程與線程1)

Linux進程 進程介紹 1. 進程的基本概念 定義&#xff1a;進程是程序的一次執行過程&#xff0c;擁有獨立的地址空間、資源&#xff08;如內存、文件描述符&#xff09;和唯一的進程 ID&#xff08;PID&#xff09;。 組成&#xff1a; 代碼段&#xff1a;程序的指令。 數據…

智馭未來:NVIDIA自動駕駛安全白皮書與實驗室創新實踐深度解析

一、引言&#xff1a;自動駕駛安全的范式革新 在當今數字化浪潮的推動下&#xff0c;全球自動駕駛技術正大步邁入商業化的深水區。隨著越來越多的自動駕駛車輛走上道路&#xff0c;其安全性已成為整個行業乃至社會關注的核心命題。在這個關鍵的轉折點上&#xff0c;NVIDIA 憑借…

多模態大模型 Qwen2.5-VL 的學習之旅

Qwen-VL 是阿里云研發的大規模視覺語言模型&#xff08;Large Vision Language Model, LVLM&#xff09;。Qwen-VL 可以以圖像、文本、檢測框作為輸入&#xff0c;并以文本和檢測框作為輸出。Qwen-VL 系列模型性能強大&#xff0c;具備多語言對話、多圖交錯對話等能力&#xff…

Redis 與 Memcache 全面對比:功能、性能與應用場景解析

Redis 和 Memcache 都是常用的內存數據庫&#xff0c;以下是它們在多個方面的能力比較&#xff1a; 一、數據類型 Redis&#xff1a;支持豐富的數據類型&#xff0c;如字符串&#xff08;String&#xff09;、哈希&#xff08;Hash&#xff09;、列表&#xff08;List&#x…

Oracle--PL/SQL編程

前言&#xff1a;本博客僅作記錄學習使用&#xff0c;部分圖片出自網絡&#xff0c;如有侵犯您的權益&#xff0c;請聯系刪除 PL/SQL&#xff08;Procedural Language/SQL&#xff09;是Oracle數據庫中的一種過程化編程語言&#xff0c;構建于SQL之上&#xff0c;允許編寫包含S…

新增優惠券

文章目錄 概要整體架構流程技術細節小結 概要 接口分析 一個基本的新增接口&#xff0c;按照Restful風格設計即可&#xff0c;關鍵是請求參數。之前表分析時已經詳細介紹過這個頁面及其中的字段&#xff0c;這里不再贅述。 需要特別注意的是&#xff0c;如果優惠券限定了使…

力扣面試經典150題(第二十三題)- KMP算法

問題 給你兩個字符串 haystack 和 needle &#xff0c;請你在 haystack 字符串中找出 needle 字符串的第一個匹配項的下標&#xff08;下標從 0 開始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;則返回 -1 。 示例 1&#xff1a; 輸入&#xff1a;haysta…

PostgreSQL 的 MVCC 機制了解

PostgreSQL 的 MVCC 機制了解 PostgreSQL 使用多版本并發控制(MVCC)作為其核心并發控制機制&#xff0c;這是它與許多其他數據庫系統的關鍵區別之一。MVCC 允許讀操作不阻塞寫操作&#xff0c;寫操作也不阻塞讀操作&#xff0c;從而提供高度并發性。 一 MVCC 基本原理 1.1 M…