• 《存储IO路径》专题:不同IO调度器的差异


    计算机世界中,有一个神秘的王国,叫做IO王国。这个王国里有四种奇怪的生物,它们分别是Noop调度器、Anticipatory调度器、Deadline调度器和CFQ调度器。IO调度器负责管理计算机中的IO请求,确保它们有序地通过。就像一个交警会根据车流量来指挥交通一样,IO调度器也会根据IO请求的到达顺序来决定处理它们的顺序。

    Noop调度器是IO王国的小学生,它非常乖巧,总是按照规矩排队。它的行为非常简单,只会按照IO请求的到达顺序进行处理。有一天,一个叫SSD的小朋友来到IO王国,它想要成为Noop调度器的好朋友。但是,Noop调度器却觉得SSD小朋友不够聪明,因为SSD小朋友不需要排队,可以直接进入王国。于是,Noop调度器就拒绝了SSD小朋友的请求。但是,SSD小朋友并不气馁,它决定要找到一个更聪明的调度器来和它做朋友。

    Anticipatory调度器是IO王国的小聪明,它能够预测未来的IO请求。有一天,一个叫Web服务器的小朋友来到IO王国,它想要成为Anticipatory调度器的好朋友。但是,Anticipatory调度器却觉得Web服务器小朋友的请求太简单了,因为它只能处理GET和POST请求。于是,Anticipatory调度器就拒绝了Web服务器小朋友的请求。但是,Web服务器小朋友并不气馁,它决定要找到一个能够理解它请求的调度器来和它做朋友。

    Deadline调度器是IO王国的小严格,它要求每个IO作业都要按时完成任务。有一天,一个叫文件服务器的小朋友来到IO王国,它想要成为Deadline调度器的好朋友。但是,Deadline调度器却觉得文件服务器小朋友的请求太慢了,因为它需要很长时间才能完成任务。于是,Deadline调度器就拒绝了文件服务器小朋友的请求。但是,文件服务器小朋友并不气馁,它决定要找到一个能够理解它请求的调度器来和它做朋友。

    CFQ调度器是IO王国的小公平,它是一个公平的裁判,确保每个进程都能得到平等的待遇。有一天,一个叫桌面多任务的小朋友来到IO王国,它想要成为CFQ调度器的好朋友。但是,CFQ调度器却觉得桌面多任务小朋友的请求太复杂了,因为它需要同时处理多个任务。于是,CFQ调度器就拒绝了桌面多任务小朋友的请求。但是,桌面多任务小朋友并不气馁,它决定要找到一个能够理解它请求的调度器来和它做朋友。

    这四个调度器都有自己的特点,各有优缺点。在选择合适的调度器时,我们要根据具体情况来考虑,是乖巧的SSD、聪明的Web服务器、严格的文件服务器还是公平的桌面多任务系统。不管怎样,这些调度器都在用自己的方式维护着计算机的IO秩序,确保IO请求有序地处理。

    接下来,结合代码来对比几种调度器的差异

    1. Noop IO调度器

    Noop调度器是一个简单的调度器,它按照请求到达的顺序服务I/O请求。它的实现比较简单,适用于一些简单的存储设备。以下是Noop调度器的代码实现:

    1. struct request_queue {
    2. struct request *requests;
    3. int head;
    4. int tail;
    5. };
    6. void enqueue_request(struct request_queue *queue, struct request *req) {
    7. queue->requests[queue->tail] = *req;
    8. queue->tail = (queue->tail + 1) % MAX_REQUESTS;
    9. }
    10. struct request *dequeue_request(struct request_queue *queue) {
    11. return &queue->requests[queue->head];
    12. }
    13. void noop_schedule(struct request_queue *queue) {
    14. struct request *req;
    15. while ((req = dequeue_request(queue)) != NULL) {
    16. // 处理请求
    17. }
    18. }
    1. Anticipatory IO调度器

    Anticipatory调度器是一个基于预计执行时间的调度器,它根据每个I/O作业的预计执行时间来决定服务顺序。它认为一些I/O作业可能会引起其他相关作业的访问,因此它会在预计执行时间更短的作业之前先服务一些其他相关作业。以下是Anticipatory调度器的代码实现:

    1. struct job {
    2. int sector;
    3. int arrival_time;
    4. int service_time;
    5. };
    6. struct job_queue {
    7. struct job *jobs;
    8. int head;
    9. int tail;
    10. };
    11. void enqueue_job(struct job_queue *queue, struct job *job) {
    12. queue->jobs[queue->tail] = *job;
    13. queue->tail = (queue->tail + 1) % MAX_JOBS;
    14. }
    15. struct job *dequeue_job(struct job_queue *queue) {
    16. return &queue->jobs[queue->head];
    17. }
    18. void anticipatory_schedule(struct job_queue *queue) {
    19. struct job *job;
    20. while ((job = dequeue_job(queue)) != NULL) {
    21. // 处理请求
    22. }
    23. }
    1. Deadline IO调度器

    Deadline调度器是一个具有超时时间的调度器,它为每个I/O作业设定一个超时时间。如果某个I/O作业等待时间超过了设定的超时时间,那么它会被优先服务。以下是Deadline调度器的代码实现:

    1. struct request {
    2. int sector;
    3. int queue_num;
    4. int arrival_time;
    5. };
    6. struct request_queue {
    7. struct request *requests;
    8. int head;
    9. int tail;
    10. };
    11. void enqueue_request(struct request_queue *queue, struct request *req) {
    12. queue->requests[queue->tail] = *req;
    13. queue->tail = (queue->tail + 1) % MAX_REQUESTS;
    14. }
    15. struct request *dequeue_request(struct request_queue *queue) {
    16. return &queue->requests[queue->head];
    17. }
    18. void deadline_schedule(struct request_queue *queue) {
    19. struct request *req;
    20. while ((req = dequeue_request(queue)) != NULL) {
    21. // 处理请求
    22. }
    23. }

    4.CFQ调度器

    CFQ调度器是一种通用型的I/O调度算法,旨在为所有进程提供公平的I/O带宽。它是Linux内核中使用最广泛的调度器之一,并且在许多操作系统中都有实现。

    CFQ调度器通过为每个进程创建一个I/O队列来实现公平的I/O分配。每个进程的队列都是一个先进先出(FIFO)的队列,其中包含了该进程的I/O请求。当一个进程的I/O请求到达时,它会被添加到该进程的队列中,等待服务。

    在CFQ调度器中,每个进程的I/O带宽是按照其进程优先级和等待时间来分配的。调度器会根据进程的优先级和等待时间来决定服务哪个进程的I/O请求。如果两个进程的优先级和等待时间相同,那么调度器会按照先来先服务的原则进行处理。

    下面是一个简单的CFQ调度器的代码实现,供参考:

    1. struct cfq_io_data {
    2. struct io_queue *queue;
    3. struct io_priority_data prio;
    4. unsigned long last_end_time;
    5. s64 slice;
    6. enum class_type type;
    7. void *problem_data;
    8. };
    9. static void cfq_dequeue_request(struct cfq_io_data *io_data)
    10. {
    11. struct io_request *rq = dequeue_next(&io_data->queue->queue);
    12. if (!rq)
    13. return;
    14. cfq_update_io_dispatch(cfqd, cfq_prio_to_weight(&io_data->prio));
    15. }
    16. static void cfq_dispatch_request(struct cfq_io_data *io_data)
    17. {
    18. struct cfq_data *cfqd = &io_data->queue->cfqd;
    19. struct io_request *rq = dequeue_next(&io_data->queue->queue);
    20. if (!rq)
    21. return;
    22. cfq_change_class_weight(&io_data->prio, cfq_classify(rq));
    23. cfq_update_io_dispatch(cfqd, cfq_prio_to_weight(&io_data->prio));
    24. }

    上述代码中,cfq_dequeue_request()函数从当前进程的队列中取出下一个I/O请求,而cfq_dispatch_request()函数则根据当前进程的队列中的I/O请求来更新I/O调度器的权重。这些权重将用于决定服务哪个进程的I/O请求。

    CFQ调度器通过公平地分配I/O带宽,使得所有进程都能够获得公平的I/O服务。这种公平性对于多任务操作系统来说非常重要,可以避免某些进程因为得不到足够的I/O资源而出现性能问题。同时,CFQ调度器的实现也比较简单和高效,因此在许多系统中都得到了广泛的应用。

    在Linux内核中,修改IO调度的算法也比较简单,简单例子参考:

    1. 1.查看当前调度算法是NOOP
    2. $ cat /sys/block/nvme0n1/queue/scheduler
    3. [noop] deadline cfq
    4. 2.将NOOP算法调整为CFQ
    5. $ echo 'cfq'>/sys/block/nvme0n1/queue/scheduler
    6. 3.查看修改后的算法为CFQ
    7. $ cat /sys/block/nvme0n1/queue/scheduler
    8. noop deadline [cfq]

    不同的调度器,在不同类型的存储设备上的性能和使用场景都有一定的差异:

    1. Noop调度器:
      • 性能表现:Noop调度器是一个简单的调度程序,它本质上是一个链表实现的FIFO队列,对请求进行简单的合并处理。由于它的实现简单,因此在各种类型的存储设备上性能表现相对较差。
      • 使用场景:Noop调度器适用于固态硬盘(SSD)等存储设备,因为固态硬盘的IO调度算法越简单,效率就越高。
    1. Anticipatory调度器:
      • 性能表现:Anticipatory调度器通过预测未来的IO请求,将硬盘的I/O请求预先发送到硬盘中,以减少磁头的移动和寻道时间,提高整体性能。它在处理有一定数据量但IO压力不是非常大的场景中表现最为出色。
      • 使用场景:Anticipatory调度器适用于机械硬盘(HDD)和SATA SSD等存储设备,因为在这些存储设备上,提高效率需要考虑寻道时间和磁头移动等因素。
    1. Deadline调度器:
      • 性能表现:Deadline调度器以减少磁头的移动和寻道时间为目标,尽可能地满足I/O请求的截止期限。它适合用在有大量I/O请求并且IO压力比较重的业务,比如数据库系统。
      • 使用场景:Deadline调度器适用于机械硬盘(HDD)和SATA SSD等存储设备,因为在这些存储设备上,提高效率需要考虑寻道时间和磁头移动等因素。对于业务比较单一并且IO压力比较重的业务,Deadline调度器是最佳的选择。
    1. CFQ调度器:
      • 性能表现:CFQ调度器是一种通用的调度算法,它以进程为出发点考虑,保证所有进程都能公平地分配到I/O资源。它适合用于多任务、多媒体等需要公平分配I/O资源的场景中。
      • 使用场景:CFQ调度器适用于机械硬盘(HDD)、SATA SSD和NVMe SSD等存储设备。然而,由于CFQ调度器的复杂度较高,因此在固态硬盘这种场景下,其效率可能会比Noop和Anticipatory调度器低。

    综上所述,不同类型的调度器在不同的存储设备上表现会有所差异。在选择合适的调度器时,需要根据具体的存储设备类型和使用场景来考虑。

  • 相关阅读:
    MFC消息映射【整理】
    家政预约服务APP小程序搭建,功能支持定制
    认识计算机
    如何使用腾讯云web应用防火墙结合API网管提供安全防护?
    上游模式用于实验室用冷冻机压力和真空度的高精度控制
    迅为itop-3568开发板qt学习手册上新
    【python技巧】文本处理-re库字符匹配
    云服务部署:AWS、Azure和GCP比较
    R语言ggplot2可视化:使用ggpubr包的show_point_shapes函数可视化ggplot2中可用的数据点pch形状参数形状及其编码
    RocketMQ消息发送源码解析
  • 原文地址:https://blog.csdn.net/zhuzongpeng/article/details/132724689