当前位置: 首页 > article >正文

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

操作系统八股文整理

    • 一、进程和线程的区别
    • 二、进程与线程的切换过程
      • 一、进程切换
        • 进程切换的步骤:
      • 二、线程切换
        • 线程切换的步骤:
      • 三、进程切换与线程切换的对比
      • 四、上下文切换的优化
    • 三、系统调用
      • 一、系统调用的触发
      • 二、从用户空间切换到内核空间
      • 三、执行内核函数
      • 四、从内核空间返回用户空间
      • 五、系统调用的完整流程示例
      • 六、系统调用的性能开销
    • 四、进程间通讯方式
      • 管道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.kler.cn/a/588967.html

相关文章:

  • Celery在Django中的作用
  • day05_Java高级
  • 深度学习正则化技术之权重衰减法、暂退法(通俗易懂版)
  • 一款基于Python的从常规文档里提取图片的简单工具开发方案
  • 30、Vuex 为啥可以进行缓存处理
  • 【Leetcode 每日一题】3306. 元音辅音字符串计数 I
  • linux:环境变量,进程地址空间
  • 网络编程基础
  • 算法日记41:思维提升(最大gcd+好数组+简单的减法+球的颜色)
  • Cookie与Session详解
  • QuickAPI 和 DBAPI 谁更香?SQL生成API工具的硬核对比(一)
  • 从零实现区块链共识算法:用Python解锁去中心化世界的关键
  • 企业管理杂谈:产品经理的选拔和培养——企业产品创新发展的关键
  • Python核心语法-数据基本运算(一)
  • 玩转python:通俗易懂掌握高级数据结构:collections模块之defaultdict
  • Android第二次面试总结(项目拷打实战)
  • 线性代数(1)用 excel 计算鸡兔同笼
  • 0CTF 2016 piapiapia 1
  • Kafka的流量控制机制
  • 玩转python:通俗易懂掌握高级数据结构-collections模块之UserList