 Okay, hi everyone. So today is our fifth recitation and as usual I will be reviewing what we've covered last week since it's important for what we're going to cover this week. So we've mentioned that how operating system provides abstraction for the users and how it enforces boundaries between the user space and kernel space. And that we've mentioned that the user space basically nothing else but the normal mode and the kernel space is the privilege mode. We've also mentioned that the system code basically allows the user to or the user program to request services from the operating system whether it's hardware or software related. We've also went through system call step by step which is the time to scroll and from the time that the user make the syscall to the time that he or she received the results. Please note that here there is your OS 161 ships with a man pages or manual. So you can refer to that manual for all the syscalls that you need to implement. It tells you what arguments that specific syscall that you're going to implement will expect and what should it return. What are the error codes that you need to think about? So basically it has everything that you need to implement your system calls. We've also mentioned how the interruption handling process goes and we've mentioned that once an interrupt happens the kernel will basically switch or the system will enter the privilege mode or the processor will enter the privilege mode and it will record the state and then it will jump to a predefined address which is the OX 8 million and this is where 80 million and this is where the kernel space starts. We've also mentioned that there is a convention between the user space and the kernel space that need to be followed which is if the user want to pass arguments through the system calls to the kernel then he has or she has to place these arguments in the first four registers if the arguments are 32 bits from A0, A1, A2, A3 but if it's 64 bit then they should be placed aligned which means it's either should be an A0, A1 or A1, A2 it can't be an A1, A2. Also the same goes with the return values so you're going to place a 32 bit value in V0 register and the 64 value in V0, V1 and the A3 will just hold a 0 that indicates success. We've also mentioned that you have the copy and copy out functionality that you need to use whenever there is a user pointer pass to the kernel space and what it will do it basically will try to detect any kind of violation that these pointers have by checking the value that they point to. We've also summarized the system call life cycle basically what happens is the user will put the argument into registers A0, A3 and it will make a syscall through the instruction syscall at that point the hardware will step in and change the mood into the privilege mood which is the kernel mood it will save the context the kernel now will identify what kind of interrupt has happened whether it is a system call hardware interrupt software interrupt timer interrupt and then it will see that it is a system call now what will happen is the system call dispatcher will come in and check what system call has been requested by the user and it will dispatch it and execute it then it will put the return value of the result into the A0 and V0 registers and then it returns these values to the user and the user space. We've also mentioned how you should add a system call so the process goes like this first of all you need to go into the manual man page for the system call that you're trying to implement see what kind of arguments that system call receives and what arguments its return by this you need to figure out in which registers these arguments are saved and this is where in the syscall see file you have the switch statement now you need to call your for the function that you're going to implement here and for the arguments as you can see here now since the times is called received the seconds and nanoseconds then and there are 32 bits values so it knows that they are replaced in register A0 and A1 so once you check the man pages you check the argument you add another branch into the switch statement and call your function and pass these registers that come from the trap frame that we went through last time and then you need to write your function into that file which is syscall.c and just put the signature of that function into the header file this is the easy way so once you're done with one or two syscalls and you know how the procedure goes then you might want to do it the standard way which is basically what putting all this file syscalls into one file and all the processes call into another file and you follow these steps and you can it will go through and everything would just like a run fine so this is last time any questions okay so today we have a lot to cover we're going to cover the file system support part today and the process support going to be next week so we're going to cover the process model file system support file table design file handle design argument checking and console file and then we're going to go through the syscalls that you need to implement so it is recommended we always recommend to students that start with the file system calls because these are easier to implement and also that the process syscalls have some dependencies with the file syscalls so we always recommend that you start with the file system call and the deadline going to be one and a half week from today no that was just like an exception this time this is why because I think the testing wasn't ready or something that's why so please start I mean it's pretty late now so everybody I think you you are familiar with this figure which is it was in the lecture slides so basically we have the process it tells you that process contains threats and addresses space and please note if you can see here that the address space since this is a user process starts from 0x0 to 0x80 million so that mean from 0x80 million and on then we're on into the we go into the kernel space so this is the address space and what we need to implement is the file table and that's what we're going to explain now so this is basically the process model and you might want to wonder okay all this process where does it reside so it is defined and process or proc.h file so if we go here and the proc.h you can see the process structure defined here and here where you need to add stuff so or to complete that figure so as you can see we have a name we have an address space and that's it and then you need to add more materials here so you need to complete that figure which is the for example one of the things that you need to do is design the file table so let's understand how the file system support works here so we have three abstraction file descriptor file handle and file object a file descriptor is basically an integer an index to the file table to the process file table every index in that process file table refers or has a reference to the file handle that is maintained by the kernel and the file handle basically what it maintains is it stores the offset of the file so just like it should through the file handle you would know that from where you should start to read or from where you should start to write and the file handle object has a reference to the file object so the file object is basically the physical file on disk and it is mapped so as we can see here file objects are mapped by the file system to blocks on disk so as a result we have three level of indirection file descriptor through the file table points to a file handle file handle points to a file object file object points to the blocks on this or the physical file on disk why do we have three level of indirection because we need to enforce three different sharing policies so the file descriptor and the file table are private to each process the file handles also are private to each process but if you fork which means if you create another process a child the process this file handle will be copied over so it will be sure sorry it will it will not be copied it will be shared between the parent process and the child the process but as long as you don't fork the file handle is a private to that process the file object is shared system-wide so if we have more than one process that is pointing to the same file so they will be pointing to the same file object through the file handles so file descriptor as we said an OS 161 is an integer and that is the index into the file table file handle is a structure and it's a process while file matter information as we said it could be private to that process or if that process forked and create a child the process then it will be shared between those two processes or more and the file object is basically a V node the V node is basically the physical file which is implemented and ready for you to use and it is shared system-wide so now you might wonder what is a V node a V node basically is a representation of the physical file it is defined and current include V node that edge and it has a very useful operation that you might need to use for implementing your system calls like VOP read VOP write VOP is seekable which you will use with LC and VOP is basically means the V node operation so if we go through the V node dot edge this is the V node structure you don't need to go into the details you can just read the comments and as you can see we have a lot of operations that is already implemented for you to use but what you really need to care about is the read VOP read and VOP write and also VOP is seekable and these are implemented as macros so you just for example VOP read if you pass the V node and as you can see here if you pass the V node pointer and the UIO pointer that we'll talk about later it will handle the reading the file the same goes with writing and the same goes with seekable so the seekable basically it will tell you if the file is seekable or not and there are a lot more work that need to be done on your part but the reader write basically it will all read and write for you it will do everything for you so this is the V node there is another layer that is built on top of V node which is called VFS virtual file system and this is a more friendly layer that has a more friendly interface for you to use with other system calls so it provides a helbar functions on V node and it is defined in VFS.h and the useful function for you to use is just like open close change directory and get the current working directory so let's go through the VFS so as you can see here the VFS has a lot of functions that are already implemented and can be used so what you care about is VFS open VFS close you have a VFS current directory and VFS get current working directory or change directory sorry so these are already implemented for you you can use them with your system calls so this is V node and so as we said the V node is the physical file you have very helpful macros to use VFS the same thing now so you might wonder okay now since all of this is already implemented for us what should we really do so your task is basically for this part of the assignment is to design the file table design the file handle and do most of the work for argument checking so the file table design it should be basic so this is a data structure that maps the file descriptor to the file handle so since it's a data structure you can use arrays you can use a link list you can use trees depends on which one you're more comfortable with but the most simple way to do it is using arrays but you need to keep in mind that so whatever data structure you use it should be able to find the available file descriptor whenever it is requested for example once you call open that data structure should be able to locate an available file descriptor and while if you called read or write it should be able to retrieve the file handle from that data structure and also it should be able to recycle the vibe descriptor so if we close a file that file descriptor should now become available for other to use it to open another files so this is all about the file table design that you need to know any questions now we go into the part of file handle design so the file handle design basically contains a reference the major job of it is to have a reference to the file object the v-node and it has a lot of requirements first of all you need to be able to tell what is the offset on the file so for example if we have let's say two processes using that file handle they should know at now if we issue a read command or a write command at which point of the of the file I'm going to start to read or I'm going to start to write this is one of the things that you need to to store in the file handle which is the offset the other thing is it should check the privileges so for example if a process opens a file as a read only so that process should not be able to write to the file or if it if it did open the file as a write only then should not be able to read the file so that means the file handle has to keep the open mode of the file and always check whether whether the service requested from the process is are compatible with the mood that were passed through the open Cisco and it should be able to determine if it's safe to be destroyed so at some point of time you need to destroy the file handle since the file handle as we said so it could be private to the process and it could be shared between processes if we fork so that means if we have two processes using the same file handle and one of them call close then that file handle should be able to know when it is safe to be destroyed or not so how should you do this you you need to do this by tracking how many references to that file handle at the current point is so if I have three processes pointing to me then if one of them call close I should not destroy myself I should wait because I still have two other processes that are pointing to me once that counter reaches zero it's safe to destroy the file handle the other thing to consider is synchronization so you have so as we as we said that the file handle is short between civil process could be short between civil processes so if one of them tries to read the other tries to write at the same time you need to be able the file handle should be should be able to handle such situations so some people might think okay so I think here I should use read a writer luck but this is not correct why because so we have one file handle it is shared between two processes and both of them are trying one of trying to read one trying to write so whether each one of them whether it's trying to read or trying to write it's going to change the offset in the file handle and that mean basically they are writing to the file handle it's the true for example I'm reading but I'm still writing to the file handle because I'm changing a value in that file handle so that means the reader writer luck will not work with a file handle I'll basically having a lock a simple like that we've implemented we've implemented will do the work because so whenever a process tries to access the file handle should acquire the lock and then read write it will change the offset of the file once it's released the lock the other process could access the file handle as a reader write is it clear so using reader writer luck is not the solution for handling the synchronization or concurrent accesses for the file handle any question on file handle design yes the file object basically no show what happens in the file handle you're going to use the vop read vop write and those should handle everything for you they will read and write the third thing that you need to do is argument checking so you always need as we said you always need to use the copy and copy out functions with user pointers to detect any kind of violation like if the user pointer passes a null or unauthorized memory location so for example we have we will have a lot of pointers with these system calls that you need to implement like an open you have the file name and read and write you have the user buffer you also need to check if the file descriptor is valid whenever you receive an argument a file descriptor you need to check if that file descriptor is valid or not you also need to check if you are allowed to read and write as we said the privileges and this for example has to be done through the flag that is sent and open let's say the open sends flags that tells you the process needs access to that file as read only or write only or read write only and the last thing also you need to consider is the error whenever an error occurs you need to return the error code where to know this where to find this you can find it into the manual or the main pages for the system code any questions okay now we have the console the console basically is a terminal so this is here where i type or i receive input or i receive output here so the console is a special file why we call a special file because there is no concept of offset in the console so basically writing will basically append to the console file and write to the print to the on the screen and the read will basically read the user input or the keyboard so we don't have for example we can't say to the console read the first 100 byte of the user input there is no such a thing in the console and that's what makes it special whatever the user prints and enter it will get it one other thing that makes it special is that it has a fixed file descriptors so the file table index zero one and two these are reserved the zero is reserved for stdn the one is reserved for standard output and the two index two is reserved for standard error and the last thing that makes the console special is that you don't need to open it to use it so it should be opened automatically for the user process so for example the user when he writes he or she writes a program just like hello world program they won't need to open the console file to write to it so they just print f and it should print or scan if it should get it get the input from the console so that's what makes the console a special file so how should we initialize it what you need to use to initialize or open the console file is the virtual file system function open so vfs underscore open and you should pass the name as con semicolon so you should pass this as the name and the flag based on what are you trying to open are you going to try to open the standard input then it should be read only if it's standard output should be right only if it's a standard error then it should be right only so based on the what are you trying to open you need to set the flag for the vfs open so you initialize it using the vfs open so when should you initialize it there are some stuff that you need to know so the console file is only used with user thread or process it's not used by the kernel kernel basically has the k print f and that will handle the printing on screen for it doesn't need to open the console file so the console file is is only used with the user process or thread and you don't need to open it with every process so this is the common mistake that students make whenever they're trying to create a new user process they try to open the console and that will not work what you need to do is you need to open it only once and this is with the first user process created and then later on as you start forking it should be copied into the new process so the console has to be opened only once with the first user thread or the first user process created and the fork should handle copying copying it to the new file table any questions on the console okay so let's now we will now go through the system calls one by one so the first one is opens this call so here are some points that you need to consider while you implement these opens this call first of all argument checking which is you need to check if the file name is valid since it's a pointer and that's done using the copy in you need to check if the flag is valid so it would be useful if we go so here this is the manual so open now it tells you this is the name of the syscall and this is the signature so it receives a file name and also receives a flags and there is a mood also so now it describes you for example the flags what values they take so this is a read only and this is a write only and this is a read write only or read write sorry and you can also specify if you want for example you want to append to the file and if we go down it will also tell you what you need to return what does that syscall returns and also the errors that you need to handle so for example if the file name is not valid so you need to return the default which is file name was invalid pointer so as you check your argument you need also to return the correct error code for for that argument if it's not valid so as you can see the manual tells you everything if we go back so the flag we need also to check if the flag is valid so as an example what if the user sends this a flag this a flag basically tells you I want to open the file as a read only and write only and that's not valid and also in the manual it tells you that these flags has to send to be sent alone that means it's either should be read only you only you should either send read only or write only or read write you cannot just like combine both and send them as the flag for open so the other question is now you want to open a file and as you can see that the open returns an end which is the file descriptor so one of the questions would be how to find an available file descriptor and that's what we discussed which should be based on your data structure the last question is to consider is when you initialize a file handle what should be the initial offset so many of you would might want to know that or answer just like it should be zero but you need to figure out for example what if the user opened the file and you want it to append to the file that means the initial offset has to be the end of file and that's what you need to figure out how to compute it and as a hint just go through the vnode operations available to you or vfs you should find one of these that will help you just get statistics of the file and for example tell you what is the size of the file and based on that you can compute the offset or the initial offset close so for close so basically close takes a file descriptor and should return a zero on success so what it needs to check is you need to check if the file descriptor is valid and the other thing is you should think how you should recycle the file descriptor so once a file closes that file descriptor should be now available to be used with other files and you also need to figure out when it is safe to destroy the file handle and that's what we discussed with the file handle so you need to check if the file handle has only you are referencing to it or if there are others referencing to it so if it's only you then it's safe to destroy it if it's not then it's not safe now we have read write let's go through them so read receives a file descriptor and this is the file that I want to read from receives a buffer address and this is where I want to put the content or the data that I read from the file and this is the buffer size the same goes or let's say the opposite gonna go with the right which it will receive a file descriptor to write to and a buffer address that I'm going to read from and what is the size for what size I should keep reading so you need to check if the file descriptor is valid you also need to check if the buffer pointer is valid so since it's pointer then you need to use the copy in and can the user read write so whenever the read write syscalls are issued you need to check if the user of the pro or the process opened the file as a read only or write only or read write and if that process is authorized to execute that system call or not now there is it's the read write it's not as basic as you call vop read and vop write it's true that they will do the reading and writing for you but there are other stuff that you need to consider which is in order for you to use the vop read and vop write you need to initialize a uio struct what is the uio struct so that is defined in uio dot h so this is the uio struct so you need to first initialize that structure before you call a vop read and vop write because with vop read and vop write you need to pass the v note pointer and the uio into the macros so you need to get familiarized with what is inside that structure and how you need to set these values so the uio or i o vector the data block this is basically the buffer and the offset is the amount of data i think you need to this is the file handle offset and the reset is basically how much should i read and the flag is should be one of these so if it's a user code then it should be user i space if it's a data should be user space and if it's a kernel so it should be sys space so the uio rw so this is should be either read or write so am i trying to read or write and the uio space this is the address space the process address space so as an example let's say i call read system call the read system call so has an fd which is as we said an index for the file table to the file handle and the buffer and how much should i read so how would this fit here so as we said the i o victor this is the buffer and the offset this is the file handle offset and the reset is this is the buffer length sec file as we said so it's going to be user space because it's a data we're reading data and rw read write so since it's a read system call then this has to be set to uio read and uio space or the address space so this is the address space of the structure or sorry of the process or the thread calling that is called so once you initialize the uio at that point you are able to call the v o we read v o p read and pass the v node pointer and that uio to it and will do the rest read for you so this is basically telling v o e v o p read where you should read what you should read and where you should store the data that you read any questions the uio is the one that many people get confused about or don't know how to how should they set it lc so lc basically what it will it will do it will basically just change the offset of the file just change the location where i am in the file i want to go to a new location in the file so i will use the lc i will pass a file descriptor position and once so the once has several values that you can use and it will tell you how you need to compute the new offset so what you should check is one of the tricky parts of the lc as you remember in the last lecture we we've said that the lc one of the arguments that it receives is a 64 bit so that means since we have three arguments received one is 32 bit that will be put into a zero a one will be unused a one register the second the position will be sent as a 64 bit that mean it will you should put it into eight a one and a two and the once has to be on the stack pointer plus 16 so how to get the once you just get the pointer from the trap frame and add 16 to it you should get the once value the other thing is checking you need to check the file descriptor if it's valid and then you need to calculate the new offset how you should do this it's mentioned in the manual so it's here here how you should calculate it you need to check to check the once value and it tells you what you need to do change directory should be easy so change directory it will basically receive a path name since it's a pointer you need to check if that pointer is valid or not and you can use the i think vfs underscore change directory a method for it get current working directory the same it will receive a buffer and a buffer length so it works as an open or sorry as a read it will read the file name and put that file name into the buffer so since the buffer is a pointer you need to check if it's valid or not now the last syscall that you need to implement which is a little bit tricky or people get confused about it is dub two so the manual tells you that dub two clones the file handle the old file descriptor onto the new file handle the new file descriptor so if the new file descriptor names are already open then you need to close that so let me explain this so you have the file table and the process diagram or figure that we have so this is the file table and the index of this item of the file table is the fd which is file descriptor and it is pointing to a file handle so you have an old fd and you have a new fd each one of them are pointing to a separate file handle so the dub two basically will take those both two fd's and what's to do is let the new fd points to the to the file handle that is pointed to from the old fd and this is what we mean by cloning so we are cloning the file handle into the new fd but you need to make sure that if the new fd is pointing to an open file then you need to close that file first and then you need to let it point or clone that the old fd file handle into the new fd so this is what basically the dub two syscall do this is how it works so what you need to check is to check if the file descriptors are valid or not so as we can see here that dub two receives the only receives two file descriptors the old and new and again you have the errors that you need to handle so for example if the file descriptors not valid you need to return that error code now let's take an example some people ask so what's is the usage of that dub two syscall so everybody should be familiar with echo linux command basically echo whatever you give echo whatever string you give it it will just like print it for you again so if i just like run echo hi sorry sorry oh oh yeah yeah it's filling yeah so it will just like print it for you so this is what echo do so if we pass hello word it should be printed for us so let's assume that this is the minimal code for echo dot c which basically takes an argument and print it for you and this is the uh so this is your right side the right side code this is the minimal shell code which basically fork a new process and then if we are in the new process which is the child the process i will call exec v which is execute the echo command else it will wait for the child to exit so now what we want to do is let's say i want to redirect that string from the echo instead of printing on the screen i want to redirect it to be printed into a file hello dot text how should we do this without changing the echo so it's not feasible for example for you if you want to do redirection to change the user code so in order to do this uh we need a syscall something like dub 2 which basically will change the shell code and not the echo code and it will be something like this so we will fork and create a child the process and now in the child the process i will open the file hello dot text and send the right only flag to it and then the open as we know that it returns the file descriptor of that file so i will take that file descriptor and run the dub 2 on it so the file descriptor is the old fd and the standard output should be my new fd so what it will do it will clone the file handle pointed by fd into the standard output fd so whatever goes into the standard output will be redirected into that file and then i will i can close fd and here if we run the exec v which is run the echo command basically it will redirect the whatever string that you pass to it it will redirect it into the file so this is one of the usages of dub 2 command any questions okay so this is what we have for today thanks for coming and next week we will cover the processes goals and please let me know if you have any questions thank you