From 778c4ffbafeee25b52bd344cf6f5236b1cfa3bde Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 20:52:32 +0200 Subject: [PATCH 01/17] Added .gitignore for lab6 --- lab6/.gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lab6/.gitignore diff --git a/lab6/.gitignore b/lab6/.gitignore new file mode 100644 index 0000000..3e14ee6 --- /dev/null +++ b/lab6/.gitignore @@ -0,0 +1,9 @@ +# Object files +obj/*.o + +# Executables +bin/*.out + +# Generated output files (simulated images, logs, reports) +*.jpg +*.txt \ No newline at end of file From fd70172f9d03605f5cf0747e513ee7e1d8dbfca7 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 20:53:51 +0200 Subject: [PATCH 02/17] Added a common.h header file --- lab6/src/common.h | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 lab6/src/common.h diff --git a/lab6/src/common.h b/lab6/src/common.h new file mode 100644 index 0000000..e753984 --- /dev/null +++ b/lab6/src/common.h @@ -0,0 +1,56 @@ +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include + +// --- SYSTEM CONSTANTS --- +#define CAMERA_FREQ_HZ 25 +#define ROBOT_FREQ_HZ 100 +#define LOG_FREQ_HZ 10 +#define SYNC_TOLERANCE_MS 20 // Max time difference for stereo matching (20 ms) +#define BUFFER_SIZE 64 // Circular buffer size + +// --- DATA STRUCTURES --- + +// Single camera frame structure +typedef struct { + int frame_id; // Sequential frame number + struct timespec timestamp; // Time of capture +} frame_t; + +// Robot state structure +typedef struct { + double x; + double y; + double theta; // Orientation + struct timespec timestamp; // State generation time +} robot_state_t; + +// Circular buffer structure (used from Task 2) +typedef struct { + frame_t buffer[BUFFER_SIZE]; + int head; // Write index + int tail; // Read index + int count; // Current number of elements + + // Synchronization mechanisms (Task 2) + pthread_mutex_t mutex; + pthread_cond_t not_empty; + pthread_cond_t not_full; +} frame_buffer_t; + +// --- UTILITY FUNCTION DECLARATIONS --- + +// Calculates time difference in milliseconds between two timestamps +double time_diff_ms(struct timespec start, struct timespec end); + +// Gets current time using CLOCK_MONOTONIC +void get_current_time(struct timespec *ts); + +// Precise thread sleep for a given frequency (e.g., 25 Hz) +void sleep_for_freq(int freq_hz); + +#endif // COMMON_H From 0c484c2c206a9a7be5f553f2af719489aaaee5f5 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 20:54:40 +0200 Subject: [PATCH 03/17] Added a Makefile --- lab6/Makefile | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lab6/Makefile diff --git a/lab6/Makefile b/lab6/Makefile new file mode 100644 index 0000000..87b9e56 --- /dev/null +++ b/lab6/Makefile @@ -0,0 +1,47 @@ +CC = gcc +# Flags: warnings, POSIX threads support, debugging, and include directory +CFLAGS = -Wall -Wextra -pthread -g -I$(INCDIR) + +# Directories +INCDIR = include +SRCDIR = src +OBJDIR = obj +BINDIR = bin + +# Create directories if they do not exist (useful for WSL/Linux) +$(shell mkdir -p $(OBJDIR) $(BINDIR)) + +# Default target +all: $(BINDIR)/level1.out $(BINDIR)/level2.out $(BINDIR)/level3.out + +# --- Object files compilation --- + +$(OBJDIR)/common.o: $(SRCDIR)/common.c $(INCDIR)/common.h + $(CC) $(CFLAGS) -c $(SRCDIR)/common.c -o $(OBJDIR)/common.o + +$(OBJDIR)/program1.o: $(SRCDIR)/program1.c $(INCDIR)/common.h + $(CC) $(CFLAGS) -DLEVEL1 -c $(SRCDIR)/program1.c -o $(OBJDIR)/program1.o + +$(OBJDIR)/program2.o: $(SRCDIR)/program2.c $(INCDIR)/common.h + $(CC) $(CFLAGS) -DLEVEL2 -c $(SRCDIR)/program2.c -o $(OBJDIR)/program2.o + +$(OBJDIR)/program3.o: $(SRCDIR)/program3.c $(INCDIR)/common.h + $(CC) $(CFLAGS) -DLEVEL3 -c $(SRCDIR)/program3.c -o $(OBJDIR)/program3.o + +# --- Linking executables --- + +$(BINDIR)/level1.out: $(OBJDIR)/common.o $(OBJDIR)/program1.o + $(CC) $(CFLAGS) $^ -o $@ + +$(BINDIR)/level2.out: $(OBJDIR)/common.o $(OBJDIR)/program2.o + $(CC) $(CFLAGS) $^ -o $@ + +$(BINDIR)/level3.out: $(OBJDIR)/common.o $(OBJDIR)/program3.o + $(CC) $(CFLAGS) $^ -o $@ + +# --- Cleanup --- +clean: + rm -rf $(OBJDIR)/* $(BINDIR)/* + rm -f *.jpg *.txt + +.PHONY: all clean From e85bf4ec02dd9360b78569a1093e908816ea4bf6 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 20:58:12 +0200 Subject: [PATCH 04/17] Implemented common.c and fixed the missing #DEFINE regarding POSIX standard in common.h --- lab6/src/common.c | 38 ++++++++++++++++++++++++++++++++++++++ lab6/src/common.h | 2 ++ 2 files changed, 40 insertions(+) create mode 100644 lab6/src/common.c diff --git a/lab6/src/common.c b/lab6/src/common.c new file mode 100644 index 0000000..b2e6be1 --- /dev/null +++ b/lab6/src/common.c @@ -0,0 +1,38 @@ +#include "common.h" +#include +#include +#include + +// Calculates time difference in milliseconds between two timestamps +double time_diff_ms(struct timespec start, struct timespec end) { + double start_ms = (double)start.tv_sec * 1000.0 + (double)start.tv_nsec / 1000000.0; + double end_ms = (double)end.tv_sec * 1000.0 + (double)end.tv_nsec / 1000000.0; + + return end_ms - start_ms; +} + +// Gets current time using CLOCK_MONOTONIC to ensure it is not affected by system time changes +void get_current_time(struct timespec *ts) { + if (ts != NULL) { + clock_gettime(CLOCK_MONOTONIC, ts); + } +} + +// Suspends execution of the calling thread to match the desired frequency +void sleep_for_freq(int freq_hz) { + if (freq_hz <= 0) { + return; + } + + // Calculate period duration in nanoseconds + // 1 Hz = 1 second = 1,000,000,000 nanoseconds + long period_ns = 1000000000L / freq_hz; + + struct timespec req; + req.tv_sec = period_ns / 1000000000L; + req.tv_nsec = period_ns % 1000000000L; + + // Sleep for the specified duration using monotonic clock + // We pass 0 as the flags argument for relative sleep, and NULL for the remaining time + clock_nanosleep(CLOCK_MONOTONIC, 0, &req, NULL); +} diff --git a/lab6/src/common.h b/lab6/src/common.h index e753984..b95a942 100644 --- a/lab6/src/common.h +++ b/lab6/src/common.h @@ -1,6 +1,8 @@ #ifndef COMMON_H #define COMMON_H +#define _POSIX_C_SOURCE 200809L + #include #include #include From 5246ebfe53c5d89d1c0fd0ef8ad71e4ef3bb3f8a Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:09:31 +0200 Subject: [PATCH 05/17] Fixed the Makefile it now allows running specified levels. Moved common.h to include/ directory for clarity's sake --- lab6/Makefile | 17 ++++++++++------- lab6/{src => include}/common.h | 0 2 files changed, 10 insertions(+), 7 deletions(-) rename lab6/{src => include}/common.h (100%) diff --git a/lab6/Makefile b/lab6/Makefile index 87b9e56..af86c85 100644 --- a/lab6/Makefile +++ b/lab6/Makefile @@ -1,18 +1,22 @@ CC = gcc -# Flags: warnings, POSIX threads support, debugging, and include directory CFLAGS = -Wall -Wextra -pthread -g -I$(INCDIR) -# Directories INCDIR = include SRCDIR = src OBJDIR = obj BINDIR = bin -# Create directories if they do not exist (useful for WSL/Linux) $(shell mkdir -p $(OBJDIR) $(BINDIR)) -# Default target -all: $(BINDIR)/level1.out $(BINDIR)/level2.out $(BINDIR)/level3.out +.PHONY: all clean level1 level2 level3 + +# Default target alerts the user to build a specific level iteratively +all: + @echo "Please specify a target to build: make level1, make level2, or make level3" + +level1: $(BINDIR)/level1.out +level2: $(BINDIR)/level2.out +level3: $(BINDIR)/level3.out # --- Object files compilation --- @@ -43,5 +47,4 @@ $(BINDIR)/level3.out: $(OBJDIR)/common.o $(OBJDIR)/program3.o clean: rm -rf $(OBJDIR)/* $(BINDIR)/* rm -f *.jpg *.txt - -.PHONY: all clean + \ No newline at end of file diff --git a/lab6/src/common.h b/lab6/include/common.h similarity index 100% rename from lab6/src/common.h rename to lab6/include/common.h From 6febe11dc09f0c258def88bb69b2bda4abb0aedb Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:13:15 +0200 Subject: [PATCH 06/17] Implemented program1, it needs some upgrades, e.g. it for now saves JPGs to home dir of the project --- lab6/src/program1.c | 233 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 lab6/src/program1.c diff --git a/lab6/src/program1.c b/lab6/src/program1.c new file mode 100644 index 0000000..ee16522 --- /dev/null +++ b/lab6/src/program1.c @@ -0,0 +1,233 @@ +#ifdef LEVEL1 + +#include +#include +#include +#include +#include +#include +#include "common.h" + +// --- GLOBAL FLAGS --- +volatile bool is_running = true; + +// --- SHARED DATA & SYNCHRONIZATION --- + +// 1. Cameras <-> Synchronizer +frame_t left_camera_frame; +frame_t right_camera_frame; +pthread_mutex_t camera_mutex; +sem_t left_cam_sem; +sem_t right_cam_sem; + +// 2. Synchronizer <-> Image Writer +frame_t sync_left_frame; +frame_t sync_right_frame; +bool has_new_sync_pair = false; +pthread_mutex_t sync_mutex; + +// 3. Robot State <-> Logger +robot_state_t current_robot_state; +pthread_mutex_t state_mutex; + +// --- THREAD FUNCTIONS --- + +void* left_camera_thread(void* arg) { + (void)arg; // Unused parameter + int id = 1; + + while (is_running) { + pthread_mutex_lock(&camera_mutex); + left_camera_frame.frame_id = id++; + get_current_time(&left_camera_frame.timestamp); + pthread_mutex_unlock(&camera_mutex); + + // Notify synchronizer that a new left frame is ready + sem_post(&left_cam_sem); + + sleep_for_freq(CAMERA_FREQ_HZ); + } + return NULL; +} + +void* right_camera_thread(void* arg) { + (void)arg; + int id = 1; + + while (is_running) { + pthread_mutex_lock(&camera_mutex); + right_camera_frame.frame_id = id++; + get_current_time(&right_camera_frame.timestamp); + pthread_mutex_unlock(&camera_mutex); + + // Notify synchronizer that a new right frame is ready + sem_post(&right_cam_sem); + + sleep_for_freq(CAMERA_FREQ_HZ); + } + return NULL; +} + +void* sync_thread(void* arg) { + (void)arg; + + while (is_running) { + // Wait for both cameras to produce a frame + sem_wait(&left_cam_sem); + sem_wait(&right_cam_sem); + + if (!is_running) break; // Exit cleanly if program is stopping + + pthread_mutex_lock(&camera_mutex); + frame_t left = left_camera_frame; + frame_t right = right_camera_frame; + pthread_mutex_unlock(&camera_mutex); + + // Calculate absolute time difference + double diff = time_diff_ms(left.timestamp, right.timestamp); + if (diff < 0) diff = -diff; + + // Create a stereo pair if timestamps are close enough + if (diff < SYNC_TOLERANCE_MS) { + pthread_mutex_lock(&sync_mutex); + sync_left_frame = left; + sync_right_frame = right; + has_new_sync_pair = true; + pthread_mutex_unlock(&sync_mutex); + } + } + return NULL; +} + +void* image_writer_thread(void* arg) { + (void)arg; + + while (is_running) { + pthread_mutex_lock(&sync_mutex); + if (has_new_sync_pair) { + frame_t left = sync_left_frame; + frame_t right = sync_right_frame; + has_new_sync_pair = false; + pthread_mutex_unlock(&sync_mutex); + + // Simulate saving .jpg files + char filename_left[64]; + char filename_right[64]; + snprintf(filename_left, sizeof(filename_left), "left_%04d.jpg", left.frame_id); + snprintf(filename_right, sizeof(filename_right), "right_%04d.jpg", right.frame_id); + + FILE* f_left = fopen(filename_left, "w"); + if (f_left) { + fprintf(f_left, "Simulated left image data for frame %d\n", left.frame_id); + fclose(f_left); + } + + FILE* f_right = fopen(filename_right, "w"); + if (f_right) { + fprintf(f_right, "Simulated right image data for frame %d\n", right.frame_id); + fclose(f_right); + } + } else { + pthread_mutex_unlock(&sync_mutex); + } + + sleep_for_freq(LOG_FREQ_HZ); + } + return NULL; +} + +void* robot_state_thread(void* arg) { + (void)arg; + double x = 0.0, y = 0.0, theta = 0.0; + + while (is_running) { + // Simulate robot movement + x += 0.01; + y += 0.01; + theta += 0.005; + + pthread_mutex_lock(&state_mutex); + current_robot_state.x = x; + current_robot_state.y = y; + current_robot_state.theta = theta; + get_current_time(¤t_robot_state.timestamp); + pthread_mutex_unlock(&state_mutex); + + sleep_for_freq(ROBOT_FREQ_HZ); + } + return NULL; +} + +void* logger_thread(void* arg) { + (void)arg; + FILE* log_file = fopen("robot_state.txt", "w"); + if (!log_file) { + perror("Failed to open log file"); + return NULL; + } + + while (is_running) { + pthread_mutex_lock(&state_mutex); + robot_state_t state = current_robot_state; + pthread_mutex_unlock(&state_mutex); + + fprintf(log_file, "X: %.2f, Y: %.2f, Theta: %.2f\n", state.x, state.y, state.theta); + fflush(log_file); + + sleep_for_freq(LOG_FREQ_HZ); + } + + fclose(log_file); + return NULL; +} + +// --- MAIN FUNCTION --- + +int main() { + printf("[LEVEL 1] Starting Robot System Simulation...\n"); + + // Initialize mutexes and semaphores + pthread_mutex_init(&camera_mutex, NULL); + pthread_mutex_init(&sync_mutex, NULL); + pthread_mutex_init(&state_mutex, NULL); + + sem_init(&left_cam_sem, 0, 0); + sem_init(&right_cam_sem, 0, 0); + + // Create threads + pthread_t threads[6]; + pthread_create(&threads[0], NULL, left_camera_thread, NULL); + pthread_create(&threads[1], NULL, right_camera_thread, NULL); + pthread_create(&threads[2], NULL, sync_thread, NULL); + pthread_create(&threads[3], NULL, image_writer_thread, NULL); + pthread_create(&threads[4], NULL, robot_state_thread, NULL); + pthread_create(&threads[5], NULL, logger_thread, NULL); + + // Let the system run for 20 seconds + sleep(20); + + // Initiate shutdown + printf("[LEVEL 1] Stopping system...\n"); + is_running = false; + + // Wake up synchronizer thread in case it's waiting on semaphores + sem_post(&left_cam_sem); + sem_post(&right_cam_sem); + + // Join threads + for (int i = 0; i < 6; i++) { + pthread_join(threads[i], NULL); + } + + // Clean up resources + pthread_mutex_destroy(&camera_mutex); + pthread_mutex_destroy(&sync_mutex); + pthread_mutex_destroy(&state_mutex); + sem_destroy(&left_cam_sem); + sem_destroy(&right_cam_sem); + + printf("[LEVEL 1] System stopped gracefully.\n"); + return 0; +} + +#endif // LEVEL1 From bb44d65518225261ef8fa98888a473c350aad62a Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:14:35 +0200 Subject: [PATCH 07/17] Modified .gitignore and Makefile in preparation for the program1 fix with JPGs' localization --- lab6/.gitignore | 7 +++---- lab6/Makefile | 10 ++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lab6/.gitignore b/lab6/.gitignore index 3e14ee6..c4385fa 100644 --- a/lab6/.gitignore +++ b/lab6/.gitignore @@ -1,9 +1,8 @@ # Object files -obj/*.o +obj/ # Executables -bin/*.out +bin/ # Generated output files (simulated images, logs, reports) -*.jpg -*.txt \ No newline at end of file +output/ \ No newline at end of file diff --git a/lab6/Makefile b/lab6/Makefile index af86c85..fb679fd 100644 --- a/lab6/Makefile +++ b/lab6/Makefile @@ -5,12 +5,13 @@ INCDIR = include SRCDIR = src OBJDIR = obj BINDIR = bin +OUTDIR = output -$(shell mkdir -p $(OBJDIR) $(BINDIR)) +# Create directories if they do not exist +$(shell mkdir -p $(OBJDIR) $(BINDIR) $(OUTDIR)) .PHONY: all clean level1 level2 level3 -# Default target alerts the user to build a specific level iteratively all: @echo "Please specify a target to build: make level1, make level2, or make level3" @@ -19,7 +20,6 @@ level2: $(BINDIR)/level2.out level3: $(BINDIR)/level3.out # --- Object files compilation --- - $(OBJDIR)/common.o: $(SRCDIR)/common.c $(INCDIR)/common.h $(CC) $(CFLAGS) -c $(SRCDIR)/common.c -o $(OBJDIR)/common.o @@ -33,7 +33,6 @@ $(OBJDIR)/program3.o: $(SRCDIR)/program3.c $(INCDIR)/common.h $(CC) $(CFLAGS) -DLEVEL3 -c $(SRCDIR)/program3.c -o $(OBJDIR)/program3.o # --- Linking executables --- - $(BINDIR)/level1.out: $(OBJDIR)/common.o $(OBJDIR)/program1.o $(CC) $(CFLAGS) $^ -o $@ @@ -45,6 +44,5 @@ $(BINDIR)/level3.out: $(OBJDIR)/common.o $(OBJDIR)/program3.o # --- Cleanup --- clean: - rm -rf $(OBJDIR)/* $(BINDIR)/* - rm -f *.jpg *.txt + rm -rf $(OBJDIR)/* $(BINDIR)/* $(OUTDIR)/* \ No newline at end of file From a7e78144f34901f9f76ebeb8bdfecdad78a16de5 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:16:39 +0200 Subject: [PATCH 08/17] Changes to the program1.c, moved program output to a designated directory, rather than letting it litter the project dir --- lab6/src/program1.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lab6/src/program1.c b/lab6/src/program1.c index ee16522..caf8a3c 100644 --- a/lab6/src/program1.c +++ b/lab6/src/program1.c @@ -110,11 +110,11 @@ void* image_writer_thread(void* arg) { has_new_sync_pair = false; pthread_mutex_unlock(&sync_mutex); - // Simulate saving .jpg files + // Simulate saving .jpg files in the output directory char filename_left[64]; char filename_right[64]; - snprintf(filename_left, sizeof(filename_left), "left_%04d.jpg", left.frame_id); - snprintf(filename_right, sizeof(filename_right), "right_%04d.jpg", right.frame_id); + snprintf(filename_left, sizeof(filename_left), "output/left_%04d.jpg", left.frame_id); + snprintf(filename_right, sizeof(filename_right), "output/right_%04d.jpg", right.frame_id); FILE* f_left = fopen(filename_left, "w"); if (f_left) { @@ -160,7 +160,9 @@ void* robot_state_thread(void* arg) { void* logger_thread(void* arg) { (void)arg; - FILE* log_file = fopen("robot_state.txt", "w"); + + // Save log file in the output directory + FILE* log_file = fopen("output/robot_state.txt", "w"); if (!log_file) { perror("Failed to open log file"); return NULL; From e43ccec76da087776c6d498156ec0ddb8a10e21f Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:29:49 +0200 Subject: [PATCH 09/17] Added a bash script for showcasing the level1 --- lab6/scripts/run_level1.sh | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 lab6/scripts/run_level1.sh diff --git a/lab6/scripts/run_level1.sh b/lab6/scripts/run_level1.sh new file mode 100755 index 0000000..038cb65 --- /dev/null +++ b/lab6/scripts/run_level1.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -e + +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$PROJECT_ROOT" + +echo "========================================" +echo " Building and Running LEVEL 1 " +echo "========================================" + +make clean > /dev/null 2>&1 +make level1 + +echo "" +echo "[INFO] Starting simulation (takes 20 seconds)..." +echo "----------------------------------------" + +./bin/level1.out + +echo "----------------------------------------" +echo "[INFO] Simulation finished. Analyzing outputs..." +echo "" + +# Check outputs in the new directory +if [ -f "output/robot_state.txt" ]; then + echo "=> Robot State Log (first 5 entries):" + head -n 5 output/robot_state.txt +else + echo "=> [ERROR] output/robot_state.txt was not generated!" +fi + +echo "" + +IMAGE_COUNT=$(ls -1 output/*.jpg 2>/dev/null | wc -l) +if [ "$IMAGE_COUNT" -gt 0 ]; then + echo "=> Total simulated images generated: $IMAGE_COUNT" + echo "=> Sample generated image files:" + ls -1 output/left_*.jpg output/right_*.jpg 2>/dev/null | head -n 6 +else + echo "=> [ERROR] No .jpg files were generated in output/!" +fi + +echo "========================================" +echo " Script Execution Completed " +echo "========================================" From 89b4710745a2ec72c6419ee2cc272adb0016c734 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:40:39 +0200 Subject: [PATCH 10/17] Implemented changes necessary for level2 in common files --- lab6/include/common.h | 14 ++++++++++ lab6/src/common.c | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/lab6/include/common.h b/lab6/include/common.h index b95a942..4b35c36 100644 --- a/lab6/include/common.h +++ b/lab6/include/common.h @@ -55,4 +55,18 @@ void get_current_time(struct timespec *ts); // Precise thread sleep for a given frequency (e.g., 25 Hz) void sleep_for_freq(int freq_hz); +// --- BUFFER OPERATIONS (Task 2) --- + +// Initializes the circular buffer and its synchronization mechanisms +void buffer_init(frame_buffer_t* buf); + +// Pushes a frame into the buffer. Overwrites the oldest frame if full. +void buffer_push(frame_buffer_t* buf, frame_t frame); + +// Pops a frame from the buffer. Blocks if empty. Returns false if interrupted. +bool buffer_pop(frame_buffer_t* buf, frame_t* frame, volatile bool* is_running); + +// Cleans up mutexes and condition variables +void buffer_destroy(frame_buffer_t* buf); + #endif // COMMON_H diff --git a/lab6/src/common.c b/lab6/src/common.c index b2e6be1..69686b1 100644 --- a/lab6/src/common.c +++ b/lab6/src/common.c @@ -36,3 +36,63 @@ void sleep_for_freq(int freq_hz) { // We pass 0 as the flags argument for relative sleep, and NULL for the remaining time clock_nanosleep(CLOCK_MONOTONIC, 0, &req, NULL); } + +// --- BUFFER IMPLEMENTATION --- + +void buffer_init(frame_buffer_t* buf) { + buf->head = 0; + buf->tail = 0; + buf->count = 0; + pthread_mutex_init(&buf->mutex, NULL); + pthread_cond_init(&buf->not_empty, NULL); + pthread_cond_init(&buf->not_full, NULL); +} + +void buffer_push(frame_buffer_t* buf, frame_t frame) { + pthread_mutex_lock(&buf->mutex); + + // Real-Time System approach: If buffer is full, drop the oldest frame + // to prevent blocking the high-frequency producer (camera) + if (buf->count == BUFFER_SIZE) { + buf->tail = (buf->tail + 1) % BUFFER_SIZE; + buf->count--; + } + + buf->buffer[buf->head] = frame; + buf->head = (buf->head + 1) % BUFFER_SIZE; + buf->count++; + + // Wake up any thread waiting for data (e.g., synchronizer or writer) + pthread_cond_signal(&buf->not_empty); + + pthread_mutex_unlock(&buf->mutex); +} + +bool buffer_pop(frame_buffer_t* buf, frame_t* frame, volatile bool* is_running) { + pthread_mutex_lock(&buf->mutex); + + // Wait until there is data OR the system is shutting down + while (buf->count == 0 && *is_running) { + pthread_cond_wait(&buf->not_empty, &buf->mutex); + } + + // If we woke up because of shutdown and buffer is empty, abort safely + if (!(*is_running) && buf->count == 0) { + pthread_mutex_unlock(&buf->mutex); + return false; + } + + *frame = buf->buffer[buf->tail]; + buf->tail = (buf->tail + 1) % BUFFER_SIZE; + buf->count--; + + pthread_cond_signal(&buf->not_full); + pthread_mutex_unlock(&buf->mutex); + return true; +} + +void buffer_destroy(frame_buffer_t* buf) { + pthread_mutex_destroy(&buf->mutex); + pthread_cond_destroy(&buf->not_empty); + pthread_cond_destroy(&buf->not_full); +} From cbf45d7feb74d73ecaf79bf832903411b5a5e19b Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:40:57 +0200 Subject: [PATCH 11/17] Added logic for level2 in program2.c --- lab6/src/program2.c | 203 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 lab6/src/program2.c diff --git a/lab6/src/program2.c b/lab6/src/program2.c new file mode 100644 index 0000000..8a18854 --- /dev/null +++ b/lab6/src/program2.c @@ -0,0 +1,203 @@ +#ifdef LEVEL2 + +#include +#include +#include +#include +#include +#include +#include "common.h" + +// --- GLOBAL FLAGS & STATS --- +volatile bool is_running = true; + +int stats_frames_generated = 0; +int stats_robot_states = 0; +pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER; + +// --- SHARED BUFFERS & STATE --- +frame_buffer_t cam_left_buf; +frame_buffer_t cam_right_buf; +frame_buffer_t sync_left_buf; +frame_buffer_t sync_right_buf; + +robot_state_t current_robot_state; +pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +// --- SIGNAL HANDLER --- +void handle_sigint(int sig) { + (void)sig; + printf("\n[SIGINT] CTRL+C detected. Initiating graceful shutdown...\n"); + is_running = false; + + // Broadcast condition variables to wake up any blocked threads (buffer_pop) + pthread_cond_broadcast(&cam_left_buf.not_empty); + pthread_cond_broadcast(&cam_right_buf.not_empty); + pthread_cond_broadcast(&sync_left_buf.not_empty); + pthread_cond_broadcast(&sync_right_buf.not_empty); +} + +// --- THREAD FUNCTIONS --- + +void* left_camera_thread(void* arg) { + (void)arg; + int id = 1; + while (is_running) { + frame_t frame = { .frame_id = id++ }; + get_current_time(&frame.timestamp); + + buffer_push(&cam_left_buf, frame); + + pthread_mutex_lock(&stats_mutex); + stats_frames_generated++; + pthread_mutex_unlock(&stats_mutex); + + sleep_for_freq(CAMERA_FREQ_HZ); + } + return NULL; +} + +void* right_camera_thread(void* arg) { + (void)arg; + int id = 1; + while (is_running) { + frame_t frame = { .frame_id = id++ }; + get_current_time(&frame.timestamp); + + buffer_push(&cam_right_buf, frame); + + sleep_for_freq(CAMERA_FREQ_HZ); + } + return NULL; +} + +void* sync_thread(void* arg) { + (void)arg; + frame_t left, right; + while (is_running) { + // Blocks efficiently using condition variables until a frame arrives + if (!buffer_pop(&cam_left_buf, &left, &is_running)) break; + if (!buffer_pop(&cam_right_buf, &right, &is_running)) break; + + double diff = time_diff_ms(left.timestamp, right.timestamp); + if (diff < 0) diff = -diff; + + if (diff < SYNC_TOLERANCE_MS) { + buffer_push(&sync_left_buf, left); + buffer_push(&sync_right_buf, right); + } + } + return NULL; +} + +void* image_writer_thread(void* arg) { + (void)arg; + frame_t left, right; + while (is_running) { + if (!buffer_pop(&sync_left_buf, &left, &is_running)) break; + if (!buffer_pop(&sync_right_buf, &right, &is_running)) break; + + char filename_left[64], filename_right[64]; + snprintf(filename_left, sizeof(filename_left), "output/left_%04d.jpg", left.frame_id); + snprintf(filename_right, sizeof(filename_right), "output/right_%04d.jpg", right.frame_id); + + FILE* fl = fopen(filename_left, "w"); + if (fl) { fprintf(fl, "Sync L: %d\n", left.frame_id); fclose(fl); } + + FILE* fr = fopen(filename_right, "w"); + if (fr) { fprintf(fr, "Sync R: %d\n", right.frame_id); fclose(fr); } + + // Writer limits its disk operations to 10 Hz + sleep_for_freq(LOG_FREQ_HZ); + } + return NULL; +} + +void* robot_state_thread(void* arg) { + (void)arg; + double x = 0.0, y = 0.0, theta = 0.0; + while (is_running) { + x += 0.01; y += 0.01; theta += 0.005; + + pthread_mutex_lock(&state_mutex); + current_robot_state.x = x; + current_robot_state.y = y; + current_robot_state.theta = theta; + get_current_time(¤t_robot_state.timestamp); + pthread_mutex_unlock(&state_mutex); + + pthread_mutex_lock(&stats_mutex); + stats_robot_states++; + pthread_mutex_unlock(&stats_mutex); + + sleep_for_freq(ROBOT_FREQ_HZ); + } + return NULL; +} + +void* logger_thread(void* arg) { + (void)arg; + FILE* log_file = fopen("output/robot_state.txt", "w"); + if (!log_file) return NULL; + + while (is_running) { + pthread_mutex_lock(&state_mutex); + robot_state_t state = current_robot_state; + pthread_mutex_unlock(&state_mutex); + + fprintf(log_file, "X: %.2f, Y: %.2f, Theta: %.2f\n", state.x, state.y, state.theta); + fflush(log_file); + + sleep_for_freq(LOG_FREQ_HZ); + } + fclose(log_file); + return NULL; +} + +// --- MAIN FUNCTION --- + +int main() { + printf("[LEVEL 2] System starting. Press CTRL+C to stop.\n"); + signal(SIGINT, handle_sigint); + + buffer_init(&cam_left_buf); + buffer_init(&cam_right_buf); + buffer_init(&sync_left_buf); + buffer_init(&sync_right_buf); + + pthread_t threads[6]; + pthread_create(&threads[0], NULL, left_camera_thread, NULL); + pthread_create(&threads[1], NULL, right_camera_thread, NULL); + pthread_create(&threads[2], NULL, sync_thread, NULL); + pthread_create(&threads[3], NULL, image_writer_thread, NULL); + pthread_create(&threads[4], NULL, robot_state_thread, NULL); + pthread_create(&threads[5], NULL, logger_thread, NULL); + + // Main thread now handles System Statistics reporting + while (is_running) { + sleep(2); // Print stats every 2 seconds + if (!is_running) break; + + pthread_mutex_lock(&stats_mutex); + int f_count = stats_frames_generated; + int r_count = stats_robot_states; + pthread_mutex_unlock(&stats_mutex); + + printf("[STATS] Camera Frames: %d | Robot States: %d | C_Freq: 25Hz | R_Freq: 100Hz\n", + f_count, r_count); + } + + for (int i = 0; i < 6; i++) { + pthread_join(threads[i], NULL); + } + + buffer_destroy(&cam_left_buf); + buffer_destroy(&cam_right_buf); + buffer_destroy(&sync_left_buf); + buffer_destroy(&sync_right_buf); + + printf("[LEVEL 2] System shutdown gracefully.\n"); + return 0; +} + +#endif // LEVEL2 From a9e758d5ff326faf9738d9fa893468ed676d5124 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:45:53 +0200 Subject: [PATCH 12/17] Added script for running the level2 of the exercise --- lab6/scripts/run_level2.sh | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 lab6/scripts/run_level2.sh diff --git a/lab6/scripts/run_level2.sh b/lab6/scripts/run_level2.sh new file mode 100755 index 0000000..98df96e --- /dev/null +++ b/lab6/scripts/run_level2.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -e +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$PROJECT_ROOT" + +echo "========================================" +echo " Building and Running LEVEL 2 " +echo "========================================" + +make clean > /dev/null 2>&1 +make level2 + +echo "" +echo "[INFO] System runs infinitely. We will simulate CTRL+C after 8 seconds..." +echo "----------------------------------------" + +# Run program in the background +./bin/level2.out & +PID=$! + +# Wait for 8 seconds, letting the program print some stats +sleep 8 + +# Send SIGINT (CTRL+C) to the process +echo "" +echo "[BASH] Sending SIGINT to process $PID..." +kill -INT $PID + +# Wait for the background process to finish gracefully +wait $PID + +echo "----------------------------------------" +echo "[INFO] System shutdown verified. Checking outputs..." +echo "" + +IMAGE_COUNT=$(ls -1 output/*.jpg 2>/dev/null | wc -l) +if [ "$IMAGE_COUNT" -gt 0 ]; then + echo "=> Total simulated images generated: $IMAGE_COUNT" +else + echo "=> [ERROR] No .jpg files found!" +fi + +echo "========================================" +echo " Level 2 Completed! " +echo "========================================" From d0c79a44e6207d9d9ee4ee98897d75a696e2a2b5 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:47:56 +0200 Subject: [PATCH 13/17] Implemented logic for Real-Time System logic for level3 --- lab6/src/program3.c | 270 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 lab6/src/program3.c diff --git a/lab6/src/program3.c b/lab6/src/program3.c new file mode 100644 index 0000000..6a907b3 --- /dev/null +++ b/lab6/src/program3.c @@ -0,0 +1,270 @@ +#ifdef LEVEL3 + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +// --- GLOBAL FLAGS --- +volatile bool is_running = true; + +// --- ATOMIC COUNTERS (Lock-free synchronization) --- +// Total counters for final report +atomic_int total_left_frames = 0; +atomic_int total_right_frames = 0; +atomic_int total_robot_states = 0; + +// Recent counters for the watchdog (reset every second) +atomic_int recent_left_frames = 0; +atomic_int recent_right_frames = 0; +atomic_int recent_robot_states = 0; + +// --- SHARED BUFFERS & STATE --- +frame_buffer_t cam_left_buf; +frame_buffer_t cam_right_buf; +frame_buffer_t sync_left_buf; +frame_buffer_t sync_right_buf; + +robot_state_t current_robot_state; +pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +// --- SIGNAL HANDLER --- +void handle_sigint(int sig) { + (void)sig; + printf("\n[SIGINT] CTRL+C detected. Initiating graceful shutdown...\n"); + is_running = false; + + pthread_cond_broadcast(&cam_left_buf.not_empty); + pthread_cond_broadcast(&cam_right_buf.not_empty); + pthread_cond_broadcast(&sync_left_buf.not_empty); + pthread_cond_broadcast(&sync_right_buf.not_empty); +} + +// --- UTILITY: SET THREAD PRIORITY --- +void set_thread_priority(pthread_t thread, int policy, int priority) { + struct sched_param param; + param.sched_priority = priority; + + // SCHED_FIFO requires elevated privileges (sudo/root) in Linux. + // If it fails, it will gracefully fall back to default scheduling and print a warning. + if (pthread_setschedparam(thread, policy, ¶m) != 0) { + // We only print warning once to avoid console spam + static atomic_bool warned = false; + bool expected = false; + if (atomic_compare_exchange_strong(&warned, &expected, true)) { + perror("[WARNING] Failed to set Real-Time priority (try running with sudo)"); + } + } +} + +// --- THREAD FUNCTIONS --- + +void* left_camera_thread(void* arg) { + (void)arg; + int id = 1; + while (is_running) { + frame_t frame = { .frame_id = id++ }; + get_current_time(&frame.timestamp); + + buffer_push(&cam_left_buf, frame); + + // Atomic increments (no mutex required) + atomic_fetch_add(&total_left_frames, 1); + atomic_fetch_add(&recent_left_frames, 1); + + sleep_for_freq(CAMERA_FREQ_HZ); + } + return NULL; +} + +void* right_camera_thread(void* arg) { + (void)arg; + int id = 1; + while (is_running) { + frame_t frame = { .frame_id = id++ }; + get_current_time(&frame.timestamp); + + buffer_push(&cam_right_buf, frame); + + atomic_fetch_add(&total_right_frames, 1); + atomic_fetch_add(&recent_right_frames, 1); + + sleep_for_freq(CAMERA_FREQ_HZ); + } + return NULL; +} + +void* sync_thread(void* arg) { + (void)arg; + frame_t left, right; + while (is_running) { + if (!buffer_pop(&cam_left_buf, &left, &is_running)) break; + if (!buffer_pop(&cam_right_buf, &right, &is_running)) break; + + double diff = time_diff_ms(left.timestamp, right.timestamp); + if (diff < 0) diff = -diff; + + if (diff < SYNC_TOLERANCE_MS) { + buffer_push(&sync_left_buf, left); + buffer_push(&sync_right_buf, right); + } + } + return NULL; +} + +void* image_writer_thread(void* arg) { + (void)arg; + frame_t left, right; + while (is_running) { + if (!buffer_pop(&sync_left_buf, &left, &is_running)) break; + if (!buffer_pop(&sync_right_buf, &right, &is_running)) break; + + char filename_left[64], filename_right[64]; + snprintf(filename_left, sizeof(filename_left), "output/left_%04d.jpg", left.frame_id); + snprintf(filename_right, sizeof(filename_right), "output/right_%04d.jpg", right.frame_id); + + FILE* fl = fopen(filename_left, "w"); + if (fl) { fprintf(fl, "Sync L: %d\n", left.frame_id); fclose(fl); } + FILE* fr = fopen(filename_right, "w"); + if (fr) { fprintf(fr, "Sync R: %d\n", right.frame_id); fclose(fr); } + + sleep_for_freq(LOG_FREQ_HZ); + } + return NULL; +} + +void* robot_state_thread(void* arg) { + (void)arg; + double x = 0.0, y = 0.0, theta = 0.0; + while (is_running) { + x += 0.01; y += 0.01; theta += 0.005; + + pthread_mutex_lock(&state_mutex); + current_robot_state.x = x; + current_robot_state.y = y; + current_robot_state.theta = theta; + get_current_time(¤t_robot_state.timestamp); + pthread_mutex_unlock(&state_mutex); + + atomic_fetch_add(&total_robot_states, 1); + atomic_fetch_add(&recent_robot_states, 1); + + sleep_for_freq(ROBOT_FREQ_HZ); + } + return NULL; +} + +void* logger_thread(void* arg) { + (void)arg; + FILE* log_file = fopen("output/robot_state.txt", "w"); + if (!log_file) return NULL; + + while (is_running) { + pthread_mutex_lock(&state_mutex); + robot_state_t state = current_robot_state; + pthread_mutex_unlock(&state_mutex); + + fprintf(log_file, "X: %.2f, Y: %.2f, Theta: %.2f\n", state.x, state.y, state.theta); + fflush(log_file); + + sleep_for_freq(LOG_FREQ_HZ); + } + fclose(log_file); + return NULL; +} + +void* watchdog_thread(void* arg) { + (void)arg; + while (is_running) { + sleep(1); // Watchdog checks system pulse every 1 second + if (!is_running) break; + + // Read and reset counters using atomic exchange + int l_frames = atomic_exchange(&recent_left_frames, 0); + int r_frames = atomic_exchange(&recent_right_frames, 0); + int r_states = atomic_exchange(&recent_robot_states, 0); + + // Check for missed deadlines (allowing a small margin for standard OS delays) + if (l_frames < CAMERA_FREQ_HZ - 2) { + printf("[WATCHDOG] LEFT CAMERA SLOW: %d Hz (Expected: %d Hz)\n", l_frames, CAMERA_FREQ_HZ); + } + if (r_frames < CAMERA_FREQ_HZ - 2) { + printf("[WATCHDOG] RIGHT CAMERA SLOW: %d Hz (Expected: %d Hz)\n", r_frames, CAMERA_FREQ_HZ); + } + if (r_states < ROBOT_FREQ_HZ - 5) { + printf("[WATCHDOG] ROBOT STATE SLOW: %d Hz (Expected: %d Hz)\n", r_states, ROBOT_FREQ_HZ); + } + } + return NULL; +} + +// --- MAIN FUNCTION --- + +int main() { + printf("[LEVEL 3] RTOS System starting. Press CTRL+C to stop.\n"); + signal(SIGINT, handle_sigint); + + buffer_init(&cam_left_buf); + buffer_init(&cam_right_buf); + buffer_init(&sync_left_buf); + buffer_init(&sync_right_buf); + + pthread_t threads[7]; + + // Create threads + pthread_create(&threads[0], NULL, left_camera_thread, NULL); + pthread_create(&threads[1], NULL, right_camera_thread, NULL); + pthread_create(&threads[2], NULL, sync_thread, NULL); + pthread_create(&threads[3], NULL, image_writer_thread, NULL); + pthread_create(&threads[4], NULL, robot_state_thread, NULL); + pthread_create(&threads[5], NULL, logger_thread, NULL); + pthread_create(&threads[6], NULL, watchdog_thread, NULL); // NEW: Watchdog + + // Assign Real-Time priorities (SCHED_FIFO) + // Higher number = higher priority + set_thread_priority(threads[4], SCHED_FIFO, 90); // Robot State - Highest priority + set_thread_priority(threads[6], SCHED_FIFO, 85); // Watchdog - Critical monitoring + set_thread_priority(threads[0], SCHED_FIFO, 80); // Camera Left + set_thread_priority(threads[1], SCHED_FIFO, 80); // Camera Right + + // Main thread acts as idle sleep (Watchdog handles monitoring now) + while (is_running) { + sleep(1); + } + + // Join threads + for (int i = 0; i < 7; i++) { + pthread_join(threads[i], NULL); + } + + buffer_destroy(&cam_left_buf); + buffer_destroy(&cam_right_buf); + buffer_destroy(&sync_left_buf); + buffer_destroy(&sync_right_buf); + + // Generate Final Report + printf("[LEVEL 3] Generating final report...\n"); + FILE* report = fopen("output/report.txt", "w"); + if (report) { + fprintf(report, "===================================\n"); + fprintf(report, " SYSTEM PERFORMANCE REPORT \n"); + fprintf(report, "===================================\n"); + fprintf(report, "Total Left Camera Frames : %d\n", atomic_load(&total_left_frames)); + fprintf(report, "Total Right Camera Frames : %d\n", atomic_load(&total_right_frames)); + fprintf(report, "Total Robot States Logged : %d\n", atomic_load(&total_robot_states)); + fprintf(report, "Expected camera frequency : %d Hz\n", CAMERA_FREQ_HZ); + fprintf(report, "Expected robot frequency : %d Hz\n", ROBOT_FREQ_HZ); + fprintf(report, "===================================\n"); + fclose(report); + } + + printf("[LEVEL 3] System shutdown gracefully.\n"); + return 0; +} + +#endif // LEVEL3 From 75f757cee41807161a3b0460e1db97e2791c8cb7 Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:52:13 +0200 Subject: [PATCH 14/17] Added a script for running level3 program --- lab6/scripts/run_level3.sh | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 lab6/scripts/run_level3.sh diff --git a/lab6/scripts/run_level3.sh b/lab6/scripts/run_level3.sh new file mode 100755 index 0000000..708a615 --- /dev/null +++ b/lab6/scripts/run_level3.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$PROJECT_ROOT" + +echo "========================================" +echo " Building and Running LEVEL 3 " +echo "========================================" + +make clean > /dev/null 2>&1 +make level3 + +echo "" +echo "[INFO] Running RTOS Watchdog simulation for 10 seconds..." +echo " (Note: You may see a permission warning for Real-Time priority if not running as sudo. This is perfectly normal on Linux.)" +echo "----------------------------------------" + +./bin/level3.out & +PID=$! + +sleep 10 + +echo "" +echo "[BASH] Sending SIGINT to process $PID..." +kill -INT $PID +wait $PID + +echo "----------------------------------------" +echo "[INFO] System shutdown verified. Checking final report..." +echo "" + +if [ -f "output/report.txt" ]; then + cat output/report.txt +else + echo "=> [ERROR] output/report.txt was not generated!" +fi + +echo "========================================" +echo " Level 3 Completed! " +echo "========================================" From eae0d4c2e30c140177c690a65766875f6f4adc8a Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:56:55 +0200 Subject: [PATCH 15/17] Added a local README.md for this lab --- lab6/README.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 lab6/README.md diff --git a/lab6/README.md b/lab6/README.md new file mode 100644 index 0000000..1439e00 --- /dev/null +++ b/lab6/README.md @@ -0,0 +1,109 @@ +# Real-Time Mobile Robot System Simulation + +A comprehensive, multithreaded simulation of a mobile robot's internal operating system written in C. This project demonstrates advanced system programming concepts, including POSIX threads (pthreads), complex inter-thread communication, synchronization mechanisms, and Real-Time Operating System (RTOS) scheduling. + +## πŸ“Œ Project Overview +In modern robotics (similar to the ROS architecture), various sensors operate at different frequencies. This project simulates a core system where two cameras generate image frames at 25 Hz, while the robot's odometry/state updates at 100 Hz. The system handles the asynchronous nature of these sensors, synchronizes stereo camera frames, and safely logs data to a simulated disk at 10 Hzβ€”all without race conditions or data corruption. + +## πŸš€ Key Features & Technologies +- **Multithreading:** Concurrent execution of up to 7 independent threads (`pthread`). +- **Synchronization:** Utilization of Mutexes (`pthread_mutex_t`) and Semaphores (`sem_t`) to prevent race conditions. +- **Advanced IPC (Inter-Process Communication):** + - Implementation of **Circular Buffers (FIFO)** to queue sensory data and prevent data loss. + - **Condition Variables (`pthread_cond_t`)** to completely eliminate busy-waiting. +- **Real-Time Scheduling:** Implementation of `SCHED_FIFO` policy for critical threads. +- **Lock-Free Operations:** Usage of C11 Atomics (`stdatomic.h`) for high-performance, lock-free system monitoring. +- **Graceful Shutdown:** Interception of `SIGINT` (`CTRL+C`) for safe resource deallocation and memory cleanup (`pthread_join`, `pthread_mutex_destroy`). + +## πŸ—οΈ System Architecture +The system consists of the following concurrent threads: +1. **Left Camera Thread (25 Hz):** Generates image frames and timestamps. +2. **Right Camera Thread (25 Hz):** Generates image frames and timestamps. +3. **Synchronizer Thread:** Evaluates timestamps and pairs left/right frames if the time difference is `< 20 ms`. +4. **Image Writer Thread (10 Hz):** Pulls paired frames and simulates disk I/O by generating `.jpg` (text) files. +5. **Robot State Thread (100 Hz):** High-priority thread simulating the robot's spatial movement (X, Y, Theta). +6. **Logger Thread (10 Hz):** Periodically captures and logs the current robot state to `robot_state.txt`. +7. **Watchdog Thread (1 Hz):** Monitors the frequency of all threads using atomic variables to ensure strict Real-Time deadlines are met. + +## πŸ“ Directory Structure +```text +projekt_os/ +β”œβ”€β”€ bin/ # Compiled executable binaries (.out) +β”œβ”€β”€ include/ # Header files (common.h) +β”œβ”€β”€ obj/ # Compiled object files (.o) +β”œβ”€β”€ output/ # Generated logs, text files, and simulated .jpgs +β”œβ”€β”€ scripts/ # Bash scripts for automated building and testing +β”œβ”€β”€ src/ # Source code (.c files) +β”œβ”€β”€ Makefile # Build automation +└── .gitignore +``` + +## πŸ› οΈ Project Progression (Levels) + +The project is built iteratively in three distinct levels, compiling different sets of features. + +### Level 1: Basic Synchronization (`program1.c`) + +* Establishes the 6 core threads. +* Implements basic synchronization using standard Mutexes and Semaphores. +* Runs for a fixed duration of 20 seconds before shutting down automatically. + +### Level 2: Circular Buffers & Conditional Variables (`program2.c`) + +* Introduces robust **64-element Circular Buffers (FIFO)** for frame handling. +* Replaces raw sleep routines in reader threads with **Condition Variables**, ensuring microsecond-level reaction times when data arrives. +* Introduces **Graceful Shutdown** via `SIGINT` (CTRL+C) handling. + +### Level 3: RTOS & Lock-Free Monitoring (`program3.c`) + +* Upgrades to **Real-Time Scheduling (`SCHED_FIFO`)** granting hardware-level priorities to critical threads (Robot State & Watchdog). +* Implements lock-free counters using ``. +* Generates a final performance report upon shutdown. + +## βš™οΈ Compilation & Execution + +### Prerequisites + +* Linux / Windows Subsystem for Linux (WSL) +* GCC Compiler +* GNU Make + +### Building the Project + +You can build specific levels using the provided Makefile: + +```bash +make clean +make level1 # Builds bin/level1.out +make level2 # Builds bin/level2.out +make level3 # Builds bin/level3.out +``` + +### Automated Testing via Bash Scripts + +The easiest way to observe the system is by using the provided bash scripts. They automatically compile the code, run the simulation, simulate keyboard interruptions (`CTRL+C`), and print a summary of the output directory. + +```bash +./scripts/run_level1.sh +./scripts/run_level2.sh +./scripts/run_level3.sh +``` + +### ⚠️ Note on Real-Time Execution (Level 3) + +In Level 3, the program attempts to use the `SCHED_FIFO` real-time scheduling policy. On standard Linux configurations, unprivileged users are not allowed to set real-time priorities (to prevent system lockups). + +If you run `./scripts/run_level3.sh` normally, the system will fall back to default scheduling and display a warning. +To observe true RTOS capabilities without dropped frames or missed deadlines, execute the script with `sudo`: + +```bash +sudo ./scripts/run_level3.sh +``` + +## 🧹 Cleanup + +To remove all compiled binaries, object files, and generated output logs, simply run: + +```bash +make clean +``` \ No newline at end of file From 1307ed6923574cef41b21dc9028042264382aeea Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 21:59:48 +0200 Subject: [PATCH 16/17] Updated the global README.md file --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 822b46b..1a96100 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Operating Systems - AGH UST 🐧 +# Operating Systems @ AGH UST 🐧 [![C](https://img.shields.io/badge/C-00599C?style=for-the-badge&logo=c&logoColor=white)](https://en.wikipedia.org/wiki/C_(programming_language)) [![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)](https://www.linux.org/) @@ -18,6 +18,7 @@ The projects focus on low-level system programming in UNIX/Linux environments, u | [`lab3/`](./lab3) | Pipes and FIFOs (IPC) | Inter-Process Communication using Unnamed Pipes (`pipe`) and Named Pipes/FIFOs (`mkfifo`), deadlock prevention, and client-server process synchronization. | | [`lab4/`](./lab4) | POSIX Message Queues (IPC) | A "hub and spoke" communication system where multiple independent terminal clients exchange text messages in real-time using `/dev/mqueue`. Features asynchronous I/O managed by duplicating processes via `fork()`. | | [`lab5/`](./lab5) | POSIX Semaphores & Shared Memory | Implementation of the classic Producer-Consumer problem within a multi-process architecture. Leverages POSIX shared memory for zero-copy data transfer and POSIX semaphores for strict access synchronization, complete with a Manager daemon mitigating starvation via an aging algorithm. | +| [`lab6/`](./lab6) | POSIX Threads, RTOS scheduling | A comprehensive, multithreaded simulation of a mobile robot's internal operating system. POSIX threads (pthreads), complex inter-thread communication, synchronization mechanisms, and Real-Time Operating System (RTOS) scheduling. | ## βš™οΈ System Requirements From 43565a0067446e63e5e03f3538c766615861a21d Mon Sep 17 00:00:00 2001 From: Wiktor Trybus Date: Sat, 23 May 2026 22:14:16 +0200 Subject: [PATCH 17/17] Updated the CI for lab6 --- .github/workflows/make.yml | 54 +++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml index fe3a186..2df8abf 100644 --- a/.github/workflows/make.yml +++ b/.github/workflows/make.yml @@ -29,30 +29,41 @@ jobs: - name: Build Lab 2 (Signals & Libraries) run: | - cd lab1 - if [ -d "zad1" ]; then gcc -Wall zad1/main.c -o zad1/main.out; fi - if [ -d "zad2" ]; then gcc -Wall zad2/main.c -o zad1/main.out && gcc zad2/child.c -o child.out; fi - if [ -f "zad3/Makefile" ]; then cd zad3 && make && cd ..; fi - cd .. + # Fixed directory change (was cd lab1) + if [ -d "lab2" ]; then + cd lab2 + if [ -d "zad1" ]; then gcc -Wall zad1/main.c -o zad1/main.out; fi + if [ -d "zad2" ]; then gcc -Wall zad2/main.c -o zad2/main.out && gcc -Wall zad2/child.c -o zad2/child.out; fi + if [ -f "zad3/Makefile" ]; then cd zad3 && make && cd ..; fi + cd .. + fi - name: Build Lab 3 (Pipes & FIFOs) run: | - cd lab3 - make all + if [ -d "lab3" ]; then + cd lab3 + make all + cd .. + fi - name: Build Lab 4 (Message Queues) run: | - cd lab4 - make all + if [ -d "lab4" ]; then + cd lab4 + make all + cd .. + fi - name: Build Lab 5 (Semaphores & Shared Memory) run: | - for d in lab5/zad*/; do - if [ -f "$d/Makefile" ]; then - echo "Building $d" - cd "$d" && make all && cd - - fi - done + if [ -d "lab5" ]; then + for d in lab5/zad*/; do + if [ -f "$d/Makefile" ]; then + echo "Building $d" + cd "$d" && make all && cd - + fi + done + fi - name: Verify IPC Cleanup Utilities run: | @@ -60,6 +71,17 @@ jobs: ./lab5/zad3/bin/cleaner.out fi + - name: Build Lab 6 (Pthreads, RTOS & Sync) + run: | + if [ -d "lab6" ]; then + cd lab6 + # We call specific targets because our Makefile's 'all' just prints a message + make level1 + make level2 + make level3 + cd .. + fi + - name: Success Notification run: echo "All laboratories compiled successfully!" - \ No newline at end of file + \ No newline at end of file