Video summary
The video serves as a comprehensive final review for the ECE344 Operating Systems course, focusing primarily on concurrency challenges such as deadlock prevention and process synchronization. The instructor begins by analyzing a locking scenario involving two threads that acquire multiple mutexes in a specific order; he explains that no deadlock occurs because there is no circular wait condition, as Thread 1 always acquires M2 before M1, while Thread 2 acquires M1 before M3. To illustrate how to intentionally create a deadlock using three threads and only two of the available mutexes (M2 and M3), he demonstrates that reversing the lock order in one thread creates a circular dependency where each process holds a resource another needs, causing all progress to halt. He further clarifies that avoiding such issues requires maintaining a consistent global ordering for acquiring locks across all threads to prevent cycles.
The discussion then shifts to using semaphores with center forces to enforce specific execution orders among four different functions without altering the internal code structure of individual threads. The instructor details how to initialize semaphore values and strategically place wait and post calls to ensure that certain functions execute only after their prerequisites are complete, such as waiting for one function to finish before allowing dependent ones to run. He emphasizes that if a dependency requires multiple conditions—like Function 5 needing both Function 2 and Function 3 to be done—one must use separate semaphores or wait on the same semaphore twice so that all necessary threads signal completion before the next thread proceeds, effectively managing parallelism while respecting strict sequential constraints between tasks.
Finally, the review covers process management through a code example involving fork calls within a loop, tracing how parent and child processes interact to create new PIDs and manage resource lifecycles like zombies and orphans. The instructor walks through the execution flow where a parent waits on its direct children while siblings run concurrently, explaining that a newly created child becomes a zombie briefly until it is reaped by the kernel after termination but before the parent exits. He also addresses file system mechanics, calculating how many blocks are needed to store files based on block sizes and pointer limits in inode structures, determining internal fragmentation losses when data does not fill an entire block perfectly, and analyzing hard link counts for directories to understand their structural relationships within a Unix-like filesystem hierarchy.
Read the full video transcript
[Music]
oh God
all righty welcome back to operating
systems for the last and final time
so this one it's all just review so
before I start just so you know your
final your functions you're given
exactly the same as 2024 winter ECE
353 you will get all these functions so
you get listed your exams Clos book yada
yada yada all right what do we want to
do any suggestions
or so locking problem locking problems
all right good which locking any locking
problem in particular one from this exam
the previous
exam all right well let's see
so I put a green check mark every next
to Everything I did in section one so
you can look at that recording so so you
can see those questions if you
want um locking want to just do this
question sure yes all right
so let's make this a bit bigger
whoops so locking examine the provided
code where two threads executing thread
one or T1 and T2 so we got two threads
we got a T1 oh it's this
room T oh I hate this
room all right we got T1
running we got T2 running they would
both start running the beginning of the
function all right says each acquire and
release multiple mutexes we have three
mutexes in total M1 M2 and M3
first part says analyze if the two
threads executing functions T1 and T2 in
the provided code result in a deadlock
explain the reasoning behind whether or
not a deadlock is possible or not in
this scenario so T1 will acquire M2 then
M1 and then thread 2 will acquire M1
then
M3 do we have a Deadlock
yeah no we do not no we do not that is
correct why because M1 will eventually
unlock at the end of
T2 won be able to so if thread one
thread two executes first gets m one I
could still run well do the First Column
T1 M2 is not shared between them and
then well if this one has M1 this one
will try and get M thread one will try
and get M1 but it's fine thread two can
make progress get M3 do whatever it
needs to do carry
on basically if I want to do it
succinctly if I look at the dependencies
there's no circular weight here right I
always get M2 before M1 and I always get
M1 before M3 so
there is no
circular so basically the only two
things we need to be concerned about for
deadlocking two things we can do
anything about is hold and weight there
is hold and weight here but we need all
four conditions for a deadlock one being
circular weight in this there is no
circular weight because we have
M2 we need to get M2 before before we
can get M1 and we need to get M1 before
we can get M3 so those are straight
lines that is not a circular weight yeah
what would it be
like then we might deadlock no I what
sample
of what's an example of circular weight
well luckily
enough says assuming in addition of a
third thread thread three executing
function thread 3
devise a sequence of lock
calls
here involving only mx's M2 and M3 that
could lead to a deadlock so we probably
want hold and weight to happen how would
I lock
M1 or sorry M2 and M3 that would result
in a deadlock
here so going here
these are the two functions I have I'll
just write it here just for sanity I
guess so say we have thread
three and I really dislike whatever
weird electrical crap happens in this
room all right so we want to use M2 and
M3 in thread 3 such that it deadlocks
so what sequence do we
want yeah probably M3 M2 probably M3 M2
basically we want to create this line
right to make our circular dependency so
that would mean I have to have M3 before
I try to get M2 so if I add it here I
could just lock
M3 lock
M2 and then it says describe this
sequence and explain how it results in a
deadlock combined with thread one and
thread 2 so how it could work now is
since I have thread
three what could happen is thread one
acquires mutex 2 So This Thread now has
M whoops let's do it in Orange so this
one has M2 and then we contact switch to
a another thread maybe we contact switch
to thread two it acquires M1 so now it
has the M1 mutex and now we contact
switch over to thread
three it gets the M3 mutex and now none
of them can make progress anymore right
m one or thread one can't make any
progress because it's trying to get M1
which is held by thread 2 thread 2 can't
make any progress cuz it's trying to get
M3 which is held by thread three and
thread 3 can't make any progress because
it's trying to get M2 which is held by
thread one so we deadlocked so everyone
okay with that so that was a very quick
18 marks yeah does that imply that if we
want to
add we need to add
another
no so we can avoid the circular weight
without like
three so in thread three if I flipped
those two lock calls around would I
deadlock no right because now I don't
have a circular weight anymore so if I
flip these two around right I do oops I
took too
much if I flip these two around there's
no situation where I can deadlock
because now I don't have this
right I essentially created this
line which is still ordered so I still
always acquire things in the same order
so I have no problems whatsoever so
there's only one sequence that will
deadlock and I was nice saying well I
constrain the problem a lot all right so
everyone okay with the Locking question
all right what's
next so we
can the hard threat
question thread let's look at the other
exam for
fun oh yeah we do a center Force
question Center Force
question uh so let's start it on the
other
exam so something like
this all right center Force question so
this is from the 2024 win or winter yeah
all right so it says you are given four
threads that get properly created and
run you want to ensure ordering between
them you decide to use Center Force to
accomplish this task recall that Center
Force after initialization use post to
increment the value and weit to
decrement the value waiting until the
value is greater than zero assume no
errors occur so you never need to check
return values consider the following
code so I get three center fors created
for us that don't have any values which
hinting that I probably need to set them
at some point and then I have thread one
to thread four all calling some
functions and then some comments that
say that
say F2 should only run after F1
completes F3 should only run after F1
completes then seems like F4 can run
whenever well I mean we have this
dependency So within a thread it would
call F3 before F4 but that's my
dependency with within a thread and then
it says thread 4 F5 should only run
after F2 and F3 complete F6 should only
run after F4
completes so first part which is most of
the question says fill in the initial
values for each Center four and insert a
post and weight calls in a way that
ensures ordering in the comments you
cannot change the ordering of the F
calls and they always execute in the
order written within a thread write your
answers on this page
oops all right
so basically with this for every kind
of ordering you want to ensure just use
a different Center for so we will start
off with the ordering that waits for F1
to complete so let's use Center for
one that should only wait until after F1
completes so easiest thing to do with
the center fors I find is to first place
my weight call
so what threads do I need to wait for F1
to complete none none what a well yeah
some so thread two and thread three need
to wait to for F1 to
complete1 no so I need to place a weight
call where do I need to place one weight
yeah F2 above F2 so I need a weight here
oh curse this
room so
wait with sem
one do I need another weight or am I
done
yeah3 above F3 so weight on the same
Center four then next thing I like to do
do I need any more weight calls anything
else need to wait for F1 to
complete nope so easiest thing to do now
is to set the initial value so the
initial value you can think about is how
many threads should be able to pass
weight without any other thing calling
post so what should the initial value be
in this
case zero right we don't want either
thread one or thread three to start
executing pass their weight For Thread
one does something so we need to
initialize it to zero and then for
posting well we post after F1 right so
we post after
F1 that's our dependency so we post sem1
here if I post sem one that would
increase it from like 0 to one that
would only let one pass right yeah so
what exactly does the initial value do
so the initial value if I set to zero
right that means that if thread 2
happens to execute first since the
initial value is zero it's not going to
make it past this call it's going to get
blocked right until someone increments
it and similarly for this if the initial
value was say
two then both the threads could just
pass by weight no
problem so that's why it's good to think
about it it's like hey without any post
that's how many threads can pass by a
weight because first one go from two to
one next one would go one to zero no
problem so that's why we have our
initial value at zero all right so then
we need to post after we call
F1 is that it just one
post I need another post right each post
just increments it so I'll have two
threads waiting for it so I need to post
twice in order to resolve it so I need
wow holy
I do not like this whatever the hell
happens in this
room all right so I need to
post sem one here so that is that first
condition right so now both thread two
and thread three will run after and only
after F1 is done so that's my first
condition so that's
check don't try and reuse center fours
or anything crazy like that so that
means I could put a little check here
this is a check this is a check now next
one I want to only run after
f 2 and F3
completes yeah we just do like have two
weights like two in before uh f25 and we
one
post
end yeah so how I can do this one is it
needs to run after F1 and F2 completes
so I'll need at least a weight here and
let's say I use
sem2
so that's probably good I need at least
a weight there the initial value should
probably be zero again because I don't
want this to run until someone else
posts
then well now I have to insert my post
calls so I want to make sure it runs
only after F2 and F3 completes so
they're in different threads so I would
need to post
here after F2 and post after
F3 so how I have it written right now
since I only wait once well either of
those threads would be able to post
increment it and then I could call F5
when either F2 or F3 Complete because I
don't know which one will go first if I
want to wait for both of them well I
just need to insert another weight call
here so now I wait twice each of each
other thread posts once so they have to
both post before I can get by to weights
so is that okay so that should check
that one
and then with the last one so I should
only run after F4 complete so again I
would just do a weight on sem 3 here
initial value should be zero and then I
could just do a post of
sem3 right after doing F4 so are we okay
with
that all right then the last one says
for each function State what functions
could run in parallel with it not
including itself
write one if it can only run by itself
so should be able to look at this and
see
F1 in this case
F1 needs to complete before F2 or
F3
wow before F1 or
F3 complete and
then
well F3 has to wait for F4 because it's
in the same
thread then for F5 it can only run after
both of these complete
right so it only runs after both F3 and
F2 complete so this is F5
and then
F5 or F6 can only run after F5 and also
after
F4 so that's all of our
dependencies all at the same time so if
I'm running F1 Well it can't run at the
same time as F2 or
F3 so can't run anything else that's
dependent on either of those so F1 can
only run by itself in parallel right
yeah in what case would we
not zero so like our producer consumer
example where we wanted like we had a
buffer size right and we
wanted at most buffer size threads
running at once that's when we set like
the initial value to uh the buffer size
so that we can have 10 threads go all at
once but they're capped at that I can't
have any more that's generally you'll
usually see it initialized at zero but
sometimes you might not if there's some
other weird limit or something like
that all right so for this can only have
F1 running by itself F2 could actually
run at the same time of F3 right so F2
here while this is running we know F1 is
complete so we could run it at the same
time in parallel with F3 from thread
three also there's no dependency it
could also run at the same time time is
F4
so and that's it and then I shouldn't
really have to go on the you can double
check the answers with the other one
I'll try and move on to something else
yeah oh if you have a question that's
fine yeah next one oh you want next one
or this one are we done with this one if
we want to be yeah we can be done with
this yeah can do this question
12 I was wondering
okay so I I'll just do this question
from the start to make sure everyone's
on the same page all right so we got
processes says consider the following
code assume all system calls are always
successful and we're running under
process ID 100
so we have process ID 100 running
whoops so process IDE 100 so we have
this Loop that
executes two times so this will be fun
so we go through the loop once we
have this will be really fun in this
room so we go into the loop process 100
creates a variable called I it's equal
to
zero then it calls
Fork so it creates a
process process
101 it has it's an exact clone so it has
an i with the value of one and the only
differentiation between them will be the
return value of fork in the parent R
will be equal to 101 in the child R will
be equal to zero so then let's
say for argument sake process 10 100
executes next So
currently I is equal to zero and R is
greater than zero so it's going to get
stuck here in the weight so it's going
to wait for it child to
perish so only thing I can do is run
this child which is 101 doesn't look
like 101 but it
is so no other choice it has to call
this print function it would
print uh it' print P
ID equal
101 r
zero I is equal to zero
right then it would go up here it's for
Loop increments its I it's I well R went
out of scope so R is dead I got
incremented to
one sorry I deleted that process 101
oh wait no I
didn't so now it calls Fork again so
process
100 we'll draw the family tree up here
process 100 created 101 101 is about to
create
102
so now we create process
102 from the
fork I'll try and put them on the same
line
so it's going to be exact copy of the
parent so it will have an i equal to one
only difference is going to be the
return value of fork so in 102 Rett is
going to be equal to zero process 101 R
is going to be equal to
102 now
I don't know which one we'll execute
next I is not equal to zero so that if
statement will always fail don't know
which one we'll execute next so for
argument sake oh
no oh I really hate this room so don't
know which one we'll execute next could
be 101 again that would print P ID 101
R
102 and then I is one or I could have P
102 R is zero I equal 1
so either of them will go ahead come up
here nothing is dead yet let's say p 101
happens first
first both of their RS go out of scope
it would increase I oh well thankfully
mercifully it would increase I equal to
two then it would drop out of the for
Loop and it can exit right so these are
now mercifully both able to exit I don't
know which one's going to
exit
102 could die first
101 could doesn't matter but as soon as
101 dies then process of 100 will be
able to return right so then it will be
able to pass by the weight because it
would wait on Pro only its direct child
which was
101 so it will then always
print P
ID
P equals 100 r was
101 I is equal to
0 and now process of 101 or 100 will
come up
here I will
go so red will go out of scope I will
increase to one and then it'll Fork
again create a new process probably 103
then
103
mercifully it'll have an
I it'll equal
one and then the only difference would
be the return value of fork so in
process 100 it would return 103 in this
process it would return zero and this is
the last time they're both going to run
they'll both exit eventually all right
so everyone keep on track with all that
crap so first question then how many new
processes get
created three right we have that little
tree at the top so we assume process 100
always exists so we created 101 102 and
103 so next was draw a tree of the
parent child relationship showing the
process IDs they would likely get y win
thankfully we already did
that so next using the pids in your tree
provide a possible sequence of outputs
of the code
so we're going to see
what so these will eventually print too
so this will
print
p102 I equals
1 or sorry whoops these are already dead
sorry
is these two that will
print so we'll get P
idal 100 I equal 1 R is 103 and the
other one we get p
103 I = 1 red = 0 and then we'd get this
line first
always then this line then this line and
this line that's a possible sequence of
orders you get one two 3
4 five six so we get six lines in total
that's one possible
sequence all right oh
yeah 100 will wait for 103 no I is not
equal to
zero yeah I is not equal to zero so
process 100 this will only happen once
process 100 will only wait for process
101 all right so there's a possible
sequence show an impossible set of
outputs for the provided code ensure
that all the sequence prints within the
same process remains consistent but the
overall sequence cannot be produced by
the code so basically just swap these
two
around so so what that's saying is like
I'll always see process 101 r i equals 0
and then I equal 1 and it won't somehow
reverse within the same process it's
just between processes so process
101 100
oops sorry this process 100 we'll never
print first before 101 because it weigh
for it so that can't
happen all right now we can finally go
to the other one so for each
direct uh child process of a 100 which
in our case the way we wrote it was 101
and 103 State whether it will always
maybe or never be a zombie or an orphan
one answer for each briefly describe
your reasoning so for process
101 can it be a zombie yes
and this is always maybe orever is there
a chance even for a brief second that it
is a
zombie yes right so why well what might
happen is as soon as process 100 forks
the first time we just immediately start
executing that new process 101 right
101 can execute till it's completed it
terminates it's technically a
zombie and then it will get cleaned up
so it will never be a zombie by the time
process 100 Exits but it will be a
zombie for a little bit until it gets
cleaned up I think I also if you
explained it well enough or if I thought
what you meant I also gave it full marks
if you always said never but you had to
be clear that it
never if you assume process a 100 Exits
was that your question for the other the
other one okay so that one's okay so
let's finish this one
maybe
briefly all right orphan can't be an
orphan
no or never because it gets weighted on
right so the parent will never outlive
it because it's always get always gets
waited on and we don't assume anything
weird with uh things dying all right for
process
103 can it be a zombie
yes and is it
always always maybe or
Never So in this case process 103
no one Waits on it
um and it can terminate before anything
else right so it could definitely maybe
be a zombie I think that should be the
[Music]
answer yeah
so maybe
May termin
before
any and the only thing that's going to
wait on it is a knit whenever it gets
reparented because nothing that I wrote
explicitly waits for it all right the
next one is Orphan so can it ever be an
orphan so an orphan if we go back to our
diagram was it
103 so orphan means means it's possible
that process 100 Exits before me right
so in this case is it possible process
100 executes before me so the way I left
this off is whatever process 103 was
created right so it could be the case
where process 103 is
created all it's going to do is print
and exit but it doesn't have to execute
next
right right
103 doesn't get weighted on by anyone
yeah
isie
dep sequence and it doesn't
matter
uh yeah I think I also took it gets a
bit Fini with the zombie cuz technically
you know gets reparented it it's going
to get terminated sometime it might be a
zombie for a little bit even if it's
like for Loop waiting so I also accepted
always for that one so always was fine
for that one and then for
Orphan possible that it could be an
orphan right might be an orphan because
process 100 doesn't wait on 103 or
anything process 100 could just print
its line terminate and then as soon as
it's terminate it just orphan
103 right could be the other way around
where process
103 executes first it terminates
so doesn't have to be an
orphan although shouldn't that be
always actually it should be always an
orphan right no because you can
exit oh yeah it could still be running
right the opposite could be true so
100 or we could exit
before yeah but if we exit before we're
done but it doesn't wait on us right So
eventually it's going to exit and you're
going to get orphaned and you're going
to be a zombie
orphan so I think technically the answer
this answer I think the answer given was
maybe but I think it should always be
always for
Orphan so if um
so
103 will always either be an orphan
that's still running or a zombie
orphan so I believe the solution's wrong
it should always be an orphan because we
never wait on it
right so should always be an orphan I
believe the solutions wrong all right
next one yeah file systems file
systems uh which
one this file systems
one
huh all right file systems how much time
we got 14 minutes so this file systems
assume a file with a block size of this
so I'll just write powers of two because
I like to 4 by to the power of two damn
this
room and 128 byte iodes which is 2 to
the
7 yeah 2 the
7 yeah iodes have 12 direct pointers one
indirect pointer one double indirect and
one triple indirect analyze the output
of
Ls all the stupid arguments the output
of the format includes I node numbers in
the First Column so these are the iodes
so it's telling you just in case you
forgot the other one should be somewhat
self-explanatory so this
column is the number of hard
links
then uh user that owns it group that
owns it although doesn't really matter
the size and then the modification date
which we which we cannot Forge
right didn't show you how to do that do
not write your
whoever and then finally is the
names so first question says when
editing file C what iodes contents get
modified so the file C is a Sim link so
it's symbolic link so it's basically
named to a name so if we try and open it
it won't open inode 22 because that
stores essentially B so we're treating
iodes as just name to a name so to find
the iode it will then refer to the name
b and then look at that iode which is
iode 20 so iode 20's contents get
modified everyone okay with that all
right next one is it possible to store
all the I noes associated with this
directory on a single block provide an
explanation supporting your conclusion
so we can figure out how many iodes we
can fit on a block right so the number
of iodes we could fit on a block is just
the size of a block so 2 to the 16 or 2
the 12 sorry divided by the number of
the size of the iode that's the number
of
iodes
per
block which thankfully I already wrote
everything out in powers of two so
that's just 2 5 which is equal to just
32 so that means I could fit
IES 1 to 32 on a single block right if I
look at my iode numbers here they all
fall within that range 1 to 32 so the
answer is
yes it
fits all
iodes wow that's hopefully You' have
better writing than me fit between
1 to
32 and my writing is usually better than
this but this room's cursed I have to
write like a
goblin I don't know why I thought that
all right next one determine the number
of data blocks that file e will occupy
so file
e its size is
5,000 so can I fit 5,000 bytes on a
single block no how many blocks do I
need two right I need at least two so
one doesn't fit but two fits they're
each
4, uh 96 so if I have two blocks that's
like
8,192 so the answer here is just two
then it says calculate the number of
bytes lost due to internal fragmentation
for block for file e so two blocks is
8,000 95 bytes or you could just say 2 *
2 to 12 again I don't really care if you
have a calculator as long as you have
the right numbers I don't care so it's
just that minus 5000
right so could write either if you just
stop there that's fine if you have your
blanket calculator with you that makes
you feel safe you can do the fal
calculation if you want which is what
3,900
192 easy calculation if you don't have
your calculator but you could use it if
you want doesn't matter so that okay
with
everyone all right next one determine
the number of directories contained
within the Parent Directory represented
by iode 19 describe the name of all the
directories linked to this iode
so
that's IOD 19 is a directory and it's
the parent of this directory
right
so well I know that let's give it a name
so let's just say oh don't know let's
call it
directory
directory D1 I don't know some mystery
directory right so in some
place in directory d1's parent it will
have an entry that has like the
name
directory
D1 19 right so it will have a name in
some other directory maybe the root
directory maybe I don't know some other
directory who knows and then inside that
directory right it will also have a
name uh period that points to itself
right and then in every other directory
it will have it will be the parent of
that so everything else whoops
nope so in everything else in any other
directory like I don't know uh call
it
C1 it'll have a dot dot to9 for example
that one the one we're currently looking
at and then every other par or every
other directory inside of it will also
increase the link by one because it will
have a dot dot that will refer to it so
every directory by default is going to
have a link of two because it will have
a DOT referring to itself also it will
have a name somewhere else that refers
to it so an empty directory will have
two and then every other directory in
that directory will increase the count
by one because it will have a dot dot
that points to it so another way you can
think of it is just like that so the
total link is
five well it has the default two and
then for every other directory inside of
it it will have a dot dot dot dot also
referring to it so it would have three
directories in total within it so I
don't know what their names are so C1 C2
C3 whatever so that okay with everyone
sweet all right next one what occurs
when you execute RMB is it possible for
the file to be deleted so let's see
let's get rid of some of this
crap so if we
remove remove file
B what
happens yeah it just removes the name
from the directory so file B refers to
iode 20 well file a also refers to iode
20 right so if I remove B all it does is
remove this entry in the
directory and now it's no longer
pointing to iode 20 but file a still is
so it's just going to decrease the count
here to one so that file is not actually
going to get deleted because there's
still a name that refers to that iode
so is it possible this file to be so
remove
name and then not
deleted all right assuming B is removed
what is the impact on C and the
associated I noes so B is now gone does
anything about C change no it just looks
a bit red if you had color in your
terminal because C is stored on Ino 20
its contents is point to B doesn't
matter if the name b doesn't exist
anymore you won't affect anything about
C so the answer is
nothing if file a is expanded to use 13
data blocks are only 13 data blocks
required in total for its contents
explain the storage mechanism
so says we have an
IOD and then we have in total 12 direct
pointers
one single indirect one
double and one triple so if this file
uses 13 data
blocks all the way to
13 can I point to them all using just
the I node no no I could point to 12 of
them right so I could point to 12 da da
du say this is up to
12 now how do I point to that last pesky
block
13 indirect yeah I need a single
indirect block right so I need to create
a block
here that's an indirect block so it's
just a block full of pointers in this
case I only need one pointer from it to
point to block 13 so in total I would
require 14 blocks right
so 13
for
data and then one for
single
indirect everyone okay with
that all right next one how many total
data blocks are needed if the file grows
to 140 blocks strange uh explain the
arrangements in the rational behind it
so we got to do a bit of math so we need
to figure out here the number of
pointers per
block so we have our block size from
before which is 2 to the 12 and we have
our pointer size which is 2 to the two
so 2 the 12 I hate this
room so 2 12 / 2 2 that's 2 10
so that is also equal to
1,24 so that's how many pointers I can
fit on a block if my file contains all
of that then I can refer to 12
direct I have my one single
indirect and then that refers to
1,24 so in
total that
is40
right data
blocks or can I not do math oh no I
cannot do
math so that's why maybe you want a
calculator all right so that's
136 right so I still have four more
blocks I need to store so my single
indirect one is full so I need to do a
double indirect one so for double
indirect I
need one
double and this case it would need to
point to one single so a new single one
single which would just have in this
case what is that four entries that
point to other blocks so four data
blocks so in total my data blocks here
are 1,40 plus I needed one block for the
single indirect one block for the double
and one block for a single referred to
by the double so in total I needed, 143
right hopefully that's right all right
so now we are out of time so thank you
for taking this class hopefully you
enjoyed it good luck on Saturday and
remember I'm pulling for you we're all
in this together