13 Commits

Author SHA1 Message Date
Fabio Scotto di Santolo
b2342bf8fd Make a simple sleep clone 2025-09-21 14:26:30 +02:00
Fabio Scotto di Santolo
af39524b33 Make a simple echo clone 2025-09-19 23:33:32 +02:00
Fabio Scotto di Santolo
cc8ddf6ebd Write a simple UNIX ls clone 2025-09-19 17:08:57 +02:00
Fabio Scotto di Santolo
a611ca2fc3 Write a simple UNIX cp clone 2025-09-17 18:33:08 +02:00
Fabio Scotto di Santolo
ef8d74ec19 Write a simple UNIX cat clone 2025-09-17 17:57:35 +02:00
Fabio Scotto di Santolo
2abf6dc82f Added README for extra arguments 2025-08-22 16:33:15 +02:00
Fabio Scotto di Santolo
538cb4559e Renaming all folders 2025-08-22 16:21:42 +02:00
Fabio Scotto di Santolo
331308b2d8 Added exercise for signal handler 2025-08-22 16:02:54 +02:00
Fabio Scotto di Santolo
035027ad06 Example for chapter on UNIX signals 2025-08-22 14:29:14 +02:00
Fabio Scotto di Santolo
99cbf7d4fb Exercise on memory management analyzer 2025-08-21 22:39:59 +02:00
Fabio Scotto di Santolo
04b33c03ef Added another examples for Chapter 9 2025-08-21 12:38:20 +02:00
Fabio Scotto di Santolo
dfa3ee19b8 Simple memory allocation examples 2025-08-11 15:17:21 +02:00
Fabio Scotto di Santolo
469ca45470 Merge branch 'feature/downloader' 2025-07-30 15:50:30 +02:00
67 changed files with 1869 additions and 18 deletions

View File

@@ -0,0 +1,212 @@
# Chapter 9 - Memory Management
This document summarizes the key concepts from **Chapter 9: Memory Management** in *Linux System Programming, 2nd Edition* by Robert Love*. It provides an overview of how memory is managed in user space, the role of the kernel, common functions for allocation and manipulation, and techniques to optimize and debug memory usage.
---
## Overview
Memory management in Linux is a fundamental component of system programming. It involves allocating, using, and releasing memory safely and efficiently. Proper memory handling avoids fragmentation, memory leaks, and undefined behavior.
The chapter focuses on:
- How user-space processes interact with the kernel for memory allocation.
- The process memory layout and its components.
- Allocation strategies in both heap and stack.
- Advanced techniques like memory mapping and page locking.
- Debugging tools for memory issues.
---
## Key Topics
### 1. Memory Layout of a Process
Every Linux process has a well-defined memory layout, divided into segments:
- **Text Segment** Holds the compiled machine code of the program. Typically marked read-only and executable to prevent accidental modification.
- **Data Segment** Contains initialized global and static variables.
- **BSS Segment** Holds uninitialized global and static variables, which the kernel initializes to zero at program start.
- **Heap** Dynamic memory allocated at runtime using `malloc`, `calloc`, or `realloc`. Grows upward as needed.
- **Stack** Stores function call frames, local variables, and return addresses. Grows downward on most architectures.
- **Memory Mappings** Regions created with `mmap()`, commonly used for shared libraries and large allocations.
> Understanding this layout is essential for writing efficient, low-level programs and debugging memory-related issues.
---
### 2. Dynamic Memory Allocation
Dynamic allocation functions allow flexible use of memory at runtime:
- **malloc(size_t size)** Allocates a memory block of the given size without initialization.
- **calloc(size_t nmemb, size_t size)** Allocates memory for an array of `nmemb` elements, initializing all bytes to zero.
- **realloc(void \*ptr, size_t size)** Resizes an existing memory block, preserving its contents up to the smaller of the old and new sizes.
- **free(void \*ptr)** Releases previously allocated memory.
#### Common pitfalls
- **Memory leaks** Occur when allocated memory is not freed before losing all references to it.
- **Double free** Freeing the same memory block twice, which causes undefined behavior.
- **Use-after-free** Accessing a memory block after it has been freed, leading to crashes or data corruption.
---
### 3. `brk` and `sbrk`
Historically, `brk()` and `sbrk()` were used to manipulate the program break, which defines the end of the processs data segment.
- `brk()` sets the end of the data segment.
- `sbrk()` increments (or decrements) the break value.
Modern applications rarely use these functions directly; instead, `malloc` and related functions manage them internally. These interfaces are considered low-level and non-portable.
---
### 4. Memory Mapping with `mmap`
`mmap()` maps files or anonymous memory into the processs address space.
#### Advantages
- Eliminates the need for additional copies when reading files.
- Enables efficient handling of large files and inter-process communication.
#### Typical use cases
- Memory-mapped file I/O.
- Shared memory regions for IPC.
- Allocating large anonymous memory regions.
To release mappings, use **munmap()**. Unlike `malloc`, which uses the heap, `mmap` provides greater flexibility for aligning memory and mapping hardware devices.
---
### 5. Advanced Memory Techniques
Several specialized APIs provide advanced control over memory:
- **mprotect()** Changes access permissions (read, write, execute) for a memory region.
- **shm_open() / shm_unlink()** Creates and removes POSIX shared memory objects for inter-process communication.
- **posix_memalign()** Allocates memory aligned to a specific boundary, useful for SIMD operations or page-aligned buffers.
These functions are essential in performance-sensitive and real-time systems.
---
### 6. Debugging Memory Issues
The GNU C Library provides several functions to inspect and analyze memory usage at runtime:
- **mallinfo()**
Returns a `struct mallinfo` with statistics about memory allocation, such as total allocated space, number of free blocks, and fragmentation levels.
- **malloc_usable_size(void *ptr)**
Returns the actual usable size of an allocated block. This value may be larger than the size requested from `malloc()` due to alignment or internal overhead.
- **malloc_trim(size_t pad)**
Attempts to return unused memory from the heap back to the operating system, retaining `pad` bytes for future allocations. Useful for long-running processes to reduce memory footprint.
- **malloc_stats()**
Prints detailed statistics about memory allocation directly to `stderr`.
This includes information about the heap, allocated blocks, and free space.
#### Notes
- These functions are **GNU-specific** and may not be portable across different C libraries or UNIX-like systems.
- They are primarily intended for **debugging and profiling**, not for production code paths that require high performance.
- Use them to analyze fragmentation patterns, memory usage trends, and allocator behavior during development or testing.
---
### 7. Stack-Based Allocation
Stack allocation can be an efficient alternative for temporary buffers:
- **alloca(size_t size)** Allocates memory on the calling functions stack frame. The allocated memory is automatically released when the function returns, so `free()` is unnecessary.
#### Advantages
- Extremely fast allocation and deallocation.
- Ideal for short-lived buffers with known size limits.
#### Limitations
- Memory is valid only within the function scope. Returning a pointer to stack memory results in undefined behavior.
- Excessive allocation can lead to **stack overflow** and program crashes.
- `alloca()` is not part of the ISO C standard, but it is available as a POSIX or compiler-specific extension.
**Example**:
```c
void example(size_t size) {
char *buffer = alloca(size);
// Use buffer safely within this function only
}
```
---
### 8. Manipulating Memory
C provides several functions for low-level memory manipulation:
- **memset(void \*s, int c, size_t n)**
Fills a block of memory with a given byte value.
Example:
```c
memset(buffer, 0, sizeof(buffer)); // Zero out buffer
```
- **memcpy(void \*dest, const void \*src, size_t n)**
Copies `n` bytes from `src` to `dest`. Behavior is undefined if regions overlap.
- **memmove(void \*dest, const void \*src, size_t n)**
Like `memcpy`, but handles overlapping memory correctly.
- **memcmp(const void \*s1, const void \*s2, size_t n)**
Compares two memory regions byte by byte, returning:
- `< 0` if `s1` < `s2`
- `0` if equal
- `> 0` if `s1` > `s2`
- **memchr(const void \*s, int c, size_t n)**
Scans the n bytes of memory pointed at by s for the character c, which is interpreted as an unsigned char
- **memmem(const void \*haystack, size_t haystacklen, const void \*needle, size_t needlelen)**
Returns a pointer to the first occurrence of the subblock needle, of length needlelen bytes, within the block of memory haystack, of length haystacklen bytes.
If the function does not find needle in haystack, it returns NULL.
> This function is a GNU extension
These functions do not perform type checking or add null terminators, making them ideal for raw data operations but requiring caution to avoid buffer overruns.
---
### 9. Locking Memory
Linux provides mechanisms to lock memory pages into RAM:
- **mlock()** Locks a specific memory range, preventing it from being swapped out.
- **mlockall()** Locks all pages mapped by the calling process.
#### Use cases
- Real-time systems that must avoid page faults.
- Applications handling sensitive data such as encryption keys, ensuring they never hit disk.
After use, memory should be unlocked with **munlock()** or **munlockall()**.
---
## Key Takeaways
- Understanding the process memory layout is critical for writing efficient and safe programs.
- Dynamic allocation with `malloc` and its variants is powerful but requires careful management to avoid leaks and corruption.
- Use `mmap` for large or shared allocations and `alloca` for small, temporary buffers.
- Memory manipulation functions provide high performance but require strict attention to bounds and alignment.
---
## References
- *Linux System Programming, 2nd Edition* Robert Love, OReilly Media.
- `man malloc`, `man mmap`, `man alloca`, `man mprotect`, `man mlock`.

View File

@@ -0,0 +1,12 @@
#include <stdio.h>
int main(void) {
char greeting[] = "Ahoy Matey";
char *c = greeting[1];
unsigned long badnews = *(unsigned long *)c;
printf("%s\n", c);
printf("%lu\n", badnews);
return 0;
}

View File

@@ -0,0 +1,45 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void *xmalloc(size_t size);
int main(void)
{
char *str = xmalloc(1024);
char *src = "HelloWorld";
for (int i = 0; i < (int)strlen(src); i++)
str[i] = src[i];
printf("%s\n", str);
int *numbers = calloc(10, sizeof(int));
for (int i = 0; i < 10; i++) {
numbers[i] = i + 1;
printf("%d ", numbers[i]);
}
printf("\n");
int *p = realloc(numbers, 2 * sizeof(int));
if (!p) {
perror("realloc");
exit(1);
}
for (int i = 0; i < 20; i++) {
printf("%d ", p[i]);
}
return EXIT_SUCCESS;
}
void *xmalloc(size_t size)
{
void *p;
p = malloc(size);
if (!p) {
perror("xmalloc");
exit(1);
}
return p;
}

View File

@@ -0,0 +1,36 @@
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
int main(void) {
/* Initialize string array */
char **names = (char **)calloc(5, sizeof(char *));
for (int i = 0; i < 5; i++) {
names[i] = (char *)malloc(sizeof(char *));
}
memcpy(names[0], "Fabio", 5);
memcpy(names[1], "Chiara", 6);
memcpy(names[2], "Valerio", 7);
memcpy(names[3], "Luca", 4);
memcpy(names[4], "Leila", 5);
printf("\nBefore freed array...\n");
malloc_stats();
printf("\n");
for (int i = 0; i < 5; i++) {
printf("%s\n", names[i]);
}
for (int i = 0; i < 5; i++) {
free(names[i]);
}
printf("\nAfter freed array...\n");
malloc_stats();
printf("\n");
return 0;
}

View File

@@ -0,0 +1,62 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <alloca.h>
#define MAX_LEN 1024
#define SYSCONF_DIR "/etc"
int open_sysconf(const char *, int, int);
int main(void)
{
int fd = open_sysconf("/passwd", O_RDONLY, 0644);
if (fd == -1) {
perror("open");
return -1;
}
int len;
if ((len = lseek(fd, 0, SEEK_END)) == -1) {
perror("lseek");
return -1;
}
if (lseek(fd, 0, SEEK_SET) == -1) {
perror("lseek");
return -1;
}
char buffer[len + 1];
memset(buffer, 0, sizeof(buffer));
ssize_t ret;
size_t bytes_read_total = 0;
while (bytes_read_total < len && (ret = read(fd, buffer + bytes_read_total, len - bytes_read_total)) != 0) {
if (ret == -1) {
if (errno == EINTR) continue;
perror("read");
break;
}
bytes_read_total += ret;
}
buffer[bytes_read_total] = '\0';
printf("%s", buffer);
close(fd);
return 0;
}
int open_sysconf(const char *file, int flags, int mode)
{
const char *etc = SYSCONF_DIR;
char *name = alloca(strlen(etc) + strlen(file) + 1);
strcpy(name, etc);
strcat(name, file);
return open(name, flags, mode);
}

View File

@@ -0,0 +1,16 @@
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(void) {
char *buf = (char *)malloc(256);
if (!buf) {
perror("malloc");
return 1;
}
size_t size = malloc_usable_size(buf);
printf("%d\n", size);
free(buf);
return 0;
}

92
10_Signals/README.md Normal file
View File

@@ -0,0 +1,92 @@
# Chapter 10 Signals (Summary)
**Based on _Linux System Programming, 2nd Edition_ by Robert Love**
This chapter dives into the mechanics, pitfalls, and POSIX-compliant interfaces of signals in Linux.
---
## What Are Signals?
- **Signals** are asynchronous software interrupts used for interprocess communication (IPC). They notify processes of events such as:
- User-generated events (e.g., pressing CtrlC sends **SIGINT**)
- Internal faults (e.g., divide by zero triggers **SIGFPE**)
- Kernel actions or process-related events (e.g., child termination sends **SIGCHLD**)
- Signals require special handling because they can occur at any moment, disrupting normal flow.
---
## Signal Handling Models
- Early Unix systems struggled with **unreliable signals**—deliveries could be missed or lost.
- **POSIX** standardized a **reliable signal model**, ensuring signals are not lost and behavior is predictable.
---
## Setting Signal Handlers
- Use the **`sigaction()`** interface (preferred) to set up handlers in a robust and portable way.
- `signal()` is outdated and has inconsistent behavior across platforms; use only for trivial cases like ignoring or using the default handler.
- **SIGKILL** and **SIGSTOP** cannot be caught, ignored, or blocked.
---
## Signal Masks and Blocking
- Each process has a **signal mask** (via `sigset_t`) that defines which signals are currently blocked.
- Signal blocking is essential when running critical code to avoid race conditions.
- Use `sigprocmask()` to modify the mask safely.
---
## Common Signal APIs
```c
int kill(pid_t pid, int signo); // Send a signal to another process
int killpg(int pgrp, int signo); // Send a signal to all processes in a given process group. Equivalent to kill(-pgrp, signo).
int raise(int signo); // Send a signal to self
unsigned int alarm(unsigned int seconds); // Schedule a SIGALRM
int pause(void); // Wait indefinitely until a signal arrives
void abort(void); // Raise SIGABRT and terminate
```
These calls allow fine-grained control over signal generation and handling.
---
## Reentrancy and Safe Signal Handling
- Signal handlers can interrupt your program at any point, even in the middle of non-reentrant functions, causing undefined behavior.
- Only call **async-signal-safe** functions within handlers (e.g., `write`, `_exit`).
- Avoid unsafe operations like `malloc()`, `printf()`, or `strtok()` inside signal handlers.
---
## Process Behavior with Signals
- Signal dispositions are inherited across `fork()` (child inherits parents handlers) but reset to default on `exec()`.
---
## Summary of Key Concepts
| Concept | Description |
|--------------------------|------------------------------------------------------------|
| Signals | Asynchronous notifications from kernel or processes |
| POSIX Signal Model | Reliable, standardized approach to signal delivery |
| `sigaction()` | Robust interface to set signal handlers |
| Signal Masks | Block/unblock signals to prevent interruptions |
| Async-signal-safe calls | Safe functions to call inside signal handlers |
| Core Functions | `kill`, `raise`, `alarm`, `pause`, `abort` |
---
## Why This Matters
Signals are indispensable for handling asynchronous events, timed operations, and interprocess interactions. Proper use of reliable signal handling, blocking, and safe coding practices ensures your applications remain responsive and stable under interruptions.
---
## References
- _Linux System Programming_ by Robert Love (2nd Ed.) — Chapter 10
- POSIX and Linux signal handling docs: `signal()`, `sigaction()`, `sigprocmask()`

53
10_Signals/multihandler.c Normal file
View File

@@ -0,0 +1,53 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* handler for SIGINT and SIGTERM */
static void signal_handler(int signo)
{
if (signo == SIGINT)
printf("Caught SIGINT!\n");
else if (signo == SIGTERM)
printf("Caught SIGTERM!\n");
else {
/* this should never happen */
fprintf(stderr, "Unexpected signal!\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
int main(void)
{
/*
* Register signal_handler as our signal handler
* for SIGINT.
*/
if (signal(SIGINT, signal_handler) == SIG_ERR) {
fprintf(stderr, "Cannot handle SIGINT!\n");
exit(EXIT_FAILURE);
}
/*
* Register signal_handler as our signal handler
* for SIGTERM.
*/
if (signal(SIGTERM, signal_handler) == SIG_ERR) {
fprintf(stderr, "Cannot handle SIGTERM!\n");
exit(EXIT_FAILURE);
}
/* Reset SIGPROF's behavior to the default. */
if (signal(SIGPROF, SIG_DFL) == SIG_ERR) {
fprintf(stderr, "Cannot reset SIGPROF!\n");
exit(EXIT_FAILURE);
}
/* Ignore SIGHUP. */
if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
fprintf(stderr, "Cannot ignore SIGHUP!\n");
exit(EXIT_FAILURE);
}
printf("Hi, I'm a process with PID %d\n", getpid());
for (;;)
pause();
return 0;
}

34
10_Signals/pause.c Normal file
View File

@@ -0,0 +1,34 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* Handler for SIGINT */
static void sigint_handler(int signo)
{
/*
* Technically, you shouldn't use printf(...) in a
* signal handler, but it isn't the end of the
* world. I'll discuss why in the section
* "Reentrancy".
*/
printf("Caught SIGINT\n");
exit(EXIT_SUCCESS);
}
int main(void)
{
/*
* Register sigint_handler as our signal handler
* for SIGINT.
*/
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
fprintf(stderr, "Cannot handle SIGINT\n");
exit(EXIT_FAILURE);
}
for (;;) {
pause();
}
return EXIT_SUCCESS;
}

20
10_Signals/simple_catch.c Normal file
View File

@@ -0,0 +1,20 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void catch_sighup(int);
int main(void)
{
signal(SIGHUP, catch_sighup);
printf("Hi, I'm a process with PID %d\n", getpid());
sleep(1000);
return EXIT_SUCCESS;
}
void catch_sighup(int sig)
{
printf("Catch signal SIGHUP\n");
exit(EXIT_SUCCESS);
}

View File

@@ -0,0 +1,99 @@
# 📘 APUE Chapter 16: Sockets Summary
### **1. Introduction to Sockets**
- Sockets are an abstraction for network communication.
- Enable communication between processes over the network.
- Support multiple protocols: TCP, UDP (IPv4, IPv6).
### **2. Socket Types**
- `SOCK_STREAM` TCP (reliable, connection-oriented).
- `SOCK_DGRAM` UDP (connectionless, unreliable).
- `SOCK_SEQPACKET` sequenced, reliable datagrams (less common).
- `SOCK_RAW` raw access to IP packets (used in advanced networking).
### **3. Socket System Calls**
1. `int socket(int domain, int type, int protocol);`
- Create a new socket descriptor.
2. `int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);`
- Assign an address/port to a socket (server side).
3. `int listen(int sockfd, int backlog);`
- Mark socket as passive (ready to accept connections).
4. `int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);`
- Accept a new incoming connection (blocking call).
5. `int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);`
- Establish connection to a server (client side).
6. `ssize_t read(int sockfd, void *buf, size_t count);`
7. `ssize_t write(int sockfd, const void *buf, size_t count);`
8. `ssize_t recv(int sockfd, void *buf, size_t len, int flags);`
9. `ssize_t send(int sockfd, const void *buf, size_t len, int flags);`
10. `int close(int sockfd);`
- Close the socket and free resources.
### **4. Socket Address Structures**
- IPv4: `struct sockaddr_in`
```c
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // port number (network byte order)
struct in_addr sin_addr; // IP address
};
```
- IPv6: `struct sockaddr_in6`
- Generic: `struct sockaddr` for compatibility.
### **5. Data Conversion Utilities**
- `htons()`, `htonl()`: host to network byte order (short/long)
- `ntohs()`, `ntohl()`: network to host byte order
- `inet_pton()`: convert string IP to `struct in_addr` or `struct in6_addr`
- `inet_ntop()`: convert address structure to string
### **6. TCP Client-Server Model**
**Server:**
1. `socket()`
2. `bind()`
3. `listen()`
4. `accept()` → handle client
5. `read()/write()` → communicate
6. `close()`
**Client:**
1. `socket()`
2. `connect()`
3. `read()/write()` → communicate
4. `close()`
### **7. UDP Model**
- Connectionless: no `listen()`/`accept()`.
- Use `sendto()` and `recvfrom()` for communication.
- No guarantee of delivery; suitable for lightweight or real-time protocols.
### **8. Concurrency in Servers**
- **Process-based**: `fork()` for each client
- **Thread-based**: `pthread_create()` for each client
- **I/O multiplexing**: `select()`, `poll()`, `epoll()`
### **9. Error Handling**
- Check return values of all socket calls.
- Use `perror()` or `strerror()` for diagnostics.
- Handle `EINTR` for interruptible syscalls.
### **10. Key Takeaways**
- Sockets abstract network communication using familiar file descriptor semantics.
- TCP ensures reliable, ordered delivery; UDP is fast but unreliable.
- Address conversions (`inet_pton`, `inet_ntop`) are essential for portability.
- Concurrency requires careful design: processes, threads, or event loops.
- Practice writing small client-server programs to solidify concepts.

View File

@@ -1,18 +1,25 @@
# 📚 Linux System Programming Chapter Index
Welcome! This is a collection of chapter summaries from the book **Linux System Programming** by Robert Love. Each chapter is organized in its own folder.
In the section extra there are other arguments take by ***Advanced Programming in the UNIX Enviroment*** by W. Richard Stevens.
## Available Chapters
- [Chapter 1 Introduction and Core Concepts](chp1/README.md)
- [Chapter 2 File I/O](chp2/README.md)
- [Chapter 3 Buffered I/O](chp3/README.md)
- [Chapter 4 Advanced File I/O](chp4/README.md)
- [Chapter 5 - Process Management](chp5/README.md)
- [Chapter 6 - Advanced Process Management](chp6/README.md)
- [Chapter 7 - Threading](chp7/README.md)
- [Chapter 1 Introduction and Core Concepts](01_Introduction/README.md)
- [Chapter 2 File I/O](02_FileIO/README.md)
- [Chapter 3 Buffered I/O](03_BufferedIO/README.md)
- [Chapter 4 Advanced File I/O](04_AdvancedFileIO/README.md)
- [Chapter 5 - Process Management](05_ProcessManagement/README.md)
- [Chapter 6 - Advanced Process Management](06_AdvancedProcessManagement/README.md)
- [Chapter 7 - Threading](07_Threading/README.md)
- [Chapter 9 - Memory Management](09_MemoryManagement/README.md)
- [Chapter 10 - Signals](10_Signals/README.md)
- [Exercises](exercises/README.md)
## Extra (APUE)
- [Chapter 16 - Network IPC: Sockets](16_NetworkIPCSockets/README.md)
> Each file contains an English summary of the chapter's key concepts.
## License

View File

@@ -61,6 +61,35 @@ This is an organized list of all exercises completed so far, including a brief d
---
## 🧠 memory_analyzer
**Description**: A simple memory analyzer that allocates memory blocks of different sizes and prints current memory usage.
- 📄 [README](mem_analyzer/README.md)
- 📂 Directory: `mem_analyzer/`
- ✅ Features:
- Allocates blocks using `malloc` and frees them after measurement
- Reports current memory usage via `/proc/self/status`
- Minimal implementation aligned with the exercise objectives
- Makefile included with standard targets
---
## 🚦 signal_handler
**Description**: A C program demonstrating robust signal handling for `SIGINT` and `SIGUSR1` using `sigaction` with `SA_SIGINFO`.
- 📄 [README](signal_handler/README.md)
- 📂 Directory: `signal_handler/`
- ✅ Features:
- Handles `SIGINT` up to three times before exiting
- Handles `SIGUSR1` with a simple message
- Masks signals during handler execution to avoid reentrancy issues
- Includes comments and clear separation of handler logic
- Makefile included with standard targets
---
## 🔧 Tooling & Automation
**Shared tools and scripts used across projects**:
@@ -69,5 +98,3 @@ This is an organized list of all exercises completed so far, including a brief d
- Targets for `build`, `test`, `valgrind`, and `clean`
- Shell script for automated tests: `run_tests.sh`
- Valgrind memory check reports generated automatically
*Last updated: 2025-07-30*

View File

@@ -0,0 +1,48 @@
# 🛠️ Coreutils Reimplementations
This directory contains reimplementations of classic **GNU Coreutils** commands in C,
using only **POSIX system calls** and the standard C library.
The goal is to gain hands-on experience with **Linux system programming**, while practicing
error handling, portability, and clean coding.
---
## 📂 Structure
Each command is implemented in its own folder:
```
coreutils/
├── ls/ # list directory contents
├── cat/ # concatenate files
├── cp/ # copy files
├── echo/ # echo a string
├── sleep/ # wait X millis
````
Each subfolder contains:
- `main.c` → source code
- `Makefile` → to build the command
---
## 🎯 Learning Objectives
- Practice **file and directory operations**
- Reinforce knowledge of **system calls**: `open`, `read`, `write`, `stat`, `opendir`, `unlink`, `link`, etc.
- Develop robust error handling
- Understand the **UNIX philosophy**: small tools, combined power
---
## ⚙️ Build & Run
Example (`ls`):
```bash
cd ls
make
./ls .
````
---
*Last updated: 2025-09-18*

View File

@@ -0,0 +1,19 @@
CC := gcc
CFLAGS := -Wall -Wextra -pedantic -std=c11 -pthread -g
TARGET := cat
SRC := main.c
OBJS := $(SRC:.c=.o)
.PHONY: all clean valgrind
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)

View File

@@ -0,0 +1,36 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 256
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s FILE...\n", argv[0]);
return EXIT_FAILURE;
}
for (int i = 1; i < argc; i++) {
FILE *fp = fopen(argv[i], "r");
if (fp == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
// Read file and print on the stdout
size_t nread;
unsigned char buff[BUFFER_SIZE];
while ((nread = fread(buff, 1, sizeof(buff), fp)) > 0) {
fwrite(buff, 1, nread, stdout);
}
if (fclose(fp) != 0) {
perror("fclose");
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,19 @@
CC := gcc
CFLAGS := -Wall -Wextra -pedantic -std=c11 -pthread -g
TARGET := cp
SRC := main.c
OBJS := $(SRC:.c=.o)
.PHONY: all clean valgrind
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)

View File

@@ -0,0 +1,59 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 4096
int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s SRC DEST", argv[0]);
return EXIT_FAILURE;
}
const char *src = argv[1];
const char *dest = argv[2];
FILE *fp1 = fopen(src, "rb");
if (fp1 == NULL) {
perror("cannot open source file");
return EXIT_FAILURE;
}
FILE *fp2 = fopen(dest, "wb");
if (fp2 == NULL) {
perror("cannot open destination file");
fclose(fp1);
return EXIT_FAILURE;
}
size_t nread;
unsigned char buff[BUFFER_SIZE];
while ((nread = fread(buff, 1, sizeof(buff), fp1)) > 0) {
if (fwrite(buff, 1, nread, fp2) != nread) {
perror("fwrite");
fclose(fp1);
fclose(fp2);
return EXIT_FAILURE;
}
}
if (ferror(fp1)) {
perror("read error");
fclose(fp1);
fclose(fp2);
return EXIT_FAILURE;
}
if (fclose(fp1) != 0) {
perror("cannot close source file");
return EXIT_FAILURE;
}
if (fclose(fp2) != 0) {
perror("cannot close destination file");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,204 @@
This a simple file for testing my clone cp program
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test
This a test

View File

@@ -0,0 +1,19 @@
CC := gcc
CFLAGS := -Wall -Wextra -pedantic -std=c11 -pthread -g
TARGET := echo
SRC := main.c
OBJS := $(SRC:.c=.o)
.PHONY: all clean valgrind
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)

View File

@@ -0,0 +1,50 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc == 1) {
write(STDOUT_FILENO, "\n", 1);
return EXIT_SUCCESS;
}
size_t argl[argc-1];
size_t bytes_count = 0;
for (int i = 1; i < argc; i++) {
size_t len = strlen(argv[i]);
bytes_count += len;
argl[i-1] = len;
}
bytes_count += argc - 2; // whitespaces
bytes_count += 1; // newline
char *str = malloc(bytes_count + 1);
if (str == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
size_t pos = 0;
for (int i = 1; i < argc; i++) {
memcpy(str + pos, argv[i], argl[i-1]);
pos += argl[i-1];
if (i < argc - 1) {
str[pos++] = ' ';
}
}
str[pos++] = '\n';
str[pos] = '\0';
if (write(STDOUT_FILENO, str, pos) == -1) {
perror("write");
free(str);
return EXIT_FAILURE;
}
free(str);
str = NULL;
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,19 @@
CC := gcc
CFLAGS := -Wall -Wextra -pedantic -std=c11 -pthread -g
TARGET := ls
SRC := main.c
OBJS := $(SRC:.c=.o)
.PHONY: all clean valgrind
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)

View File

@@ -0,0 +1,114 @@
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <sys/stat.h>
char filetype(mode_t mode);
char *getpermissions(mode_t mode);
int main(int argc, char *argv[])
{
char *dirname = ".";
if (argc > 1)
dirname = argv[1];
DIR *dir = opendir(dirname);
if (dir == NULL) {
int errsv = errno;
fprintf(stderr, "%s: cannot access %s: %s\n", argv[0], dirname, strerror(errsv));
return EXIT_FAILURE;
}
struct stat statbuf;
struct dirent *dirp;
while ((dirp = readdir(dir)) != NULL) {
// Skip current and previous directory symbols
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
continue;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s", dirname, dirp->d_name);
if (stat(path, &statbuf) < 0) {
int errsv = errno;
fprintf(stderr, "%s: cannot stat %s: %s\n", argv[0], path, strerror(errsv));
continue;
}
char type = filetype(statbuf.st_mode);
char *permissions = getpermissions(statbuf.st_mode);
/*
* Format output:
* TYPE: d (directory) | f (regular file) | l (symbolic link) | o (others)
* PERMISSIONS (ex. rwxrwxrwx)
* NR HARD LINKS
* USER
* GROUP
* SIZE
* NAME
*/
printf("%c%s %lu %u %u %6lu %s\n",
type, permissions, statbuf.st_nlink,
statbuf.st_uid, statbuf.st_gid, statbuf.st_size,
dirp->d_name);
if (permissions != NULL) {
free(permissions);
permissions = NULL;
}
}
if (closedir(dir) == -1) {
int errsv = errno;
fprintf(stderr, "%s: cannot close %s: %s\n", argv[0], dirname, strerror(errsv));
return EXIT_FAILURE;
}
}
char filetype(mode_t mode)
{
if (S_ISREG(mode)) return '-';
if (S_ISDIR(mode)) return 'd';
if (S_ISLNK(mode)) return 'l';
if (S_ISCHR(mode)) return 'c';
if (S_ISBLK(mode)) return 'b';
if (S_ISFIFO(mode)) return 'p';
#ifdef S_ISSOCK
if (S_ISSOCK(mode)) return 's';
#endif
return '?';
}
char checkpermission(mode_t mode, int perm, char c)
{
return (mode & perm) ? c : '-';
}
char *getpermissions(mode_t mode)
{
char *permissions = malloc(9 + 1);
/* User */
permissions[0] = checkpermission(mode, S_IRUSR, 'r');
permissions[1] = checkpermission(mode, S_IWUSR, 'w');
permissions[2] = checkpermission(mode, S_IXUSR, 'x');
/* Group */
permissions[3] = checkpermission(mode, S_IRGRP, 'r');
permissions[4] = checkpermission(mode, S_IWGRP, 'w');
permissions[5] = checkpermission(mode, S_IXGRP, 'x');
/* Other */
permissions[6] = checkpermission(mode, S_IROTH, 'r');
permissions[7] = checkpermission(mode, S_IWOTH, 'w');
permissions[8] = checkpermission(mode, S_IXOTH, 'x');
permissions[9] = '\0';
return permissions;
}

View File

@@ -0,0 +1,19 @@
CC := gcc
CFLAGS := -Wall -Wextra -pedantic -std=c11 -pthread -g
TARGET := sleep
SRC := main.c
OBJS := $(SRC:.c=.o)
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)

View File

@@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void alarm_handler(int, siginfo_t *, void *);
int main(int argc, char *argv[])
{
struct sigaction act, old_act;
sigset_t newmask, oldmask, suspmask;
if (argc < 2) {
fprintf(stderr, "Usage: %s SECONDS", argv[0]);
return EXIT_FAILURE;
}
unsigned int timeout = (unsigned int)strtol(argv[1], (char **)NULL, 10);
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_sigaction = alarm_handler;
if (sigaction(SIGALRM, &act, &old_act) < 0) {
perror("sigaction");
return EXIT_FAILURE;
}
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
perror("sigprocmask");
return EXIT_FAILURE;
}
alarm(timeout);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);
sigsuspend(&suspmask);
alarm(0);
if (sigaction(SIGALRM, &old_act, NULL) == -1) {
perror("sigaction restore");
}
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
perror("sigprocmask restore");
}
return EXIT_SUCCESS;
}
static void alarm_handler(int signo, siginfo_t *info, void *context)
{
//printf("Catch signal %d from %u\n", signo, info->si_pid);
}

View File

@@ -0,0 +1,29 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wpadded -g -fsanitize=address
TARGET = mem_analyzer
SRC = mem_utils.c main.c
OBJ = $(SRC:.c=.o)
.PHONY: all build run test valgrind clean
all: build
build: $(TARGET)
$(TARGET): $(OBJ)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
run: build
./$(TARGET)
test: build
./$(TARGET) test
valgrind: build
valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET)
clean:
rm -f $(TARGET) $(OBJ)

View File

@@ -0,0 +1,77 @@
# 🧠 Memory Management Analyzer
This project demonstrates how different memory allocation methods (`malloc`, `calloc`, and `mmap`) perform in terms of speed and memory usage in a simulated workload.
---
## 📜 Description
The program dynamically allocates and frees memory blocks of varying sizes, measuring:
- Allocation and deallocation times.
- Memory usage via `/proc/self/status` or `/proc/self/statm`.
- Peak memory consumption during execution.
It produces a report comparing performance across allocation methods.
---
## 🚀 Features
- **Dynamic Allocation Testing** compares `malloc`, `calloc`, and `mmap`.
- **Performance Metrics** measures allocation/deallocation times.
- **Memory Usage Tracking** monitors real-time process memory usage.
- **Optional Memory Leak Simulation** detects unfreed memory using Valgrind.
- **CLI Mode Selection** choose allocator at runtime (`--malloc`, `--calloc`, `--mmap`).
---
## 🛠️ Build
```bash
make
```
---
## ▶️ Run
```bash
./mem_analyzer --malloc
./mem_analyzer --calloc
./mem_analyzer --mmap
```
---
## 🧪 Test & Debug
```bash
make valgrind
```
---
## 📊 Example Output
```
Allocator: malloc
Blocks: 10000
Average Allocation Time: 0.24 μs
Average Free Time: 0.18 μs
Peak Memory Usage: 2.5 MB
```
---
## 📂 Project Structure
```
mem_analyzer/
├── README.md
├── Makefile
└── main.c
```
---
## 🔍 Learning Objectives
- Understand differences between `malloc`, `calloc`, and `mmap`.
- Learn to measure memory usage through `/proc`.
- Practice debugging memory errors with Valgrind.
- Gain insight into memory allocation strategies in Linux.
---

View File

@@ -0,0 +1,168 @@
#include "mem_utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
/* Run allocation benchmark using malloc */
struct Stats *bench_malloc();
/* Run allocation benchmark using calloc */
struct Stats *bench_calloc();
/* Run allocation benchmark using mmap */
struct Stats *bench_mmap();
typedef struct Stats *(*StatsFn)(void);
/* Memory blocks size to allocate for testing */
static size_t size_blocks[BLOCK_NUM] = {1024, 2048, 4096, 100000, 8192};
int main(int argc, char const *argv[])
{
const StatsFn benchmarks[] = {bench_malloc, bench_calloc, bench_mmap};
for (int i = 0; i < 3; i++) {
StatsFn benchmark = benchmarks[i];
struct Stats *result = benchmark();
print_stats(result);
free(result);
printf("-------------------------------------------\n");
}
return 0;
}
struct Stats *bench_malloc()
{
// Benchmark malloc function
double peak_mem = 0.0;
double m_elapsed_times[BLOCK_NUM] = {0};
double f_elapsed_times[BLOCK_NUM] = {0};
for (int i = 0; i < BLOCK_NUM; i++) {
size_t size = size_blocks[i];
struct timespec m_start, f_start, m_end, f_end;
// malloc
clock_gettime(CLOCK_MONOTONIC, &m_start);
void *ptr = malloc(size);
clock_gettime(CLOCK_MONOTONIC, &m_end);
m_elapsed_times[i] = ((m_end.tv_sec - m_start.tv_sec) +
(m_end.tv_nsec - m_start.tv_nsec)) /
1e9;
double curr_mem_usage = get_mem_usage();
if (peak_mem <= curr_mem_usage) {
peak_mem = curr_mem_usage;
}
// free
clock_gettime(CLOCK_MONOTONIC, &f_start);
free(ptr);
clock_gettime(CLOCK_MONOTONIC, &f_end);
f_elapsed_times[i] = ((f_end.tv_sec - f_start.tv_sec) +
(f_end.tv_nsec - f_start.tv_nsec)) /
1e9;
}
struct Stats *stats = (struct Stats *)malloc(sizeof(struct Stats));
if (!stats) {
perror("malloc");
exit(1);
}
stats->allocator_type = "malloc";
stats->nr_allocations = BLOCK_NUM;
stats->avg_alloc_time = avg(m_elapsed_times, BLOCK_NUM);
stats->avg_free_time = avg(f_elapsed_times, BLOCK_NUM);
stats->mem_usage_mb = peak_mem;
return stats;
}
struct Stats *bench_calloc()
{
// Benchmark malloc function
double peak_mem = 0.0;
double m_elapsed_times[BLOCK_NUM] = {0};
double f_elapsed_times[BLOCK_NUM] = {0};
for (int i = 0; i < BLOCK_NUM; i++) {
size_t size = size_blocks[i];
struct timespec m_start, f_start, m_end, f_end;
// malloc
clock_gettime(CLOCK_MONOTONIC, &m_start);
void *ptr = calloc(1, size);
clock_gettime(CLOCK_MONOTONIC, &m_end);
m_elapsed_times[i] = ((m_end.tv_sec - m_start.tv_sec) +
(m_end.tv_nsec - m_start.tv_nsec)) /
1e9;
double curr_mem_usage = get_mem_usage();
if (peak_mem <= curr_mem_usage) {
peak_mem = curr_mem_usage;
}
// free
clock_gettime(CLOCK_MONOTONIC, &f_start);
free(ptr);
clock_gettime(CLOCK_MONOTONIC, &f_end);
f_elapsed_times[i] = ((f_end.tv_sec - f_start.tv_sec) +
(f_end.tv_nsec - f_start.tv_nsec)) /
1e9;
}
struct Stats *stats = (struct Stats *)malloc(sizeof(struct Stats));
if (!stats) {
perror("malloc");
exit(1);
}
stats->allocator_type = "calloc";
stats->nr_allocations = BLOCK_NUM;
stats->avg_alloc_time = avg(m_elapsed_times, BLOCK_NUM);
stats->avg_free_time = avg(f_elapsed_times, BLOCK_NUM);
stats->mem_usage_mb = peak_mem;
return stats;
}
struct Stats *bench_mmap()
{
// Benchmark malloc function
double peak_mem = 0.0;
double m_elapsed_times[BLOCK_NUM] = {0};
double f_elapsed_times[BLOCK_NUM] = {0};
for (int i = 0; i < BLOCK_NUM; i++) {
size_t size = size_blocks[i];
struct timespec m_start, f_start, m_end, f_end;
// malloc
clock_gettime(CLOCK_MONOTONIC, &m_start);
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
clock_gettime(CLOCK_MONOTONIC, &m_end);
m_elapsed_times[i] = ((m_end.tv_sec - m_start.tv_sec) +
(m_end.tv_nsec - m_start.tv_nsec)) /
1e9;
double curr_mem_usage = get_mem_usage();
if (peak_mem <= curr_mem_usage) {
peak_mem = curr_mem_usage;
}
// free
clock_gettime(CLOCK_MONOTONIC, &f_start);
munmap(ptr, size);
clock_gettime(CLOCK_MONOTONIC, &f_end);
f_elapsed_times[i] = ((f_end.tv_sec - f_start.tv_sec) +
(f_end.tv_nsec - f_start.tv_nsec)) /
1e9;
}
struct Stats *stats = (struct Stats *)malloc(sizeof(struct Stats));
if (!stats) {
perror("malloc");
exit(1);
}
stats->allocator_type = "mmap";
stats->nr_allocations = BLOCK_NUM;
stats->avg_alloc_time = avg(m_elapsed_times, BLOCK_NUM);
stats->avg_free_time = avg(f_elapsed_times, BLOCK_NUM);
stats->mem_usage_mb = peak_mem;
return stats;
}

View File

@@ -0,0 +1,47 @@
#include "mem_utils.h"
#include <stdio.h>
#include <string.h>
void print_stats(struct Stats *stats)
{
printf("Allocator: %s\nBlocks: %ld\nAvarage Allocation Time: %.2f "
"μs\nAvarage Free Time: %.2f μs\nPeak Memory Usage: %.2f MB\n",
stats->allocator_type, stats->nr_allocations,
stats->avg_alloc_time*1e6, stats->avg_free_time*1e6,
stats->mem_usage_mb);
}
double avg(double *values, int size)
{
double sum = 0.0;
for (int i = 0; i < size; i++) {
sum += *(values + i);
}
return size > 0 ? sum / size : 0.0;
}
double get_mem_usage()
{
FILE *fp = fopen("/proc/self/status", "r");
if (!fp) {
perror("fopen");
return 1;
}
int found = 0;
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "VmRSS:", 6) == 0) {
found = 1;
break;
}
}
fclose(fp);
if (!found) {
fprintf(stderr, "VmRSS not found in /proc/self/status\n");
return 0.0;
}
return strtod(line + 6, NULL) / 1024.0;
}

View File

@@ -0,0 +1,26 @@
#ifndef MEM_UTILS_H
#define MEM_UTILS_H
#include <stdlib.h>
/* Block number under testing */
#define BLOCK_NUM 5
struct Stats {
const char *allocator_type;
size_t nr_allocations;
double avg_alloc_time;
double avg_free_time;
double mem_usage_mb;
};
/* Print on STDOUT the memory stats collected */
void print_stats(struct Stats *);
/* Calculate arithmetic average */
double avg(double *, int size);
/* Read in the a Linux system at /proc/self/status file the RSS field */
double get_mem_usage();
#endif

View File

@@ -0,0 +1,12 @@
CC = gcc
CFLAGS = -Wall -Wextra -O2
TARGET = signal_handler
SRC = main.c
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $(TARGET) $(SRC)
clean:
rm -f $(TARGET)

View File

@@ -0,0 +1,55 @@
# Signal Handler
## Overview
This project demonstrates how to handle UNIX signals in C using `sigaction`.
It specifically intercepts **SIGINT** and **SIGUSR1** and prints messages when these signals are received.
---
## Features
- Intercepts **SIGINT** (usually generated with `Ctrl + C`) and **SIGUSR1**`.
- Prints a custom message for each signal.
- Tracks the number of `SIGINT` signals received and terminates after a defined count.
- Demonstrates proper use of:
- `sigaction()` instead of `signal()` for consistent behavior.
- `volatile sig_atomic_t` for safe signal state handling.
---
## Build
```bash
make
```
---
## Run
```bash
./signal_handler
```
---
## Test
- Open another terminal and run:
```bash
kill -USR1 <PID>
```
where `<PID>` is the process ID of `signal_handler`.
- Press `Ctrl + C` in the terminal running the program to send `SIGINT`.
---
## Clean
```bash
make clean
```

View File

@@ -0,0 +1,58 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
volatile sig_atomic_t sigint_caught = 0;
void signal_handler(int signo, siginfo_t *si, void *ucontext)
{
(void)si;
(void)ucontext;
if (signo == SIGINT) {
sigint_caught += 1;
if (write(STDOUT_FILENO, "Caught SIGINT signal\n", 22) == -1) {
_exit(EXIT_FAILURE);
}
if (sigint_caught >= 3) {
if (write(STDOUT_FILENO, "Reach SIGINT limit\n", 20) ==
-1) {
_exit(EXIT_FAILURE);
}
_exit(EXIT_FAILURE);
}
} else if (signo == SIGUSR1) {
if (write(STDOUT_FILENO, "Caught SIGUSR1 signal\n", 23) == -1) {
_exit(EXIT_FAILURE);
}
} else {
if (write(STDOUT_FILENO, "Unknown signal\n", 16) == -1) {
_exit(EXIT_FAILURE);
}
}
}
int main(void)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGINT);
sigaddset(&action.sa_mask, SIGUSR1);
action.sa_sigaction = signal_handler;
action.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &action, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
if (sigaction(SIGUSR1, &action, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("Hi, I'm process with PID %d\n", getpid());
for (;;)
pause();
return EXIT_SUCCESS;
}