 Welcome back to 353. So today we're at the quarter point of the course. So this lecture will be a bit of a more straightforward one, bit of a sigh of relief after learning about processes and all the troubles they bring until we jump into virtual memory and then our brains break again. So when we talk about scheduling we are talking about just scheduling some type of resources and there are two broad categories of resources. So there are preemptible resources and non-preemptible resource. The difference between them is a preemptible resource is something that can easily be taken away and used for something else. So for like a CPU you can easily take it away from a process give it to another one and you can just share it through scheduling. So you just get to decide whose turn it is. Doesn't matter no one really owns anything they just get to share some resource. So a non-preemptible resource is something where the resources cannot be taken away at any time. So for instance disk space I can't just randomly borrow a gigabyte that some other file is using change it modify it and expect the other thing not to suffer. So whenever you have non-preemptible resources then they're shared through like allocations and deallocations. So for some real high computing applications some of them may allow you to just allocate a CPU. If you really need to know you know be sure that you have a CPU for a set amount of time you may be able to allocate and deallocate a CPU but you won't see that in general Linux like a just a general purpose operating system more high performance. So there are two generic names for these things so there's a dispatcher and a scheduler and they are kind of work together. So a dispatcher that's the low level mechanism responsible for context switching which is saving all of the registers and actually restoring them and actually physically doing the work of switching processes specifically for a CPU. The scheduler that we get to talk about today is that high level policy so that's deciding which process to run and when. So the scheduler generally runs whenever a process changes state so first we can consider non-preemptible processes so like the cooperative one or just processes will just run until they are finished and we can't switch between them. So we'll start off with that world so process starts process starts doesn't stop until it completes. So in this case our only decision we have to make is when a process terminates we have to decide what one we should run. So preemptive sometimes you'll see that if you do a uname-fee your kernel won the words it might tell you that it's preemptible so if it says that it means they can just interrupt processes at any time take the CPU away from them at least for ARM it doesn't say that but you might see that whenever you do uname-fee the word preempt might be there that's what it means. So scheduling has some metrics that we care about and some of them are at odds with the other ones so one of the most obvious metrics is we want to minimize waiting time and response time so we don't want to have processes waiting for a long time doing nothing or have them wait a long time to even start doing something. We also want to maximize CPU utilization we don't want a CPU core just sitting there not doing anything it's a if it's available we want to actually use the cores we have paid for on our machine so if I have eight cores hopefully I'm using all eight of them if I have enough processes to run. Other things maximize throughput so I want to complete as many processes as possible go as fast as possible but the thing that might be at odds with that is fairness so I might want to try to give each process the same percentage of the CPU which might may increase waiting time and response time but it's more fair to all processes yep yeah so the question is can fairness be toggled and we'll talk about that later some things we can do so we can kind of play with them to get something that feels better but with scheduling since everything's at odds with each other it's just a bunch of trade-offs all the time so we'll see some more advanced things today will be fairly basic scheduling algorithms the first one well we should know it if we've ever been to I guess any place with a cash register or a kiosk or something like that first come first serve that is a basic scheduling algorithm so it's the most basic form of scheduling we've done it every day same thing applies to two processes so you just essentially form a line the first process that arrives gets the CPU and then they're stored in a FIFO queue so normal queue we have first in first out and they're just in that queue in arrival order so whatever the first process in line is done we go to the next one whatever it's done we go to the next one it's the most basic form of scheduling we got so throughout all of this and on the exam so there'll be a question that will be like essentially a freebie hopefully on the exam that'll be a scheduling question to like draw the schedule with this algorithm it will look something like this so again chart we can use to illustrate the schedule so you'll always be given like a list of processes so here I have four processes and then their arrival time so at what time does it arrive and I just generally use time units that are just whole numbers and then there's this burst time looks a little weird so burst time essentially what that means is how long this process wants to execute on the CPU for so how many time units it's called burst time don't look at me I didn't name it it's how long it wants to actually run for so considering they all arrived at the same time instant t zero I will just tell you the like nanosecond order that they arrive so you can actually do this question so we'll just say assume that they arrive p1 arrived before p2 arrived before p3 arrived before p4 and then in that case if we have first come first serve well we just run the processes in that order so our schedule would look like well for the first seven time units p1 was the first process in line so we would execute that for seven time units so one two three four all the way up to seven then after it's done it's terminated it's done running whatever it was running scheduler gets to run again make a new decision since it's first come first serve the second process in line was p2 and its burst time or how long it wants to execute is four time units so it would execute for its four time units that time 12 or 11 well we have to decide what's the next process to run p3 was the next one in line one time unit and then p4 is the last one in line so it executes for four time units so one of the questions you'll be asked for scheduling questions just how to evaluate that is what is the average waiting time for all the processes so in this case nothing tricky you just figure out the time from the time it arrives to the time it finishes how long is it waiting around for not executing so process one waiting around for zero time units before it starts executing because it started executing immediately p2 arrived at time zero and then didn't start executing to time seven so it waited around for seven time units p3 waited around for 11 p4 waited around for 12 so average waiting time not going to be a complicated calculation zero plus seven plus 11 plus 12 all divided by four i think it's like 7.5 or something like that we'll get into more involved ones where i'll go through all the numbers so you have them so if we just tweak this a little bit and just assume that p3 arrives first and then p2 then p4 then p1 well if we do first come first serve we execute them in order so now p3 goes first based off this order then p2 then p4 then p1 and now if we calculate the average waiting time it's substantially different now so p3 waited around for zero p2 waited around for one p4 waited around for five and then p1 waited around for nine so if we take that average waiting time that average waiting time is like 3.75 which is half of what it was before so just based off luck this algorithm just gave us wildly different numbers so we can probably do better than just relying on blind so that's where the first if you want to call an algorithm comes in so it is called shortest job first so it's just a very slight tweak to first come first serve is you always pick the process with the shortest first time first and we're assuming no preemption still at this point and the idea behind that is well if i run the shorter programs first they will finish but free up the cpu and then my average waiting time will be minimized in that case yep yes yeah so the question is if one job comes in that shorter while you're executing something longer does it take over or what happens so for this this is not preemptible so it would whatever we schedule just goes until it finishes but that will be our first tweak to this because well that's one of our things we would want to do if we make it preemptible so shortest job first that'll minimize that average waiting time so now this will be our running example for the rest of the lecture so we have those same four processes i just tweak the arrival time so p1 arrives at zero p2 arrives at two p3 arrives at four p4 arrives at five so what i like to do is just at the top of these i just write the arrival time so i don't have to look at the table again so p1 arrives here p2 arrives at two p3 arrives at four p4 arrives at five and then their burst times are the same so now in this case if i do shortest job first and i can't preempt anything well at time zero i have a process to run p1 and at that time that's my only option so i have to run p1 so i will run it for seven time units and because i can't take away the cpu from process run one doesn't matter if p2 or p3 or p4 are now want to run doesn't matter i have to wait until p1 is done so after p1 is done that's when i can make my next scheduling decision i decide between process two three and four in this case the shortest job of all those is process three so i would just execute that for one time unit and then between process two and four well if there's a tie so they run for the same amount of time i just go by who arrived first so in this case p2 and then p4 yep yeah so that's a good comment in practice do we know the burst time of these programs in practice absolutely not so this is more of a fundamental thing so you can run programs and then know how long they execute like after the fact and then that's how you can evaluate your scheduling but your scheduling will depend on you know how long the process is run for what they're used for it gets into a more involved topic that we'll see later but this yeah completely impractical as an algorithm because in practice we won't know how long a process actually runs for especially you know if you just write your own program before you execute it do you have any idea how long it's going to take no because could have an infant loop or something like that i've done that lots of times so you won't really know but in this yeah maybe you can stick an ai in front of it but you know might be wrong who knows you can do whatever you want nowadays and as we'll see there's no right answer to any scheduling algorithm so in this impractical system if we compute our average waiting time now well p1 waited around for zero time units p2 well it waited around for six time units so it started at eight and it was arrived at two so it waited around for six p3 well it arrived at four and waited around until seven so it waited around for three time units and then p4 well it arrived at 12 or sorry it arrived at five started at 12 so it waited around for 12 minus five which is seven so it waited around for seven so if we do the average waiting time our average waiting time now is four yay hopefully this one's more straightforward than arguing about forking processes and murdering children and all that fun stuff so yeah like i said not not a practical thing to do so if you know the burst times well it's probably optimal at minimizing the average wait time assuming there's no preemption but yeah you don't know the burst times of all the processes maybe you can use the past predict the future throw a i in it do whatever but it also has even if you could like practically do it for like if you had a magic crystal ball or something like that still wouldn't be a great thing to do because that algorithm has a problem called starvation which means that some processes will never execute so you could consider the fact that hey what happens if i have a really long running process but i just have a constant stream of very very short ones well if we just follow the algorithm there's a case where that long running process might never execute and that means it doesn't get fed if you want to think of it that way so it doesn't get any of the resources so you just say it suffers from starvation so if we have scheduling we do not want starvation no one wants starvation generally a bad thing to have but the first week we could do to this so from your comment is if we have preemptions well we could just tweak this a bit and change the algorithm to shortest remaining time first so the idea behind this is if a new process comes in that has a shorter runtime left then we would immediately just switch to that and try and do it as quick as possible so here i'm assuming the minimum execution time is one time unit so i can't interrupt it like half a time unit into it so similar to shortest job first this will also minimize the average waiting time in the preemptible case so how would that look so in this case well at time zero same processes at time zero we only have p1 to run so we would run it for two time units and then whenever p2 arrives then we make a new scheduling decision so now we have to decide between those processes which one has the shortest remaining time left so process one well its burst time is seven and at that point it has executed for two time units so it has five time units remaining and p2 that just arrived well it hasn't executed at all it has four time units so it has the shorter remaining time between the two processes so i would decide to immediately stop p1 from whatever the hell it was doing and switch immediately to p2 then i would run p2 for two time units and then oh no p3 comes in my scheduler has to run again so at this point p1 has five time units remaining p2 has executed for two time units and its total time is four so it has two seconds remaining or two time units i guess and then p3 that just arrived well it only has one time unit to execute so i will just immediately switch to that because now that is my shortest remaining time left so i would switch to p3 execute it and then it would be done done at time five when a new p4 comes in so now at this point i have p1 with five remaining time units p2 with two remaining time units and then that p4 has four remaining time units so i would pick p2 it has two remaining time units at time seven it's done so the next shortest one is p4 so it would execute for its four time units and then finally at time 11 well p1 it finally wraps up and finishes up so now calculating the average waiting time is a bit harder but you can use some math tricks if you don't want to count so like for p1 what you would want to count is like how from the time it arrives to the time it finishes how much time was spent just waiting around not executing so you could just count like one two three four five six seven eight nine or you could use math to your advantage so if you look at the total time it was sitting around on the cpu so the time it finished which is here 16 and the time it arrived so it was around for 16 time units and well since it's finished you know it must have executed for seven so you don't have to count so it's just total time it was around 16 minus the time it was actually running so 16 minus 7 in that case is just nine so you don't have to count in p1's case it's not too bad but p2 switches back and forth well actually p2 is not that bad either it just waited around for one p3 waited for absolutely zero and then p4 well it waited around for two time units because it started it would arrived at five started executing at seven so now in our preemptible case our average waiting time is now three instead of four when we didn't have preemptions so it's slightly better but yeah so far these aren't really used in real scheduling well first come first serve is sometimes used but the first real algorithm that's actually used is something called round robin so at this point we haven't handled fairness at all since it's a trade-off with the other metrics we've just been trying to minimize the average waiting time which is both impractical and suffers from starvation so how round robin works is the operating system divides the execution time into like set time slices some some textbooks and things want to be fancy and call a time slice a quanta go for it if you want and an individual time slice can be called a quantim makes you sound smart but whatever it's a time slice but that's there if you want to call it cool words kind of sounds cooler um so all it does same idea it maintains a fifo queue of processes similar to first come first serve and it will have preemptions so it will preempt the process if it's still running at the end of its quantum or time slice and then just read it to the back of the queue so essentially this is if you have siblings this is sharing with your siblings you get 10 minutes on the nintendo or whatever they go they go back maybe you're an older sibling so you always go first or something like that but idea everyone gets a turn so what are some practical considerations determining that quantum length or the length of the time slice so what happens if my time slice is just gigantic so yeah one of the answers seems like a law wasted time if you have to wait to the end so if the rule here is if you finish before the time slice is up you just immediately switch to another process and restart the time slice so wouldn't waste that time yep yeah just becomes first come first serve so if it's gigantic well it starts off with a fifo queue same as first come first serve if the time slice is longer than any one process takes well suddenly this is first come first serve what about the other problem where you make it really really really small so in some yep yeah they get preempted all the time and if your time slice is anywhere close to how long it takes to context switch so remember when we're switching processes we have to like save a set of registers and restore a set of registers that takes some amount of time if the number of times like if the amount of time you take to just switch is about the same size as like the time slice say they were equal say you took one time unit to switch and then your time slice was one time unit well suddenly you're wasting half your time just not doing any useful work you just have overhead you're just wasting time switching between processes so generally for this you want your like time spent context switching to be like 1% ish or less than 1% so you don't actually notice it so if we set it too high it's first come first serve which sucks if we set it too low while then we're just wasting a bunch of time just due to overhead just switching between the tasks so there's some tradeoff here so we can go over some examples of just generally with these you'll be told here I'll just switch to this oops you'll just be told hey here the here are the processes here are their arrival times and their burst times and then you'll be given the quantum length or the time slice whatever you want to say so we can do this example with a time slice so say it's something like three time units so how I like to organize this is again at the top that's where I have my arrival times and then at the bottom is where I draw my queue so at time zero well if I draw my queue only p1 is available it just arrived so it's the only thing in the queue so I would go ahead and schedule p1 for three time units or until it has done executed but in this case actually let's just write ones instead of writing p all the time so here I process one that executes for its time slice and then after its time slice it gets thrown at the end of the queue but if I were to draw my queue at time two p2 arrives so p2 would go to the front of the queue because there's currently nothing in the queue so p2 is at the head of the queue now at p3 whenever I draw my queue p1 is finished its time slice it still has time remaining so we just throw it at the back of the queue so my queue would look whoa that's an eraser that's not good so my queue would look like p2 and then p1 so here well at time three I would schedule p2 since that's at the front of the queue so I would schedule it for its three time units because it takes four so it's not done yet so now at time four p1 would be the front of the queue so I remove p2 because it's currently running and then p3 comes in so it would go behind p1 so now it would look like that and then at time five we have p4 so it would get thrown to the back of the queue so now my queue looks like p1 p2 p3 p4 now at time six okay well now p2 is done at its time slice so it's going to be thrown at the back of the queue and now p1 is at the head of the queue so it gets to run for its three time units then it gets thrown to the back my queue would look like p3 p4 p2 p1 p3 only runs for one time unit and then it's done so immediately because the process is terminated the kernel is going to know about it and then pick the next process to run in this case we would our queue would be p4 p2 p1 so p4 takes four time units it's got a qualm length of three so it would use its full time slice and then go to the back now we would have p2 p1 p4 so p1 just has one time unit left so we go to two now p1 that's next it's got a one then p4 it's the only one left so now it's done so then we would have to figure out our average waiting so our average waiting time for p4 so p4 if we want to argue about this it was sitting around for the entire 16 time units so it didn't finish until the very end and it started at the very beginning so it was sitting around for 16 time units it took seven to execute so by logic it must have been waiting around for nine time units p2 well it arrived at time two here and it finished at what's that 15 so it finished at 14 so in total p2 so p2 was around for 12 time units we know that it must have executed for four of those so 12 minus four must have been waiting for eight if i don't want to go ahead and actually just count it up oh whoops i screwed up process one sorry process one it finished at 15 not 16 so it's eight whoops sorry about that so yes process one actually finished at 15 not 16 so it was around for 15 time units 15 minus seven that's eight sorry i thought it ended at 16 all right waiting time for process three well process three started at arrived at four it finished at oh whatever the hell this is so it finished at 10 so it was sitting around for six time units executed for one of those or other ways other words it waited around for five or if i wanted to i could count all the boxes that waited for so it waited for one two three four five until it actually executed for its one time unit and then finally with process four it finished at time 16 started at five so it was sitting around for 11 time units its burst time was four so it must have waited around for seven time units so since it's an average divide that all by four and we should get something like seven all right next thing we would want to compute here is our average response time so average so the response time for a process is just the time it arrived through the time it first got time on the cpu so just time it starts till time it first executes so for process one well it arrived at zero started executing at zero so its response time was zero process two well it arrived at time two and then started executing at three so it only waited around for one time unit process three started executing at nine arrived at four so it was waiting around for five time units process four arrived here at time five didn't start executing until time 10 so it was also waiting around for five so if we take that divide by four we also get seven the final metric we generally care about is the number of times we contact switch because right now we're assuming that they are instant so to count the number of contact switches use count how many times we change between processes so whoops that's an eraser again so we change between one and two here then two to one then one to three then three to four then four to two then two to one then one to four so if we just count the number of red lines our number of context switches is equal to seven and whoops sorry response time was not that two point seven five yeah so the question is at time let's point so at time four when p three arrived why didn't it go to the front yes so why p three didn't immediately go to the front is because at time three whenever p one was done well its time slice is done but it just gets re-added to the back of the queue so at time three that's when it was re-added to the queue so by time four it's already been sitting in the queue so but that's a good point there might be the situation where what about if p three actually just arrived at the same time we re-queued it so in that situation you should always prefer like if there's a tie between re-queuing an old task and the new task arriving always put the newly arriving task ahead of the re-queue one reason behind that well you're not going to change the average waiting time at all but you're going to lower the response time for the new arriving task so always just prefer the arriving one in the event of a tie yep so the eight for the waiting time for p one is p one finished at time 15 and it arrived at time zero so in total it was hanging around for 15 time units and it must have been executing for seven of those so 15 minus seven is eight or I could just count the number of blocks it was waiting around for so I could do that in orange so it was waiting around for like one two three not there four five six seven eight but if you don't want to count it like that I usually just use the math version of it because I don't like counting I'm yeah people like hiring lazy programmers because they just do things faster because they're lazy and I like being lazy all right any questions with that I could be mean and say hey do it again but now qualm like one oh guess what you just have to do it over and over again do you want me to do this example or should we yeah no no one really wants to do this okay so on the slides we can go over that um answers are on the slides so don't worry about this fairly straightforward could also ask hey do it again quantum length is now 10 oh guess what that's longer than any process so looks like our first come first serve again so it's exactly the same that we had at the beginning so the round robin performance going to depend on that quantum length and job length so typically round robin has low response good interactivity so like if you're using it for like scheduling something where you are typing and you want to see what you typed immediately generally round robin's pretty good at that as fair allocation the cpu fairly low average waiting time when the job lengths vary but the performance depends on the quantum length too high it becomes first come first serve too low too many context switches again probably want that context switches to be like one percent or less of the total time and also if you simulate this with a whole bunch of different jobs if the jobs have like similar job lengths round robin's actually pretty bad but to summarize all that scheduling it involves some type of trade-off so we looked at a few scheduling algorithms today two are actually used so we saw first come first serve that's the most basic one shortest job first when we were trying to minimize waiting time so that was the non preemptible version where we just start a process and keep on going until it's done then make a new decision then we had shortest remaining time first where hey if a shorter job comes in let's immediately switch to that but in general you won't know how long a job takes and well that'll optimize the lowest waiting time or turnaround time you might see it referred to but generally that's not the be all and end all with scheduling algorithms you want to have fairness round robin optimizes fairness and response time and beginning at next letter we'll see more advanced scheduling to actually tweak this so that's it for the content so i'm gonna in the extra time i will do this fun question that's like an exammy question oh yeah question sorry yeah so the question is there a rule thumb for selecting the time slice size generally rule thumb is set it as low as you can such that the overhead is like one percent or less generally the way you do round robin but round robin typically that's not used for anything that's not like real time or something like that where you need something predictable so we'll see what's actually used in the next lecture but before that i'll just do this in the spare time because i haven't got a round to this so yeah some questions on the exam well you don't have a computer or anything but you should know how processes work so they might ask something like hey this program does it produce the same output every time it's run may it produce different outputs when it's run multiple times why does the program behave like this what the hell is going on this is a fun one so we have if we just gloss at it we have a fork and then it's in a while loop so we like when forks are in wall loops because that makes our life interesting and that's why we get paid money for reading crap like this so if you had something like this and you had to reason about what was going on well we can just step through it and then just give names to a process so let's assume my first process i'll just creatively call it process two i'll give it the process id of two because while process one that's a useful one that's defined so i'll just use the next number i'll call it process two so to keep track of multiple things i generally just write in the margin wherever some process is so process two would just start executing it would create you know nothing's different than a beginner course we create a variable called i assign it the value two so in process two we got an i equal to four so does four equal zero no so we go into this while loop uh crap now our life gets difficult we immediately fork so once we fork we would create a new process called let's do a new color called p three i'll creatively name it and at the time of the fork it is a complete copy of its parent so it would have a variable called i and it would also have the value four the only difference is going to be the return value of the fork so p three so what will fork return what will fork return in process two anyone remember this id of the child in this case i was creative i said it was process three so fork would just return a three right and in the child fork