 Hopefully last presentation and assignment two and hopefully next week we're going to start giving joint introduction to assignment three. So we're going to have overview assignment two. Today we're going to come up with a process model, process structure. So processes tools in general. We're going to come up with getPID and forkWavePID and exactly. So by now you should have the process tools implemented. You should already start with the processes tools. If you did finish your process tools by last Friday that means you're on track and on time. Because you do need it in around two weeks for processes tools to complete them. So the deadline is going to be not this Friday but the after on March 17th. So let's start by reviewing what we've covered currently now. So we've said that a process basically contains a thread or multiple threads but in OS 161 we only have one thread. It contains an address space that is private to that process. It contains a file table that is private to the process. And once you start forking you're going to start copying the first thread. And then you're going to start copying the address space to the file table which again it has to be private to that child process that is created. And you're going to already have console initialized then that means you're going to copy over the references to the console file handle without creating or initializing the console all over again. And if you have any other files that are already open then you're going to just like reference the file handle in the new child process and increase the reference count by one. So the file handle as we said that it is a private to each process. Unless you start forking then it's going to be shared between the parent process and the child process. The file object is basically shared system 1. So we have three levels of indirection, file table, file handle and then file object. So starting with the processes columns, again you do need the data structures or framework that will handle the processes. So you should have, every process should have its own structure that's already given to you but it's not complete. You need to add a lot more stuff to it. So beside what it's giving to you and product edge and the existing process structure. So let's have a look at what we have here. So we do have a name and a fill block and number of threads that should always be one since OS161 only has one thread. And then you also have an address space and current working directory of the current process. So beside all of these what you need to add into process structure is a reference to process threads. So every process should be able to identify what threads did the one get to it and every thread should know what is the process that it wanted. So just like every process should know what's on the thread that it has and every thread should know what is the process that it wanted. So you should have a reference in the process structure, you should have a reference to process threads. In our case one thread you should have the file table which maintains references to file handles and you should have a PID which is a process ID and process ID should be unique for every process. You should also have the PPID which is the parent process ID. So once before can create a new process, challenge process, the PPID should be set to the PID of the parent. You should also have an exit status which tells us that that process did exit or not. And then you should also have an exit code that will tell me what is, if the process existed then what is that code for the, or the exit code for that process. Which will be collected by the parent process later on. You should also have some synchronization primitives in your process structure that were just like synchronized between the parent and the child. So the parent and wait PID could go to sleep waiting for the child to exit and collect the exit. So these are the major stuff that you should have in the process structure that doesn't mean it covers everything. But these are the main information that you should maintain in the process structure. But you could also add some other stuff to it that you will need based on your data structure that you will come structure to maintain. Process table design. So as we said that every process in OS 161, every process contains only one of them. And a process and its thread should be able to identify each other. So process table is similar to the file table. But I'm not going to use the word table like an array because you should decide how you're going to design the data structure. So I'm going to say just like process table is a data structure that maps the PID to the process structure. How should you implement it? So you can go with an array or you can go with a link list. But a link list is much better because you're not going to waste space. Since for the file table it did make sense to use an array because it's only 64 but not for processes. So a link list would be a better design option but still it's up to you. You can't test with both. What are the requirements for a process table? So when you create a new process, that table should be able to allocate a new PID for that new process. And allocating a new PID means that you need to allocate a new process structure. So how should you allocate a PID? There are several approaches. So some students were just like thinking of using a static array and you're going to use some people who use indexes and then you go back unless you run out of index but that's not really a good practice. We would recommend that you use an increasing counter for your PID, allocating PIDs. And in limits.edge you do have a PID map which still tells you that you could just modify it. So this is limits.edge where you have the max PID max. Now this is max.edge for process ID. Change this to match your documentation. I would recommend that you keep that as it is and use an increasing counter. If you then run into problems, then you might want to consider changing them. But for the minimum one, you need to start making sure that the minimum should be 2. That means you need to start from 3. So one or two are... I think no, I think you can start from 2. So you cannot definitely start from 0 or 1. These are reserved. As long as I know the one is reserved for the kernel process and you have these, if you search for them in a kernel PID if you search for that you can retrieve that. So this is for allocating a new PID. The other thing that you need to be able to do is that given a PID you should be able to retrieve the process structure. When a process exits it should be able to destroy the process structure. So depending on how you implement your process table you should just like... If it's a linkless then you need to destroy that process structure. If it's an array that is pointing to a process structure again you need to destroy it. But you don't need to recycle for example the PID. The PID should be only be allocated. So whenever I inquire about the PID I give you a PID. It should tell me that this is an invalid PID since the process did pick up and you're done. So these are basically the requirements for process table design. Any questions? On the process structure and on the process table. Starting with the processes course starting with the easiest one. This is the get PID. Basically we return the ID of the current process that holds that system. If you also look into the man pages so for get PID get PID doesn't matter. So that is the most easiest system that you need to implement. Basically returning the process ID of the current process. Now starting with fork. So this is an example of a user program that you could write to test fork, test exit and wait the ID system. So what it does is basically fork. The parent process would call fork and at this stage two processes we will have two processes at one. So with the parent process now we will continue into the end statement where we will start to branch based on the current process. So if the current process is the parent process and before PID successfully complete then we're going to go into the last branch where we're going to call wait PID and that would put the parent process ID into sleep waiting for the child process to exit. And if the current process, the child process on the other hand then we're going to branch into the PID zero. Why is that? Because fork returns twice. With child it returns zero with the parent it returns the child process ID. So we have else here because any positive value, that's what you mean because the PID would be positive value. The positive value would branch into the weighted section for the statement and if the return value of fork is zero then that means the current process is the child process and it will branch into the PID called zero. Here the child process will basically print that on the child and then call the exit passing that the exit success, that's just like an example a code that was successfully executed so just like obtaining the exit code and then that basically a user program that you can test for and it's a good idea to have that before you start implementing or after you implement your fork you should have something like this to test your fork. So what is a fork? A fork is a system that creates an exact copy of the calling process but with few exceptions. It's not really an exact copy it's an almost copy so after fork the process called fork would be the current process and the one that is created is the child process. Now we have some points that you need to consider while implementing fork. One of them, what should you copy? What shouldn't you copy and what should be shared between the two processes the parent and the child? So what should we copy? First we should copy the parent's child frame the context of the parent and that should be saved in the current scheme using the K-mailer. You should also copy the parent's address space how you should do that the address space has its own framework that is given to you which you should complete it's not completed but you can use it it's just like the VM version so with address.edge you have all these functions address space functions that you control which one of them is as copied so you can use that function to copy the parent's address space and create an address space for the child process so this is the node that tells you this is the dump VM version so now that this is using dump VM in the same way you're going to complete all these, just like re-implement all these interfaces so this is how you should go about copying the parent's address space then you have your parent's file table and that's basically your implementation show you should have the functionality your file table should provide you you should have pretty implemented that functionality that will allow you to copy the file table into the child's file table having in mind things that should be shared between the two processes so this is all the stuff that you should copy there are some stuff that you shouldn't copy like the PID as we said the process ID should be unique to every process so the child should get a new PID the PID should be assigned to the parent's to the process that called for which is the parent process PID if you just like to complete it over then it will have the parent's parent PID so make sure that this is set to the correct parent PID now you have the return value of 4 that shouldn't be copied over from the parent to the child and this is where you need to recall the maps return value convention where things are the return value saved in V0 and V1 and the A3 register should be set to 0 to indicate success so these are the stuff that should not be copied over to the child process what about the stuff that should be shared between them this at this point all of you should know what are the stuff that should be shared between the two processes like Barhand for example so you need to figure this out so these three may be stuff that you need to really consider by implementing 4 what should be copied, what shouldn't be copied what should be shared between them so as a result how summarizing how to 4 work it should first create a new child process with a new PID and then you need to set the PPI into the parent's PID copy the parent's track frame copy the parent's idos space copy the parent's file table while you copy and paste the stuff you need to have in mind stuff that should be copied and should be shared so for example the track page can be copied fully to the child so and then once you're done with the copying then you need to call thread fork which should do the remaining work for you there are, I've got some questions about thread fork and the stuff that you need to pass with thread fork I think I shouldn't go into that much detail but if you couldn't find answers in the comments in thread.edge or thread.c then do search for thread fork because it's being used before and it is already used in your in the source tree so you should find examples that how should thread fork be used and what should be received and what should be returned so as we said fork returns twice returns once for the child process with the child process returning zero and with the parent process it returns to the child process ID so any questions so wait PID so that is used with the parent process to wait for the child process to exit and again some points that you need to think about while implementing PID with PID is to check if the PID passed again valid because and wait PID what we receive is a PID we also receive a pointer that will hold the status and then we receive options please for the process this code go through fully read the main pages it gives you a lot of information and answers to how you should implement these systems so for the PID again we need to check if that process if that PID is a valid PID that is that comes to a process structure and then we also need to check if that process structure is the child of the calling process the current process with the parent process so and that current process allowed to wait for that child process to exit you also need to check if the status pointer is valid since it's a pointer you need to check if the options passed are valid I think at this point in the for the options just the options arguments should be 0 so you just need to buy 0 so you should just only just like to make sure the values are valid so if you haven't as it says you haven't implemented any options for the PID then you should check you will not receive the values for not implemented options so that's for the the options and then the return and exit status and the status pointer that is what we go for and here with PID you should use a synchronization primitive that will allow the parent process to wait for the child process so that parent process should go to sleep wait for the child process to exit so you should think about having the synchronization primitive implemented we have also the exit process goal that the parent process to exit and if we look into the arguments that so it receives an exit code that you should store that code in the current process but please keep in mind that you should use that MK wait exit macro to just like prepare the users like exit code to prepare the exit code that should be passed to the parent why is that because there are some exit statuses that are already being passed so what it will do it will append your exit code to the already passed exit status and then prepare that for you and send it back to the or set the exit code in the process structure so after setting the exit code in the process structure you need to indicate in the child process structure that that child process did exit and the exit status value once you are done with that you call thread exit so the thread has just like I ready for you to use a framework just like in fork where you are going to use thread fork and exit you are going to use something similar to the file cisco that you already used you did the for example the auto checking and then you use the vnode helper macros or the dfs player functions that would help you to do the remaining work for the cisco the same goes with fork and exit for example any questions up until this point exactly is the most difficult cisco that you need to implement so any questions so exactly what is exactly exactly basically takes a newly created process and replaces the current program with a newly loaded so when we fork and create a new process that process holds the program that was the same as the parent process so when you call exactly you are going to place that program with the program that the child process should run we did brought up here the running program because they have a similar functionality but with some differences for example running program is responsible for running program from the kernel using the for example key as you already done before but with the exactly is the one that would be used with the shell so if you go to your kernel I think it's the old if you enter the s option it will go into the shell and then from there you can start using calling user code exactly comes into play and will be used so exactly and run program you have some similarities so if we go through the code of run program then we're going to see that so it's going to receive the programming opens the program the vfs open it will create an address space load executable load health and enter new process so all these stuff or even as defined stack which sets the user stack so all these are similar to what you will also have in exactly so the vfs open the AES create load health the stack pointer the defined stack and then you have the enter new process functions so these are the similarities what are the differences run program is where you initialize the console so but in exactly as we said we don't really need to initialize the console because we said that the console should be initialized only once with the first user and then should be copied out as you fork so one of the differences is that run program is the place where you initialize the console but exactly doesn't initialize console exactly handles receives arguments from the user space so it needs to copy them into the kernel space and then copy them back into the user space and that's what run program does because it's already in the kernel space so these are the similarities and differences between exactly and run program so if we go through an example here let's say we started the shell in the our OS161 kernel and then we called that program R test and we passed these arguments to OS161 and so the signature for the exec syscode so it receives program a pointer, a character pointer program name and then to receive pointer to array of pointer which is the arguments args so and that's basically which the array that calls the arguments so here is the most difficult part for exec P is you need to now after you see this stuff from the user space you need to copy them into the kernel space and then you need before you pass them back to the user you need to organize them in some way that will be accessible to the user and put them in the user stack so a lot of computation is going to be should happen here so for the exec P it receives a pointer program which basically points to the program name and then it receives as we said that pointer to array of pointers so the args is a pointer that points to array of pointers so the first index in that array basically points back to the program name and that's nebs convention starting the args the first index in that array is where you're going to receive your arguments so as you can see the first index has a pointer to a strength or array of characters which is foo and that should be non-terminated so keep in mind that both your args array should be the array itself should be non-terminated that means we should have an extra index into that array that is a null pointer and the args basically because since they are strengths or array of characters then by default the base should be non-terminated too so as we can see each argument in the array each index in the array points to be one of the arguments now how does exec P work first you need to copy the arguments the program and args into the kernel space and then start doing whatever the render program does like vfs-open the copying the address space load-out as stack as define stack and then once you're done with that then you need to copy the arguments back to the user where should you copy the arguments you should prepare these arguments on the user stack and that's what we're going to let's go now it's the right time so you need to arrange your the array of pointers that point into the arguments you do need to arrange it on the user stack so this is the user stack so how you should go about that first you need to compute the size that you need for all of these for the array and for the arguments so the array as we said is array of pointers that means since we have we have the program name we have three arguments and then the fifth index is a null because that array must be an outerminated array so each of these end fixes are 32 bit the size of these should be 32 bit because they are connected so they are 32 bit so other than that since they are pointers then they should point to the beginning of the arguments so after computing the size of the array what you need for the array then you need to compute the size for the argument that you receive and how should you place these so we know that the user stack is by convention it's a four byte so each plot is a four byte that means we can place characters in every block since every character takes one byte so if the argument is less than four bytes we should be fine because we can place the argument and the terminating null into the same one but if the arguments is more than four bytes that means we need to allocate more bots for that argument and we shouldn't combine two arguments in the same block once you're done with one argument if you have some bytes left then you need to pad them using null and this is what's happening here so in our example we passed foo, os161 and bar as arguments and we have the testbin slash artist as the programming so this is organized here so as we said the first index in that array points to the programming so we're going to start just like slash t, e, s so each character is going to take one byte so every block four bytes so we're going to continue up until the end so if you can see that the programming by itself takes exactly four blocks but still it's not terminated so technically you do need to allocate one more block to terminate that that's just an example just to make you understand so the actual communication basically differ but having the argument not terminated this is a must you have that in the man page this is very important so the argument is an array of zero terminated strength the array itself should be terminated by an argument so this is a requirement that you should really handle in your computation and how you organize the array and the argument so it doesn't really matter how you really organize the argument themselves but as long as you have the array you didn't know how you organize your array and each index in that array has the address of the beginning of the argument and the argument should be not terminated so you know where it starts or it ends but the important thing is that once you're done organizing all that or oh sorry so as we said you make the size of piece of the whole thing then you can just like advance the stack pointer so you have that space reserved and then you start putting this argument into that allocating and putting the arguments into the stack and once done you need to make sure the stack pointer and the argue pointer are set to the end of your space where you allocated for these arguments so keep in mind that the user stack pointer starts at the address 0x80 million and throws down this is the convention of the address space so make sure once you're done with allocating all of this you have your stack pointer and R3 pointing to the right place which would be you so this is how you should come out your arguments back to the user space and then you return to the user space using the inter-new-process function that is implemented in fab.c and note that understand things that that function takes Rc and Rv and these are the same variables or arguments that are received by whenever you write a C program in the main program you receive two arguments which is the Rc and the Rv these are the same as the ones that are in the new process so make sure you set the Rc and Rv correctly so Rc as we said it's the number of arguments that you have and Rv is a pointer to a way to a way of pointers pointing to the arguments so a few points that you need to consider while copying arguments from user, from kernel space to user space that you should copy the arguments on the user stack and MIPS pointer should be aligned by 4 where to place stack pointer this is what we mentioned that space that you allocated for the arguments and then because every index in the array of pointers that Rv points to is an array so it's a pointer that means it is of size 32 bits each address in the so each index in the array holding an address to the beginning of the argument and then the argument should be in alternate so use padding to just like not have two arguments in the same block on your stack ok and also for some stuff that I forgot to mention the mkwait for the exit syscall the mkwait exit macro is in your the way that edge you can go through that so you have all the these are the encoding macros used by some way to resolve so these are this is the macro that you need to use with the syscall to prepare your exit code so it will basically append your exit code to it where is the status or exit code that being created so for the pid allocating the new pid the limits so as I said the pid should start from 2 the mkwait should start from 2 and the first pid these are reserved for the kernel to use and you should use an increase encounter for the for your pid this is one way one way to do it and the recommended way of doing it so yeah that's all I had for today any question of any of the process syscalls or say the way it exists we have a question do you need to worry about the process that you're taking for the syscall so like exiting through a signal yeah, I'm not sure I need to check over that I'm doing the for a process exiting through a signal not directly in other words there's no formal signal process subsystem in OS 161 there is one corner case sometimes when let's say a process catches a page fall it's handled as an apparent signal if you're looking like trap.c there's some code for that but yeah you don't have to worry about the case of a process getting a signal in a formal sense because there is no signaling subsystem unless you want to write one so the user's tax starts from here all x8 million and this is and you should throw it down starting from that has this thrown down you compute the size advance from start pointer and then you start from that so in this so yeah in case I have a character buffer in this case I have a buffer so and so size how do I allocate 4 bytes of the buffer as a pointer how do I make 4 bytes of the character buffer as a pointer the pointer is 30 no how do I do this why do I have to start no no any other questions thanks for coming that's all for today we might start with a sign of 3 actually so we might start with a sign of 3