HOORAY!!! I can convert temperatures among Fahrenheit, Celsius, and Kelvin!!! Woo hoo!!! We’ve probably all done these fun little calculations in one of our early classes, so nothing special at the application layer. However…
OK, so stdin/stdout, right? Probably twenty billion ways they can be implemented. At the lowest level, you’re talking to some piece of hardware that translates the bytes into something a human can see somehow. When I wrote my original OS 20 years ago, that involved writing to the PC’s video memory buffer. At the moment on my Arduino, I’m just using the default serial connection.
Now, the simple way to do input/output would be to just use the serial port directly. And, indeed, that’s what I started with. However, there are multiple problems with this. For starters, my goal here is to practice some real OS fundamentals and just doing that alone doesn’t help with anything. Much more importantly, however, my ultimate goal is to get to a multi-user system. Having multiple concurrent users means having multiple ways to interact with users. And, each user has to only see the input and output they’re using. I can’t just go and broadcast everything to everyone. So, in short, just using the serial port directly doesn’t scale and has no hope of achieving my goals.
I had stdout working a week ago with message passing. All things considered, it’s relatively easy: Grab a memory buffer, write what you want to print to the console to the buffer, then send an inter-process message to the console process and tell it to print it. With that much, I got printf to work reliably.
stdin, however, is a totally different beast, especially when considering scaling out to handle multiple simultaneous connections. For one thing, it’s no good to just blindly read from the serial port anymore, you have to have a concept of multiple ports that may be in use and you have to make sure you’re reading from the correct one. And, that then presents another problem: The concept of there being a correct port introduces the concept of port ownership. When input is captured from the user, it has to be directed to the process that’s currently got ownership of the port.
This is all a long-winded way of saying there was a ton of infrastructure that had to be developed for this to work, and a lot of changes to the existing infrastructure. Because I introduced the concept of ownership, I also had to go back and change stdout to align it to the new model such that a printf went to only the port(s) owned by the process making the call, not just any or all arbitrary ports.
I got most of the data structures and communication mechanisms in place last night. I wrote an implementation of fgets and it worked flawlessly the first time!!! I was shocked! Then I moved on to scanf… Big mistake.
In order to do a regular scanf from stdin, I have to parse a string buffer. The sequence here is to send a message to the console that the process is waiting for input, then wait for the console to send what it captures. After that, the scanf has to hand off the parsing to an sscanf implementation. However, the scanf family of functions use a variable argument list, and the top-level call either has to parse the arguments itself or pass the initialized va_list down to a function that accepts one as a parameter. The function I would need in this case would be vsscanf, which is part of the modern C standard… but it’s not in the implementation provided by the Arduino compiler… Great…
Literally the only way to fix this was for me to write my own implementations of sscanf and vsscanf. Fortunately, I woke up at about 4:30 AM this morning and got a head start on it. I did not implement support for everything. I figured, if the Arduino libraries weren’t going to support it at all, anything I did that got remotely close was way better.
When I finally got done with everything, I needed to test it, so I modified one of my commands to prompt the user for a double and print out what was read. And do you know what silly mistake I made? I passed in the value of the variable I wanted to update instead of its pointer. That is such an incredibly junior mistake to make. The thing is, I don’t ever use scanf. Most of my work involves encoding and decoding messages that come over a socket. I can’t even remember the last time I used scanf, but I’d be willing to be it’s been over a decade. I just don’t have it in my head anymore.
So, long story short, the implementation worked just fine when I used it correctly. So, then I needed a real “program” to really exercise it.
This is where CS 101 comes in: The very first programming class I took in college, in C no less! I still have all my source code from my college classes, and I went back to those programs for ideas on what I could do with basic standard input and output. One of the first things I came to was a program that converted Fahrenheit to Celsius. Simple enough. I extended it to run in a loop with menu options for what conversion to do and also included conversions with Kelvin.
Then I got thrown another curve ball: It turns out that the Arduino’s implementation of the printf calls don’t include support for displaying floating-point numbers. D’Oh!!! So, I had to get creative with this one. I wrote a macro to split a value into two integer components: One for the whole number portion of the value and one for the decimal portion. I used the fast pow algorithm I wrote a few weeks ago to raise 10 to the specified number of decimal places so that the values would be formatted correctly. A pain in the rear, for sure, but doable.
So, after all that, I now have a command in my operating system that will display a nice menu to the user about what kind of temperature conversion they want to do, prompt them for input, read the input, and handle it gracefully. Phew!!!
With this much infrastructure in place, I now have the ability to write a proper shell instead of the root console process handling commands directly. So, that’s next on my to-do list after a little cleanup. Once that’s up, I will then, FINALLY, have the ability to support multiple concurrent users… sort of. I will have the ability from a software standpoint. I’ll still have to get some other piece of hardware (likely an RS232 connection) to manage the IO for a second connection.
I did learn one annoying thing during these upgrades. I went looking to see if it was possible for an Arduino to read and write from its own flash storage. Apparently, with the right bootloader, it is actually possible. However, the Arduino I have (Arduino Nano Every) has an architecture that doesn’t support that bootloader. That means I’ll have no ability to practice writing a file system with the hardware that I have. If I want to continue down that road, I’ll have to get a different board. Sigh… That’s really disappointing because I was looking for an environment that was as close as possible to a PDP-11 like the first versions of UNIX ran on. Any higher-end board I get is likely to be faster and have more RAM, which gets me further away from my target environment. Well, nothing I can do about that, I guess.
One of the other things I did during all this was to add a ‘help’ command. It just prints out a one-line summary of each command, however running it produced a lot more output than any of the other commands I had made. I was disappointed by how slow the output was. Doing prints involves a lot of message passing back and forth and I knew that the Arduino was slow (it’s a 20 MHz, 8-bit processor), but I didn’t think it would be as slow as was. Then I realized something: The serial port was still configured for 9600 baud. So, I bumped it up to 1 Mbps. WOW!!! What a difference!!! The system is blazingly fast. MUCH faster than I expected it to be. I understand now why there are still proponents of Mac OS 9 in the world. They like it because, being a cooperative multitasking system, it felt more performant and responsive than the preemptive multitasking OS versions that came in OS X and beyond. I see now why cooperative multitasking was used for so long.
So, good progress overall and still more yet to accomplish. To be continued…