I took CSE 333, Systems Programming, initially for strictly practical reasons: I inferred it must be useful, since it’s a prerequisite for important upper-division electives; I noticed that my junior year schedule would be less crowded by taking a core CSE class over the summer; and I had heard good things about the professor, Hal Perkins. Prior to summer quarter, I found the term “systems programming” to be vague, but by exploring a course homepage from a previous quarter, I began to grasp why it’s such an important foundational course.
At its most basic level, the course covers the structure of computer systems and how one writes programs that draw strength from sitting closer to the implementation instead of being shielded by an abstraction. We practiced writing code in C and C++, two related low-level languages that confer both great power and great responsibility. We also learned about the relationship between applications and the operating system, and we got a brief introduction to networks from a system programmer’s perspective. Understanding the inner workings of computing machinery helps us write more efficient programs, find more subtle bugs, and build more powerful algorithms.
Hal Perkins himself was a notable positive contribution to the course. He always came with a good attitude about the material, as well as a good sense of humor. Having been around the computing world for some time, he has a deep memory for computer history, and can recount the debates from the early days of C++, the politics between Java and C# in the ‘90s, the origins of today’s POSIX network APIs in a DARPA-funded Berkeley project, and much more. Supplemented by those anecdotes, computer systems came to life.
Besides its sheer helpfulness in terms of understanding foundations, one of the things that personally struck me most strongly about this class was the close interplay between its lessons and my research. Below are three case studies involving work I did this summer:
1. Speaking Inheritance in Another Language
Object-oriented programming (OOP) languages usually have a concept of “inheritance” in class hierarchies, which lets the programmer leverage a description of a more general thing (the superclass) to write a description of a more specific kind of that general thing (the subclass). Though the concepts of inheritance were familiar to me – I’ve been using them regularly ever since the introductory programming courses I took in high school – the semantics differ by language. This summer I was working with two OOP languages I have less experience with: C++ in CSE 333, and C# in my research.
While building a class hierarchy in C# one day, I was surprised to discover that a method of the superclass was being called instead of the corresponding method of the subclass: contrary to expectations, the behavior of the specific thing was not superseding the behavior of the general thing. A quick web search showed me that in C# I needed to add the keyword “virtual” to the general behavior and “override” to the specific behavior. I did so, and the program worked as expected.
Less than a week later, we discussed C++ inheritance in class, and the virtual and override keywords came up again as we took a deeper dive into the paradigms. I found this interesting since C++ and C# are actually very different in many ways, with C# being very similar to Java, and yet this was a semantic point where C# more closely resembled C++ than Java. In this unexpected coincidence, the C# case served as early exposure to a new set of language defaults, helping me to grasp the C++ version more quickly; the C++ from class in turn gave me a more in-depth explanation of the context behind the explicit keywords, helping me more clearly understand the C# version.
2. A Fresh Look at Event-Driven Programming
Accessing data can be slow. Reading from and writing to a disk drive, and especially over a network, is multiple orders of magnitude slower than a CPU operation; this was discussed in spring quarter’s CSE 351 class. In order to optimize program performance, then, it’s good to make slow system calls asynchronous and keep the CPU busy while it’s waiting for the data to be transferred.
One of the techniques we covered in CSE 333 was event-driven programming. I was familiar with the term from graphical user interfaces (GUIs), which wait for an event such as a mouse click to happen and then execute logic based on that. But the application of the concept to slow system calls was new to me. In a systems programming context, events are return values from system calls instead of mouse clicks, and the program saves the state of tasks that are pending on events while making progress on tasks that aren’t similarly blocked.
The discussion of this technique helped give me a fresh perspective on a piece of code from a toolkit my research code depends on. I had previously understood the code to be some sort of state machine that changed its state based on network I/O. Looking through an event-driven lens, though, helped me realize this bookkeeping strategy was also a boost in efficiency.
3. The Case of the Bad Network Connection
Toward the end of CSE 333, we discussed the basics of network programming. There are many layers of abstractions that all pile on top of each other to create the illusion of simple, streamlined data transfer, and there are multiple steps that comprise a connectivity handshake between client and server – it’s rather amazing that the Internet works at all.
I had heard some networking terminology prior to CSE 333, but some definitions were rather fuzzy and I certainly hadn’t worked with networking code before. This section of 333 thus contained a lot of material that was new to me. Little did I know that about two weeks later I would need that knowledge in my research.
In my work with the Microsoft HoloLens, I need the HoloLens device to connect back to the desktop computer I develop the code on. One afternoon, the code started inexplicably failing to connect. Since there are so many abstractions and steps involved in establishing a network connection, there are a lot of places for things to go wrong, and I spent the next several hours trying everything I could think of to pinpoint the issue. Despite my frustration at the time, within a few days I could look back and feel happy about how much diagnostic work I was able to do on my own by knowing a few basics about the underlying system.
As for the connection issue, I asked for help when I finally got stuck, and I eventually talked to Karl, a researcher in the lab who has a keen instinct for computer problems and an encyclopedic knowledge base; he discovered after about 2 minutes of heuristics and diagnostics that it was simply a bad Ethernet cable (it had probably failed during a period of power flickers that the building had experienced).
To me, CSE 333 represented the best of how coursework and the real world can interact. I learned skills and concepts that will help me well beyond the final exam, in both the short term and long term.
Below is an image of something I made to help me wrap my head around Homework 3, in which we serialized a nested data structure to write it to disk: I doodled the end goal on a long strip of receipt paper using a handful of colored pens.