 Welcome back to three five three. Yeah, this is our last lecture before reading week and before the midterm. That's scary So Well, I expected like no one to show up today. So this is nice. So What do you want to do today? Want to hear me? Lather on review the course or do like the latest midterm or what? You want me to blather on about the course real quick? Yep View the course. Okay, so I can give some thoughts on the latest midterm too. So All right, so we can speedrun the course then and then go into stuff so started off intro lecture three major topics in this course and How they applied operating system. So we've only really touched on the first two so virtualization and concurrency so virtualization well Processes kind of virtualize the CPU. Also, they virtualize memory now We went on talked about all how virtual memories implemented all that fun stuff And then we talked about concurrency. So that was like, you know, switching back and forth between processes which the schedulers responsible for and Touched on it briefly with threads threads get switched back and forth to only differences threads share memory so important kernel interfaces, that's what operated between CPU Boundaries, so there there was like kernel mode and user mode. So We kind of we're talking about like what the hell even is an operating system and we're like, oh, well It's the software that interacts with the hardware that supports your application and it's like, okay Well the definition of what you might consider an offering system depends on what application you run So we learned that hey There is like this special hardware mode on your CPU called kernel mode That actually lets you have access to more instructions that interact directly with the hardware and that's you know How the operating system provides some security? The kernel is the only thing that runs in this kernel mode and it is definitely part of the operating system So the only way to actually ask the kernel to do things for you Aside from tricks with virtual memory that we kind of discovered is by doing system calls So that's the interface between kernel mode user mode. Basically doesn't interrupt Kernel handles it all that fun stuff Every program has to use this interface no matter what so we can S trace it all that fun stuff We saw the file format. Don't worry about that for the midterm not gonna ask you about that That was just so you could see what the operating system has to deal with And then the different kernel architectures somewhat important like more Monolithic so just put most of the stuff in kernel mode if you have a micro kernel then you try and shove as Much stuff out of kernel mode as you can idea behind that is well less code less bugs You hopefully Don't have any security issues with that because if someone can control your kernel They can control your whole system. You don't have security anymore. Yeah Specifics as to how the kernel interacts with the hardware as long as you know, it's just like it's essentially just an interrupt That's fine But remember I showed like that big trampoline thing and like the tricks you would have to do Don't really have to worry about that that much. That's basically just an interrupt that the kernel handles All right, then we saw libraries so you know most of the stuff we don't just use system calls all the time We have to use some type of library at least the standard C library we just kind of looked at dynamic libraries compared them to static libraries and How to manipulate the dynamic loader don't have to know that for the exam That was just fun little tricks that you might actually use in your career if you would need to you Go ahead and try and debug or like instrument a program or something like that Saw some examples of like hey if you define a library Well that corresponds to a specific ABI and if you change it you can change it without changing the API and really really bad things happen So if you write code that other people are going to use you have to be really really really really careful about it And essentially whenever you write something you're now no longer allowed to change it from here until Eternity or when you give up supporting your software, so like That's like the major reason why Windows has like a bunch of very very odd things. It does because if they made that decision in the 80s Guess what they still have to support it. They made that decision in the 90s They have to support that and like weird stuff happen in the 90s Wait, you guys are born before we 90s was before your time Yikes. All right, great Yikes, okay, so Unix so we saw how to create processes, so that's very important and in Unix there's always a parent-child relationship between Processes so like there's a direct you have to create a process you create them using fork We did lots of forking in this course So after the fork remember both processes exactly the same Except for the value for PID ID that's returned from fork they should be exact clones of each other and If you have got most of the way through lab 3 or at least the first part well you know a lot of it is accomplished by doing copying the page tables making copies of stuff and after that they can be independent one shouldn't affect the other and And then we talked about the scheduler. That's the thing that decides when to work run processes For us whenever we're like using them After we fork we have two processes in general. We have no idea which one is going to run So then we were talking about like your responsibility also very very important So the operating system has that parent-child relationship We talked about zombie processes orphan processes So you should be able to identify them figure out how I screwed up if I give you some code You should be of say hey Do we create zombie processes? Do we create orphan processes? Probably how would I fix that or what am I missing and that has to do with like oh I should probably you know call wait and take care of my children unless I Want them to outlive me in which case they become an orphan They might probably get adopted by a knit or a sub reaper because we love weird names All right, then we saw IPC read write very important It's basically what printf does anyways Sob redirect ring some file descriptors for communication using pipes So we created a pipe good stuff to know signals Signals kind of a pain in the ass. I might ask you a short question about that But for your Sandy and mine, I probably wouldn't do a long question on signals because they're kind of a bad idea They're kind of like interrupts for user processes Colonel has to like handle three types of hardware interrupts The three types of hardware interrupts not that important for us But if you have to implement this might be good to know them, but for the exam don't worry about it All right any questions so far am I going too fast or all right All right, so then we saw oh, yeah sorry the three types of interrupts they are Like actual hardware interrupts like faults and stuff like that and then like user requested ones So like if you trigger your own interrupt or if you like have an illegal instruction or something like that So those are all types of different hardware interrupts and they're Given names. I can't remember off the top of my head, but those are basically the three categories But don't really need to know them all we really knows that well signals user interrupts and The Colonel might get something like a divide by zero and then signal your process that you did something silly and You get you it essentially propagates that interrupt up to you All right, then we saw scheduling looked at a few different algorithms. It was a fairly fairly boring straightforward lecture So soft first come first serve aka the fast food restaurant algorithm or like the basic thing you would ever think of Then we did shortest job first to reduce average waiting time Then shortest remaining time first where we interrupt preemption so we could just Before we were just letting a process run until it was complete Then we're like hey, we can just interrupt it. We can take the CPU away from it if a shorter job comes in and If we do that, that's the lowest waiting time or turnaround time, but With scheduling processes, you don't really care about just minimizing the average waiting time You want fairness you want good response time, especially if it's a process a user is actually using if You opened a process and you had to wait. I don't know until your Until you're finished using discord to like use your web browser that would be really stupid So probably want to be able to interact with them switch back and forth between them So then we saw shortest remaining time first and then we saw the first one That's actually somewhat useful is round Robin to try and optimize fairness and response time Probably get a boring question about this as like a freebie for the exam So you'll see going through all mine. I basically always ask one just to make sure that I don't know you've shown up to the course So they might have a little tweak where I add something from this lecture So like more solutions more issues so we could introduce priority Have that priority inheritance issue where if a high priority process is depending on a low priority process Well, then that low priority process should actually be treated as a high priority process in order for that high priority process To actually run as a high priority process So solution was that just if some process is waiting on another you just inherit the highest priority After that dependency is broken You can reset it back to default Other things you could do is like you could try and categorize the processes between the ones you care about Interactivity ones you don't really care about that much So you might try and just do round Robin for the interactive ones and first come first serve for the rest or Just different algorithms completely Then we talked about you know what we would do if we had multiple cores We had to schedule first silly thing to do is just Do the same old algorithm But just throw your processes on CPUs as long as you have CPUs available then we were like oh well That's probably slow because there's only one thing making a decision. I can't make that decision in parallel So we could just have each CPU core keep track of its own schedule But then they might be imbalanced and the solution to that is like to switch a process back and forth between CPUs called work stealing All that fun stuff Then we finally got into like oh real time. So real time Needs predictability needs to be like things have a definite deadline that you have to meet and On Linux too complicated. You don't have enough control over the system So it does soft real time So it just uses like round Robin or first come first serve and the idea behind that as it meets its Deadlines in practice because it doesn't really do anything that complicated for real Like real real times that probably be just like an embedded Processor that's only running one application at a time and you know exactly how many applications you're running There would be simpler hardware and they would have a simpler scheduling algorithm Then we finally saw the completely fair scheduling that tries to model that idea of fairness tries to like keep track of how long each process is running for tries to even them all out in terms of total CPU usage and But that's basically all it does and that's what Linux uses to this day All right, then we got on to the fun topics of page tables So page tables They are the mechanism so we can translate virtual addresses to physical addresses so the MMU is the hardware that actually uses the page tables and the kernel as We should kind of know by now The kernel is the thing that manages the page table and then MMU is the hardware on your CPU That actually uses the hardware to or uses the page tables to do the translations So we saw okay Well, we can just do like we can do big blocks of memory and only do Translations for blocks of memory at a time We called them pages They're 4096 bytes Usually is the default and we saw okay Well, if we just had a large table which was basically an array that kept track of all the translations That would be really wasteful even on 32-bit machines But especially for like 64-bit machines that used a 39-bit virtual address They were like one gigabyte. So not something we would want to do so we We kind of simplified things we got like We thought of multi-level page tables and we can simplify the kernel memory allocation because the kernel only allocates pages And one page is just as good as any other page. So it can just use a free list This is where link lists come in so we can rejoice So link list just link list of free pages to get a new one front of the list when you free it Just goes back on the list so Then we were like, okay Well gigabyte page tables aren't going to work So we do multi-level page tables because we would notice that most processes Aside from web browsers don't use all of your memory. They don't need that many translations So if I have a multi-level page table where each of my page tables fits exactly on a page Well, I only need three of them to translate a one address instead of just one giant table and then We went back to our goal of hey, this should also be fast because well If I'm trying to access like physical memory, that's just one One access but if I'm using a page table I have to do a lookup for every level of page table and then figure out that Physical address and then do the final access so it becomes really really really slow Every additional level of page table we add just makes this process slower So that's why we don't like use a 64-bit virtual address We try and keep it as small as we need to so we can actually just run all of our programs normally So the solution to speed it up was to use a TLB or translation look aside buffer To speed up memory accesses basically access a cache So it just essentially saves any recent lookups so that it can just look them up fast without going at Going and stepping through all the page tables over and over again And TLB you should know what that means so Last year for the midterm People like asked me the question while the exam was happening like what is a TLB you never said what it was I was like So I can't answer those questions during the exam. So please know what a TLB is yeah a Translation look aside buffer I can I can answer it now. So it's basically that cache Which is why things go fast And remember there are all the things too with like If we're switching between processes We need to like delete the TLB or flush the TLB so that well when we switch to the new process That might use the same virtual address We don't get the same physical address because that would be bad Because I might be able to access another Processes memory and I shouldn't be allowed to do that But yeah, ask me questions like before the exam Don't ask me these things during the exam because unfortunately I cannot help you so Then we finally wrapped up with threads they enable concurrency or more explicit concurrency I guess so we kind of related them to processes So threads were just lighter weight. They share memory by default. They are all in the same address space and Each process can have multiple threads just one at the start There's no concept of orphan threads or anything like that But the concept of zombie threads exists, but there's a few other different things with threads like If we have default thread that's joinable same rules as when we had processes We would have to wait on our thread which is in this case called p thread join we can terminate a thread by calling p thread exit like we can terminate a process with exit and Unlike processes if we don't need the return value We can make them detached and that means that as soon as that process or sorry soon as that thread terminates Cleans up false resources. It will never be a zombie thread Otherwise if it's joinable and it terminates and we haven't joined it yet. It would be a zombie thread all right so both processes and kernel threads so Enable paralyzation so process can have multiple kernel threads most implementation. It's like one-to-one so P threads one p thread is equal to one kernel thread But there's other different mapping so we could have one like p thread or let's call them a a what thread one what thread can Correspond to a single kernel thread Oh, but we might have multiple what threads that all actually correspond to the same Kernel thread and that would be many to one the kernel doesn't know about them So if any of our user threads blocks the whole process gets blocked because the kernel doesn't know about them So we also saw more fun stuff. So the operating system has to manage what happens during a fork so if a we have a process with multiple threads and We call fork the rule is that that a new process will get created With the single thread and that thread will be a copy of whoever called fork So now we had synchronization Synchronization issues and I mercifully cut us off there before the midterm so Synchronization issues that's where all the hard bugs are like the things that live in the kernel for like seven years before someone figures Out how to fix them. So Mercifully we get to save that for after the midterm Yeah, yeah, yeah, so Two's yeah, I'll tell you the fix after the midterm not immediately after cuz well, I can Yeah, so well, even though if I tell you the fix it doesn't mean you will always do the fix So you might miss it that thing could exist for a long time Especially when we saw that kind of work most of the time especially with smaller numbers All right, that was quick. All right, any other quick questions about that like whirlwind tour the course More or less good All right, so now we have oh, yeah Oh, I need to update that yeah, cuz the vote was like big closed books So I'll just do close book for it and I'll write a page of like functions and stuff like that. Yeah Yeah, I can give you the page Sure, I Have to make it first though. So I'll make a page soon sooner rather than later All right Yeah, I forgot to change that. Yes. All right any other quick quest any other questions. Yeah, sorry so You don't need to know like the details of the lab because lab 3 is not due until after the midterm But like we discussed the ideas all before that so the ideas are okay any like weird Like implementation detail I wouldn't ask you about yeah, what you mean by do we need to know how? Schedulers are actually implemented. No, I don't like programmed the questions Yeah, so you can see so All my midterms are on my site that I've ever written So there's like this course and 344 are exactly the same This course and like CS 111 are pretty much exactly the same and Yeah, that so that's UCLA's version of it It is not a first-year course even though it's like CS 111 Here to like normal rational people that sounds like a first-year course. I Still I don't know why the way they order the way they number courses if it's a 100 level course That means it is a third or a fourth year course And if it only has two digits that mean it is a first or second year I don't know why but if you see CS 111 you're like damn those UCLA people are smart No It's the it's the same thing they just have a very strange numbering that I Asked lots of people no one could explain why All right, so do we want to I oh, yeah other things that we would use fork for Making anything that you want to be independent just different processes So like like you can run each top and see everything runs in a different process essentially, right Other single problems Basically, it's a trade-off of like do you want things to be independent or do you want things to be fast? so like web browsers started off using threads and That way they could like any IPC they do is essentially free because they all share memory But then you have the problem of oh if something bad happens in the thread it brings down the whole process Right, so that's the trade-off any program could make that trade-off so web browser started off in threads and then they discovered that hey things crash a lot and If your whole browser disappears you get angry So there's always going to be a trade-off of like How much IPC you do verse how much you want them to be independent? So you can make that trade-off with literally and anything because if you want to run anything in parallel now You have to use kernel threads or you have to use multiple processes, right? So you'll be doing that trade-off as well when you're developing software. So after this course Essentially everything you write should be able to run in parallel Because how many cores do we all have does anyone have like less than four cores on their machine? Probably not really so you're essentially you like only getting the fourth of the performance you should get so like Especially within infrastructure and other courses. I keep on it They're like hey this thing is slow and then I asked them. Oh Are you using all the cores and they're like no Okay, so what all right All right, so options I can go over this fun thing and explain it in Gory detail or like quickly run over the latest midterm and Give my thoughts on it any All right, this question Like no one. All right other midterm. All right more than one good enough All right, so this is the 2023 full midterm for 344 So some notes on it this one had too much writing for my liking So this one had a lot of short answer questions and people ran out of time So I would expect for you that you will have less short answer But I would also expect from you. You will have a threading question or a more Question that has like a longer question that has to do with threads So Yeah, so this is the one where people asked me what a TLB is because I was the first two questions It's like, okay So why does OS need to flush the TLB when context switching between processes? So you can't accidentally made it yet Yeah, what's it? What's a TLB? So I can let you notice save yourself some time. So if you ask me it My answer was that's the question So I can't answer a question for you on an exam So why does it need to flush the TLB? Well, because the TLB contains like all the translations for that processes If I don't flush it when I switch to a new process, I might still use another processes translation I can access its memory don't have security blah blah blah lots of bad things happen All right, does the OS need to flush the TLB when context switching between threads in the same process? No, because they all you like they share memory. They're in the same address space Same translation share everything. So don't need to do that How does the OS prevent your process from directly accessing hardware? The kernel runs in Kernel mode, which is can access instructions that you can't and that's those instructions let you access hardware Oh, why can you not call whoops blocking weight PID twice on the same process? Yeah Yeah, if you blocking weight on it and it returns that means it's terminated and when weight returns You also clean up all the resources. So now no longer exists. So if you wait on it again, it doesn't exist anymore All right, if you only ever run a single application on an embedded system, do you need virtual memory? Yeah Yeah, no, you have one application virtual memory is to like protect processes from one another and Well, if you only have a single thing running on a simple chip You don't need virtual memory. So what benefit would you see if you don't use virtual memory? Yeah Faster you don't have to do all those stupid look-ups and while you don't even need a TLB or anything like that You're just accessing memory and then guess what it's random access memory We all never questioned before so it all be fast All right, see what change can you make that will change the API and not the API Well, lots of different ones, but the main one was like I reordered fields of obstruct Even if I reordered the arguments of a function Things of that nature just like shifting code around without actually changing the meaning of it All right, let's see if we get what system called this print f and then I didn't f flush So this in retrospect was a mistake because I never said what this does So f flush like forces print f to actually do a system call because as you might Notice especially when you fork stuff if you do print f Well, see wants to make that fast So it might collect a bunch of print f calls all together and only do one system call So this essentially forces a system call. What system call would it force from print print f? right and What is the value of the first argument? One it's the file descriptor file descriptor one standard out So that should be that would not be on the sheet. I give you you should know zero in one out two error or If you want to forget one zero is in to it or one is out All right, why does links use FIFO and round robin for soft real-time processes? Basically, they're simple they're predictable You don't really want anything complicated What once you want real-time because you want to be predictable? So having some complicated algorithm running is not going to be great And this is where the writing was way too much. So this is like 10 questions of writing. So retrospect bad idea So describe how you can use the output from standard out from one process running LS To another one process using standard in running WC Explain any system calls you'd make so this basically you just make a pipe so pipe system call and then In one process you would replace standard out with the right end of the pipe in the WC process You'd replace standard in with the read end of the pipe and then you would fork or sorry You would exec and then you're done so pipe change file descriptors which would be dupe dupe to something like that I didn't really actually care about that as long as you said change the file descriptors I think I let that slide and then exact All right describe a situation where it would be beneficial to create multiple threads in your process Even though the machine only has a single core cannot run anything in parallel Assume your process does a lot of IO. So that was a dead hint right there Would it be beneficial if the threads were kernel threads instead of user threads explain? Why are why not so would still be beneficial if you're doing a lot of IO if threads block all the time If you have multiple threads, then you can switch do something else while you're waiting for hardware to Complete do a write whatever waiting for network something slow so in the case that Yeah, so that would be a benefit to using threads, but they would have to be kernel threads Otherwise the kernel wouldn't know to switch between anything else so if they were user threads it would just block your process and Everything would be slow Okay The question I'll skip to the virtual memory question because people screwed up this one More than other ones So let's see if we can get it So consider a system with a page size as 64 kilobytes quick a1 what is 64 empowers of two To the six right so remember the good one to remember to the five is 32 and I can multiply by two once to get 64 So 64 kilobytes. It's like two the six and then a kilobytes two to the ten So that means 64 kilobytes is to the 16. I Would highly recommend just to write start writing things in powers of two for virtual memory questions All right Why'd that disappear? All right the page table entry size is 16 bytes What's that powers of two? Four right to the four all right system supports 128 bit physical address some truncated page tables so Through some people off that wearing basically I didn't show a full page table. I just showed the first three entries in some page tables so since my page size is Two to the 16 that means 16 bits are used for the offset, which was nicely each hex is like four Yeah, each hex is four bits. So that would be four hex characters. So like this is the offset Which is why all these pages start at ending in zero zero zero zero All right First question how many page table entries can you fit in a single page? Yeah to the 12 right so This is the page size to the 16 divided by the size of page table entry because it's basically a gigantic array So here I can fit to the 16 in or if I wanted to write that so For Purposes of writing this course just leave you can leave in powers of two. I don't care so just leave in powers of two if you want to save yourself some time and Yeah, you're allowed to have a non program will calculator But honestly if you're punching numbers into a calculator, you're probably just wasting your time Just leave in powers of two and then don't bother using a calculator is my advice but again Don't have to listen to me. I If it makes you feel better go for it all right So with only a single level page table, so it has to fit on a page What is the maximum size in bits of the virtual address we could have or we could actually do translations for? so yep 12 bits so full size of the virtual address So remember my virtual address is Made up of two big components, so it's like the virtual page number and then the offset 12 plus yet Yeah, so I need 16 bits for my offset here and then If I only have one level of page table then I would have I would have this many entries I have to pick between so I would have 12 bits for my index for my single page table So in total that means I have a 20 I can support up to a 28 bit virtual address So anything higher than that. I need another level page table. This just says for a single level So just one All right Are we okay so far? All right so For the system with a single level page table assume the root page table is at ppn a Which means this would be at address like Since everything is page aligned it would start at address a 000 in hex so that means It is this page table right here So this page table if we only have a single level of page table this would act essentially as L zero, right? so We're given that our L zero is supposed to be that page table, and then we're translating the virtual address 00177 so we can break this apart So the last four hex digits That's the offset which we don't have to translate that's where we are within a page and then this Would be our L zero index So we need to look at the page table entry at index one which would be This one right and doing the translation that We've talked about it would have to check. Okay. Is it valid? Okay. It's valid. It's valid bit as one Perfect, and then since this is an L zero one the ppn is what we use for the actual translation So we would get the address 00e or just e 7777 because this is The ppn and then our offsets the same Yeah Yeah, yeah, if if the valid bit was instead zero you just say page fault can't No translation Seg fault page fault. Whatever you want to call it. I Probably not too picky All right, so has well, yeah, here's that right now for the same setup above we translate the address to 777 What physical address do we get or page fault? So in this case we're using index two valid bit zero. So page fault page good All right So now we do have two levels of page table. So each page table still fits on a page What's the maximum size and bits of virtual address supported? So now we have an L one we have an L zero and Then an offset to make up her virtual address our offset is still 16 bits and Each of our indexes are 12 bits So now this is what? 40 Wait bits if I can do math on the fly all right so For a system with two level page table assume the root page table for process 100 is at PPN B and Then for this process what happens if we translate the virtual address this so For this address What is my L one index? Zero right leading zeros it all has leading zeros so This is my offset This is my L zero index and Then the next three hexes that I haven't filled in here that would be our L one index Everything has leading zeros Just like in math so my L one index would be zero my L zero index in this case would be one so I Have to start at PPN B. So here this is process of hundreds L L one page table Okay, so For this process I'd go index one so index one is valid Great, so that means I can use this as my translation and then this PPN Tells me what page table my L zero is at so it is this one. So this is process a hundreds L zero page table Then I'm supposed to use Index one so it's this one. It's valid. So that PPN is The PPN of the final Physical address so it's nine. So we would get nine seven seven seven seven All right All right So this says assume the same system. So still two-level page table We have a root page table for process 101 and it is Zero C what happens if we translate this big old address? So again, this is the offset This is the L zero index This is the L one index and it's at C. So what happens here? So This is process 101 L one page table and then Here we are Using index one for the translation. So we'll see okay. It's valid and the PPN is D so that means this is also process 101 L zero page table and Then we go index two. So it's not valid So we would get a page fault and it says when there's a page fault tell me the page table entry or What look up fails? So the L zero look up generates a page fault All right, so might have noticed something weird that they both share an L zero page table So that was what this last question is all about. So given that our Processes and their root page tables. Is there any issues to make sure that they're completely independent? Oh, well explain why or why not? So here you could just give an example or say They're using the same L zero page table. So they'll have access to the same physical memory Possibly through different addresses so basically If I had in process a hundred if I use the address like zero zero zero and then doesn't matter here and So this would be like the L zero index And then the offset If I had that in process a hundred and then in process 101 I used This virtual address so zero zero one and then any L Zero index and then any offset Those would actually go to the exact same physical address, right? So if I just did I don't know zero zero Seven seven that would actually translate to the same physical address in both processes They're not independent. They're both accessing the same physical memory. That's bad All right Whoo. All right questions about that So the Hopefully the rest was okay. The last part was only two points anyways. So Yeah, this one usually my exam shouldn't be too long Usually I like to give you time to think about what you wrote this one. Yeah a lot of writing turns out I Don't know what I test my exams. I can write really fast. So I should just not do that. Yeah The average was 75 ish Yeah, 75 ish. Yeah, I I Try to have it like 75 is like good, you know, most of the course content. We're all you're good I'm not like one of those people. It's like, yeah, whatever. I'll just make sure you all fail And then I'll sort it out later. Like I don't really like that So I mean it works for like not having an average too high and not getting yelled at but Kind of makes you feel like crap and kind of makes me feel like crap and then your grade feels really random I've had that before we're like you come out of a midterm. You're like, yeah, that sucked I didn't really learn anything and then you suddenly get a grade back and it's okay. And you're like, yeah, whatever Not satisfying on either end So yeah during the reading week I'll be on discord around. I'm an old man. So I go to sleep early So don't expect me to be up past like if the sun goes down assume. I'm asleep If you want, I'm up at I'll be up at like 6 a.m. Though if you're basically if the sun's up, I'm probably up Yeah, so I might be able to catch you on the tail end before you go to sleep But yeah, I can't do that anymore. So, um, yeah, I think that's it. We're out of time anyway So just remember phone for you. We're all in this together and