Skip to content

Userspace Programs

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

This page teaches you how to write userspace programs for MentOS.

What are Userspace Programs?

Userspace programs are applications that run in user mode (ring 3), separate from the kernel. They:

  • Run in their own isolated address space (0x00000000 - 0xBFFFFFFF)
  • Cannot directly access hardware or kernel memory
  • Interact with the kernel only through system calls
  • Use the C standard library for common functionality

MentOS provides 40+ built-in programs (ls, cat, shell, ps, etc.) that demonstrate these concepts.

How Programs Work

Program Lifecycle

1. Kernel loads ELF binary from disk
   ↓
2. Creates new process with isolated address space
   ↓
3. Maps program into memory (text, data, bss, stack)
   ↓
4. Jumps to _start (entry point in crt0.S)
   ↓
5. _start calls __libc_start_main
   ↓
6. __libc_start_main calls main(argc, argv, envp)
   ↓
7. Program executes
   ↓
8. main returns to __libc_start_main
   ↓
9. _start performs the exit syscall (int 0x80)
   ↓
10. Kernel cleans up process

Memory Layout

When a program is loaded:

0xBFFFFFFF  ┌─────────────────┐
            │     Stack       │  Grows downward
            │       ↓         │
            ├─────────────────┤
            │       ↑         │
            │     Heap        │  Grows upward (malloc/brk)
            ├─────────────────┤
            │   .bss (zeros)  │  Uninitialized data
            ├─────────────────┤
            │   .data         │  Initialized data
            ├─────────────────┤
0x0xxxxxxx  │   .text         │  Program code (randomized address)
            └─────────────────┘
0x00000000  (Invalid - NULL pointer)

Note: The .text section address is randomized per-program (between 0x10000000 and 0xB0000000) so symbols don't clash when debugging multiple programs.

Interacting with the Kernel

Programs use system calls to request kernel services:

// User calls libc function
int fd = open("/file.txt", O_RDONLY);

// libc wrapper invokes syscall
ssize_t n = read(fd, buffer, 100);

// Under the hood:
// 1. Set syscall number in eax
// 2. Set arguments in ebx, ecx, edx, esi, edi
// 3. Execute INT 0x80
// 4. CPU switches to kernel mode (ring 0)
// 5. Kernel handles syscall
// 6. Return to userspace with result in eax

Creating Your First Program

Let's create a simple hello program step by step.

Step 1: Write the Source Code

Create userspace/bin/hello.c:

/// @file hello.c
/// @brief A simple hello world program
/// @copyright (c) 2014-2024 This file is distributed under the MIT License.
/// See LICENSE.md for details.

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

int main(int argc, char **argv)
{
    printf("Hello, MentOS!\n");
    
    // Show arguments if provided
    if (argc > 1) {
        printf("You passed %d argument(s):\n", argc - 1);
        for (int i = 1; i < argc; i++) {
            printf("  [%d]: %s\n", i, argv[i]);
        }
    }
    
    return 0;
}

What's happening:

  • #include <stdio.h> - Use standard I/O functions (printf)
  • main(argc, argv) - Entry point called by libc
  • printf() - Calls write() syscall internally
  • return 0 - Success exit code

Step 2: Add to Build System

Edit userspace/bin/CMakeLists.txt and add hello.c to the PROGRAM_LIST:

set(PROGRAM_LIST
    cat.c
    # ... other programs ...
    hello.c      # <-- Add this line
    # ... more programs ...
)

That's it! The CMake configuration automatically:

  1. Creates a build target prog_hello
  2. Links with libc
  3. Sets up the entry point (_start)
  4. Randomizes the .text address
  5. Outputs binary to filesystem/bin/hello

Step 3: Build and Test

# Build the program
cd build
make prog_hello

# Rebuild filesystem with new program
make filesystem

# Run MentOS
make qemu

# In MentOS shell:
$ hello
Hello, MentOS!

$ hello world foo bar
Hello, MentOS!
You passed 3 argument(s):
  [1]: world
  [2]: foo
  [3]: bar

Example: File Operations

A more complex example showing file I/O:

/// @file wordcount.c
/// @brief Count lines, words, and characters in a file

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Usage: %s <file>\n", argv[0]);
        return 1;
    }
    
    // Open file (uses sys_open syscall)
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    // Count lines, words, chars
    int lines = 0, words = 0, chars = 0;
    int in_word = 0;
    char c;
    
    // Read file one byte at a time (uses sys_read syscall)
    while (read(fd, &c, 1) > 0) {
        chars++;
        
        if (c == '\n') {
            lines++;
        }
        
        if (isspace(c)) {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            words++;
        }
    }
    
    // Close file (uses sys_close syscall)
    close(fd);
    
    printf("%d lines, %d words, %d chars in %s\n",
           lines, words, chars, argv[1]);
    
    return 0;
}

Key concepts:

  • Error handling with perror()
  • File descriptor lifecycle: open → read → close
  • Working with syscalls through libc wrappers

Example: Process Management

Creating child processes:

/// @file runner.c
/// @brief Execute a program and wait for it

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Usage: %s <program> [args...]\n", argv[0]);
        return 1;
    }
    
    printf("Running: %s\n", argv[1]);
    
    // Create child process (uses sys_fork syscall)
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork");
        return 1;
    }
    
    if (pid == 0) {
        // Child process: execute the program
        // (uses sys_execve syscall)
        execve(argv[1], &argv[1], NULL);
        
        // If we get here, execve failed
        perror("execve");
        exit(1);
    }
    
    // Parent process: wait for child
    int status;
    waitpid(pid, &status, 0);
    
    if (WIFEXITED(status)) {
        printf("Program exited with code: %d\n", WEXITSTATUS(status));
    } else {
        printf("Program terminated abnormally\n");
    }
    
    return 0;
}

Key concepts:

  • fork() creates identical copy of process
  • Child gets return value 0, parent gets child PID
  • execve() replaces process image
  • waitpid() waits for child termination

Common Patterns

Signal Handling

#include <signal.h>

void handle_sigint(int sig)
{
    printf("\nCaught SIGINT (Ctrl+C)\n");
    exit(0);
}

int main()
{
    signal(SIGINT, handle_sigint);
    
    printf("Press Ctrl+C to exit...\n");
    while (1) {
        sleep(1);
    }
    
    return 0;
}

Reading from stdin

#include <stdio.h>

int main()
{
    char buffer[256];
    
    printf("Enter your name: ");
    
    // Read from standard input (fd 0)
    if (fgets(buffer, sizeof(buffer), stdin)) {
        printf("Hello, %s", buffer);
    }
    
    return 0;
}

Error Handling Pattern

#include <stdio.h>
#include <errno.h>
#include <string.h>

int fd = open("/nonexistent", O_RDONLY);
if (fd < 0) {
    // Option 1: Simple message
    perror("open");
    
    // Option 2: Custom message
    printf("Failed to open file: %s\n", strerror(errno));
    
    // Option 3: Check specific error
    if (errno == ENOENT) {
        printf("File does not exist\n");
    }
    
    return 1;
}

Build System Details

When you add a program to PROGRAM_LIST, CMake automatically:

  1. Creates executable target: prog_<name>
  2. Includes libc headers: From lib/inc/
  3. Links with libc: Static library libc.a
  4. Sets entry point: -u_start (defined in lib/src/crt0.S)
  5. Randomizes .text address: Prevents symbol conflicts
  6. Sets linker flags: -Ttext=<random> -e_start -melf_i386
  7. Outputs to filesystem: filesystem/bin/<name>

You can build individual programs:

make prog_hello      # Build just hello
make programs        # Build all programs

Installing Programs

Programs must be in filesystem/bin/ to be accessible in MentOS:

# After building
make filesystem      # Creates rootfs.img with all programs

# Programs are then accessible in MentOS:
$ ls /bin
cat  chmod  cp  echo  hello  ls  mkdir  ...

Available Programs Reference

MentOS includes these built-in programs (see source in userspace/bin/):

System & Core

  • init - First process, spawns login
  • login - User authentication
  • shell - Command interpreter with pipes and job control

File Operations

  • cat - Display file contents
  • ls - List directory contents
  • cp - Copy files
  • rm - Remove files
  • mkdir - Create directory
  • rmdir - Remove directory
  • chmod - Change permissions
  • chown - Change ownership
  • touch - Create/update file
  • stat - Show file metadata
  • head - Show first lines
  • more - Page through file

Process & System

  • ps - List processes
  • kill - Send signals
  • nice - Run with priority
  • showpid - Display PID
  • uptime - System uptime
  • uname - System information
  • date - Show date/time
  • poweroff - Shutdown system

Utilities

  • echo - Print text
  • env - Show environment
  • pwd - Print working directory
  • clear - Clear screen
  • reset - Reset terminal
  • id - Show user/group IDs
  • logo - Display welcome banner
  • cpuid - CPU information

IPC Tools

  • ipcs - List IPC resources
  • ipcrm - Remove IPC resources

Development

  • runtests - Test suite runner
  • edit - Simple text editor
  • man - Manual pages

Other

  • false - Always returns failure (for scripting)
  • sleep - Delay execution

Each program demonstrates different aspects of system programming. Check their source code in userspace/bin/ for implementation examples.

Further Reading

Location: userspace/bin/login.c

Prompts user for credentials and starts shell.

Flow:

1. Display "login:" prompt
2. Read username
3. Display "password:" prompt
4. Read and verify password
5. Set UID/GID to user's credentials
6. Change to user's home directory
7. Execute user's shell

Key Functions:

  • Username validation (from /etc/passwd)
  • Password verification (from /etc/shadow)
  • Session setup
  • Environment variables

shell - Command Interpreter

Location: userspace/bin/shell.c

Interactive command interpreter.

Features:

  • Command execution (with fork/exec)
  • Built-in commands (cd, exit, help)
  • Job control (background processes)
  • Command history (if supported)
  • Environmental variables ($VAR expansion)
  • Pipe support (|) for command chaining
  • Redirections (>, <, >>)

Built-in Commands:

cd <dir>          # Change directory
exit [code]       # Exit shell
help              # Show help
export VAR=val    # Set environment variable

Example Interactions:

$ ls -la
total 256
drwxr-xr-x  5 root root  4096 Jan  1  1970  .
drwxr-xr-x  3 root root  4096 Jan  1  1970  ..
-rw-r--r--  1 root root   256 Jan  1  1970  file.txt

$ cat file.txt
Hello from MentOS

$ echo "Test" > output.txt

$ ps
  PID TTY STAT  TIME COMMAND
    1   ?  S     0:01 init
   42   ?  S     0:00 login
   44  #0 S     0:00 shell
   49  #0 R     0:00 ps

$ ls | grep bin
bin

File Utilities

ls - List Directory Contents

Location: userspace/bin/ls.c

Lists files and directories.

Flags:

  • -l - Long format (permissions, size, date, name)
  • -a - Show all files (including hidden)
  • -h - Human-readable sizes
  • -R - Recursive listing
  • -S - Sort by size
  • -t - Sort by time

Output Format:

drwxr-xr-x  2 root root  4096 Jan  1  1970  bin
-rw-r--r--  1 root root  1024 Jan  1  1970  config.txt
lrwxrwxrwx  1 root root    14 Jan  1  1970  link -> /home/file

cat - Concatenate Files

Location: userspace/bin/cat.c

Outputs file contents to stdout.

Usage:

cat file.txt              # Print file
cat file1.txt file2.txt   # Print multiple files
cat > output.txt          # Create file from stdin

cp - Copy Files

Location: userspace/bin/cp.c

Copies files or directories.

Flags:

  • -r - Recursive (for directories)
  • -f - Force overwrite

Usage:

cp source dest           # Copy file
cp -r source/ dest/      # Copy directory

rm - Remove Files

Location: userspace/bin/rm.c

Deletes files.

Flags:

  • -r - Recursive (for directories)
  • -f - Force (no confirmation)

Usage:

rm file.txt          # Delete file
rm -rf directory/    # Delete directory

mkdir - Make Directory

Location: userspace/bin/mkdir.c

Creates directories.

Usage:

mkdir newdir              # Create directory
mkdir -p a/b/c            # Create nested (if supported)

rmdir - Remove Directory

Location: userspace/bin/rmdir.c

Removes empty directories.

Usage:

rmdir emptydir    # Remove only if empty

pwd - Print Working Directory

Location: userspace/bin/pwd.c

Shows current directory path.

Usage:

$ pwd
/home/user

touch - Create Empty File

Location: userspace/bin/touch.c

Creates empty file or updates modification time.

Usage:

touch file.txt           # Create empty file
touch -t timestamp file  # Set time (if supported)

head - Print First Lines

Location: userspace/bin/head.c

Outputs first N lines of file (default 10).

Usage:

head file.txt       # First 10 lines
head -20 file.txt   # First 20 lines

more - Pager

Location: userspace/bin/more.c

Displays file contents with pagination.

Usage:

more file.txt          # Page through file
cat file.txt | more    # Pipe with more

chmod - Change File Permissions

Location: userspace/bin/chmod.c

Modifies file permissions.

Usage:

chmod 644 file.txt      # Set rwxr--r--
chmod +x script.sh      # Add execute bit
chmod g+w file.txt      # Add group write

chown - Change Ownership

Location: userspace/bin/chown.c

Changes file owner and group.

Usage:

chown user:group file.txt  # Change owner:group
chown 1000 file.txt        # Change by UID

System Information Programs

ps - List Processes

Location: userspace/bin/ps.c

Shows running processes.

Output Columns:

  • PID - Process ID
  • TTY - Terminal (? = not attached)
  • STAT - Process state (S=sleeping, R=running, Z=zombie)
  • TIME - CPU time used
  • COMMAND - Program name

Usage:

$ ps
  PID TTY STAT  TIME COMMAND
    1   ?  S     0:00 init
   42   ?  S     0:00 login
   45  #0 S     0:00 shell
   51  #0 R     0:00 ps

Process States:

  • S (INTERRUPTIBLE_SLEEP) - Waiting for event
  • R (RUNNING) - Currently executing
  • Z (ZOMBIE) - Terminated, awaiting parent reaping
  • D (UNINTERRUPTIBLE_SLEEP) - Cannot be interrupted

uptime - Show System Uptime

Location: userspace/bin/uptime.c

Displays system uptime and load average.

Output:

$ uptime
 12:34:56 up 1 day, 2:30, 3 users, load average: 0.25, 0.50, 1.00

Information:

  • Current time
  • Uptime (days, hours, minutes)
  • Number of logged-in users
  • Load average (1 min, 5 min, 15 min)

uname - System Information

Location: userspace/bin/uname.c

Shows system information.

Flags:

  • -a - All information
  • -s - System name
  • -r - Kernel release
  • -v - Kernel version
  • -m - Machine type

Output:

$ uname -a
MentOS 0.1.0 #1 Mentoring OS i686 GNU/Linux

clear - Clear Screen

Location: userspace/bin/clear.c

Clears terminal screen.

Usage:

clear

User and Group Programs

id - Show User Identity

Location: userspace/bin/id.c

Displays user and group IDs.

Output:

$ id
uid=0(root) gid=0(root) groups=0(root)

Development and Testing

echo - Print Arguments

Location: userspace/bin/echo.c

Outputs text to stdout.

Flags:

  • -n - Don't add newline
  • -e - Interpret escape sequences

Usage:

$ echo "Hello, World!"
Hello, World!

$ echo -n "No newline"
No newline$

env - Show Environment

Location: userspace/bin/env.c

Displays environment variables.

Usage:

$ env
PATH=/bin:/usr/bin
HOME=/root
USER=root
...

kill - Send Signal

Location: userspace/bin/kill.c

Sends signals to processes.

Usage:

kill 1234              # Send SIGTERM to PID 1234
kill -9 1234           # Send SIGKILL (force kill)
kill -l                # List all signals

nice - Run with Priority

Location: userspace/bin/nice.c

Runs program with modified priority.

Usage:

nice -n 10 command     # Run with lower priority
nice -n -5 command     # Run with higher priority (root only)

showpid - Display Process ID

Location: userspace/bin/showpid.c

Shows current process ID.

Usage:

$ showpid
5

stat - File Status

Location: userspace/bin/stat.c

Shows file metadata.

Output:

$ stat file.txt
  File: file.txt
  Size: 1024      Blocks: 8
  Access: (0644/-rw-r--r--) Uid: 0 Gid: 0
  Access: 1970-01-01 00:00:00.000000000
  Modify: 1970-01-01 00:00:00.000000000
  Change: 1970-01-01 00:00:00.000000000

date - Show Current Date/Time

Location: userspace/bin/date.c

Displays current date and time.

Usage:

$ date
Thu Jan  1 00:00:00 1970

cpuid - CPU Information

Location: userspace/bin/cpuid.c

Displays CPU capabilities (x86-specific).

Output:

$ cpuid
CPUID: Family=6, Model=42, Stepping=1
Features: PAE, PSE, MMU, ...

poweroff - Shutdown System

Location: userspace/bin/poweroff.c

Gracefully shuts down the system.

Usage:

poweroff           # Power off system

login - User Authentication

Location: userspace/bin/login.c

Authenticates user and starts session.

logo - Display Welcome Banner

Location: userspace/bin/logo.c

Shows ASCII art welcome screen.

Output:

 ___  ___      _   ___  _____
|  \/  |     | | / _ \|  _  \
| .  . | __ _| |/ /_\ \ | | |
| |\/| |/ _` | ||  _  | | | |
| |  | | (_| | || | | | |/ /
\_|  |_/\__,_|_/\_| |_/___/

MentOS - Mentoring Operating System

runtests - Test Suite

Location: userspace/bin/runtests.c

Runs automated tests.

Usage:

runtests          # Run all tests

Program Compilation

Programs are compiled and linked with libc:

// Example program: userspace/bin/echo.c
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    for (int i = 1; i < argc; i++) {
        printf("%s", argv[i]);
        if (i < argc - 1) printf(" ");
    }
    printf("\n");
    return 0;
}

Build Process:

# In CMakeLists.txt
add_executable(echo echo.c)
target_link_libraries(echo c)  # Link with libc

Filesystem Organization

Programs in the filesystem image:

files/
├── bin/              # Executable programs
│   ├── cat
│   ├── chmod
│   ├── cp
│   ├── echo
│   ├── init
│   ├── ls
│   ├── mkdir
│   ├── ps
│   ├── pwd
│   ├── rm
│   ├── shell
│   └── (40+ more)
├── etc/              # System configuration
│   ├── passwd        # User database
│   ├── shadow        # Password database
│   ├── group         # Group database
│   ├── inittab       # Init configuration
│   └── ...
├── home/             # User home directories
├── proc/             # Process filesystem (virtual)
└── usr/              # User programs (optional)

Key Patterns

Standard Program Structure

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

int main(int argc, char *argv[], char *envp[]) {
    // Parse arguments
    for (int i = 1; i < argc; i++) {
        char *arg = argv[i];
        if (arg[0] == '-') {
            // Handle flags
        } else {
            // Handle positional arguments
        }
    }
    
    // Perform operation
    
    // Return exit code
    return 0;  // Success
    // return 1;  // Error
}

Signal Handling

#include <signal.h>

void handle_signal(int sig) {
    if (sig == SIGINT) {
        printf("Interrupted\n");
        exit(0);
    }
}

int main() {
    signal(SIGINT, handle_signal);
    // ... main code
}

Process Creation

#include <unistd.h>

pid_t pid = fork();
if (pid == 0) {
    // Child process
    execve("/bin/program", argv, envp);
    exit(1);  // If exec fails
} else if (pid > 0) {
    // Parent process
    wait(NULL);  // Wait for child
}

Hands-On Exercises

Exercise 1: Write Your First MentOS Program

Goal: Create a simple program and run it on MentOS.

Program - userspace/bin/hello_os.c:

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

int main(int argc, char *argv[], char *envp[]) {
    printf("Hello from MentOS!\n");
    printf("My PID: %d\n", getpid());
    printf("I have %d arguments\n", argc);
    
    for (int i = 0; i < argc; i++) {
        printf("  arg[%d]: %s\n", i, argv[i]);
    }
    
    return 0;
}

Build and Test:

  1. Add to userspace/bin/CMakeLists.txt:
add_executable(hello_os hello_os.c)
target_link_libraries(hello_os PUBLIC c)
install(TARGETS hello_os DESTINATION ${MENTOS_ROOT_PATH}/bin)
  1. Rebuild:
cd build
make
make qemu
  1. In MentOS shell:
# Run with no arguments
/bin/hello_os

# Run with arguments
/bin/hello_os arg1 arg2 arg3

Expected output:

Hello from MentOS!
My PID: 42
I have 4 arguments
  arg[0]: /bin/hello_os
  arg[1]: arg1
  arg[2]: arg2
  arg[3]: arg3

Exercise 2: Implement a Simple Calculator

Goal: Parse command-line arguments and perform operations.

Program - userspace/bin/calc.c:

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

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("Usage: calc <number> <operator> <number>\n");
        printf("Operators: +, -, *, /\n");
        return 1;
    }
    
    int a = atoi(argv[1]);
    char op = argv[2][0];
    int b = atoi(argv[3]);
    
    int result = 0;
    char success = 1;
    
    switch (op) {
        case '+': result = a + b; break;
        case '-': result = a - b; break;
        case '*': result = a * b; break;
        case '/':
            if (b == 0) {
                printf("Error: Division by zero\n");
                success = 0;
            } else {
                result = a / b;
            }
            break;
        default:
            printf("Unknown operator: %c\n", op);
            success = 0;
    }
    
    if (success) {
        printf("%d %c %d = %d\n", a, op, b, result);
    }
    
    return success ? 0 : 1;
}

Test:

/bin/calc 10 + 5        # Output: 10 + 5 = 15
/bin/calc 20 - 3        # Output: 20 - 3 = 17
/bin/calc 4 '*' 6       # Output: 4 * 6 = 24
/bin/calc 15 / 3        # Output: 15 / 3 = 5
/bin/calc 10 / 0        # Error: Division by zero

Exercise 3: Create an Echo Program

Goal: Read stdin and write to stdout (like Unix echo).

Program - userspace/bin/echo2.c:

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

int main(int argc, char *argv[]) {
    // Echo all arguments, space-separated
    for (int i = 1; i < argc; i++) {
        printf("%s", argv[i]);
        if (i < argc - 1) {
            printf(" ");
        }
    }
    printf("\n");
    
    return 0;
}

Enhancement - Add -n flag (no newline):

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    int newline = 1;  // Default: add newline
    int start = 1;
    
    // Check for -n flag
    if (argc > 1 && strcmp(argv[1], "-n") == 0) {
        newline = 0;
        start = 2;
    }
    
    // Print arguments
    for (int i = start; i < argc; i++) {
        printf("%s", argv[i]);
        if (i < argc - 1) {
            printf(" ");
        }
    }
    
    if (newline) {
        printf("\n");
    }
    
    return 0;
}

Test:

/bin/echo2 Hello World
/bin/echo2 -n No newline

# Use in shell pipeline
/bin/echo2 test | /bin/cat

Exercise 4: Implement wc (Word Count)

Goal: Read input and count lines, words, and characters.

Program - userspace/bin/wc.c:

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

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: wc <file>\n");
        return 1;
    }
    
    FILE *f = fopen(argv[1], "r");
    if (!f) {
        printf("Error: cannot open %s\n", argv[1]);
        return 1;
    }
    
    int lines = 0, words = 0, chars = 0;
    int c;
    int in_word = 0;
    
    while ((c = fgetc(f)) != EOF) {
        chars++;
        
        if (c == '\n') {
            lines++;
            in_word = 0;
        } else if (c == ' ' || c == '\t' || c == '\n') {
            in_word = 0;
        } else if (!in_word) {
            words++;
            in_word = 1;
        }
    }
    
    if (in_word) lines++;  // Count last line
    
    printf("%d %d %d %s\n", lines, words, chars, argv[1]);
    
    fclose(f);
    return 0;
}

Test:

# Create a test file
echo -e "Hello World\nThis is a test" > /tmp/test.txt

# Count it
/bin/wc /tmp/test.txt
# Expected: 2 5 26 /tmp/test.txt

Exercise 5: Multi-Process Program (Shell Launcher)

Goal: Use fork/exec to launch other programs.

Program - userspace/bin/launcher.c:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: launcher <program> [args...]\n");
        return 1;
    }
    
    // Print parent process info
    printf("[Parent] PID %d launching: %s\n", getpid(), argv[1]);
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // Child: prepare arguments and exec
        char *child_argv[] = {argv[1], argv[2], NULL};
        char *child_envp[] = {NULL};
        
        printf("[Child] PID %d executing %s\n", getpid(), argv[1]);
        
        execve(argv[1], child_argv, child_envp);
        
        // If we get here, exec failed
        printf("[Child] ERROR: exec failed\n");
        return 1;
    } else if (pid > 0) {
        // Parent: wait for child
        int status;
        printf("[Parent] Waiting for child %d...\n", pid);
        
        waitpid(pid, &status, 0);
        
        int exit_code = WEXITSTATUS(status);
        printf("[Parent] Child exited with code: %d\n", exit_code);
    } else {
        printf("Fork failed!\n");
        return 1;
    }
    
    return 0;
}

Test:

# Launch a program through the launcher
/bin/launcher /bin/echo Hello from launcher

# Verify process hierarchy
/bin/ps

Challenge: Build Your Own Shell

Advanced Goal: Implement a basic shell that:

  1. Reads commands from user
  2. Parses arguments (space-separated)
  3. Forks and execs the program
  4. Waits for completion
  5. Loops for next command

Hints:

  • Use fgets() to read input
  • Use strtok() to parse arguments
  • Use fork/execve/wait pattern
  • Implement built-in commands (cd, exit, history)

Further Reading

Clone this wiki locally