Loading content...
In the previous module, we explored anonymous pipes—the fundamental IPC mechanism that allows parent and child processes to communicate through a unidirectional byte stream. While powerful for related processes, anonymous pipes have a critical limitation: they exist only in kernel memory, invisible to the filesystem, and cannot be accessed by unrelated processes.
Named Pipes, also known as FIFOs (First-In, First-Out), solve this problem by creating a special file in the filesystem that multiple processes can open, regardless of their ancestry relationships. When you create a FIFO, you create a rendezvous point—a named channel that any process with appropriate permissions can use for communication.
This architectural shift from in-memory-only pipes to filesystem-visible pipes opens up entirely new patterns for process coordination, service communication, and system design.
By the end of this page, you will understand:
• The fundamental difference between anonymous pipes and named pipes (FIFOs)
• How to create FIFOs using both the mkfifo command and the mkfifo() system call
• The kernel data structures and mechanisms underlying FIFO creation
• Permission models and ownership semantics for FIFOs
• Best practices for robust FIFO creation in production systems
A Named Pipe (FIFO) is a special type of file that provides a communication channel between processes. Unlike regular files, data written to a FIFO does not persist on disk—it exists only in a kernel buffer until another process reads it. The name "FIFO" reflects its First-In, First-Out semantics: bytes are read in the same order they were written.
The key insight is that a FIFO appears in the filesystem like any other file. It has a path, permissions, ownership, and can be listed with ls. But its behavior is fundamentally different from regular files:
| Characteristic | Anonymous Pipe | Named Pipe (FIFO) |
|---|---|---|
| Filesystem presence | None—exists only in kernel memory | Has a filesystem path (inode entry) |
| Creation mechanism | pipe() system call only | mkfifo() syscall or mkfifo command |
| Process relationship | Requires common ancestor (typically parent-child) | Any process with appropriate permissions |
| Persistence after creation | Destroyed when all file descriptors closed | File entry persists until explicitly deleted |
| Discovery mechanism | File descriptors inherited across fork() | Processes open the named path independently |
| Typical use case | Pipeline composition (shell |) | Client-server patterns, logging systems |
| Cleanup required | Automatic when processes exit | Explicit removal with unlink() or rm |
FIFOs belong to Unix's elegant abstraction of "everything is a file." Along with devices (/dev/*), sockets, and symbolic links, FIFOs are special files that implement file operations (open, read, write, close) but with non-file-like behavior. When you ls -l a FIFO, you'll see 'p' as the first character of its permissions.
The simplest way to create a FIFO is using the mkfifo command from the shell. This is the standard utility for creating named pipes on Unix-like systems and is available on all POSIX-compliant operating systems.
Basic Syntax:
mkfifo [OPTIONS] NAME...
The command creates a FIFO with the specified name. If no mode (permissions) is specified, the default is 0666 (read/write for all), modified by the process's umask.
1234567891011121314151617181920212223242526
# Create a simple FIFOmkfifo my_pipe # Create a FIFO with explicit permissions (rw for owner only)mkfifo -m 600 secure_pipe # Create multiple FIFOs at oncemkfifo pipe1 pipe2 pipe3 # Create a FIFO in a specific directorymkfifo /tmp/my_application/request_pipe # Inspect the created FIFOls -l my_pipe# Output: prw-r--r-- 1 user group 0 Jan 16 10:30 my_pipe# ^ 'p' indicates this is a pipe (FIFO) # View file type using statstat my_pipe# Output includes: File: my_pipe# Size: 0# Type: fifo # View file type using file commandfile my_pipe# Output: my_pipe: fifo (named pipe)Understanding the ls -l output:
When you list a FIFO with ls -l, several fields reveal its nature:
Type indicator (p): The first character is 'p' for pipe—not 'd' (directory), '-' (regular file), or 'l' (symbolic link).
Permissions: Standard Unix permissions apply. A process needs read permission to open for reading, write permission to open for writing.
Size (always 0): FIFOs report size as 0 because they don't store data—they're channels for data in transit.
Link count (typically 1): FIFOs can have hard links like regular files, though this is rarely useful.
Common Options:
-m, --mode=MODE: Set file permission bits to MODE, not a=rw - umask. MODE is specified in octal (like 600) or symbolic (like u=rw) notation.-Z: Set SELinux security context of each created FIFO to the default type.--context[=CTX]: Set SELinux security context. If CTX is specified, use it; otherwise, use the default.--help: Display help message and exit.--version: Output version information and exit.The mkfifo command will fail (return non-zero) if:
• Path already exists: Unlike regular files, mkfifo won't overwrite existing files or FIFOs • Permission denied: No write permission on the parent directory • Parent directory doesn't exist: Must create parent directories first • Filesystem doesn't support FIFOs: Some filesystems (FAT, some network filesystems) don't support special files • Disk quota exceeded: If quotas are enforced on the filesystem
For applications that need to create FIFOs dynamically, the mkfifo() system call (actually a library function that wraps mknod()) provides programmatic FIFO creation.
Function Signature:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
Parameters:
pathname: Path where the FIFO should be createdmode: Permission bits for the new FIFO (modified by umask)Return Value:
0 on success-1 on failure, with errno set to indicate the error123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <unistd.h> /** * Creates a FIFO with comprehensive error handling. * * This function demonstrates production-quality FIFO creation, * handling all common error cases appropriately. * * @param fifo_path Path for the new FIFO * @param mode Permission bits (e.g., 0666) * @return 0 on success, -1 on failure */int create_fifo_robust(const char *fifo_path, mode_t mode) { // Attempt to create the FIFO if (mkfifo(fifo_path, mode) == 0) { printf("FIFO created successfully: %s\n", fifo_path); return 0; } // Handle specific error cases switch (errno) { case EEXIST: // FIFO or file already exists at this path // Check if it's actually a FIFO we can use struct stat st; if (stat(fifo_path, &st) == 0 && S_ISFIFO(st.st_mode)) { printf("FIFO already exists, reusing: %s\n", fifo_path); return 0; // Existing FIFO is acceptable } fprintf(stderr, "Error: Path exists but is not a FIFO: %s\n", fifo_path); return -1; case ENOENT: // A directory component doesn't exist fprintf(stderr, "Error: Parent directory does not exist: %s\n", fifo_path); return -1; case EACCES: // Permission denied on parent directory fprintf(stderr, "Error: Permission denied creating FIFO: %s\n", fifo_path); return -1; case EROFS: // Filesystem is read-only fprintf(stderr, "Error: Filesystem is read-only: %s\n", fifo_path); return -1; case ENOSPC: // No space left on device fprintf(stderr, "Error: No space left on device: %s\n", fifo_path); return -1; case ENAMETOOLONG: // Pathname too long fprintf(stderr, "Error: Pathname too long: %s\n", fifo_path); return -1; default: // Generic error handling fprintf(stderr, "Error creating FIFO '%s': %s\n", fifo_path, strerror(errno)); return -1; }} /** * Demonstrates FIFO creation with cleanup registration. * Uses atexit() to ensure cleanup on normal program exit. */static const char *cleanup_fifo_path = NULL; void cleanup_fifo(void) { if (cleanup_fifo_path != NULL) { unlink(cleanup_fifo_path); printf("Cleaned up FIFO: %s\n", cleanup_fifo_path); }} int main(int argc, char *argv[]) { const char *fifo_path = "/tmp/example_fifo"; mode_t fifo_mode = 0666; // rw-rw-rw- (modified by umask) // Register cleanup handler cleanup_fifo_path = fifo_path; atexit(cleanup_fifo); // Create the FIFO if (create_fifo_robust(fifo_path, fifo_mode) != 0) { return EXIT_FAILURE; } // Verify creation by examining file type struct stat st; if (stat(fifo_path, &st) == 0) { printf("File type: %s\n", S_ISFIFO(st.st_mode) ? "FIFO (named pipe)" : "Other"); printf("Permissions: %o\n", st.st_mode & 0777); printf("Owner UID: %d\n", st.st_uid); printf("Group GID: %d\n", st.st_gid); } printf("FIFO ready for use. Press Enter to cleanup and exit...\n"); getchar(); return EXIT_SUCCESS;}Understanding the Code:
This example demonstrates several important patterns:
Comprehensive error handling: Each potential failure mode is handled specifically, providing meaningful error messages for debugging.
EEXIST handling: A particularly common case—if the FIFO already exists and is usable, we treat this as success. This pattern enables idempotent service startup.
Cleanup registration: Using atexit() ensures the FIFO is removed when the program exits normally. For robust cleanup, you'd also need signal handlers for abnormal termination.
Post-creation verification: Using stat() with S_ISFIFO() macro confirms the created file is indeed a FIFO.
The S_ISFIFO(mode) macro from <sys/stat.h> tests whether a file's mode indicates it's a FIFO. Other useful macros include:
• S_ISREG(m) — is it a regular file?
• S_ISDIR(m) — is it a directory?
• S_ISCHR(m) — is it a character device?
• S_ISBLK(m) — is it a block device?
• S_ISLNK(m) — is it a symbolic link?
• S_ISSOCK(m) — is it a socket?
The mkfifo() function is actually a convenience wrapper around the more general mknod() system call. Understanding mknod() provides deeper insight into how the kernel handles special file creation.
Function Signature:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
Parameters:
pathname: Path for the new special filemode: Permission bits OR'ed with file type (e.g., S_IFIFO | 0666)dev: Device number (ignored for FIFOs, used for device files)How mkfifo() Uses mknod():
Internally, mkfifo(path, mode) is typically implemented as:
mknod(path, (mode & ~S_IFMT) | S_IFIFO, 0)
This masks off any file type bits in mode, OR's in S_IFIFO (the FIFO type indicator), and passes 0 for the device number.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>#include <string.h> /** * Creates a FIFO using mknod() directly. * This demonstrates the underlying system call that mkfifo() wraps. */int create_fifo_with_mknod(const char *path, mode_t mode) { // S_IFIFO = 0010000 in octal (FIFO special file type) // We OR this with the permission bits to create the full mode mode_t full_mode = S_IFIFO | (mode & 0777); // dev parameter is ignored for FIFOs (no device backing) if (mknod(path, full_mode, 0) == -1) { fprintf(stderr, "mknod failed: %s\n", strerror(errno)); return -1; } printf("FIFO created via mknod(): %s\n", path); return 0;} /** * Compares mkfifo() and mknod() for FIFO creation. */int main(void) { const char *path_mkfifo = "/tmp/fifo_via_mkfifo"; const char *path_mknod = "/tmp/fifo_via_mknod"; // Clean up any previous runs unlink(path_mkfifo); unlink(path_mknod); // Create using mkfifo() printf("Creating FIFO using mkfifo()...\n"); if (mkfifo(path_mkfifo, 0666) == -1) { perror("mkfifo"); } // Create using mknod() printf("Creating FIFO using mknod()...\n"); if (create_fifo_with_mknod(path_mknod, 0666) == -1) { perror("mknod"); } // Verify both are FIFOs struct stat st1, st2; stat(path_mkfifo, &st1); stat(path_mknod, &st2); printf("\nVerification:\n"); printf("%s is FIFO: %s\n", path_mkfifo, S_ISFIFO(st1.st_mode) ? "yes" : "no"); printf("%s is FIFO: %s\n", path_mknod, S_ISFIFO(st2.st_mode) ? "yes" : "no"); // Cleanup unlink(path_mkfifo); unlink(path_mknod); return 0;}While mkfifo() is preferred for creating FIFOs due to its clarity and portability, mknod() is occasionally used when:
• Creating multiple types of special files in the same code path
• Working with older systems where mkfifo() might not be available
• Implementing low-level system utilities that need fine-grained control
For application code, always prefer mkfifo()—it's cleaner and more clearly expresses intent.
Understanding what happens inside the kernel when a FIFO is created illuminates why FIFOs behave the way they do. When mkfifo() is called, a carefully orchestrated sequence of kernel operations occurs:
Step-by-Step FIFO Creation:
S_IFIFO (pipe/FIFO type), distinguishing it from regular files, directories, etc.The Pipe Inode Structure:
In the Linux kernel, a FIFO's inode contains a pointer to a pipe_inode_info structure, which manages:
Key Insight: The FIFO's filesystem presence (inode + directory entry) is merely a naming mechanism. The actual communication infrastructure is pure kernel memory, identical to anonymous pipes. This is why FIFO size is always reported as 0—there's no on-disk data.
A common misconception is that FIFOs write data to disk. They don't. The filesystem entry is just a name and permissions—data flows through kernel memory buffers exactly as with anonymous pipes. This is why FIFOs are fast (no disk I/O for data) but volatile (data vanishes when readers disconnect or system reboots).
FIFOs inherit the Unix permission model, but their permissions control access differently than regular files. Understanding these semantics is crucial for secure FIFO-based IPC.
Permission Semantics for FIFOs:
| Permission | Effect on FIFO |
|---|---|
| Read (r) | Process can open FIFO for reading |
| Write (w) | Process can open FIFO for writing |
| Execute (x) | No effect—FIFOs cannot be executed |
Ownership Semantics:
chown() and chmod() with appropriate privileges123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h>#include <string.h> /** * Demonstrates various FIFO permission patterns for different use cases. */void demonstrate_permission_patterns(void) { // Pattern 1: World-accessible FIFO (least secure) // Typical for simple examples, NOT for production printf("Pattern 1: World-accessible (0666)\n"); unlink("/tmp/fifo_world"); if (mkfifo("/tmp/fifo_world", 0666) == 0) { system("ls -l /tmp/fifo_world"); } // Pattern 2: Owner-only FIFO (most secure) // Good for single-user applications printf("\nPattern 2: Owner-only (0600)\n"); unlink("/tmp/fifo_private"); if (mkfifo("/tmp/fifo_private", 0600) == 0) { system("ls -l /tmp/fifo_private"); } // Pattern 3: Group-shared FIFO // Useful for service communication within same group printf("\nPattern 3: Group-shared (0660)\n"); unlink("/tmp/fifo_group"); if (mkfifo("/tmp/fifo_group", 0660) == 0) { system("ls -l /tmp/fifo_group"); } // Pattern 4: Write-only to others // Good for logging: others can write, only owner can read printf("\nPattern 4: Public write, owner read (0622)\n"); unlink("/tmp/fifo_logger"); if (mkfifo("/tmp/fifo_logger", 0622) == 0) { system("ls -l /tmp/fifo_logger"); } // Cleanup unlink("/tmp/fifo_world"); unlink("/tmp/fifo_private"); unlink("/tmp/fifo_group"); unlink("/tmp/fifo_logger");} /** * Shows how umask affects FIFO permissions. */void demonstrate_umask_effect(void) { printf("\n=== Umask Effects ===\n"); // Get current umask mode_t old_umask = umask(0); umask(old_umask); // Restore printf("Current umask: %04o\n", old_umask); // Create FIFO with current umask unlink("/tmp/fifo_umask_test"); mkfifo("/tmp/fifo_umask_test", 0666); struct stat st; stat("/tmp/fifo_umask_test", &st); printf("Requested: 0666, Actual: %04o\n", st.st_mode & 0777); printf("Expected: %04o (0666 & ~umask)\n", 0666 & ~old_umask); // Create with different umask umask(0077); // Very restrictive unlink("/tmp/fifo_umask_test2"); mkfifo("/tmp/fifo_umask_test2", 0666); stat("/tmp/fifo_umask_test2", &st); printf("\nWith umask 0077:\n"); printf("Requested: 0666, Actual: %04o\n", st.st_mode & 0777); // Restore original umask umask(old_umask); // Cleanup unlink("/tmp/fifo_umask_test"); unlink("/tmp/fifo_umask_test2");} /** * Changes FIFO ownership (requires appropriate privileges). */void demonstrate_ownership_change(void) { printf("\n=== Ownership Changes ===\n"); unlink("/tmp/fifo_chown"); if (mkfifo("/tmp/fifo_chown", 0666) != 0) { perror("mkfifo"); return; } struct stat st; stat("/tmp/fifo_chown", &st); printf("Original owner: UID=%d, GID=%d\n", st.st_uid, st.st_gid); // Change group (may require group membership or root) // This example shows the API; actual effect depends on privileges if (chown("/tmp/fifo_chown", -1, getgid()) == 0) { stat("/tmp/fifo_chown", &st); printf("After chown: UID=%d, GID=%d\n", st.st_uid, st.st_gid); } else { printf("chown: %s (this is normal for non-root)\n", strerror(errno)); } // Change permissions chmod("/tmp/fifo_chown", 0640); stat("/tmp/fifo_chown", &st); printf("After chmod 0640: mode=%04o\n", st.st_mode & 0777); unlink("/tmp/fifo_chown");} int main(void) { demonstrate_permission_patterns(); demonstrate_umask_effect(); demonstrate_ownership_change(); return 0;}Common Security Pitfalls:
• World-writable FIFOs: Allow any user to write data, potentially causing DoS or injection attacks • Predictable names in /tmp: Race conditions allow attackers to create symlinks before your FIFO (symlink attacks) • Ignoring return values: Not checking open() failures can lead to unexpected behavior
Mitigations:
• Use restrictive permissions (0600 or 0660)
• Create FIFOs in application-specific directories with proper permissions
• Use mkdtemp() for ephemeral FIFO directories
• Validate opened file is actually a FIFO using fstat() + S_ISFIFO()
Where you create your FIFO significantly impacts security, reliability, and system integration. Different placements suit different use cases:
Common FIFO Locations:
| Location | Use Case | Pros | Cons |
|---|---|---|---|
/tmp | Temporary, ephemeral FIFOs | World-writable, always exists, no setup needed | Security risks, cluttered namespace, cleaned on reboot |
/var/run or /run | System services, daemons | Standard location for runtime data, proper permissions | Requires root or proper group membership, cleaned on reboot |
| Application-specific directory | Production applications | Full control over permissions, organized namespace | Must create directory, ensure persistence across restarts |
| User home directory | User-level applications | User owns it, persistent, private by default | Path varies per user, may have quotas |
$XDG_RUNTIME_DIR | Modern desktop applications | Per-user, secure, standard location for runtime files | Only available in desktop environments with systemd |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <unistd.h>#include <pwd.h>#include <errno.h> /** * Creates a FIFO in a secure, application-specific directory. * This pattern is recommended for production applications. */int create_application_fifo(const char *app_name, const char *fifo_name, char *fifo_path_out, size_t path_size) { char dir_path[256]; // Strategy 1: Use XDG_RUNTIME_DIR if available (modern Linux) const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); if (runtime_dir != NULL) { snprintf(dir_path, sizeof(dir_path), "%s/%s", runtime_dir, app_name); } else { // Fallback: Create in /tmp with unique directory per user struct passwd *pw = getpwuid(getuid()); if (pw == NULL) { perror("getpwuid"); return -1; } snprintf(dir_path, sizeof(dir_path), "/tmp/%s-%s", app_name, pw->pw_name); } // Create the application directory if it doesn't exist if (mkdir(dir_path, 0700) == -1 && errno != EEXIST) { fprintf(stderr, "Failed to create directory %s: %s\n", dir_path, strerror(errno)); return -1; } // Verify directory ownership (security check) struct stat st; if (stat(dir_path, &st) != 0 || st.st_uid != getuid()) { fprintf(stderr, "Security error: directory %s not owned by us\n", dir_path); return -1; } // Build FIFO path snprintf(fifo_path_out, path_size, "%s/%s", dir_path, fifo_name); // Remove any existing FIFO (to handle stale FIFOs from crashed runs) unlink(fifo_path_out); // Create the FIFO with restrictive permissions if (mkfifo(fifo_path_out, 0600) == -1) { fprintf(stderr, "Failed to create FIFO %s: %s\n", fifo_path_out, strerror(errno)); return -1; } printf("Created secure FIFO: %s\n", fifo_path_out); return 0;} /** * Example usage: Multi-instance safe FIFO with PID suffix */int create_instance_fifo(const char *base_name, char *fifo_path_out, size_t path_size) { // Include PID so multiple instances don't conflict snprintf(fifo_path_out, path_size, "/tmp/%s.%d.fifo", base_name, getpid()); unlink(fifo_path_out); if (mkfifo(fifo_path_out, 0600) == -1) { perror("mkfifo"); return -1; } printf("Created instance-specific FIFO: %s\n", fifo_path_out); return 0;} int main(void) { char fifo_path[256]; printf("=== Production FIFO Placement ===\n\n"); // Production pattern if (create_application_fifo("myapp", "control.fifo", fifo_path, sizeof(fifo_path)) == 0) { printf("Use this FIFO: %s\n", fifo_path); unlink(fifo_path); // Cleanup demo } printf("\n=== Instance-Specific FIFO ===\n\n"); // Multi-instance pattern if (create_instance_fifo("worker", fifo_path, sizeof(fifo_path)) == 0) { unlink(fifo_path); // Cleanup demo } return 0;}For production applications, always create FIFOs in dedicated, application-controlled directories:
/var/run/myapp/ (for system services) or $XDG_RUNTIME_DIR/myapp/ (for user apps)This pattern provides isolation, easy cleanup, and strong security.
Unlike anonymous pipes that disappear when all file descriptors close, FIFOs persist in the filesystem until explicitly removed. Proper lifecycle management prevents accumulation of stale FIFOs.
Key Lifecycle Considerations:
/etc/tmpfiles.d/ for automatic creation and cleanup by system123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h> // Global FIFO path for cleanup handlersstatic char g_fifo_path[256] = {0};static volatile sig_atomic_t g_shutdown_requested = 0; /** * Signal handler for graceful shutdown. * Sets flag rather than doing cleanup directly (signal safety). */void signal_handler(int signum) { g_shutdown_requested = 1; // Re-raise to get default behavior after cleanup signal(signum, SIG_DFL);} /** * atexit cleanup handler. * Called on normal exit() or return from main(). */void cleanup_atexit(void) { if (g_fifo_path[0] != '\0') { if (unlink(g_fifo_path) == 0) { printf("Cleaned up FIFO (atexit): %s\n", g_fifo_path); } }} /** * Perform cleanup from main loop when signal received. */void perform_signal_cleanup(void) { if (g_fifo_path[0] != '\0') { if (unlink(g_fifo_path) == 0) { printf("Cleaned up FIFO (signal): %s\n", g_fifo_path); } }} /** * Check if a stale FIFO exists and can be cleaned up. */int cleanup_stale_fifo(const char *fifo_path, const char *pid_file_path) { struct stat st; // Check if FIFO exists if (stat(fifo_path, &st) != 0) { return 0; // Doesn't exist, nothing to clean } if (!S_ISFIFO(st.st_mode)) { fprintf(stderr, "Error: %s exists but is not a FIFO\n", fifo_path); return -1; } // If we have a PID file, check if the process is still running if (pid_file_path != NULL) { FILE *pf = fopen(pid_file_path, "r"); if (pf != NULL) { int old_pid; if (fscanf(pf, "%d", &old_pid) == 1) { fclose(pf); // Check if process exists if (kill(old_pid, 0) == 0 || errno == EPERM) { // Process exists! Don't remove its FIFO fprintf(stderr, "Error: Another instance (PID %d) appears to be running\n", old_pid); return -1; } } else { fclose(pf); } } } // Safe to remove stale FIFO if (unlink(fifo_path) == 0) { printf("Removed stale FIFO: %s\n", fifo_path); } return 0;} /** * Complete FIFO setup with lifecycle management. */int setup_fifo_with_lifecycle(const char *fifo_path) { // Copy path for cleanup handlers strncpy(g_fifo_path, fifo_path, sizeof(g_fifo_path) - 1); // Register atexit handler atexit(cleanup_atexit); // Register signal handlers struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGHUP, &sa, NULL); // Clean up any stale FIFO cleanup_stale_fifo(fifo_path, NULL); // Create the FIFO if (mkfifo(fifo_path, 0600) == -1) { perror("mkfifo"); return -1; } printf("FIFO created with lifecycle management: %s\n", fifo_path); return 0;} int main(void) { const char *fifo_path = "/tmp/lifecycle_demo.fifo"; if (setup_fifo_with_lifecycle(fifo_path) != 0) { return EXIT_FAILURE; } printf("\nFIFO is ready. Press Ctrl+C to demonstrate cleanup...\n"); // Main loop while (!g_shutdown_requested) { sleep(1); } // Signal was received, perform cleanup perform_signal_cleanup(); printf("Graceful shutdown complete.\n"); return EXIT_SUCCESS;}For system services on Linux with systemd, you can declare FIFOs in /etc/tmpfiles.d/myapp.conf:
p /run/myapp/control.fifo 0660 myuser mygroup -
This creates the FIFO with proper ownership on boot, and integrates with systemd's lifecycle management. The FIFO is automatically recreated across reboots and cleaned up when the configuration is removed.
Creating FIFOs correctly requires understanding both the mechanisms and the operational considerations. Let's consolidate the key principles:
mkfifo() for programmatic creation: It's cleaner and more portable than mknod() for creating FIFOs.stat() + S_ISFIFO()./tmp.atexit(), signal handlers, and startup cleanup to prevent stale FIFOs.What's Next:
Now that we understand how to create FIFOs, the next page explores their primary value proposition: enabling communication between unrelated processes. We'll examine the open semantics, synchronization patterns, and practical examples of FIFO-based IPC between independent programs.
You now understand the mechanics of FIFO creation—from shell commands to system calls to kernel internals. You can create production-quality FIFOs with appropriate permissions, secure placement, and proper lifecycle management. Next, we'll explore how FIFOs enable the powerful pattern of unrelated process communication.