00 · The Problem
Processes Cannot Directly Share "Data" Without Authorization.
Communication between processes is generally straightforward when the participating processes have been granted permission to do so. Modern Operating System enforce multiple levels of privilege, with the Operating System kernel possessing the highest level of authority. Consequently, user-space processes must normally request Operating System assistance when performing privileged or potentially unsafe operations, including many forms of interaction with other processes.
This project investigates whether two processes can communicate without using an authorized, direct inter-process communication (IPC) mechanism. More specifically, it asks whether two processes can transmit information indirectly through their interaction with a shared system resource, while making that communication difficult to distinguish from ordinary system activity. The principal challenge is reliability. If indirect communication is possible, the receiver must be able to identify the transmitted information despite interference from unrelated system activity. Bandwidth, and therefore the practical capacity of the channel, presents an additional challenge.
After completing COMP 3430, I reasoned that if conventional IPC mechanisms were unavailable, the two processes would require access to some shared resource from which information could be inferred. My initial ideas involved the file system. For example, one process could create files of predetermined sizes, write recognizable sequences of values to a file, or encode information through filenames within a shared directory.
These approaches, however, would still rely on explicit Operating System-managed state. Both processes would require access to the same files, directories, or metadata, making the communication comparatively direct and observable. Such activity would also leave persistent evidence that could be inspected, logged, or traced back to the transmitting process. As a result, the file system did not appear to provide the type of indirect and difficult-to-observe communication mechanism that I wanted to learn about.
I next considered other resources shared by concurrently executing processes, particularly the processor and physical memory. I ultimately chose to investigate CPU utilization because every active process depends on processor time, and the Operating System scheduler must continually distribute that time among competing workloads. I had also recently implemented a basic multilevel feedback queue scheduler, which gave me an introductory understanding of how schedulers organize workloads and of some of the metrics used to evaluate scheduling behaviour.
The initial concept was therefore straightforward: execute a process with different workload intensities and determine whether a different process could detect the changes in processor availability caused by the other. This starting point immediately introduced two significant problems:
1) Modern systems contain multiple processor cores and logical CPUs. Unless both processes execute on the same logical CPU, the receiver may observe little or no measurable contention from the sender. For the initial implementation, this problem can be addressed using sched_setaffinity() to restrict both processes to the same logical CPU. Although this requires a system call and imposes an artificial experimental constraint, it provides a controlled environment in which the underlying communication mechanism can first be evaluated. A later project would be to determine whether this constraint can be relaxed.
2) A typical computer executes many processes and kernel activities concurrently. Context switches, interrupts, background services, and other scheduler decisions introduce variation into the amount of processor time available to the receiver. The sender must therefore create a sufficiently large and consistent change in processor contention for the receiver to distinguish the intended signal from ordinary system noise.
After formulating this initial design, I recognized two limitations in my own understanding:
1) COMP 3430 was my first substantial introduction to Operating System implementation. My understanding of scheduling, processor contention, and hardware–software interaction was still developing.
2) I did not yet know how to measure the processor time available to an individual process or how to separate sender-induced contention from Operating System overhead and unrelated background activity.
This raised a broader methodological question: how should I implement a system when I do not yet possess all of the knowledge required to design it? One possible approach would be to ask a large language model to generate the complete implementation. Although this might produce working code more quickly, it would remove much of the process through which the problem is defined, assumptions are tested, implementation difficulties are encountered, and technical understanding is developed. This would go against what this project was intended to accomplish.
Using large language models (LLMs) to obtain solutions can be compared to consulting the answer key at the back of a mathematics textbook. Although doing so can be useful, the educational value of the answer is often greatest only after the learner has first attempted to work through the problem independently. The process of struggling with a problem helps reveal gaps in understanding (by defining sub-problems) and provides the context necessary to evaluate the proposed solution critically (by solving those sub-problems and using that knowledge to analyze the initial problem).
A major difficulty in implementing a system is that my initial mental models of both the problem and its solution are often incomplete and partially inaccurate. In this project, for example, my understanding of CPU scheduling and of how two C programs might indirectly communicate through shared processor activity contains significant gaps. Unlike a mathematics textbook, an open-ended systems project does not provide a predetermined sequence of exercises designed to expose and correct these misconceptions. I must first determine which questions need to be asked before I can begin answering them. In doing so, I will hopefully understand the problem scope.
An incomplete mental model still provided me with an useful starting point. My development process therefore consists of the following stages:
- Articulate my initial mental model as precisely as possible so that its assumptions and proposed mechanisms can be evaluated.
- Use an LLM to identify inaccurate assumptions, missing concepts, and potential approaches that warrant further investigation.
- Implement simplified versions of the proposed approaches while independently verifying the underlying mathematics and systems concepts through textbooks, documentation, experimentation, and other non-LLM sources.
- Iteratively revise my understanding of CPU behaviour and the mechanisms required for reliable communication in response to experimental results.
- Continue this process until the refined mental model is sufficiently accurate to support the development of a functional and reliable tool.
In this way, the LLM is not treated as a substitute for understanding, but as a tool for identifying gaps, generating questions, and guiding further study. The ultimate objective is to develop an understanding of why the resulting system works. After much back and forth with my LLM of choice (ChatGPT), I compiled a list of 4 "Major" problems, of which had roughly 58 sub problems (some of which have no bearing being uttered in a public setting. I suppose it is a requirement to play the part of the fool before you can play the master - or someone who understands the Scheduler haha).
Four Main Problems to Overcome