I'm studying operating system on this book and my prof's slides. I'm arrived at the "Process scheduling algorithms" chapter. Talking about the RoundRobin (RR) algorithm i found some inconsistencies. I understand that is a preemptive version of the FCFS algorithm with a time-slice (quatum).
From now on, I will use the following notation:
#1 = prof's version
#2 = book's version
#3 = other version
Here's the inconsistency (suppose a quantum of 100ms):
#1 The RR uses two queue (Q1, Q2):
Q1: queue for processes that did not end their quantum;
Q2: queue for processes that did end their quantum;
The scheduler takes the process from the head of Q1;
If the process ends before the quantum expires, the process release the CPU on purpose and the scheduler takes the next process from Q1
If the process doesn't end before the quantum expires, is preempted and placed at the end of Q2;
When a process is ready it's placed at the end of Q1;
When Q1 is empty, Q1 and Q2 are swapped;
So when a process is blocked for an I/O request (e.g. after 30ms) and its quantum is not expired yet, is placed at the end of Q1 (I guess) and when it will be scheduled again it will use the CPU for his remaining time (70ms in this case).
#2 (The book did not talk about multiple queues, so I assume it will use just one queue)
The scheduler takes the process from the head of the ready queue;
If the process ends before the quantum expires, the process release the CPU on purpose and the scheduler takes the next process from the ready queue
If the process doesn't end before the quantum expires, is preempted and placed at the end of the ready queue;
#3 Source
The scheduler takes the first process in the ready queue;
If the process ends before the quantum expires, the process release the CPU on purpose and the scheduler takes the next process from the ready queue
If the process doesn't end before the quantum expires, is preempted and placed at the end of the ready queue;
If the process is blocked by a I/O request, it's placed in a waiting queue and when it will became ready will be placed again in the ready queue;
To me, these are 3 different implementations of the RR scheduling algorithm. I think that the most valuable is the #3 because the #1 can cause a starvation (if the process is placed in Q2 and new processes keep coming in Q1, then the process will never be scheduled again) and #2 will waste CPU time when a process is blocked for an I/O request. So, my question is: which one is the right one?
Round robin in theory
Round Robin scheduling can be quite good visualized when thinking about an analog clock: The hand is turning around at constant speed, so it's in the slice of a single digit for 1/12 of the time it takes for one complete run.
A single digit thus has some slice of the total available amount of resource. And, most importantly, there's a fixed order in which the digits get served: After the hand just passed some digit, it'll only get visited again after the hand passed all the other digits.
Looking at the variants you presented, number #2 the version from the book matches this exactly: After a task has been served it is put at the end of a (often so called) ready queue, and thus only gets served again after all of the other tasks have been served once.
Round robin is, as a theoretical scheduling algorithm, only considering the scheduling of multiple consumers (tasks) to a single resource (CPU).
Variants
Some common variations of the basic round robin scheduling are to either use different slice sizes for different tasks, or to dynamically adjust the slice of a task based on some metric, or even to provide more than a single slice to some tasks.
In practice
When you are scheduling tasks, you have to schedule them to more than the CPU as single resource, there will be other resources that need to be managed, like IO devices.
Very simple schedulers just ignore that fact, and leave tasks that are currently waiting for some other resource in the task queue for the CPU.
So when such a task gets its time slice, all it'll do is find that it still needs to wait for that other resource and hands back the CPU, just to get put back into the task queue by the scheduler. Starting the task, checking that the task still needs to wait for the other resource, and stopping the task takes some time that could better be spend for a task that actually can use the CPU.
To solve this, one usually has a task queue for each resource that is managed, i.e. one for the CPU, one for each IO device, etc. When a task is doing a blocking IO call, it is then removed from the queue for the CPU and put into the queue of the device it is accessing. This way, tasks that are waiting for a resource other than the CPU don't sit in the CPU task queue (and thus waste no time getting started and immediately stopped again).
This is what #3 is talking about (when you look again you see that they're talking about "waiting queues")
Another kind of "waiting queue"
Often there's also a "waiting queue" when you're talking about multi level schedulers: In that case, the ready queue is the queue of tasks that are ready and get scheduled by the primary scheduler (using round robin, for example). If a task blocks because of an IO operation, it is - as described above - put into the queue of the corresponding resource. When that resource gets available again, the task is first put into the waiting queue, from which a secondary scheduler (which is just a task for the primary scheduler) eventually takes it and puts it into the ready queue of the primary scheduler.
What your prof is about
The version #1 is probably better to understand if you rename the queues into something like "queue with tasks that did not yet run in this turn" and "queue with tasks that did already run in this turn". This is basically just a "workaround" if you don't want to have circular lists. So it's round robin, too, but a bit obscured.
[..] can cause a starvation (if the process is placed in Q2 and new processes keep coming in Q1, then the process will never be scheduled again) [..]
This is a very good observation. This could be solved if new, ready tasks get inserted at the end of Q2 instead of Q1. If suitable, this is a very good start for a discussion when your prof is asking for questions.
The first implementation can not only cause starvation, it can also cause deadlocks. If only we modify the step 5 of first implementation, the method can be made just fine.
The second approach is round-robin in its purest form.
The third approach is not round-robin but is in fact a smarter version which understands that I/O bound process should not be given another chance too soon as it will probably not be ready yet.
If you will continue reading that book you will read the next scheduling algorithm called Multi-level Queue. That is actually the best of all implementations of different variations of Round-robin. In Multi-level queue we put all incoming processes in one queue and then depending upon whether they finished in their first time slice or not, put them in another queue. This new queue has higher time slice and ends up holding CPU bound processes.
Using Multi-level queues (like 4-5 of them) the CPU combs out all the incoming processes into various classes and then picks optimum number of processes from each queue so that it is neither over-subscribed nor under-subscribed.
Any process which arrives in the system is queue at the end of the ready queue. A process which is at the head of the ready queue is selected and allowed to execute on the CPU for a time quantum q.At the expiry of q, the process is queued at the tail of the ready queue.The next process scheduled is the one at the head of ready queue.This is know Round Robin Scheduling.
OR
In round robin scheduling, processes are dispatched FIFO but are given a limited amount of CPU time called a time-slice or a quantum.If a process does not complete before its CPU times expires,the CPU is preempted and given to the next waiting process,the preempted process is than placed at the back of the ready queue.
Related
I am recently taking a course called The principle of operating system, and I learned CPU scheduling. I am confused about Round robin scheduling, for I/O-bound process, for example, the process will use CPU for 2ms and does I/O for 8ms. Will scheduler still assign a quantum to this process when it is doing I/O? Also, when this process is doing I/O, will the scheduler wait for the I/O to complete even when the quantum expires or it will just start to execute next process? Any help would be appreciated!
Will scheduler still assign a quantum to this process when it is doing I/O?
Typically each task has a state, maybe one of:
running, currently using CPU
ready to run, waiting to use CPU
blocked, waiting for something (disk IO, a mutex, a time delay, a network packet to arrive, ...)
The scheduler only cares about tasks that are running and ready to run - e.g. it might have a (circular singly linked?) list of tasks that want CPU time, and when a task blocks the task is removed from that list (and then later when whatever the task was waiting for happens and the task is unblocked, the task is put back on the list).
Traditionally; when a task is put back on the list it's put back on the end of the list, so that a task can't repeatedly block briefly to get a new time slice and hog the CPU.
This means that if there are 2 tasks and one blocks, a round robin scheduler might do "task A, task B, task A, task B" while task A is running/ready to run; then switch to "task B, task B, task B, task B" while task A is blocked; then after task A unblocks it'd go back to "task B, task A, task B, task A, ..." (starting with task B because task A was put on the end of the list and not the start of the list).
The other thing is that tasks literally can't decide to do something that would cause them to block unless they're currently running; which means that whenever a task blocks it doesn't use its whole time slice. For example, if the scheduler is giving out 1 ms time slices then a task may block after using 0.3 ms of its time slice, leaving a remainder of 0.7 ms. For this reason the scheduler needs a timer with higher precision and the length of time slices will be rounded to the precision of the timer IRQ (e.g. if the scheduler is using a timer IRQ that occurs every 0.2 ms; then that remaining 0.7 ms left after one task blocks might be rounded to 0.8 ms leaving a spare 0.1 ms due to rounding, and the next task might actually get 1.1 ms of CPU time instead of 1.0 ms of CPU time because of that "rounding to the timer's precision").
Also; when all tasks are blocked the scheduler's timer can be suppressed/disabled (and the CPUs put into a power saving state) to reduce power consumption by preventing pointless timer IRQs from waking the CPU out of a power saving state; and when only one task can run the scheduler's timer can be also be suppressed/disabled (and the task given an "effectively infinite" time slice) to prevent the overhead of pointless timer IRQs from decreasing the performance of the task.
Note 1: Almost all universities ignore reality; starting with the extremely dodgy assumption that its possible to know how long a task will use CPU time and when it will block or use IO (followed by the assumption that everything happens in nicely "aligned to time slice duration" boundaries).
Note 2: Almost all universities assume that "IO" means the initiating task is blocked; either because the disk controller does the IO while the CPU does other things, or because one or more different task/s use the CPU to do the IO while the initiating task blocks (e.g. your task calls "read()", your task is blocked and a file system task is unblocked, the file system task asks the disk controller's driver to fetch some data, the file system task is blocked and the disk controller driver's task is unblocked, then ...). This isn't strictly true in all cases (but may be true in all cases for some operating systems).
In general time critical I/O will typically be handled by an interrupt handler rather than a round-robin scheduled process.
For example, say you have a UART with no hardware FIFO, a character arriving in its data-register, must be read before it is overwritten by the next received character. In this case the character might be placed in a software FIFO buffer (a pipe or queue). That buffer would need to be large enough to capture all data received while the receiving process is not running. When the receiving process is scheduled it will receive all buffered data at once.
In other cases, I/O may use DMA operations, which occur in parallel to CPU operations. There is still often an interrupt handler involved but it would be a DMA controller interrupt rather than an interrupt from the I/O device.
Non time-critical I/O may simply be polled or asserted in a round-robin process when no precise timing is required.
If an application has a great deal of time-critical I/O and also time critical data processing. Round-robin scheduling may not be appropriate. Real-time operating systems generally use priority based premptive scheduling, with round-robin for tasks if equal priority.
The concept that a process either uses the CPU or does I/O however makes no sense, a process runs on the CPU, whether it is performing I/O or data processing. In fact for memory mapped I/O the CPU makes no real distinction.
When a process and/or thread waits on mutex in which state the process and/or thread is? Is it in WAIT or READY or some other state? I tried to search the answer over the web but could not find a clear, definitive answer, maybe there isn't one or maybe there is, to find out that I am posting this question here.
tl;dr: Nothing happens when it is waiting; it is simply a kernel data structure.
Without loss of generality, all operating systems have some sort of model where a unit of execution (task) moves between the states : Ready, Running, Waiting. This Task has a data structure associated with it, where its state and registers (among other things) are recorded.
When a task moves from Ready to Running, its saved registers are loaded on a cpu, and it continues execution from its last saved state. Initially, its saved registers are set to reasonable values for the program to start.
From Running to Waiting or Ready, its registers are stored in its task data structure, and this structure is placed on either a list of Ready or Waiting tasks.
From Waiting to Ready, the task data structure is removed from the Waiting list and appended to the Ready list.
When a task tries to acquire a mutex that is unavailable, it moves from Running (how else could it try to get the mutex) to Waiting. If the mutex was available, it remains Running, and the mutex becomes unavailable.
When a task releases a mutex, and another task is Waiting for that mutex, the Waiting task becomes Ready, and acquires the mutex. If many tasks are Waiting, one is chosen to acquire the mutex and become Ready, the rest remain Waiting.
This is a very abstract description; real systems are complicated by both a plurality of synchronization mechanisms (mailbox, queue, semaphore, pipe, ...), a desire to optimise the various paths, and the utilization of multiple CPUs.
Assume there are 2 processes with tickets A:75 and B:25. Now if lottery results in ticket number = 66, that means we run A.
This is okay for non-preemptive kernels because A will run until A is complete and then will not participate in the lottery.
But if Kernel is preemptive and A is selected,then wouldn't we need to decrease the tickets A has.
I'm not sure that I understand the concern that you're raising in your question, but I'll try to address your points. I assume that the system has 1 core and that each process has 1 thread.
Non-preemptive: If 66 is chosen, then A will run until it voluntarily yields to the scheduler. Once the scheduler has control, it will randomly choose another ticket and run the associated process. Thus, A could immediately run again or B could run.
Preemptive: If 66 is chosen, then A will run until it either voluntarily yields to the scheduler or it is preempted (e.g. by a timer interrupt) and the scheduler is given control. Just like in the non-preemptive version, the scheduler will randomly choose another ticket and run the associated process, so in this case A has a 75/100 = 75% chance of running and B has a 25/100 = 25% chance of running.
I assume that what you're asking is how do you revoke A's tickets while it is running so that it is not chosen by a scheduler on another core to run at the same time (i.e. how does lottery scheduling work on a multicore system?). When the scheduler chooses a ticket, it can simply mark the process's other tickets as invalid or manipulate the ticket data structure in the appropriate way so that the process cannot be chosen by a scheduler. The process's ticket should be chosen and the other tickets should be marked invalid at the same time (i.e. the two operations should look like one operation that is atomic) and before the scheduler performs a context switch to the process. The tickets should be marked valid again immediately after the scheduler regains control from that process.
The idea remains the same when processes can have more than 1 thread. It just comes down to the scheduler implementation to determine how to distribute the tickets among the process's threads and which ones to take away when one of the threads is chosen.
I am reading "Embedded Software Primer" by David E.Simon.
In it discusses RTOS and its building blocks Scheduler and Task. It says each Task is either in Ready State, Running State, or Blocking State. My question is how the scheduler determines a Task is in Blocking State? Assume it's waiting for a Semaphore. Then it likely Semaphore is in a state it can't return. Does Scheduler see if a function does not return, then mark its state as Blocking?
The implementation details will vary by RTOS. Generally, each task has a state variable that identifies whether the task is ready, running, or blocked. The scheduler simply reads the task's state variable to determine whether the task is blocked.
Each task has a set of parameters that determine the state and context of the task. These parameters are often stored in a struct and called the "task control block" (although the implementation varies by RTOS). The ready/run/block state variable may be a part of the task control block.
When the task attempts to get the semaphore and the semaphore is not available then the task will be set to the blocked state. More specifically, the semaphore-get function will change the task from running to blocked. And then the scheduler will be called to determine which task should run next. The scheduler will read through the task state variables and will not run those tasks that are blocked.
When another task eventually sets the semaphore then the task that is blocked on the semaphore will be changed from the blocked to the ready state and the scheduler may be called to determine if a context switch should occur.
As I'm writing a RTOS ( http://distortos.org/ ), I thought that I may chime in.
The variable which holds the state of each thread is indeed usually implemented in RTOSes, and this includes mine version:
https://github.com/DISTORTEC/distortos/blob/master/include/distortos/ThreadState.hpp#L26
https://github.com/DISTORTEC/distortos/blob/master/include/distortos/internal/scheduler/ThreadControlBlock.hpp#L329
However this variable usually is used only as a debugging aid or for additional checks (like preventing you from starting a thread that is already started).
In RTOSes targeted at deeply embedded systems the distinction between ready/blocked is usually made using the containers that hold the threads. Usually the threads are "chained" in linked lists, usually also sorted by priority and insertion time. The scheduler has its own list of threads that are "ready" ( https://github.com/DISTORTEC/distortos/blob/master/include/distortos/internal/scheduler/Scheduler.hpp#L340 ). Each synchronization object (like a semaphore) also has its own list of threads which are "blocked" waiting for this object ( https://github.com/DISTORTEC/distortos/blob/master/include/distortos/Semaphore.hpp#L244 ) . When a thread attempts to use a semaphore that is currently not available, it is simply moved from the scheduler's "ready" list to semaphores's "blocked" list ( https://github.com/DISTORTEC/distortos/blob/master/source/synchronization/Semaphore.cpp#L82 ). The scheduler doesn't need to decide anything, as now - from scheduler's perspective - this thread is just gone. When this semaphore is now released by another thread, first thread which was waiting on this semaphore's "blocked" list is moved back to scheduler's "ready" list ( https://github.com/DISTORTEC/distortos/blob/master/source/synchronization/Semaphore.cpp#L39 ).
Usually there's no need to make special distinction between threads that are ready and the thread that is actually running. As the amount of threads that can actually run is fixed and equal to the number of available CPU cores, then all you need is a pointer for each CPU core which points to the thread from the "ready" list which is running at that core at that moment. In my system I do the same - the thread that is at the head of the "ready" list is the one that is running, but I also manage an iterator which points to that thread ( https://github.com/DISTORTEC/distortos/blob/master/include/distortos/internal/scheduler/Scheduler.hpp#L337 ). You could have a separate list for running threads, but in most cases it would be a waste of space (there's usually just one) and makes other things slightly more complicated.
I've actually wrote an article about thread states and their transitions if you're interested - http://distortos.org/documentation/task-states/ This article has no special distinction between the thread that is "ready" and the one that is actually running. I don't consider this distinction to be actually useful for anything, as long as you have other means to tell which of the "ready" threads is running.
I understand that CPU scheduling algorithms are classified into
Interactive - Round Robin, Priority scheduling
Batch Scheduling - FCFS,SJF
But I cant understand the reason behind the naming Interactive and Batch Scheduling..??
Why are algorithms like RR called interactive and those like FCFS called batch scheduling??
Thanks in advance...
The idea of Batch Scheduling is that there will be no change in the schedule during runtime: a process is scheduled to do an operation on data, and it runs until the process is finished. In 'interactive' scheduling, a new process could be launched while another process is running, and so time would be allocated for that process as well as the other. In batch scheduling the schedule is determined at the beginning of the operation.
Example of priority (interactive) scheduling:
Process A has a high priority, and process B has a low priority. Process A runs until it requires some input from the user. While A is waiting, the CPU gives some time to process B. Once the input for A has been gathered, process B is swapped out and process A is given the CPU, due to its higher priority.
Example of batch (FCFS) scheduling:
Process A and process B are processes to be scheduled. Process A is given to the CPU first, so B will not receive any time until A finishes running. Even if A pauses for user input, B will not run (and the CPU time while waiting for input is effectively wasted).
Of course, as with everything this low-level, it's not entirely that simple: to gain the illusion of multi-tasking, time is generally divided up between processes even when nothing is waiting for I/O. In priority scheduling, this may mean that more time slices are given to A than B while both are running so that A executes quicker. Both interactive and batch scheduling have their pros and cons: while interactive scheduling gives a quicker response time to the user and divides time up more 'fairly', an overhead is incurred due to how long a 'context switch' takes, which is the time taken for the processor to switch from working on process A to process B.
Interactive scheduling policies assign a time-slice to each process. Once the time-slice is over, the process is swapped even if not yet terminated. It can also be said that scheduling of this kind are preemptive.
Batch scheduling policies, instead, are non-preemptive. Once a Process is in the Running-status, it will not change status until it terminates.