在计算机世界中,有一个神秘的王国,叫做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请求有序地处理。
接下来,结合代码来对比几种调度器的差异
Noop调度器是一个简单的调度器,它按照请求到达的顺序服务I/O请求。它的实现比较简单,适用于一些简单的存储设备。以下是Noop调度器的代码实现:
- struct request_queue {
- struct request *requests;
- int head;
- int tail;
- };
-
- void enqueue_request(struct request_queue *queue, struct request *req) {
- queue->requests[queue->tail] = *req;
- queue->tail = (queue->tail + 1) % MAX_REQUESTS;
- }
-
- struct request *dequeue_request(struct request_queue *queue) {
- return &queue->requests[queue->head];
- }
-
- void noop_schedule(struct request_queue *queue) {
- struct request *req;
- while ((req = dequeue_request(queue)) != NULL) {
- // 处理请求
- }
- }
Anticipatory调度器是一个基于预计执行时间的调度器,它根据每个I/O作业的预计执行时间来决定服务顺序。它认为一些I/O作业可能会引起其他相关作业的访问,因此它会在预计执行时间更短的作业之前先服务一些其他相关作业。以下是Anticipatory调度器的代码实现:
- struct job {
- int sector;
- int arrival_time;
- int service_time;
- };
-
- struct job_queue {
- struct job *jobs;
- int head;
- int tail;
- };
-
- void enqueue_job(struct job_queue *queue, struct job *job) {
- queue->jobs[queue->tail] = *job;
- queue->tail = (queue->tail + 1) % MAX_JOBS;
- }
-
- struct job *dequeue_job(struct job_queue *queue) {
- return &queue->jobs[queue->head];
- }
-
- void anticipatory_schedule(struct job_queue *queue) {
- struct job *job;
- while ((job = dequeue_job(queue)) != NULL) {
- // 处理请求
- }
- }
Deadline调度器是一个具有超时时间的调度器,它为每个I/O作业设定一个超时时间。如果某个I/O作业等待时间超过了设定的超时时间,那么它会被优先服务。以下是Deadline调度器的代码实现:
- struct request {
- int sector;
- int queue_num;
- int arrival_time;
- };
-
- struct request_queue {
- struct request *requests;
- int head;
- int tail;
- };
-
- void enqueue_request(struct request_queue *queue, struct request *req) {
- queue->requests[queue->tail] = *req;
- queue->tail = (queue->tail + 1) % MAX_REQUESTS;
- }
-
- struct request *dequeue_request(struct request_queue *queue) {
- return &queue->requests[queue->head];
- }
-
- void deadline_schedule(struct request_queue *queue) {
- struct request *req;
- while ((req = dequeue_request(queue)) != NULL) {
- // 处理请求
- }
- }
4.CFQ调度器
CFQ调度器是一种通用型的I/O调度算法,旨在为所有进程提供公平的I/O带宽。它是Linux内核中使用最广泛的调度器之一,并且在许多操作系统中都有实现。
CFQ调度器通过为每个进程创建一个I/O队列来实现公平的I/O分配。每个进程的队列都是一个先进先出(FIFO)的队列,其中包含了该进程的I/O请求。当一个进程的I/O请求到达时,它会被添加到该进程的队列中,等待服务。
在CFQ调度器中,每个进程的I/O带宽是按照其进程优先级和等待时间来分配的。调度器会根据进程的优先级和等待时间来决定服务哪个进程的I/O请求。如果两个进程的优先级和等待时间相同,那么调度器会按照先来先服务的原则进行处理。
下面是一个简单的CFQ调度器的代码实现,供参考:
- struct cfq_io_data {
- struct io_queue *queue;
- struct io_priority_data prio;
- unsigned long last_end_time;
- s64 slice;
- enum class_type type;
- void *problem_data;
- };
-
- static void cfq_dequeue_request(struct cfq_io_data *io_data)
- {
- struct io_request *rq = dequeue_next(&io_data->queue->queue);
- if (!rq)
- return;
- cfq_update_io_dispatch(cfqd, cfq_prio_to_weight(&io_data->prio));
- }
-
- static void cfq_dispatch_request(struct cfq_io_data *io_data)
- {
- struct cfq_data *cfqd = &io_data->queue->cfqd;
- struct io_request *rq = dequeue_next(&io_data->queue->queue);
- if (!rq)
- return;
- cfq_change_class_weight(&io_data->prio, cfq_classify(rq));
- cfq_update_io_dispatch(cfqd, cfq_prio_to_weight(&io_data->prio));
- }
上述代码中,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.查看当前调度算法是NOOP
- $ cat /sys/block/nvme0n1/queue/scheduler
- [noop] deadline cfq
-
- 2.将NOOP算法调整为CFQ
- $ echo 'cfq'>/sys/block/nvme0n1/queue/scheduler
-
- 3.查看修改后的算法为CFQ
- $ cat /sys/block/nvme0n1/queue/scheduler
- noop deadline [cfq]
不同的调度器,在不同类型的存储设备上的性能和使用场景都有一定的差异:
综上所述,不同类型的调度器在不同的存储设备上表现会有所差异。在选择合适的调度器时,需要根据具体的存储设备类型和使用场景来考虑。