* What resource does a process in "READY" (or "RUNNABLE") state waiting for? Q: What is this READY/RUNNABLE state? A: process scheduler Process scheduler has multiple states that a process can be in: 1. RUNNING: currently running on a CPU/core 2. READY/RUNNABLE: ready to run on a CPU/core 3. WAITING: waiting for ... I/O (slow) A process gets to spend some time RUNNING and actually getting useful work done, but it can't run for too long. What if there's other processes that also want to get a share of the CPU? A process is given a "time quantum" or a "slice" of time to run on the CPU. When that time period is done, the running process is de-scheduled, and its state is set to READY or RUNNABLE. That state means that all the process is waiting for now, for the CPU to be freed again. The scheduler maintains lists of processes and their states. For simplicity, assume we have only one CPU/core, so only one "thing" can run at a time. If there are several more who want the CPU, they have to wait in READY state. The scheduler keeps a data-structure of all those waiting. When the current process completes its execution on the CPU, it is de-scheduled, and moved to the list (or queue) of READY processes. Usually a process that just ran is placed at the end of the READY queue. Next, the scheduler picks another READY process, changes its state to RUNNING, and moves it to run on the CPU, again, for a time period. If there are N processes in READY state, the scheduler will get each on of them in turn to run (process 1, then 2, then 3, etc. until process N). When done, the scheduler will cycle back and start from process 1 again. This cycle is also known as a "round-robin scheduling algorithm." Q: How does a process get de-scheduled from RUNNING to READY state? A: Two ways. A1: The kernel sets up interrupt timers that tick at specific intervals. When an "clock" interrupt happens, the current state of the CPU (registers, etc.) is preserved, and control passes on to an interrupt handler function. In this case, the function that gets executed is the entry point to the scheduler. Think of the scheduler is a function or "program" running in the kernel's context. A2: any process can yield to the CPU before its time quantum has ended, esp. if it has completed its work and has nothing more to do. This also happens if the process is terminating (normally or abruptly), in which case the process state is terminated, and the scheduler is invoked to pick the next process to run. In sum, when N processes all want the CPU, you'll see them moving back and forth between RUNNING and READY states. Next, imagine a process now needs to perform some I/O (reading from a file, or waiting to get some data from a network socket). Recall that I/O is at least 1000x slower than memory, and I/O is at east 1,000,000x slower than the CPU. So what if a process now is asking to read() data from a file and the data isn't cached in memory? That process can't run on the CPU, b/c it's waiting for data. Should that process be on the READY queue? No, b/c the scheduler will attempt to run that process many times before it's ready to run, again b/c I/O is slow. Solution: move a process that's waiting on something much slower than CPU/memory, into a separate queue, called the WAIT queue. Only READY processes will get a slice of the CPU. WAITing processes have to wait until their I/O is ready. Q: Who can places a process into WAIT state? A1: the process itself can ask to be moved to WAIT state, esp. if it just issued an I/O request. A2: the kernel may place the process in WAIT state, if the kernel knows that the resource that process is waiting for, is one that would take a long time to arrive (e.g., waiting on keyboard input, waiting or network/storage I/O, waiting on certain kinds of blocking "mutex" locks). After a process is put to WAIT state (directly from RUNNING state), the scheduler is invoked to schedule the next process. Q: How does a process get OUT of WAIT state? A: The most common way, is when an interrupt associated with the I/O has arrived. Note that when a process is placed into WAIT state, the "work" that it requests (e.g., I/O) is being scheduled ASYNCHRONOUSLY. It makes no sense to busy-wait on slow I/O, so you issue a request for the I/O, knowing it'll take a while (in relative terms), and then the process puts itself to sleep (WAIT state). As part of issuing the async I/O request, a callback handler is setup, to know whom to notify when the I/O request has completed. Note that often, any async request requires passing some sort of state or callback function, to be invoked when the async request has concluded (see the aio*(s) syscalls). Important: the scheduler does NOT wake up WAIT-ing processes. Someone ELSE must wake you up! That someone else has to be an interrupt handler, or some other kthread (as we'll see later in the course). If no one else can wake up a WAITing process, that process will wait forever. In the simplest case, an I/O interrupt happens. An I/O interrupt handler is invoked, it picks up the data that arrived (finally) from the slow I/O device, it finds out who was waiting for it, copies the data to some buffer, and then it "wakes up" the process that was waiting for that data. E.g., if a process was waiting in read(2). Now, that I/O handler (or some other kthread invoked from it), will "wake up" the user process, by changing its state from WAITING to READY (not RUNNING!) and being placed usually at the end of the READY queue. Then, the scheduler, some time later, will pick up the now-ready process and let it run (RUNNING state). Round-Robin is just one simple scheduler alg. There are dozens of them. Some will schedule short-lived processes first; some give priority to "interactive" processes (e.g., shell for kbd access, mouse movement, etc.). Of course, there are multi-queue schedulers, with priority queues, and users/admins can set higher/lower priority. See nice(2), setpriority(2). Q: If there is a new process enter into CPU. Will it appear in the top of the queue? A: Generally not a good idea, b/c it provides reverse incentives to users, to run many "small" programs, while possibly starving other processes that have been patiently waiting for the CPU. It's important esp. if you have scheduling priorities -- it should NOT result in low-priority processes getting no time on the CPU. You can find kernel states, including scheduler, in /proc on linux. /proc/PID/ is a folder that shows the current state of process with PID number. Q: What happens if you have multiple cores/CPUs? A: The scheduler will schedule one process per core, and be invoked as soon as one core becomes free. Often, the scheduler tries to schedule the same process on the same CPU: this helps with faster access to memory state, b/c some architectures have memory that is directly attached to a specific CPU/core. Q: are the kernel threads scheduled similar to processes? A: yes! kthreads don't have a user context, or virtual memory, but they can be seen as small programs running in kernel mode in kernel space, that can be invoked, and can also yield() to the scheduler. In linux, kthreads show up in "ps -ef" output as processes in [square brackets] Q: do user and kernel threads share a WAIT queue? A: generally yes, but in practice there are multiple WAIT queues (more on that later in the course). Q: what if the I/O is too long .. is there an upper bound on the wait state for a process ? A: If it takes too long, you're going to be stuck waiting, possibly forever, unless someone else wakes you up. Suppose you sent a packet and waiting for response. You'd be waiting forever, unless a kthread associated with TCP, will realize that you've waited too long for the packet to arrive, and will wake you up with an "error" or "timeout" state. Q: How does the CPU know that it has to next run the "callback" function? The callback function also needs to be scheduled on the READY queue of the scheduler!? For the CPU to be picked up? A: the interrupt handler, will typically pass the access to a specific kthread designed to handle this kind of callback (e.g., network, disk, etc.). This is done via "ksoftirqd" kthreads. * programming question #include "4.c"