Skip to content

LibC Internals

TilekarOS includes a minimal C Standard Library (libc) tailored for kernel development and future user-space applications. This document explains the implementation of core functions.

1. Formatted I/O (stdio.h)

Source Files: printf.c, sprintf.c, putchar.c

The implementation of printf is designed to be Freestanding.

LibK vs. User LibC:

  • __is_libk: When defined (during kernel build), printf calls terminal_write directly.
  • User Mode: When NOT defined, printf uses the SYS_WRITE system call to output data to stdout (File Descriptor 1).
Code Preview: printf.c
#include "kernel/tty.h"
#include <limits.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#if !defined(__is_libk)
#include <sys/syscall.h>
#endif

static bool print(const char* data, size_t length) {
#if defined(__is_libk)
    terminal_write(data, length);
#else
    __syscall(SYS_WRITE, 1, (uint32_t)data, length, 0, 0);
#endif
    return true;
}

static char* itoa(unsigned long long value, char* str, int base) {
    char* rc;
    char* ptr;
    char* low;
    // Check for supported base.
    if (base < 2 || base > 36) {
        *str = '\0';
        return str;
    }
    rc = ptr = str;
    // Set '-' for negative decimals is handled in printf wrappers,
    // we treat value as unsigned long long here for generic base conversion.

    // Remember where the numbers start.
    low = ptr;
    // The actual conversion.
    do {
        // Modulo is negative for negative numbers, not needed for unsigned.
        *ptr++ = "0123456789abcdefghijklmnopqrstuvwxyz"[value % base];
        value /= base;
    } while (value);
    // Terminate the string.
    *ptr = '\0';
    // Invert the numbers.
    char* start = low;
    char* end = ptr - 1;
    while (start < end) {
        char tmp = *start;
        *start++ = *end;
        *end-- = tmp;
    }
    return rc;
}

int printf(const char* restrict format, ...) {
    va_list parameters;
    va_start(parameters, format);

    int written = 0;

    while (*format != '\0') {
        size_t maxrem = INT_MAX - written;

        if (format[0] != '%' || format[1] == '%') {
            if (format[0] == '%')
                format++;
            size_t amount = 1;
            while (format[amount] && format[amount] != '%')
                amount++;
            if (maxrem < amount) {
                // TODO: Set errno to EOVERFLOW.
                return -1;
            }
            if (!print(format, amount))
                return -1;
            format += amount;
            written += amount;
            continue;
        }

        const char* format_begun_at = format++;

        if (*format == 'c') {
            format++;
            char c = (char) va_arg(parameters, int /* char promotes to int */);
            if (!maxrem) {
                // TODO: Set errno to EOVERFLOW.
                return -1;
            }
            if (!print(&c, sizeof(c)))
                return -1;
            written++;
        } else if (*format == 's') {
            format++;
            const char* str = va_arg(parameters, const char*);
            size_t len = strlen(str);
            if (maxrem < len) {
                // TODO: Set errno to EOVERFLOW.
                return -1;
            }
            if (!print(str, len))
                return -1;
            written += len;
        } else if (*format == 'd' || *format == 'i') {
            format++;
            int i = va_arg(parameters, int);
            char buffer[32];
            if (i < 0) {
                if (!print("-", 1)) return -1;
                written++;
                i = -i;
            }
            itoa((unsigned long long)i, buffer, 10);
            size_t len = strlen(buffer);
            if (maxrem < len) return -1;
            if (!print(buffer, len)) return -1;
            written += len;
        } else if (*format == 'u') {
            format++;
            unsigned int u = va_arg(parameters, unsigned int);
            char buffer[32];
            itoa(u, buffer, 10);
            size_t len = strlen(buffer);
            if (maxrem < len) return -1;
            if (!print(buffer, len)) return -1;
            written += len;
        } else if (*format == 'x') {
            format++;
            unsigned int x = va_arg(parameters, unsigned int);
            char buffer[32];
            itoa(x, buffer, 16);
            size_t len = strlen(buffer);
            if (maxrem < len) return -1;
            if (!print(buffer, len)) return -1;
            written += len;
        } else if (*format == 'X') {
            format++;
            unsigned int x = va_arg(parameters, unsigned int);
            char buffer[32];
            itoa(x, buffer, 16);
            // Convert to uppercase
            for(int k=0; buffer[k]; k++) {
                if (buffer[k] >= 'a' && buffer[k] <= 'z')
                    buffer[k] -= 32;
            }
            size_t len = strlen(buffer);
            if (maxrem < len) return -1;
            if (!print(buffer, len)) return -1;
            written += len;
        } else if (*format == 'p') {
            format++;
            void* ptr = va_arg(parameters, void*);
            char buffer[32];
            if (!print("0x", 2)) return -1;
            written += 2;
            itoa((unsigned long long)(uintptr_t)ptr, buffer, 16);
            size_t len = strlen(buffer);
            if (maxrem < len) return -1;
            if (!print(buffer, len)) return -1;
            written += len;
        } else {
            format = format_begun_at;
            size_t len = strlen(format);
            if (maxrem < len) {
                // TODO: Set errno to EOVERFLOW.
                return -1;
            }
            if (!print(format, len))
                return -1;
            written += len;
            format += len;
        }
    }

    va_end(parameters);
    return written;
}

Supported Specifiers:

  • %s: String
  • %c: Character
  • %d / %i: Signed Integer
  • %u: Unsigned Integer
  • %x / %X: Hexadecimal (Lowercase / Uppercase)
  • %p: Pointer (Formatted as 0x...)

2. String Manipulation (string.h)

Source Files: string/

TilekarOS prioritizes correctness and simplicity in its string implementation.

Key Functions:

  • memcpy: Fast byte-by-byte copying.
  • memmove: Handles overlapping memory regions by checking if the source is before or after the destination.
  • memset: Fills memory with a constant byte.
  • strcmp / strncmp: Standard string comparison.
Code Preview: memmove.c
#include <string.h>

void* memmove(void* dstptr, const void* srcptr, size_t size) {
    unsigned char* dst = (unsigned char*) dstptr;
    const unsigned char* src = (const unsigned char*) srcptr;
    if (dst < src) {
        for (size_t i = 0; i < size; i++)
            dst[i] = src[i];
    } else {
        for (size_t i = size; i != 0; i--)
            dst[i-1] = src[i-1];
    }
    return dstptr;
}

3. Standard Library (stdlib.h)

Source Files: abort.c

  • abort(): In the kernel, this triggers a Kernel Panic message and halts the CPU. In user mode, it would eventually send a signal to terminate the process.
Code Preview: abort.c
#include <stdio.h>
#include <stdlib.h>

__attribute__((__noreturn__))
void abort(void) {
#if defined(__is_libk)
    // TODO: Add proper kernel panic.
    printf("kernel: panic: abort()\n");
#else
    // TODO: Abnormally terminate the ktask as if by SIGABRT.
    printf("abort()\n");
#endif
    while (1) { }
    __builtin_unreachable();
}

4. Test/Example: Complex Formatting

You can use sprintf to build complex strings before outputting them:

#include <stdio.h>

void log_event(const char* module, int code) {
    char log_buf[128];
    sprintf(log_buf, "[LOG] %s: Error Code %d", module, code);
    puts(log_buf);
}

References