CS 452/652 Winter 2020 - Kernel (Part 4)
(Version: Feb 10 11:33:18)
Due Date: Mon, Feb 24, 2020, 10:00am
In the final part of your development of the kernel, you use the interrupt processing capabilities of your kernel to create tasks that provide interrupt driven input/output. User programs will then be able to call Getc(), Putc() and any other primitives you choose to implement to access input and output streams on either of the two UARTs. It is also expected that during this final stage of development you polish the code of your kernel, integrating tightly the functionality you have implemented and thereby making the code robust.
To demonstrate your serial I/O you are to redo the functionality of Assignment 0, but using interrupt-mediated I/O and task-structured application code. As well as testing your I/O this provides you with a valuable capability that you will certainly use when developing your train application. We explicitly expect that you will have these capabilities available for later demos.
The documentation you provide with this assignment should document your kernel completely, and will be evaluated with this in mind. If you have been providing good documentation as you went along, most of what you need to submit has already been written.
If you have skimped on error-checking in your kernel development, this is a good time to flesh it out. In doing so divide the errors you can detect into two categories:
You will probably find many more in the second category than in the first, because detecting an error is much easier that recovering from it. However, internal error-detection that requires reprogramming is very useful because in most cases it provides precise diagnosis of the error.
- errors from which it is possible to recover at run-time, and
- errors that require reprogramming.
This is the part of the kernel in which you have the most freedom to make your own design decisions.
To accomplish this part of the kernel you must have the following kernel primitives operating:
int Getc(int tid, int channel), and
int Putc(int tid, int channel, char ch),
and you might wish to add others.
At the very least, it is necessary to add one or more cases to the event handling part of your kernel. In practice, most students have a to-do list of modifications they had insufficient time to do during the earlier parts of kernel development, and this is a good opportunity to incorporate them.
Aa variety of task structures are possible. You need to create at least one server, and possibly as many as four: do what you think is best. Describe the task structure you chose, and why you chose it, in your documentation.
Regardless of task structure, you must implement Putc() and Getc() with the semantics given in the kernel description. They should be wrapper functions that wrap communicating with the serial server(s). The task ids should be obtained by the client, using a call to the name server.
There is not a unique 'best' task structure, but some task structures are better than others. As mentioned in class, servers should never use Send() or AwaitEvent() and mostly be blocked waiting on Receive(). Notifiers should almost always be in AwaitEvent(). Keep in mind that the two serial devices (UARTs) play very different roles in train applications. One is much more performance-limiting than the other.
There needs to be at least one notifier task. Notifiers may be implemented separately, or there may be a single implementation which is specialized during its initialization. The implementation should take into account the task structure of the task group that works together to provide serial I/O.
To test your serial I/O you are asked to redo the functionality of Assignment 0, this time using interrupt I/O. What collection of tasks you use is implementation-dependent, but you should describe and explain your design decisions.
Once your UART implementation is complete you do not need to do much more than connecting terminal commands to output for the train controller, and connecting train input to the terminal display. In doing so please remember to retain features such as the display of idle time, which you added earlier in your kernel development.
Also, remember that typing a command at the terminal does not test either terminal or train controller I/O very rigorously: human typing is way slower than a machine sending at full bandwidth.
- Until now you have been using busy-wait I/O for debugging. In class we covered how to mix busy-wait I/O with interrupt-mediated I/O. If this is not done robustly, it might not be possible to continue doing interrupt-mediated I/O after using a busy-wait primitive. In that case, however, it is still possible to do busy-wait I/O on one UART while doing interrupt I/O on the other.
- The operation of your system depends on the priorities you choose. Starting with all tasks at the same priority and adjusting up and down is a good way of avoiding dead-ends.
Hand in the following, nicely formatted and printed.
- A description of how to access, make, and operate your program in a README file, including the full pathname of your executable file, which we will download for testing.
- The names of your group members in the same README file.
- A description of the structure of your kernel so far, highlighting the changes for this assignment. We will judge your kernel primarily on the basis of this description. Describe which algorithms and data structures you used and why you chose them.
- A pointer to your code repository, readable by the TAs and instructor, containing the source code of your assignment, instructions how to make the executable and the PDFs containing the descriptions of your kernel. The code and documentation must remain unmodified after submission until the assignments have been marked. Email the commit SHA to the TAs and instructor before the deadline.
- Output produced by your client tasks and an explanation of why it occurs in the order it does.