• the boundaries of your modules have to be simple and easy to understand
  • you want hard boundaries via specific interfaces
  • the point of creating the abstraction and modularity is to make the API simpler than the underlying operations within the operating systems

Example Function Signature Creation

char *readline(void); // bad interface because you need memory for this
int readline(char*); // improvement because caller allocates memory
int readline(char* buf; int bufsize); // now we know caller bufsize 
int ssizetread(int fd, void* buf, size_t bufsize): // actual interface 

Requirements for Modularity

  1. Performance - often when we split our properties into our modular components, it often impacts the performance of the underlying mechanisms
    1. IE for the read API, the underlying mechanism uses prefers buffer sizes of 512 and we are allowing for any sort of buffer within our API, thus the performance might be impacted
    2. There is a tradeoff between performance and modularity/abstraction in this case because our program might not always be optimized for the system call we use
  2. Robustness - We want the system to keep running even if there is a bug in one module.
  3. Simplicity - We want people to be able to use the API properly without any sort of complication
  4. Flexibility - There should be generality of the input parameters and the outputs

Soft Modularity

  • Soft Modularity: Each module in the system promises to the other modules in the system that it will not cause an issue, when a module does mess up, it impacts the entire system
  • Trust Among Modules: Modules are designed with the assumption that they will perform their tasks correctly and not perform actions that could corrupt the state of other modules. This is a “gentleman’s agreement” rather than a strictly enforced rule.
  • Impact of Errors: If a module does not adhere to the agreed conventions—intentionally or due to a bug—it can cause system-wide issues. Since there’s no strict isolation, a fault in one module can propagate and cause failures in other modules, potentially leading to a system crash or unexpected behavior.
// foo.c
movq $27, 96(%rsp) // moves the instruction pointer causing errors
movq $1096B, 0(%rsp) // executes random code into the return address

Hard Modularity

Hard modularity is a system design principle that enforces strict boundaries between different modules or components. Unlike soft modularity, where the separation is based on conventions or agreements, hard modularity uses mechanisms to ensure that the actions of one module cannot adversely affect others. This is typically enforced by hardware and operating systems through techniques such as memory protection, process isolation, and access controls.

Here’s a more detailed overview of hard modularity:

  • Strict Enforcement of Boundaries:
    • In hard modularity, the system enforces strict boundaries between modules. If a module attempts to access or modify the resources of another module without the proper permissions, the system will prevent it, often resulting in an access violation or a similar error.
  • Isolation:
    • Modules operate in isolation from one another. For example, in an operating system, processes are isolated through the use of virtual memory, where each process sees its own private memory space.
  • Access Controls:
    • The system defines what resources each module can access and how they can interact with one another. This is managed through access control lists, capability systems, or other security models that define permissions.
  • Fault Containment:
    • Errors or crashes in one module are contained within that module and do not propagate to others. This limits the impact of faults and aids in maintaining system stability.
  • Security:
    • Hard modularity is essential for system security. It prevents a compromised module from affecting other modules or the system as a whole.
  • Predictability:
    • The behavior of the system becomes more predictable because modules cannot have side effects on each other, leading to fewer unexpected interactions.

Here is an example C code snippet that might operate within a system enforcing hard modularity:

// file_manager.c
 
#include "file_system.h"
 
// Function to read a file; assumes proper permissions are set
int read_file(const char* file_path, char* buffer, size_t buffer_size) {
    FILE* file = fopen(file_path, "r");
    if (!file) {
        // Unable to open the file, could be due to access restrictions
        return -1;
    }
 
    // Read the file into the buffer
    size_t bytes_read = fread(buffer, 1, buffer_size, file);
    fclose(file);
    return bytes_read;
}
 

In the snippet above, the read_file function opens and reads from a file. If the file_manager module doesn’t have the necessary permissions to access the file, the operating system will not allow it to open the file (fopen will return NULL), and the function will return -1. This demonstrates hard modularity at work: the file system module enforces access controls, and the file_manager cannot bypass these controls due to the system’s strict enforcement mechanisms.

Expanded System Abstractions

  1. Memory Abstraction:
    • The operating system abstracts physical memory into a model that can be more easily managed and utilized by applications. This abstraction typically takes the form of virtual memory, where each process is given the illusion of having access to a contiguous block of addresses that are mapped to physical memory by the system.
    • This abstraction layer also provides protection features that prevent processes from accessing each other’s memory space, reinforcing the principles of modularity and security.
  2. Interpreter Abstraction:
    • An interpreter in the context of system design is the computational entity that executes instructions. In modern CPUs, this role is fulfilled by the processor, which follows the instruction pointer (%rip in x86_64 architecture) through the control flow of a program.
    • The environment pointer (often the base pointer %rbp in x86_64), facilitates access to function parameters and local variables within the currently executing function’s stack frame.
    • The instruction set defines the low-level operations that the processor can perform, effectively translating the high-level code into actions that manipulate the system’s state.
    • This abstraction allows programs to be written without concern for the physical hardware’s specifics, focusing instead on a consistent set of instructions and behaviors.
  3. Links Abstraction:
    • Links represent the communication pathways between interpreters, allowing one process (an interpreter of one set of instructions) to communicate with another.
    • This can take the form of inter-process communication (IPC) mechanisms, such as sockets, pipes, shared memory, or remote procedure calls (RPCs).
    • Links abstract the underlying complexity of communication protocols and provide a simple interface for data exchange, thus supporting the principle of modularity.

Linked Map of Contexts