This project is a comprehensive collection of C programs demonstrating advanced operating system mechanisms from the UNIX/Linux family. It focuses on POSIX signal handling, Inter-Process Communication (IPC), creating modular libraries, and automating the build and test processes.
- Project Description
- Task Structure
- Requirements
- Compilation & Execution (Task 3)
- Automated Testing (CI)
- Key Concepts
The project is divided into three stages (tasks), each expanding the previous one with new system-level concepts: from basic signal interception, through parent-child communication via signal queues, to full modularization using dynamically loaded libraries (dlopen).
The main program allows testing 4 different system reactions to the SIGUSR1 signal:
default- Default system behavior (terminates the process).ignore- Ignores the signal completely (SIG_IGN).handle- Intercepts the signal with a custom function and safely prints a message (using async-signal-safewrite).mask- Blocks (masks) the signal, checks for its presence in the pending queue (sigpending), and unblocks it later.
A monolithic program executing a 20-second loop. At the 5th and 15th seconds, the program sends the SIGUSR1 signal to itself. At the 10th second, it checks the pending signals queue and unblocks the signal if it was previously masked.
Separation of logic into two independent processes. The main program (parent) creates a new process (fork) and replaces its image (exec) with the child program. The parent uses sigqueue to send a SIGUSR2 signal containing a payload (the chosen operation mode) to the child. This triggers the appropriate mask and handler configuration in the child process.
Refactoring the signal handling functions into separate modules (sig_default.c, sig_mask.c, etc.). Implementation of a Makefile that builds the project in three different architectures:
- Static (
.a) - Compiled directly into the executable. - Shared (
.so) - Linked at compile time (utilizingrpath). - Dynamically Loaded - Using preprocessor directives (
-DUSE_DYNAMIC) and the<dlfcn.h>interface to manually open the.solibrary and resolve function symbols at runtime.
- Linux / UNIX operating system (POSIX.1-2008 compliant).
gcccompiler.makebuild automation tool.bashshell (for running the automated test suite).
The project utilizes a smart Makefile that compiles only the modified files and automatically injects appropriate file paths via the CHILD_PATH macro.
Clean the project directory:
make cleanCompile and run the Static version:
make static
./main.out <default|mask|ignore|handle>Compile and run the Shared version:
make shared
./main.out <default|mask|ignore|handle>Compile and run the Dynamically Loaded (dlopen) version:
make dynamic
./main.out <default|mask|ignore|handle>(Note: You can simply run make without arguments to build all 3 variants simultaneously).
The project includes a bash script test.sh that acts as a simple Continuous Integration system. The script:
- Sequentially builds all 3 library variants.
- Runs tests for all 4 operation modes concurrently in the background, resolving stream buffering issues using
stdbuf. - Parses the logs using regular expressions (
grep) to evaluate assertions, outputting a clear[PASS]or[FAIL]. - Manages execution logs in a dedicated, temporary
logs/directory.
Running the test suite:
chmod +x test.sh # (Required only once)
./test.shThe following system-level programming practices and mechanisms were successfully implemented in this project:
- Mitigating Race Conditions between
execcalls and signal delivery through synchronization and process sleeping. - Async-Signal-Safety: Replacing standard
printffunctions with low-levelwritesystem calls inside signal handlers, combined with manual buffer formatting. - Understanding kernel-level behavior regarding the restoration of signal masks upon returning from a handler.
- Configuring the
rpath($ORIGIN) for proper local shared object resolution without polluting system directories.