Domain Track
Difficulty 3/5Systems Programming in C++
C++ for systems programming — POSIX APIs, process management, signals, memory-mapped files, sockets, file descriptors, and Linux-specific facilities.
TL;DR
Systems programming in C++ means wrapping POSIX/Linux C APIs with RAII. Use std::system_error for error reporting. Always check return values; use errno via std::errc.
RAII File Descriptor
cpp
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>
#include <system_error>
class FileDescriptor {
int fd_ = -1;
public:
FileDescriptor() = default;
explicit FileDescriptor(int fd) : fd_{fd} {}
// Non-copyable, movable
FileDescriptor(const FileDescriptor&) = delete;
FileDescriptor& operator=(const FileDescriptor&) = delete;
FileDescriptor(FileDescriptor&& o) noexcept : fd_{std::exchange(o.fd_, -1)} {}
FileDescriptor& operator=(FileDescriptor&& o) noexcept {
if (this != &o) { close(); fd_ = std::exchange(o.fd_, -1); }
return *this;
}
~FileDescriptor() { close(); }
int get() const { return fd_; }
bool valid() const { return fd_ >= 0; }
int release() { return std::exchange(fd_, -1); }
private:
void close() { if (fd_ >= 0) ::close(fd_); }
};
// Usage
FileDescriptor fd{::open("/etc/hosts", O_RDONLY)};
if (!fd.valid()) throw std::system_error(errno, std::generic_category());
char buf[1024];
ssize_t n = ::read(fd.get(), buf, sizeof(buf));
// fd closed automatically at end of scopeProcess Management
cpp
#include <unistd.h>
#include <sys/wait.h>
// Fork a child process
pid_t child = fork();
if (child < 0) {
throw std::system_error(errno, std::generic_category(), "fork");
} else if (child == 0) {
// Child process
execl("/usr/bin/ls", "ls", "-la", nullptr);
_exit(1); // exec failed
} else {
// Parent process: wait for child
int status;
waitpid(child, &status, 0);
if (WIFEXITED(status))
std::println("child exited: {}", WEXITSTATUS(status));
}
// Run a command and capture output (popen)
FILE* pipe = popen("date", "r");
if (!pipe) throw std::runtime_error("popen failed");
char line[256];
while (fgets(line, sizeof(line), pipe))
std::print("{}", line);
pclose(pipe);Memory-Mapped Files
cpp
#include <sys/mman.h>
#include <sys/stat.h>
class MappedFile {
void* addr_ = MAP_FAILED;
size_t size_ = 0;
public:
MappedFile(const char* path, bool writable = false) {
int flags = writable ? O_RDWR : O_RDONLY;
FileDescriptor fd{::open(path, flags)};
if (!fd.valid()) throw std::system_error(errno, std::generic_category());
struct stat st;
if (fstat(fd.get(), &st) < 0)
throw std::system_error(errno, std::generic_category());
size_ = st.st_size;
int prot = PROT_READ | (writable ? PROT_WRITE : 0);
addr_ = mmap(nullptr, size_, prot, MAP_SHARED, fd.get(), 0);
if (addr_ == MAP_FAILED)
throw std::system_error(errno, std::generic_category());
}
~MappedFile() { if (addr_ != MAP_FAILED) munmap(addr_, size_); }
MappedFile(const MappedFile&) = delete;
MappedFile& operator=(const MappedFile&) = delete;
std::span<const std::byte> bytes() const {
return {static_cast<const std::byte*>(addr_), size_};
}
std::string_view str() const {
return {static_cast<const char*>(addr_), size_};
}
size_t size() const { return size_; }
};
// Parse a large file without reading it into memory
MappedFile f{"/var/log/app.log"};
auto content = f.str();
// Process content as string_view — no copyPOSIX Sockets
cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// TCP server
int setup_server(uint16_t port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) throw std::system_error(errno, std::generic_category());
// Allow reuse of port immediately after close
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(sock, (sockaddr*)&addr, sizeof(addr)) < 0)
throw std::system_error(errno, std::generic_category());
if (listen(sock, SOMAXCONN) < 0)
throw std::system_error(errno, std::generic_category());
return sock;
}
// Accept loop
FileDescriptor server{setup_server(8080)};
while (true) {
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server.get(), (sockaddr*)&client_addr, &client_len);
if (client_fd < 0) continue;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));
std::println("connection from {}", ip);
FileDescriptor client{client_fd};
// handle client in a thread
}Signal Handling
cpp
#include <signal.h>
#include <atomic>
// Use atomic for signal-handler communication — signal-safe
std::atomic<bool> g_running{true};
// Simple signal handler
extern "C" void on_sigint(int) {
g_running.store(false, std::memory_order_relaxed);
}
int main() {
struct sigaction sa{};
sa.sa_handler = on_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; // restart interrupted syscalls
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
while (g_running.load(std::memory_order_relaxed)) {
// main loop
}
std::println("shutting down");
}
// Block signals in threads, handle in a dedicated signal thread
void signal_thread_setup() {
sigset_t mask;
sigfillset(&mask);
pthread_sigmask(SIG_BLOCK, &mask, nullptr); // block all in this thread
// Then create a thread that calls sigwait() to handle signals
}Pipes and IPC
cpp
#include <unistd.h>
// Anonymous pipe (parent-child communication)
int pipefd[2];
if (pipe(pipefd) < 0) throw std::system_error(errno, std::generic_category());
FileDescriptor read_end{pipefd[0]};
FileDescriptor write_end{pipefd[1]};
pid_t child = fork();
if (child == 0) {
// Child: close write end, read from pipe
write_end = {}; // closes write_end
char buf[256];
ssize_t n = read(read_end.get(), buf, sizeof(buf));
// process buf[0..n]
_exit(0);
}
// Parent: close read end, write to pipe
read_end = {}; // closes read_end
const char msg[] = "hello from parent";
write(write_end.get(), msg, sizeof(msg));
write_end = {}; // close to send EOF
waitpid(child, nullptr, 0);epoll — Scalable I/O Multiplexing
cpp
#include <sys/epoll.h>
class EventLoop {
int epfd_;
std::unordered_map<int, std::function<void(uint32_t)>> handlers_;
public:
EventLoop() {
epfd_ = epoll_create1(EPOLL_CLOEXEC);
if (epfd_ < 0) throw std::system_error(errno, std::generic_category());
}
~EventLoop() { ::close(epfd_); }
void add(int fd, uint32_t events, std::function<void(uint32_t)> cb) {
handlers_[fd] = std::move(cb);
epoll_event ev{};
ev.events = events;
ev.data.fd = fd;
epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);
}
void run() {
std::array<epoll_event, 64> events;
while (true) {
int n = epoll_wait(epfd_, events.data(), events.size(), -1);
for (int i = 0; i < n; ++i) {
int fd = events[i].data.fd;
if (auto it = handlers_.find(fd); it != handlers_.end())
it->second(events[i].events);
}
}
}
};
EventLoop loop;
loop.add(server_fd, EPOLLIN, [&](uint32_t events) {
int client = accept(server_fd, nullptr, nullptr);
loop.add(client, EPOLLIN | EPOLLET, [client](uint32_t ev) {
char buf[4096];
ssize_t n = read(client, buf, sizeof(buf));
if (n <= 0) { ::close(client); return; }
write(client, buf, n); // echo
});
});
loop.run();