Skip to content

Development Guide

Enrico Fraccaroli (Galfurian) edited this page Jan 29, 2026 · 5 revisions

Learn how to add new programs and features to MentOS.

Adding a New Program

1. Create the Source File

Create a new .c file in userspace/bin/ (or userspace/tests/ for tests):

cd userspace/bin

Create hello.c:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    printf("Hello from MentOS!\n");
    
    if (argc > 1) {
        printf("Arguments:\n");
        for (int i = 1; i < argc; i++) {
            printf("  argv[%d] = %s\n", i, argv[i]);
        }
    }
    
    return 0;
}

2. Add to CMakeLists.txt

Edit userspace/bin/CMakeLists.txt and add your program to the list:

# Add the executables (manually).
set(PROGRAM_LIST
    cat.c
    chmod.c
    # ... existing programs ...
    hello.c        ← Add your program here
)

For tests, edit userspace/tests/CMakeLists.txt instead.

3. Build

cd build
make
make filesystem

4. Test

make qemu
# At shell prompt:
hello
hello arg1 arg2

Adding a System Call

System calls allow userspace programs to request kernel services.

1. Define Syscall Number

Edit lib/inc/system/syscall_types.h:

#define __NR_fork       2
#define __NR_read       3
// ... existing syscalls ...
#define __NR_myfunction 100  ← Add new syscall number

2. Implement in Kernel

Create kernel/src/system/my_function.c:

#include "process/scheduler.h"
#include "system/printk.h"

long sys_myfunction(int arg1, int arg2)
{
    // Your kernel code here
    printk("sys_myfunction called with %d, %d\n", arg1, arg2);
    
    // Access current process
    task_struct *task = scheduler_get_current_process();
    
    // Return value
    return arg1 + arg2;
}

3. Register in Dispatcher

Edit kernel/src/system/syscall.c:

long sys_myfunction(int arg1, int arg2); // Declare

// In syscall_init():
sys_call_table[__NR_myfunction] = (SystemCall)sys_myfunction;

4. Create Userspace Wrapper

Create lib/src/unistd/myfunction.c:

#include "errno.h"
#include "system/syscall_types.h"
#include "unistd.h"

int myfunction(int arg1, int arg2)
{
    long __res;
    __inline_syscall_2(__res, myfunction, arg1, arg2);
    __syscall_return(int, __res);
}

5. Add Header Declaration

Edit lib/inc/unistd.h (or appropriate header):

int myfunction(int arg1, int arg2);

6. Test

Create test program userspace/tests/t_myfunction.c:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int result = myfunction(10, 20);
    printf("myfunction(10, 20) = %d\n", result);
    return 0;
}

Build and test:

make
make filesystem
make qemu
# At shell:
/bin/tests/t_myfunction

Adding a Device Driver

1. Create Driver Source

Create kernel/src/drivers/mydevice.c:

#include <io/port_io.h>
#include <system/printk.h>

#define MYDEVICE_PORT 0x3F8  // Example I/O port

void mydevice_init(void)
{
    pr_info("Initializing mydevice driver\n");
    
    // Initialize hardware
    outportb(MYDEVICE_PORT, 0x00);
}

int mydevice_read(void)
{
    return inportb(MYDEVICE_PORT);
}

void mydevice_write(uint8_t data)
{
    outportb(MYDEVICE_PORT, data);
}

2. Add Header

Create kernel/inc/drivers/mydevice.h:

#pragma once

void mydevice_init(void);
int mydevice_read(void);
void mydevice_write(uint8_t data);

3. Initialize Driver

Edit kernel/src/kernel.c in kmain():

#include <drivers/mydevice.h>

void kmain(/* ... */)
{
    // ... existing initialization ...
    
    mydevice_init();  ← Initialize your driver
    
    // ... rest of kernel init ...
}

4. Use Driver (Optional Syscall)

Create syscalls to interact with the device from userspace (follow "Adding a System Call" steps).

Modifying the Scheduler

See Scheduling for details on scheduler algorithms.

Changing Algorithm

cd build
cmake .. -DSCHEDULER_TYPE=SCHEDULER_CFS
make

Adding Custom Scheduler

  1. Create kernel/src/process/sched_custom.c

  2. Implement scheduler interface:

    void scheduler_custom_init(void);
    task_struct *scheduler_custom_pick_next(void);
    void scheduler_custom_enqueue(task_struct *task);
  3. Register in kernel/CMakeLists.txt (add a new entry to SCHEDULER_TYPES)

  4. Add your implementation to kernel/src/process/ (sources are picked up automatically)

Adding File System Support

1. Implement VFS Operations

Create kernel/src/fs/myfs.c:

#include <fs/vfs.h>
#include <fs/vfs_types.h>

static vfs_file_t *myfs_mount_callback(const char *path, const char *device);

static file_system_type_t myfs_file_system_type = {
    .name  = "myfs",
    .fs_flags = 0,
    .mount = myfs_mount_callback,
};

int myfs_init(void)
{
    return vfs_register_filesystem(&myfs_file_system_type);
}

2. Implement Functions

static vfs_file_t *myfs_open(const char *path, int flags, mode_t mode)
{
    // Open file logic
}

static int myfs_read(vfs_file_t *file, char *buf, off_t offset, size_t nbyte)
{
    // Read logic
}

// ... etc

3. Register Filesystem

In kernel/src/kernel.c:

myfs_init();

Testing Your Changes

Unit Tests

Create tests in userspace/tests/:

// t_myfeature.c
#include <stdio.h>
#include <assert.h>

int main(void)
{
    int result = myfunction(5, 10);
    assert(result == 15);
    
    printf("Test passed!\n");
    return 0;
}

Integration Tests

Run full test suite:

make qemu-test

Manual Testing

make qemu
# Manually test features

Debugging Tips

1. Use printk in Kernel

pr_debug("Value = %d\n", value);
pr_info("Function called\n");
pr_err("Error occurred: %d\n", errno);

See Debugging#kernel-logging.

2. Use printf in Userspace

printf("Debug: x=%d, y=%d\n", x, y);

3. Use GDB

make qemu-gdb
# In another terminal (from build/):
gdb --quiet --command=gdb.run

4. Check Logs

Kernel messages appear in terminal running make qemu.

Code Style

Follow the C coding guidelines from .github/copilot-instructions.md:

  • Functions/variables: snake_case
  • Types/structs: CamelCase or snake_case_t
  • Constants: UPPER_CASE
  • Initialization: Always initialize variables
  • Error handling: Use return codes or errno
  • Comments: Document non-obvious code

Example:

// Good
int calculate_sum(int a, int b)
{
    int result = a + b;  // Initialize variables
    return result;
}

// Bad
int CalculateSum(int a, int b)
{
    int result;  // Uninitialized
    result = a + b;
    return result;
}

Common Patterns

Getting Current Process

#include <process/scheduler.h>

task_struct *current = scheduler_get_current_process();
pid_t pid = current->pid;

Allocating Memory in Kernel

#include <mem/alloc/slab.h>

void *ptr = kmalloc(size);
// Use ptr
kfree(ptr);

Copying Data Between Kernel/User Space

#include <mem/mm/vmem.h>

// Copy between address spaces (src/dst are mm_struct_t* + virtual addrs)
vmem_memcpy(dst_mm, dst_vaddr, src_mm, src_vaddr, size);

File Operations

#include <fs/vfs.h>

// Open file
vfs_file_t *file = vfs_open("/path/to/file", O_RDONLY, 0);

// Read
char buf[256];
ssize_t bytes = vfs_read(file, buf, 0, sizeof(buf));

// Close
vfs_close(file);

Build System Tips

Rebuild After Changes

# Kernel change
make kernel.bin
make bootloader.bin

# Userspace program
make programs
make filesystem

# Library change
make libc
make programs
make filesystem

# CMake config change
cd build
cmake ..
make

Parallel Builds

make -j$(nproc)

Clean Build

make clean
# or
rm -rf build && mkdir build && cd build && cmake .. && make

Next Steps


Previous: Running MentOS | Next: Debugging

Clone this wiki locally