This project implements a freestanding C standard library for Linux x86_64, built directly on syscall primitives without external dependencies. The implementation provides string manipulation, memory management, file I/O, and process control—core functionalities required by C programs.
Educational Goal: Understand the boundary between user-space and kernel by reimplementing standard library functions from scratch using raw system calls. This project demonstrates how high-level C abstractions are built atop the Linux syscall interface.
All library functions are built atop the syscall wrapper in src/syscall.c:
long syscall(long number, ...); // Direct kernel invocation via assemblyExample: open() invokes syscall(2, filename, flags, mode) for the open syscall.
| Module | Functions Implemented |
|---|---|
String (src/string/) |
strcpy, strncpy, strcat, strncat, strcmp, strncmp, strchr, strrchr, strstr, strrstr, memcpy, memset, memmove, memcmp |
Memory (src/mm/) |
malloc, free, calloc, realloc, reallocarray |
File I/O (src/io/) |
open, close, read, write, lseek, truncate, ftruncate, puts |
Metadata (src/stat/) |
stat, fstat |
Process (src/process/) |
nanosleep, sleep |
Challenge: Implement standard string functions without library dependencies.
Example (strcpy):
char *strcpy(char *dest, const char *src) {
int i = 0;
while (src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
return dest;
}Learning: Pointer arithmetic, null-termination, buffer boundaries. Functions like strncpy require padding with null bytes when copying partial strings.
Design: Uses mmap for direct memory mapping instead of traditional brk/sbrk.
malloc Implementation:
void *malloc(size_t size) {
void *addr = (void*)syscall(9, NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == (void*)-1)
return NULL;
mem_list_add(addr, size); // Track allocation metadata
return addr;
}Tracking: A linked list (mem_list) stores {address, size} pairs. free() uses this to retrieve allocation sizes for munmap.
Trade-off: Simpler than slab allocators but higher syscall overhead for small allocations.
Requirement: Properly set errno on failures.
Pattern:
int open(const char *filename, int flags, ...) {
va_list arg_list;
int mode = 0;
va_start(arg_list, flags);
if (flags & O_CREAT)
mode = va_arg(arg_list, int);
va_end(arg_list);
int res = syscall(2, filename, flags, mode);
if (res < 0) {
errno = -res; // Convert negative error code
return -1;
}
return res;
}Learning: Linux syscalls return negative error codes (e.g., -ENOENT). These must be negated and stored in errno while returning -1.
Implementation:
unsigned int sleep(unsigned int seconds) {
struct timespec req = {seconds, 0}, rem;
if (nanosleep(&req, &rem) == -1)
return rem.tv_sec; // Return unslept time on interrupt
return 0;
}nanosleep directly wraps syscall 35; sleep constructs a timespec and delegates.
The library is compiled as a static archive that can be linked with programs:
cd src/
make # Produces libc.a (static library)Programs can link against the mini-libc:
gcc -nostdlib -L./src -lc my_program.c -o my_programThe -nostdlib flag prevents linking against system libc, forcing the use of this implementation.
| Concept | Application |
|---|---|
| Syscall ABI | Direct assembly invocation; register-based argument passing |
| Memory Management | Implementing malloc via mmap; tracking without OS assistance |
| Error Propagation | Converting kernel error codes to errno |
| Variadic Functions | Handling optional parameters (e.g., open mode) |
| POSIX Compliance | Adhering to man page specifications |
- Pointer Manipulation: Raw pointer operations without safety abstractions.
- Assembly Integration: Understanding C-to-kernel transitions via
syscallinstruction. - Debugging: Using
straceto verify syscall parameters. - Buffer Safety: Preventing overflows in
strncpy, handling overlaps inmemmove.
- Simplicity vs Performance:
mmap-based allocator prioritizes correctness over speed. - Standards Compliance: Edge cases (e.g.,
strcmpreturn values) require careful POSIX adherence.
Problem: Naive memcpy corrupts overlapping data.
Solution: Detect overlap direction and copy accordingly:
if (src < dest && src + n > dest)
// Copy backward
else
// Copy forwardProblem: free() requires allocation size.
Solution: Maintain mem_list linked list of allocations. Lookup on free() to retrieve size for munmap.
Problem: Tests fail until time.h and puts exist.
Missing Standard Headers
Problem: Standard library programs expect headers like time.h.
Solution: Created include/time.h with struct timespec definition; implemented puts() as a wrapper around write(1, ...)
src/
├── string/string.c # 13 string functions
├── mm/malloc.c # Memory allocator
├── io/ # File operations (open, close, lseek, puts)
├── stat/ # Metadata (stat, fstat)
├── process/ # Process control (nanosleep, sleep)
├── syscall.c # Syscall dispatcher
└── include/ # Headers (string.h, stdlib.h, unistd.h, etc.)
samples/
└── sample_*.c # Example programs demonstrating library usage
- Linux Syscall Reference: https://man7.org/linux/man-pages/man2/syscalls.2.html
- POSIX.1-2017: https://pubs.opengroup.org/onlinepubs/9699919799/
- x86_64 ABI: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
Author: Iazinschi Mihail
Course: Operating Systems — System Programming Assignment
This implementation was developed as part of an Operating Systems course focused on system programming and kernel interfaces.
Author: Iazinschi Mihail
License: BSD-3-Clause