操作系统八股文整理(一)

news2025/3/18 8:54:05

操作系统八股文整理

    • 一、进程和线程的区别
    • 二、进程与线程的切换过程
      • 一、进程切换
        • 进程切换的步骤:
      • 二、线程切换
        • 线程切换的步骤:
      • 三、进程切换与线程切换的对比
      • 四、上下文切换的优化
    • 三、系统调用
      • 一、系统调用的触发
      • 二、从用户空间切换到内核空间
      • 三、执行内核函数
      • 四、从内核空间返回用户空间
      • 五、系统调用的完整流程示例
      • 六、系统调用的性能开销
    • 四、进程间通讯方式
      • 管道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 从一个进程切换到另一个进程的过程。它通常发生在以下几种情况:

  1. 时间片用完:当前进程的时间片用完,操作系统需要选择下一个进程运行。
  2. 进程阻塞:当前进程因等待资源(如 I/O)而阻塞,操作系统需要切换到其他就绪的进程。
  3. 更高优先级进程到来:有更高优先级的进程就绪,操作系统需要切换到该进程。
进程切换的步骤:
  1. 保存当前进程的上下文
    • 保存程序计数器(PC):记录当前进程执行到的位置。
    • 保存寄存器状态:包括通用寄存器、状态寄存器等,这些寄存器保存了进程执行时的临时数据和状态。
    • 保存进程状态信息:如进程的优先级、资源使用情况等。
  2. 更新进程控制块(PCB)
    • 将当前进程的状态从“运行态”改为“就绪态”或“阻塞态”,并更新其在进程队列中的位置。
  3. 选择下一个进程
    • 操作系统根据调度算法(如轮转调度、优先级调度等)选择下一个要运行的进程。
    • 如果没有就绪的进程,可能会选择进入空闲进程或等待外部事件。
  4. 恢复新进程的上下文
    • 恢复程序计数器(PC):将新进程上次运行时的位置加载到 PC 中。
    • 恢复寄存器状态:将新进程的寄存器状态加载到 CPU 的寄存器中。
    • 更新新进程的状态:将新进程的状态从“就绪态”改为“运行态”。
  5. 开始新进程运行
    • 将 CPU 的控制权交给新进程,新进程从上次保存的位置继续执行。

二、线程切换

线程切换是指操作系统将 CPU 从一个线程切换到另一个线程的过程。线程切换的开销通常比进程切换小,因为线程共享进程的资源,切换时不需要切换进程的上下文。

线程切换的步骤:
  1. 保存当前线程的上下文
    • 保存线程的程序计数器(PC):记录当前线程执行的位置。
    • 保存线程的寄存器状态:线程有自己的线程控制块(TCB),保存线程的局部变量、栈指针等信息。
    • 保存线程状态信息:如线程的优先级、阻塞原因等。
  2. 更新线程控制块(TCB)
    • 将当前线程的状态从“运行态”改为“就绪态”或“阻塞态”,并更新其在线程队列中的位置。
  3. 选择下一个线程
    • 操作系统根据线程调度算法(如时间片轮转、优先级调度等)选择下一个要运行的线程。
    • 如果没有就绪的线程,可能会选择进入空闲线程或等待外部事件。
  4. 恢复新线程的上下文
    • 恢复线程的程序计数器(PC):将新线程上次运行时的位置加载到 PC 中。
    • 恢复线程的寄存器状态:将新线程的寄存器状态加载到 CPU 的寄存器中。
    • 更新新线程的状态:将新线程的状态从“就绪态”改为“运行态”。
  5. 开始新线程运行
    • 将 CPU 的控制权交给新线程,新线程从上次保存的位置继续执行。

三、进程切换与线程切换的对比

  1. 上下文切换的开销
    • 进程切换:开销较大,需要切换进程的代码段、数据段、寄存器状态、文件描述符等。
    • 线程切换:开销较小,只需要切换线程的寄存器状态和栈指针等局部信息。
  2. 资源共享
    • 进程切换:进程之间是独立的,切换时需要切换资源。
    • 线程切换:线程共享进程的资源,切换时不需要切换资源。
  3. 调度单位
    • 进程切换:以进程为单位进行调度。
    • 线程切换:以线程为单位进行调度,线程是 CPU 调度的最小单位。
  4. 适用场景
    • 进程切换:适用于需要独立资源的场景,如运行不同的程序。
    • 线程切换:适用于需要高并发的场景,如多线程服务器。

四、上下文切换的优化

上下文切换虽然可以提高系统的并发性,但频繁的上下文切换会增加系统的开销,降低系统性能。因此,操作系统和应用程序需要采取一些优化措施:

  1. 减少上下文切换的次数
    • 增加时间片的长度,减少进程或线程切换的频率。
    • 使用高效的调度算法,减少不必要的上下文切换。
  2. 优化上下文切换的开销
    • 减少保存和恢复的寄存器数量。
    • 使用缓存技术,减少对内存的访问。
  3. 减少线程的阻塞时间
    • 提高 I/O 操作的效率,减少线程因 I/O 阻塞而切换的次数。
    • 使用非阻塞 I/O 或异步 I/O,减少线程的阻塞时间。

总之,进程切换和线程切换是操作系统中非常重要的机制,它们的合理使用可以提高系统的并发性和性能。

三、系统调用

系统调用是用户程序与操作系统内核之间进行交互的桥梁。它允许用户程序请求操作系统提供的服务,例如文件操作、进程控制、通信等。系统调用的整个流程涉及用户空间和内核空间的切换,以及参数传递和结果返回。以下是系统调用的详细流程:


一、系统调用的触发

用户程序需要操作系统服务时,会通过系统调用来请求。这通常通过以下方式触发:

  1. 使用特定的指令
    • 在 x86 架构中,通常使用 int 0x80(中断指令)或 syscall 指令来触发系统调用。
    • 在 ARM 架构中,使用 svc 指令。
    • 这些指令会中断用户程序的执行,将控制权转移到操作系统内核。
  2. 设置系统调用号和参数
    • 在触发系统调用之前,用户程序需要将系统调用号(标识要调用的服务)和参数(如文件名、缓冲区地址等)放置在特定的寄存器或栈中。
    • 例如,在 x86 架构中,系统调用号通常放在 eax 寄存器中,参数放在 ebxecxedx 等寄存器中。

二、从用户空间切换到内核空间

当用户程序触发系统调用时,CPU 会从用户态切换到内核态,同时操作系统会进行以下操作:

  1. 保存用户态上下文
    • 操作系统会保存用户程序的上下文,包括寄存器状态(如程序计数器、栈指针等)和 CPU 的当前状态(用户态或内核态)。
    • 这些信息通常保存在内核为每个进程分配的内核栈中。
  2. 切换到内核态
    • CPU 的特权级别从用户态(较低特权级别)切换到内核态(较高特权级别)。
    • 内核态允许访问系统的全部资源,包括硬件设备和内核内存。
  3. 查找系统调用表
    • 操作系统根据系统调用号在系统调用表中查找对应的内核函数。
    • 系统调用表是一个数组,每个系统调用号对应一个内核函数指针。

三、执行内核函数

找到对应的内核函数后,操作系统会执行以下操作:

  1. 参数传递
    • 内核函数会从寄存器或栈中获取用户程序传递的参数。
    • 如果参数是用户空间的地址(如文件名字符串),内核需要进行地址检查,确保这些地址是合法的。
  2. 执行内核函数
    • 内核函数根据用户请求执行相应的操作,例如:
      • 打开文件时,内核会查找文件系统,分配文件描述符。
      • 写文件时,内核会将数据写入磁盘缓冲区。
      • 创建进程时,内核会分配内存和资源,创建新的进程控制块(PCB)。
  3. 处理错误和异常
    • 如果操作失败(如文件不存在、权限不足),内核会设置错误码(如 errno)。

四、从内核空间返回用户空间

内核函数执行完毕后,操作系统需要将控制权返回给用户程序:

  1. 保存内核态上下文
    • 操作系统保存内核态的上下文信息,包括内核函数的返回值(通常放在某个寄存器中)。
  2. 恢复用户态上下文
    • 操作系统从内核栈中恢复用户程序的上下文,包括寄存器状态和程序计数器。
    • 这样用户程序可以从上次中断的地方继续执行。
  3. 切换回用户态
    • CPU 的特权级别从内核态切换回用户态。
  4. 返回结果
    • 内核将系统调用的结果(如文件描述符、返回值等)传递给用户程序。
    • 如果发生错误,用户程序可以通过错误码(如 errno)获取错误信息。

五、系统调用的完整流程示例

假设用户程序要调用 write() 系统调用来写文件,其流程如下:

  1. 用户程序准备参数
    • 将系统调用号(如 1 表示 write)放入 eax 寄存器。
    • 将文件描述符、缓冲区地址和写入字节数分别放入 ebxecxedx 寄存器。
  2. 触发系统调用
    • 用户程序执行 int 0x80 指令,触发中断。
  3. 切换到内核态
    • 操作系统保存用户态上下文,切换到内核态。
    • 根据系统调用号 1,查找系统调用表,找到 write() 的内核函数。
  4. 执行内核函数
    • 内核函数从寄存器中获取参数(文件描述符、缓冲区地址等)。
    • 检查文件描述符是否有效,缓冲区地址是否合法。
    • 将数据从用户空间的缓冲区复制到内核空间的缓冲区。
    • 写入数据到磁盘缓冲区。
    • 如果成功,返回写入的字节数;如果失败,设置错误码。
  5. 返回用户态
    • 操作系统保存内核态上下文,恢复用户态上下文。
    • 切换回用户态,将返回值放入用户程序的寄存器中。
  6. 用户程序继续执行
    • 用户程序根据返回值判断写操作是否成功,并继续执行后续代码。

六、系统调用的性能开销

系统调用涉及用户空间和内核空间的切换,因此会产生一定的性能开销:

  1. 上下文切换开销
    • 保存和恢复寄存器状态、切换特权级别等操作会消耗时间。
  2. 参数传递和检查开销
    • 内核需要验证用户空间的地址是否合法,这可能涉及额外的内存访问。
  3. 内核函数执行开销
    • 内核函数的执行时间取决于系统调用的复杂性(如 I/O 操作可能涉及磁盘 I/O 延迟)。
  4. 系统调用的优化
    • 现代操作系统通过减少上下文切换的次数、使用更快的切换指令(如 syscall)等方式来优化系统调用的性能。
    • 一些系统调用(如 getpid())可以通过轻量级的机制(如 vsyscallvdso)直接在用户空间执行,避免切换到内核态。

总之,系统调用是用户程序与操作系统交互的重要机制,其流程涉及用户空间和内核空间的切换、参数传递、内核函数执行以及结果返回。虽然系统调用会产生一定的开销,但它是实现操作系统功能的关键机制。

四、进程间通讯方式

画板

管道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;
}
编译与运行
  1. 创建管道
    • 如果没有提前创建管道,运行 writer 时会阻塞,直到管道被创建。
    • 可以手动创建管道:bash复制
mkfifo /tmp/myfifo
- <font style="color:rgb(6, 6, 7);">或者运行创建管道的程序。</font>
  1. 编译代码
    • writer.cppreader.cpp 分别保存为文件。
    • 使用以下命令编译:bash复制
g++ -o writer writer.cpp
g++ -o reader reader.cpp
  1. 运行程序
    • 在一个终端运行读取者程序:
./reader
- <font style="color:rgb(6, 6, 7);">在另一个终端运行写入者程序:</font>
./writer
输出示例
  • reader终端Message received: Hello from writer process!
  • writer终端Message sent: Hello from writer process!
注意事项
  1. 阻塞特性
    • 默认情况下,读取者会阻塞,直到有数据可读。
    • 写入者会阻塞,直到有读取者打开管道。
    • 如果不希望阻塞,可以在 open() 时使用 O_NONBLOCK 标志。
  2. 管道文件路径
    • 确保管道文件路径一致(这里是 /tmp/myfifo)。
    • 如果路径不存在,写入者会阻塞,直到管道被创建。
  3. 清理
    • 使用完管道后,可以手动删除管道文件:bash复制
rm /tmp/myfifo
  1. 跨进程通信

命名管道允许不相关的进程之间通信,因此读取者和写入者可以是完全独立的程序。

通过这个示例,你可以看到命名管道如何实现进程间通信,非常适合需要跨进程传递数据的场景。

信号量

信号量(Semaphore)是一种用于进程间或线程间同步的机制,用于控制对共享资源的访问。它通过维护一个计数器来实现同步,当计数器大于零时,表示资源可用;当计数器为零时,表示资源已被占用。信号量通常用于解决互斥(Mutex)和同步(Sync)问题。

以下是信号量的基本操作:

  1. P操作(Wait/Down/Decrease):将信号量的值减1。如果减1后信号量的值小于0,则调用进程或线程阻塞,等待信号量的值变为非负。
  2. 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机制的信号量实现,使用semgetsemop等函数。

示例代码:

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 key
    int semid = semget(key, 1, 0666 | IPC_CREAT); // 创建信号量集

    // 初始化信号量
    union semun {
        int val;
        struct semid_ds* buf;
        unsigned short* array;
    } arg;
    arg.val = 1; // 初始值为1
    semctl(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_opensem_waitsem_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. 信号量的使用场景

信号量主要用于以下场景:

  1. 互斥(Mutex):确保多个线程或进程不会同时访问共享资源。
  2. 同步(Sync):协调线程或进程的执行顺序,例如生产者-消费者问题。
4. 注意事项
  • 死锁:如果信号量的使用不当,可能会导致死锁。例如,多个线程或进程同时等待同一个信号量。
  • 资源泄漏:在使用系统V或POSIX信号量时,需要确保信号量在程序结束时被正确删除,否则可能会导致资源泄漏。
  • 性能:信号量的使用会引入上下文切换的开销,因此需要合理设计同步机制。
总结

信号量是一种强大的同步机制,适用于多种并发场景。根据具体需求,可以选择系统V信号量、POSIX信号量、Boost信号量或C++20标准中的std::semaphore

消息队列demo

在C++中,消息队列是一种常见的进程间通信(IPC)机制,允许不同进程之间以异步方式交换消息。以下是关于C++消息队列的实现和使用方法的总结:

1. 系统V消息队列

系统V消息队列是一种传统的IPC机制,基于msggetmsgsndmsgrcv等函数。

示例代码:

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 key
    int 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_openmq_sendmq_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::queuestd::mutexstd::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)
  • 原理:将就绪队列分为多个优先级队列,每个队列采用不同的调度算法。进程在不同队列之间动态迁移。
  • 优点:综合了多种调度算法的优点,兼顾公平性和效率。
  • 缺点:实现复杂,需要合理设计队列之间的迁移策略。

六、线程同步的方式

画板

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2317121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20250317笔记本电脑在ubuntu22.04下使用acpi命令查看电池电量

20250317笔记本电脑在ubuntu22.04下使用acpi命令查看电池电量 2025/3/17 18:05 百度&#xff1a;ubuntu查看电池电量 百度为您找到以下结果 ubuntu查看电池电量 在Ubuntu操作系统中&#xff0c;查看电池电量通常可以通过命令行或者图形界面来完成。下面是一些常见的方法&…

蓝桥杯备考----模拟算法 phone number

嗯。这道题可以在两个和三个数字加-&#xff0c;我们只要随便输出一个奏行 那么&#xff01;我们规范一下&#xff0c;我们尽可能的只在两个数字之间加&#xff0c;但是如果一共奇数个的话&#xff0c;我们就让最后三个成一组&#xff0c;也就是说&#xff0c;我们用的是个小贪…

【数据分享】2000—2024年我国省市县三级逐月归一化植被指数(NDVI)数据(Shp/Excel格式)

之前我们分享过2000—2024年逐月归一化植被指数&#xff08;NDVI&#xff09;栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff0c;该数据来源于NASA定期发布的MOD13A3数据集&#xff01;很多小伙伴拿到数据后反馈栅格数据不太方便使用&#xff0c;问我们能不…

蓝桥杯真题——洛谷 Day10 并查集(枚举)

目录 P8651 [蓝桥杯 2017 省 B] 日期问题 P8635 [蓝桥杯 2016 省 AB] 四平方和 P8651 [蓝桥杯 2017 省 B] 日期问题 思路&#xff1a; 使用scanf读入&#xff0c;枚举从1960到2059&#xff0c;若符合题目形式&#xff0c;加入答案&#xff0c; 从小到大输出&#xff1a;存入结…

Linux应用:程序运行

kill kill命令的这种用法是向指定的进程发送特定信号编号的信号。信号在操作系统中是一种软件中断机制&#xff0c;用于通知进程发生了某种特定事件或要求进程执行特定操作。​ kill - 信号编号 进程 ID 信号编号的含义&#xff1a;不同的信号编号代表不同的事件或操作。例如…

基于SpringBoot+Vue3实现的宠物领养管理平台功能一

一、前言介绍&#xff1a; 1.1 项目摘要 随着社会经济的发展和人们生活水平的提高&#xff0c;越来越多的人开始关注并参与到宠物领养中。宠物已经成为许多家庭的重要成员&#xff0c;人们对于宠物的关爱和照顾也日益增加。然而&#xff0c;传统的宠物领养流程存在诸多不便&a…

SpringCloud 学习笔记2(Nacos)

Nacos Nacos 下载 Nacos Server 下载 | Nacos 官网 下载、解压、打开文件&#xff1a; 更改 Nacos 的启动方式 Nacos 的启动模式默认是集群模式。在学习时需要把他改为单机模式。 把 cluster 改为 standalone&#xff0c;记得保存&#xff01; 启动startup.cmd Ubuntu 启动…

Blender-MCP服务源码4-初始化项目解读

Blender-MCP服务源码4-初始化项目解读 上篇文章针对Blender开发框架完成了一个基础模板的搭建&#xff0c;并在Blender中成功进行了运行&#xff0c;那这个初始化项目中是如何进行页面效果呈现的&#xff0c;尝试手动进行功能精简来拆解项目代码 1-核心知识点 1&#xff09;如…

基于eNSP的IPV4和IPV6企业网络规划

基于eNSP的IPV4和IPV6企业网络规划 前言网络拓扑设计功能设计技术详解一、网络设备基础配置二、虚拟局域网&#xff08;VLAN&#xff09;与广播域划分三、冗余协议与链路故障检测四、IP地址自动分配与DHCP相关配置五、动态路由与安全认证六、广域网互联及VPN实现七、网络地址转…

C#特性和反射

1。特性概念理解&#xff1f; 特性&#xff08;Attribute&#xff09;是用于在【运行时】传递程序中各种元素&#xff08;比如类、属性、方法、结构、枚举、组件等&#xff09;行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所…

mysql5.x和mysql8.x查看和设置隔离级别

MySQL的隔离级别 级别标志值描述读未提交READ-UNCOMMITTED0存在脏读、不可重复读、幻读的问题读已提交READ-COMMITTED1解决脏读的问题&#xff0c;存在不可重复读、幻读的问题可重复读REPEATABLE-READ2mysql 默认级别&#xff0c;解决脏读、不可重复读的问题&#xff0c;存在幻…

3.17学习总结

写了两道题 刚开始用的之前做组合输出的方法&#xff0c;时间超限了&#xff0c;想不出怎么优化&#xff0c;后面看了题解&#xff0c;代码如下 #include <stdio.h> #include <stdlib.h> int n,min2e9; int a[11],b[11]; //搜索 void hly(int s,int x,int y) {//当…

Blender材质 - 层权重

层权重 混合着色器 可以让 面朝向的一面显示一种材质 另一面显示另一种材质 就能实现挺不错的材质效果 移动视角 材质会跟着变化 有点类似虚幻的视差节点BumpOffset

【JavaEE】Spring Boot 日志

目录 一、日志概述二、使用日志2.1 打印日志2.2 日志框架2.2.1 门面 / 外观 模式 2.3 日志级别2.3.1 六大分类2.3.2 使用 2.4 日志级别配置2.5 日志的持久化2.6 日志文件分割2.7 日志文件格式2.8 Slf4j 简单打印日志 一、日志概述 ⽇志主要是为了发现问题, 分析问题, 定位问题…

如何用solidworks画齿轮

齿轮还是很有技术含量的,专业名词太多看不懂, 只会画 (这个东西不能自己想当然画, 齿轮之间不啮合是很有问题的,会积累磨损) 步骤1 打开设计库里的toolbox 选择正齿轮,右键生成零件 需要改的有几个关键的地方,我是只知道内圆外圆所以,对我来说最重要的是标称轴直径 (即正中间…

详解布隆过滤器及其模拟实现

目录 布隆过滤器 引入 概念 工作原理 模拟实现布隆过滤器 哈希函数集 布隆过滤器基本框架 add函数&#xff08;添加到布隆过滤器中&#xff09; contains函数&#xff08;判断是否存在该值&#xff09; 完整代码 布隆过滤器的删除 布隆过滤器的误判率 布隆过滤器的…

element-plus中DatePicker 日期选择器组件的使用

1.选择某一天 代码&#xff1a; <el-date-pickerv-model"invoice_date"type"date"placeholder"请选择日期"style"width: 200px;"clearable /> 运行效果&#xff1a; 问题所在&#xff1a;这个数据的格式不是我们后端需要的那种&…

SvelteKit 最新中文文档教程(4)—— 表单 actions

前言 Svelte&#xff0c;一个语法简洁、入门容易&#xff0c;面向未来的前端框架。 从 Svelte 诞生之初&#xff0c;就备受开发者的喜爱&#xff0c;根据统计&#xff0c;从 2019 年到 2024 年&#xff0c;连续 6 年一直是开发者最感兴趣的前端框架 No.1&#xff1a; Svelte …

力扣hot100二刷——二叉树

第二次刷题不在idea写代码&#xff0c;而是直接在leetcode网站上写&#xff0c;“逼”自己掌握常用的函数。 标志掌握程度解释办法⭐Fully 完全掌握看到题目就有思路&#xff0c;编程也很流利⭐⭐Basically 基本掌握需要稍作思考&#xff0c;或者看到提示方法后能解答⭐⭐⭐Sl…

字符串哈希从入门到精通

一、基本概念 字符串哈希是将任意长度的字符串映射为固定长度的哈希值&#xff08;通常为整数&#xff09;的技术&#xff0c;核心目标是实现O(1)时间的子串快速比较和高效查询。其本质是通过数学运算将字符串转换为唯一性较高的数值&#xff0c;例如&#xff1a; ​​​​​​…