操作系統八股文整理
- 一、進程和線程的區別
- 二、進程與線程的切換過程
- 一、進程切換
- 進程切換的步驟:
- 二、線程切換
- 線程切換的步驟:
- 三、進程切換與線程切換的對比
- 四、上下文切換的優化
- 三、系統調用
- 一、系統調用的觸發
- 二、從用戶空間切換到內核空間
- 三、執行內核函數
- 四、從內核空間返回用戶空間
- 五、系統調用的完整流程示例
- 六、系統調用的性能開銷
- 四、進程間通訊方式
- 管道demo
- 命名管道demo
- 1. 創建命名管道
- 2. 寫入者進程
- 3. 讀取者進程
- 編譯與運行
- 輸出示例
- 注意事項
- 信號量
- 1. 信號量的分類
- 2. 信號量的實現
- 2.1 系統V信號量
- 示例代碼:
- 2.2 POSIX信號量
- 示例代碼:
- 2.3 Boost信號量
- 示例代碼:
- 2.4 C++20 `std::semaphore`
- 示例代碼:
- 3. 信號量的使用場景
- 4. 注意事項
- 總結
- 消息隊列demo
- 1. 系統V消息隊列
- 示例代碼:
- 2. POSIX消息隊列
- 示例代碼:
- 3. Boost.Interprocess消息隊列
- 示例代碼:
- 4. 自定義線程安全消息隊列
- 示例代碼:
- 總結
- 五、進程的調度
- 進程調度的算法
- (1)先來先服務(FCFS,First-Come-First-Served)
- (2)短作業優先(SJF,Shortest Job First)
- (3)優先級調度(Priority Scheduling)
- (4)時間片輪轉(RR,Round Robin)
- (5)多級反饋隊列(Multilevel Feedback Queue)
- 六、線程同步的方式
一、進程和線程的區別
二、進程與線程的切換過程
進程和線程的切換是操作系統中非常重要的概念,它們涉及到系統資源的分配、調度以及上下文切換等操作。以下是進程和線程切換過程的詳細解釋:
一、進程切換
進程切換是指操作系統將 CPU 從一個進程切換到另一個進程的過程。它通常發生在以下幾種情況:
- 時間片用完:當前進程的時間片用完,操作系統需要選擇下一個進程運行。
- 進程阻塞:當前進程因等待資源(如 I/O)而阻塞,操作系統需要切換到其他就緒的進程。
- 更高優先級進程到來:有更高優先級的進程就緒,操作系統需要切換到該進程。
進程切換的步驟:
- 保存當前進程的上下文:
- 保存程序計數器(PC):記錄當前進程執行到的位置。
- 保存寄存器狀態:包括通用寄存器、狀態寄存器等,這些寄存器保存了進程執行時的臨時數據和狀態。
- 保存進程狀態信息:如進程的優先級、資源使用情況等。
- 更新進程控制塊(PCB):
- 將當前進程的狀態從“運行態”改為“就緒態”或“阻塞態”,并更新其在進程隊列中的位置。
- 選擇下一個進程:
- 操作系統根據調度算法(如輪轉調度、優先級調度等)選擇下一個要運行的進程。
- 如果沒有就緒的進程,可能會選擇進入空閑進程或等待外部事件。
- 恢復新進程的上下文:
- 恢復程序計數器(PC):將新進程上次運行時的位置加載到 PC 中。
- 恢復寄存器狀態:將新進程的寄存器狀態加載到 CPU 的寄存器中。
- 更新新進程的狀態:將新進程的狀態從“就緒態”改為“運行態”。
- 開始新進程運行:
- 將 CPU 的控制權交給新進程,新進程從上次保存的位置繼續執行。
二、線程切換
線程切換是指操作系統將 CPU 從一個線程切換到另一個線程的過程。線程切換的開銷通常比進程切換小,因為線程共享進程的資源,切換時不需要切換進程的上下文。
線程切換的步驟:
- 保存當前線程的上下文:
- 保存線程的程序計數器(PC):記錄當前線程執行的位置。
- 保存線程的寄存器狀態:線程有自己的線程控制塊(TCB),保存線程的局部變量、棧指針等信息。
- 保存線程狀態信息:如線程的優先級、阻塞原因等。
- 更新線程控制塊(TCB):
- 將當前線程的狀態從“運行態”改為“就緒態”或“阻塞態”,并更新其在線程隊列中的位置。
- 選擇下一個線程:
- 操作系統根據線程調度算法(如時間片輪轉、優先級調度等)選擇下一個要運行的線程。
- 如果沒有就緒的線程,可能會選擇進入空閑線程或等待外部事件。
- 恢復新線程的上下文:
- 恢復線程的程序計數器(PC):將新線程上次運行時的位置加載到 PC 中。
- 恢復線程的寄存器狀態:將新線程的寄存器狀態加載到 CPU 的寄存器中。
- 更新新線程的狀態:將新線程的狀態從“就緒態”改為“運行態”。
- 開始新線程運行:
- 將 CPU 的控制權交給新線程,新線程從上次保存的位置繼續執行。
三、進程切換與線程切換的對比
- 上下文切換的開銷:
- 進程切換:開銷較大,需要切換進程的代碼段、數據段、寄存器狀態、文件描述符等。
- 線程切換:開銷較小,只需要切換線程的寄存器狀態和棧指針等局部信息。
- 資源共享:
- 進程切換:進程之間是獨立的,切換時需要切換資源。
- 線程切換:線程共享進程的資源,切換時不需要切換資源。
- 調度單位:
- 進程切換:以進程為單位進行調度。
- 線程切換:以線程為單位進行調度,線程是 CPU 調度的最小單位。
- 適用場景:
- 進程切換:適用于需要獨立資源的場景,如運行不同的程序。
- 線程切換:適用于需要高并發的場景,如多線程服務器。
四、上下文切換的優化
上下文切換雖然可以提高系統的并發性,但頻繁的上下文切換會增加系統的開銷,降低系統性能。因此,操作系統和應用程序需要采取一些優化措施:
- 減少上下文切換的次數:
- 增加時間片的長度,減少進程或線程切換的頻率。
- 使用高效的調度算法,減少不必要的上下文切換。
- 優化上下文切換的開銷:
- 減少保存和恢復的寄存器數量。
- 使用緩存技術,減少對內存的訪問。
- 減少線程的阻塞時間:
- 提高 I/O 操作的效率,減少線程因 I/O 阻塞而切換的次數。
- 使用非阻塞 I/O 或異步 I/O,減少線程的阻塞時間。
總之,進程切換和線程切換是操作系統中非常重要的機制,它們的合理使用可以提高系統的并發性和性能。
三、系統調用
系統調用是用戶程序與操作系統內核之間進行交互的橋梁。它允許用戶程序請求操作系統提供的服務,例如文件操作、進程控制、通信等。系統調用的整個流程涉及用戶空間和內核空間的切換,以及參數傳遞和結果返回。以下是系統調用的詳細流程:
一、系統調用的觸發
用戶程序需要操作系統服務時,會通過系統調用來請求。這通常通過以下方式觸發:
- 使用特定的指令:
- 在 x86 架構中,通常使用
int 0x80
(中斷指令)或syscall
指令來觸發系統調用。 - 在 ARM 架構中,使用
svc
指令。 - 這些指令會中斷用戶程序的執行,將控制權轉移到操作系統內核。
- 在 x86 架構中,通常使用
- 設置系統調用號和參數:
- 在觸發系統調用之前,用戶程序需要將系統調用號(標識要調用的服務)和參數(如文件名、緩沖區地址等)放置在特定的寄存器或棧中。
- 例如,在 x86 架構中,系統調用號通常放在
eax
寄存器中,參數放在ebx
、ecx
、edx
等寄存器中。
二、從用戶空間切換到內核空間
當用戶程序觸發系統調用時,CPU 會從用戶態切換到內核態,同時操作系統會進行以下操作:
- 保存用戶態上下文:
- 操作系統會保存用戶程序的上下文,包括寄存器狀態(如程序計數器、棧指針等)和 CPU 的當前狀態(用戶態或內核態)。
- 這些信息通常保存在內核為每個進程分配的內核棧中。
- 切換到內核態:
- CPU 的特權級別從用戶態(較低特權級別)切換到內核態(較高特權級別)。
- 內核態允許訪問系統的全部資源,包括硬件設備和內核內存。
- 查找系統調用表:
- 操作系統根據系統調用號在系統調用表中查找對應的內核函數。
- 系統調用表是一個數組,每個系統調用號對應一個內核函數指針。
三、執行內核函數
找到對應的內核函數后,操作系統會執行以下操作:
- 參數傳遞:
- 內核函數會從寄存器或棧中獲取用戶程序傳遞的參數。
- 如果參數是用戶空間的地址(如文件名字符串),內核需要進行地址檢查,確保這些地址是合法的。
- 執行內核函數:
- 內核函數根據用戶請求執行相應的操作,例如:
- 打開文件時,內核會查找文件系統,分配文件描述符。
- 寫文件時,內核會將數據寫入磁盤緩沖區。
- 創建進程時,內核會分配內存和資源,創建新的進程控制塊(PCB)。
- 內核函數根據用戶請求執行相應的操作,例如:
- 處理錯誤和異常:
- 如果操作失敗(如文件不存在、權限不足),內核會設置錯誤碼(如
errno
)。
- 如果操作失敗(如文件不存在、權限不足),內核會設置錯誤碼(如
四、從內核空間返回用戶空間
內核函數執行完畢后,操作系統需要將控制權返回給用戶程序:
- 保存內核態上下文:
- 操作系統保存內核態的上下文信息,包括內核函數的返回值(通常放在某個寄存器中)。
- 恢復用戶態上下文:
- 操作系統從內核棧中恢復用戶程序的上下文,包括寄存器狀態和程序計數器。
- 這樣用戶程序可以從上次中斷的地方繼續執行。
- 切換回用戶態:
- CPU 的特權級別從內核態切換回用戶態。
- 返回結果:
- 內核將系統調用的結果(如文件描述符、返回值等)傳遞給用戶程序。
- 如果發生錯誤,用戶程序可以通過錯誤碼(如
errno
)獲取錯誤信息。
五、系統調用的完整流程示例
假設用戶程序要調用 write()
系統調用來寫文件,其流程如下:
- 用戶程序準備參數:
- 將系統調用號(如
1
表示write
)放入eax
寄存器。 - 將文件描述符、緩沖區地址和寫入字節數分別放入
ebx
、ecx
和edx
寄存器。
- 將系統調用號(如
- 觸發系統調用:
- 用戶程序執行
int 0x80
指令,觸發中斷。
- 用戶程序執行
- 切換到內核態:
- 操作系統保存用戶態上下文,切換到內核態。
- 根據系統調用號
1
,查找系統調用表,找到write()
的內核函數。
- 執行內核函數:
- 內核函數從寄存器中獲取參數(文件描述符、緩沖區地址等)。
- 檢查文件描述符是否有效,緩沖區地址是否合法。
- 將數據從用戶空間的緩沖區復制到內核空間的緩沖區。
- 寫入數據到磁盤緩沖區。
- 如果成功,返回寫入的字節數;如果失敗,設置錯誤碼。
- 返回用戶態:
- 操作系統保存內核態上下文,恢復用戶態上下文。
- 切換回用戶態,將返回值放入用戶程序的寄存器中。
- 用戶程序繼續執行:
- 用戶程序根據返回值判斷寫操作是否成功,并繼續執行后續代碼。
六、系統調用的性能開銷
系統調用涉及用戶空間和內核空間的切換,因此會產生一定的性能開銷:
- 上下文切換開銷:
- 保存和恢復寄存器狀態、切換特權級別等操作會消耗時間。
- 參數傳遞和檢查開銷:
- 內核需要驗證用戶空間的地址是否合法,這可能涉及額外的內存訪問。
- 內核函數執行開銷:
- 內核函數的執行時間取決于系統調用的復雜性(如 I/O 操作可能涉及磁盤 I/O 延遲)。
- 系統調用的優化:
- 現代操作系統通過減少上下文切換的次數、使用更快的切換指令(如
syscall
)等方式來優化系統調用的性能。 - 一些系統調用(如
getpid()
)可以通過輕量級的機制(如vsyscall
或vdso
)直接在用戶空間執行,避免切換到內核態。
- 現代操作系統通過減少上下文切換的次數、使用更快的切換指令(如
總之,系統調用是用戶程序與操作系統交互的重要機制,其流程涉及用戶空間和內核空間的切換、參數傳遞、內核函數執行以及結果返回。雖然系統調用會產生一定的開銷,但它是實現操作系統功能的關鍵機制。
四、進程間通訊方式
管道demo
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cstdlib>int main() {int pipefd[2]; // 用于存儲管道的文件描述符pid_t pid;// 創建管道if (pipe(pipefd) == -1) {exit(EXIT_FAILURE);}// 創建子進程pid = fork();if (pid == -1) {exit(EXIT_FAILURE);}if (pid > 0) { // 父進程// 關閉管道的寫端close(pipefd[1]);// 從管道的讀端讀取數據char buffer[128];ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);if (bytes_read > 0) {buffer[bytes_read] = '\0'; // 確保字符串以 null 結尾std::cout << "Parent received: " << buffer << std::endl;} else {std::cerr << "Failed to read from pipe" << std::endl;}// 關閉管道的讀端close(pipefd[0]);} else { // 子進程// 關閉管道的讀端close(pipefd[0]);// 向管道的寫端寫入數據const char *msg = "Hello from child process!";ssize_t bytes_written = write(pipefd[1], msg, strlen(msg));if (bytes_written < 0) {std::cerr << "Failed to write to pipe" << std::endl;}// 關閉管道的寫端close(pipefd[1]);}return 0;
}
命名管道demo
1. 創建命名管道
我們首先需要創建一個命名管道。這可以通過命令行工具 mkfifo
或在程序中使用 mkfifo()
系統調用來完成。
創建管道的命令行方式:
mkfifo /tmp/myfifo
創建管道的程序方式:
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>int main() {// 創建命名管道if (mkfifo("/tmp/myfifo", 0666) == -1) {perror("mkfifo");return -1;}std::cout << "Named pipe created at /tmp/myfifo" << std::endl;return 0;
}
2. 寫入者進程
寫入者進程向命名管道中寫入數據。
writer.cpp:
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>int main() {const char *fifo_path = "/tmp/myfifo";const char *message = "Hello from writer process!\n";// 打開管道文件(寫模式)int fd = open(fifo_path, O_WRONLY);if (fd == -1) {perror("open");return -1;}// 向管道寫入數據if (write(fd, message, strlen(message)) == -1) {perror("write");close(fd);return -1;}std::cout << "Message sent: " << message << std::endl;// 關閉管道文件描述符close(fd);return 0;
}
3. 讀取者進程
讀取者進程從命名管道中讀取數據。
reader.cpp
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>int main() {const char *fifo_path = "/tmp/myfifo";char buffer[128];// 打開管道文件(讀模式)int fd = open(fifo_path, O_RDONLY);if (fd == -1) {perror("open");return -1;}// 從管道讀取數據ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);if (bytes_read == -1) {perror("read");close(fd);return -1;}buffer[bytes_read] = '\0'; // 確保字符串以 null 結尾std::cout << "Message received: " << buffer << std::endl;// 關閉管道文件描述符close(fd);return 0;
}
編譯與運行
- 創建管道:
- 如果沒有提前創建管道,運行
writer
時會阻塞,直到管道被創建。 - 可以手動創建管道:bash復制
- 如果沒有提前創建管道,運行
mkfifo /tmp/myfifo
- <font style="color:rgb(6, 6, 7);">或者運行創建管道的程序。</font>
- 編譯代碼:
- 將
writer.cpp
和reader.cpp
分別保存為文件。 - 使用以下命令編譯:bash復制
- 將
g++ -o writer writer.cpp
g++ -o reader reader.cpp
- 運行程序:
- 在一個終端運行讀取者程序:
./reader
- <font style="color:rgb(6, 6, 7);">在另一個終端運行寫入者程序:</font>
./writer
輸出示例
- reader終端:
Message received: Hello from writer process!
- writer終端:
Message sent: Hello from writer process!
注意事項
- 阻塞特性:
- 默認情況下,讀取者會阻塞,直到有數據可讀。
- 寫入者會阻塞,直到有讀取者打開管道。
- 如果不希望阻塞,可以在
open()
時使用O_NONBLOCK
標志。
- 管道文件路徑:
- 確保管道文件路徑一致(這里是
/tmp/myfifo
)。 - 如果路徑不存在,寫入者會阻塞,直到管道被創建。
- 確保管道文件路徑一致(這里是
- 清理:
- 使用完管道后,可以手動刪除管道文件:bash復制
rm /tmp/myfifo
- 跨進程通信:
命名管道允許不相關的進程之間通信,因此讀取者和寫入者可以是完全獨立的程序。
通過這個示例,你可以看到命名管道如何實現進程間通信,非常適合需要跨進程傳遞數據的場景。
信號量
信號量(Semaphore)是一種用于進程間或線程間同步的機制,用于控制對共享資源的訪問。它通過維護一個計數器來實現同步,當計數器大于零時,表示資源可用;當計數器為零時,表示資源已被占用。信號量通常用于解決互斥(Mutex)和同步(Sync)問題。
以下是信號量的基本操作:
- P操作(Wait/Down/Decrease):將信號量的值減1。如果減1后信號量的值小于0,則調用進程或線程阻塞,等待信號量的值變為非負。
- V操作(Signal/Up/Increase):將信號量的值加1。如果加1后信號量的值大于0,則喚醒一個等待該信號量的進程或線程。
1. 信號量的分類
信號量主要分為兩種:
- 二進制信號量(Binary Semaphore):值只能為0或1,用于互斥訪問。
- 計數信號量(Counting Semaphore):值可以是任意非負整數,用于控制多個資源的訪問。
2. 信號量的實現
在C++中,信號量可以通過多種方式實現,包括系統V IPC信號量、POSIX信號量、Boost庫以及C++20標準中的std::semaphore
。
2.1 系統V信號量
系統V信號量是基于IPC機制的信號量實現,使用semget
、semop
等函數。
示例代碼:
cpp復制
#include <iostream>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>// P操作
void P(int semid) {struct sembuf sb;sb.sem_num = 0;sb.sem_op = -1; // 等待信號量sb.sem_flg = 0;semop(semid, &sb, 1);
}// V操作
void V(int semid) {struct sembuf sb;sb.sem_num = 0;sb.sem_op = 1; // 釋放信號量sb.sem_flg = 0;semop(semid, &sb, 1);
}int main() {key_t key = ftok("semfile", 65); // 創建IPC keyint semid = semget(key, 1, 0666 | IPC_CREAT); // 創建信號量集// 初始化信號量union semun {int val;struct semid_ds* buf;unsigned short* array;} arg;arg.val = 1; // 初始值為1semctl(semid, 0, SETVAL, arg);// 模擬生產者和消費者if (fork() == 0) {// 子進程:消費者sleep(1); // 等待生產者P(semid); // 等待信號量std::cout << "Consumer: Consumed resource" << std::endl;V(semid); // 釋放信號量} else {// 父進程:生產者P(semid); // 等待信號量std::cout << "Producer: Produced resource" << std::endl;V(semid); // 釋放信號量}wait(nullptr); // 等待子進程結束semctl(semid, 0, IPC_RMID, arg); // 刪除信號量集return 0;
}
2.2 POSIX信號量
POSIX信號量是另一種實現方式,使用sem_open
、sem_wait
和sem_post
等函數。
示例代碼:
cpp復制
#include <iostream>
#include <semaphore.h>
#include <thread>
#include <unistd.h>int main() {sem_t sem;sem_init(&sem, 0, 1); // 初始化信號量,初始值為1// 生產者線程std::thread producer([&sem]() {sem_wait(&sem); // 等待信號量std::cout << "Producer: Produced resource" << std::endl;sem_post(&sem); // 釋放信號量});// 消費者線程std::thread consumer([&sem]() {sleep(1); // 等待生產者sem_wait(&sem); // 等待信號量std::cout << "Consumer: Consumed resource" << std::endl;sem_post(&sem); // 釋放信號量});producer.join();consumer.join();sem_destroy(&sem); // 銷毀信號量return 0;
}
2.3 Boost信號量
Boost庫提供了跨平臺的信號量實現,使用boost::interprocess::named_semaphore
。
示例代碼:
cpp復制
#include <boost/interprocess/sync/named_semaphore.hpp>
#include <iostream>
#include <thread>
#include <unistd.h>int main() {using namespace boost::interprocess;named_semaphore sem(open_or_create, "test_semaphore", 1); // 創建或打開信號量// 生產者線程std::thread producer([&sem]() {sem.wait();std::cout << "Producer: Produced resource" << std::endl;sem.post();});// 消費者線程std::thread consumer([&sem]() {sleep(1); // 等待生產者sem.wait();std::cout << "Consumer: Consumed resource" << std::endl;sem.post();});producer.join();consumer.join();named_semaphore::remove("test_semaphore"); // 刪除信號量return 0;
}
2.4 C++20 std::semaphore
C++20標準引入了std::semaphore
,使得信號量的使用更加簡潔。
示例代碼:
cpp復制
#include <iostream>
#include <semaphore>
#include <thread>
#include <unistd.h>int main() {std::counting_semaphore<1> sem(1); // 創建信號量,初始值為1// 生產者線程std::thread producer([&sem]() {sem.acquire(); // 等待信號量std::cout << "Producer: Produced resource" << std::endl;sem.release(); // 釋放信號量});// 消費者線程std::thread consumer([&sem]() {sleep(1); // 等待生產者sem.acquire(); // 等待信號量std::cout << "Consumer: Consumed resource" << std::endl;sem.release(); // 釋放信號量});producer.join();consumer.join();return 0;
}
3. 信號量的使用場景
信號量主要用于以下場景:
- 互斥(Mutex):確保多個線程或進程不會同時訪問共享資源。
- 同步(Sync):協調線程或進程的執行順序,例如生產者-消費者問題。
4. 注意事項
- 死鎖:如果信號量的使用不當,可能會導致死鎖。例如,多個線程或進程同時等待同一個信號量。
- 資源泄漏:在使用系統V或POSIX信號量時,需要確保信號量在程序結束時被正確刪除,否則可能會導致資源泄漏。
- 性能:信號量的使用會引入上下文切換的開銷,因此需要合理設計同步機制。
總結
信號量是一種強大的同步機制,適用于多種并發場景。根據具體需求,可以選擇系統V信號量、POSIX信號量、Boost信號量或C++20標準中的std::semaphore
。
消息隊列demo
在C++中,消息隊列是一種常見的進程間通信(IPC)機制,允許不同進程之間以異步方式交換消息。以下是關于C++消息隊列的實現和使用方法的總結:
1. 系統V消息隊列
系統V消息隊列是一種傳統的IPC機制,基于msgget
、msgsnd
和msgrcv
等函數。
示例代碼:
cpp復制
#include <sys/ipc.h>
#include <sys/msg.h>
#include <iostream>
#include <cstring>struct message {long msg_type;char msg_text[100];
};int main() {key_t key = ftok("progfile", 65); // 創建IPC keyint msgid = msgget(key, 0666 | IPC_CREAT); // 創建消息隊列message msg;msg.msg_type = 1;strcpy(msg.msg_text, "Hello World!");msgsnd(msgid, &msg, sizeof(msg), 0); // 發送消息std::cout << "Message sent: " << msg.msg_text << std::endl;// 接收消息msgrcv(msgid, &msg, sizeof(msg), 1, 0);std::cout << "Message received: " << msg.msg_text << std::endl;msgctl(msgid, IPC_RMID, nullptr); // 刪除消息隊列return 0;
}
此代碼展示了如何創建消息隊列、發送和接收消息。
2. POSIX消息隊列
POSIX消息隊列提供了另一種實現方式,使用mq_open
、mq_send
和mq_receive
等函數。
示例代碼:
cpp復制
#include <mqueue.h>
#include <iostream>
#include <cstring>int main() {mqd_t mq;struct mq_attr attr;attr.mq_flags = 0;attr.mq_maxmsg = 10;attr.mq_msgsize = 1024;attr.mq_curmsgs = 0;mq = mq_open("/my_mq", O_CREAT | O_WRONLY, 0644, &attr); // 創建消息隊列std::string msg = "Hello POSIX!";mq_send(mq, msg.c_str(), msg.size(), 0); // 發送消息std::cout << "Message sent: " << msg << std::endl;mq_close(mq);mq_unlink("/my_mq"); // 刪除消息隊列return 0;
}
3. Boost.Interprocess消息隊列
Boost庫提供了跨平臺的消息隊列實現,基于共享內存。
示例代碼:
cpp復制
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>int main() {using namespace boost::interprocess;// 發送端message_queue mq(open_or_create, "test_mq", 100, sizeof(int));int msg = 42;mq.send(&msg, sizeof(msg), 0);std::cout << "Message sent: " << msg << std::endl;// 接收端int rcv_msg;size_t msg_size;unsigned priority;mq.receive(&rcv_msg, sizeof(rcv_msg), msg_size, priority);std::cout << "Message received: " << rcv_msg << std::endl;message_queue::remove("test_mq"); // 刪除消息隊列return 0;
}
4. 自定義線程安全消息隊列
如果需要在多線程環境中使用消息隊列,可以結合std::queue
、std::mutex
和std::condition_variable
實現線程安全的消息隊列。
示例代碼:
cpp復制
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <iostream>struct Message {int messageType;std::string payload;
};class MessageQueue {
public:void enqueue(const Message& message) {std::unique_lock<std::mutex> lock(mutex_);queue_.push(message);condition_.notify_one();}Message dequeue() {std::unique_lock<std::mutex> lock(mutex_);condition_.wait(lock, [this] { return !queue_.empty(); });Message message = queue_.front();queue_.pop();return message;}private:std::queue<Message> queue_;std::mutex mutex_;std::condition_variable condition_;
};void producer(MessageQueue& mq) {Message msg{1, "Hello"};mq.enqueue(msg);
}void consumer(MessageQueue& mq) {Message msg = mq.dequeue();std::cout << "Received: " << msg.payload << std::endl;
}int main() {MessageQueue mq;std::thread prod(producer, std::ref(mq));std::thread cons(consumer, std::ref(mq));prod.join();cons.join();return 0;
}
總結
- 系統V和POSIX消息隊列適用于進程間通信,但需要處理IPC資源的創建和銷毀。
- Boost.Interprocess提供了跨平臺的實現,基于共享內存。
- 自定義線程安全消息隊列適用于多線程環境。
根據具體需求選擇合適的消息隊列實現方式。
五、進程的調度
進程調度的算法
進程調度的核心是調度算法,不同的算法適用于不同的場景。常見的調度算法包括:
(1)先來先服務(FCFS,First-Come-First-Served)
- 原理:按照進程到達的順序分配CPU。
- 優點:簡單直觀。
- 缺點:可能導致短作業等待時間過長(“短作業饑餓”問題)。
(2)短作業優先(SJF,Shortest Job First)
- 原理:優先調度預計運行時間最短的進程。
- 優點:可以有效減少平均等待時間。
- 缺點:可能導致長作業饑餓,且需要預估進程運行時間。
(3)優先級調度(Priority Scheduling)
- 原理:根據進程的優先級分配CPU,優先級高的進程優先運行。
- 優點:可以滿足不同進程的緊急程度需求。
- 缺點:低優先級的進程可能會被餓死(“優先級倒置”問題)。
(4)時間片輪轉(RR,Round Robin)
- 原理:將CPU時間分成固定長度的時間片(Time Quantum),每個就緒態進程輪流運行一個時間片。
- 優點:公平性好,響應速度快,適合交互式系統。
- 缺點:時間片大小的選擇會影響系統性能。
(5)多級反饋隊列(Multilevel Feedback Queue)
- 原理:將就緒隊列分為多個優先級隊列,每個隊列采用不同的調度算法。進程在不同隊列之間動態遷移。
- 優點:綜合了多種調度算法的優點,兼顧公平性和效率。
- 缺點:實現復雜,需要合理設計隊列之間的遷移策略。