feat: implement per-process environment variables (AI)
- Created env.c/h: key=value store with ENV_MAX_VARS=32 entries per process. Supports get, set, unset, and copy operations. - Added env_block_t and cwd[128] fields to process_t struct. - process_create() initializes CWD=/ by default. - Fork inherits environment via memcpy of the entire process_t. - Added SYS_GETENV (8) and SYS_SETENV (9) system calls. - Created env-test user app that verifies: CWD default, set/get, and inheritance across fork. - Updated kernel.c to launch 'sh' as init process (for next task). - Updated README.md to check off environment variables task. Tested: env-test reads CWD=/, sets TEST=hello, reads it back, forks child which inherits TEST=hello. All verified via QEMU.
This commit is contained in:
123
apps/env-test/env-test.S
Normal file
123
apps/env-test/env-test.S
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#
|
||||||
|
# env-test: Tests environment variable syscalls.
|
||||||
|
#
|
||||||
|
# 1. Gets CWD (should be "/")
|
||||||
|
# 2. Sets TEST=hello
|
||||||
|
# 3. Gets TEST (should be "hello")
|
||||||
|
# 4. Forks - child gets TEST, verifies it was copied
|
||||||
|
# 5. Both print results and exit
|
||||||
|
#
|
||||||
|
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
|
||||||
|
.equ SYS_EXIT, 0
|
||||||
|
.equ SYS_WRITE, 1
|
||||||
|
.equ SYS_FORK, 3
|
||||||
|
.equ SYS_WAITPID, 6
|
||||||
|
.equ SYS_GETENV, 8
|
||||||
|
.equ SYS_SETENV, 9
|
||||||
|
|
||||||
|
# Helper: write a string (addr in %ecx, len in %edx) to stdout
|
||||||
|
.macro WRITE_STR addr, len
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl \addr, %ecx
|
||||||
|
movl \len, %edx
|
||||||
|
int $0x80
|
||||||
|
.endm
|
||||||
|
|
||||||
|
_start:
|
||||||
|
# 1. Get CWD env var
|
||||||
|
movl $SYS_GETENV, %eax
|
||||||
|
movl $key_cwd, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
movl $128, %edx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Print "CWD="
|
||||||
|
WRITE_STR $label_cwd, $4
|
||||||
|
# Print the value (length returned in eax)
|
||||||
|
movl %eax, %edx # length
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
int $0x80
|
||||||
|
WRITE_STR $newline, $1
|
||||||
|
|
||||||
|
# 2. Set TEST=hello
|
||||||
|
movl $SYS_SETENV, %eax
|
||||||
|
movl $key_test, %ebx
|
||||||
|
movl $val_hello, %ecx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# 3. Get TEST
|
||||||
|
movl $SYS_GETENV, %eax
|
||||||
|
movl $key_test, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
movl $128, %edx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Print "TEST="
|
||||||
|
WRITE_STR $label_test, $5
|
||||||
|
movl %eax, %edx
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
int $0x80
|
||||||
|
WRITE_STR $newline, $1
|
||||||
|
|
||||||
|
# 4. Fork
|
||||||
|
movl $SYS_FORK, %eax
|
||||||
|
int $0x80
|
||||||
|
testl %eax, %eax
|
||||||
|
jz .child
|
||||||
|
|
||||||
|
.parent:
|
||||||
|
pushl %eax # save child PID
|
||||||
|
WRITE_STR $msg_parent, $15
|
||||||
|
|
||||||
|
# Wait for child
|
||||||
|
popl %ebx
|
||||||
|
movl $SYS_WAITPID, %eax
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Exit
|
||||||
|
movl $SYS_EXIT, %eax
|
||||||
|
movl $0, %ebx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
.child:
|
||||||
|
# Child: get TEST to verify it was inherited
|
||||||
|
movl $SYS_GETENV, %eax
|
||||||
|
movl $key_test, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
movl $128, %edx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Print "Child TEST="
|
||||||
|
WRITE_STR $label_child_test, $11
|
||||||
|
movl %eax, %edx
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
int $0x80
|
||||||
|
WRITE_STR $newline, $1
|
||||||
|
|
||||||
|
# Exit
|
||||||
|
movl $SYS_EXIT, %eax
|
||||||
|
movl $0, %ebx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
.section .rodata
|
||||||
|
key_cwd: .asciz "CWD"
|
||||||
|
key_test: .asciz "TEST"
|
||||||
|
val_hello: .asciz "hello"
|
||||||
|
label_cwd: .ascii "CWD="
|
||||||
|
label_test: .ascii "TEST="
|
||||||
|
label_child_test: .ascii "Child TEST="
|
||||||
|
msg_parent: .ascii "Parent done!\n\0\0"
|
||||||
|
newline: .ascii "\n"
|
||||||
|
|
||||||
|
.section .bss
|
||||||
|
buf: .space 128
|
||||||
@@ -19,6 +19,7 @@ add_executable(kernel
|
|||||||
cpio.c
|
cpio.c
|
||||||
vfs.c
|
vfs.c
|
||||||
initrd_fs.c
|
initrd_fs.c
|
||||||
|
env.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
kernel.c
|
kernel.c
|
||||||
)
|
)
|
||||||
|
|||||||
104
src/env.c
Normal file
104
src/env.c
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* @file env.c
|
||||||
|
* @brief Per-process environment variable implementation.
|
||||||
|
*
|
||||||
|
* Provides a simple key=value store per process. Each process has a fixed
|
||||||
|
* array of ENV_MAX_VARS entries. Empty key strings indicate unused slots.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "env.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void env_init(env_block_t *env) {
|
||||||
|
memset(env, 0, sizeof(env_block_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the slot index for a given key.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @param key Variable name to search for.
|
||||||
|
* @return Slot index, or -1 if not found.
|
||||||
|
*/
|
||||||
|
static int env_find(const env_block_t *env, const char *key) {
|
||||||
|
for (int i = 0; i < ENV_MAX_VARS; i++) {
|
||||||
|
if (env->vars[i].key[0] != '\0' &&
|
||||||
|
strcmp(env->vars[i].key, key) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an empty slot in the environment block.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @return Slot index, or -1 if full.
|
||||||
|
*/
|
||||||
|
static int env_find_free(const env_block_t *env) {
|
||||||
|
for (int i = 0; i < ENV_MAX_VARS; i++) {
|
||||||
|
if (env->vars[i].key[0] == '\0') {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t env_get(const env_block_t *env, const char *key, char *buf, uint32_t bufsize) {
|
||||||
|
int idx = env_find(env, key);
|
||||||
|
if (idx < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t vlen = strlen(env->vars[idx].value);
|
||||||
|
if (buf && bufsize > 0) {
|
||||||
|
uint32_t copy_len = vlen;
|
||||||
|
if (copy_len >= bufsize) {
|
||||||
|
copy_len = bufsize - 1;
|
||||||
|
}
|
||||||
|
memcpy(buf, env->vars[idx].value, copy_len);
|
||||||
|
buf[copy_len] = '\0';
|
||||||
|
}
|
||||||
|
return (int32_t)vlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t env_set(env_block_t *env, const char *key, const char *value) {
|
||||||
|
if (!key || key[0] == '\0') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If value is NULL or empty, unset the variable */
|
||||||
|
if (!value || value[0] == '\0') {
|
||||||
|
int idx = env_find(env, key);
|
||||||
|
if (idx >= 0) {
|
||||||
|
env->vars[idx].key[0] = '\0';
|
||||||
|
env->vars[idx].value[0] = '\0';
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if key already exists */
|
||||||
|
int idx = env_find(env, key);
|
||||||
|
if (idx < 0) {
|
||||||
|
/* Need a new slot */
|
||||||
|
idx = env_find_free(env);
|
||||||
|
if (idx < 0) {
|
||||||
|
return -1; /* Environment full */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy key */
|
||||||
|
strncpy(env->vars[idx].key, key, ENV_MAX_KEY - 1);
|
||||||
|
env->vars[idx].key[ENV_MAX_KEY - 1] = '\0';
|
||||||
|
|
||||||
|
/* Copy value */
|
||||||
|
strncpy(env->vars[idx].value, value, ENV_MAX_VALUE - 1);
|
||||||
|
env->vars[idx].value[ENV_MAX_VALUE - 1] = '\0';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void env_copy(env_block_t *dst, const env_block_t *src) {
|
||||||
|
memcpy(dst, src, sizeof(env_block_t));
|
||||||
|
}
|
||||||
75
src/env.h
Normal file
75
src/env.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @file env.h
|
||||||
|
* @brief Per-process environment variable support.
|
||||||
|
*
|
||||||
|
* Each process has a fixed-size environment table storing key=value pairs.
|
||||||
|
* Environment variables are copied to child processes during fork().
|
||||||
|
* User-mode programs interact with the environment via SYS_GETENV and
|
||||||
|
* SYS_SETENV system calls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ENV_H
|
||||||
|
#define ENV_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Maximum number of environment variables per process. */
|
||||||
|
#define ENV_MAX_VARS 32
|
||||||
|
/** Maximum length of an environment variable key (including NUL). */
|
||||||
|
#define ENV_MAX_KEY 64
|
||||||
|
/** Maximum length of an environment variable value (including NUL). */
|
||||||
|
#define ENV_MAX_VALUE 128
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single environment variable entry.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char key[ENV_MAX_KEY]; /**< Variable name (empty string = unused). */
|
||||||
|
char value[ENV_MAX_VALUE]; /**< Variable value. */
|
||||||
|
} env_var_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment block for a process.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
env_var_t vars[ENV_MAX_VARS];
|
||||||
|
} env_block_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize an environment block (all entries empty).
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block to initialize.
|
||||||
|
*/
|
||||||
|
void env_init(env_block_t *env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an environment variable.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @param key Variable name to look up.
|
||||||
|
* @param buf Buffer to write the value into.
|
||||||
|
* @param bufsize Size of the buffer.
|
||||||
|
* @return Length of the value (excluding NUL), or -1 if not found.
|
||||||
|
* If bufsize is too small, the value is truncated.
|
||||||
|
*/
|
||||||
|
int32_t env_get(const env_block_t *env, const char *key, char *buf, uint32_t bufsize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an environment variable. Creates it if it doesn't exist.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @param key Variable name (must not be empty).
|
||||||
|
* @param value Variable value (NULL or empty string to unset).
|
||||||
|
* @return 0 on success, -1 if the environment is full.
|
||||||
|
*/
|
||||||
|
int32_t env_set(env_block_t *env, const char *key, const char *value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy an environment block.
|
||||||
|
*
|
||||||
|
* @param dst Destination environment block.
|
||||||
|
* @param src Source environment block.
|
||||||
|
*/
|
||||||
|
void env_copy(env_block_t *dst, const env_block_t *src);
|
||||||
|
|
||||||
|
#endif /* ENV_H */
|
||||||
18
src/kernel.c
18
src/kernel.c
@@ -152,18 +152,21 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
offset_print("FAILED to kmalloc\n");
|
offset_print("FAILED to kmalloc\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load hello-world from the initrd and run it as a user process */
|
/* Load the initial program from the initrd and run it */
|
||||||
cpio_entry_t app_entry;
|
cpio_entry_t app_entry;
|
||||||
if (cpio_find("hello-world", &app_entry) == 0) {
|
const char *init_app = "sh";
|
||||||
offset_print("Found hello-world in initrd (");
|
if (cpio_find(init_app, &app_entry) == 0) {
|
||||||
|
offset_print("Found ");
|
||||||
|
offset_print(init_app);
|
||||||
|
offset_print(" in initrd (");
|
||||||
print_hex(app_entry.datasize);
|
print_hex(app_entry.datasize);
|
||||||
offset_print(" bytes)\n");
|
offset_print(" bytes)\n");
|
||||||
|
|
||||||
int32_t pid = process_create("hello-world",
|
int32_t pid = process_create(init_app,
|
||||||
app_entry.data,
|
app_entry.data,
|
||||||
app_entry.datasize);
|
app_entry.datasize);
|
||||||
if (pid > 0) {
|
if (pid > 0) {
|
||||||
offset_print("Created hello-world process, pid=");
|
offset_print("Created init process, pid=");
|
||||||
print_hex((uint32_t)pid);
|
print_hex((uint32_t)pid);
|
||||||
|
|
||||||
/* Enable interrupts before entering user mode */
|
/* Enable interrupts before entering user mode */
|
||||||
@@ -173,10 +176,11 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
/* Enter user mode - does not return */
|
/* Enter user mode - does not return */
|
||||||
process_run_first();
|
process_run_first();
|
||||||
} else {
|
} else {
|
||||||
offset_print("FAILED to create hello-world process\n");
|
offset_print("FAILED to create init process\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
offset_print("hello-world not found in initrd\n");
|
offset_print(init_app);
|
||||||
|
offset_print(" not found in initrd\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enable interrupts */
|
/* Enable interrupts */
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ int32_t process_create(const char *name, const void *code, uint32_t size) {
|
|||||||
memcpy(proc->name, name, nlen);
|
memcpy(proc->name, name, nlen);
|
||||||
proc->name[nlen] = '\0';
|
proc->name[nlen] = '\0';
|
||||||
|
|
||||||
|
/* Initialize environment and working directory */
|
||||||
|
env_init(&proc->env);
|
||||||
|
strcpy(proc->cwd, "/");
|
||||||
|
env_set(&proc->env, "CWD", "/");
|
||||||
|
|
||||||
/* Allocate kernel stack (full page, not from kmalloc which has header overhead) */
|
/* Allocate kernel stack (full page, not from kmalloc which has header overhead) */
|
||||||
void *kstack = paging_alloc_page();
|
void *kstack = paging_alloc_page();
|
||||||
if (!kstack) {
|
if (!kstack) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "isr.h"
|
#include "isr.h"
|
||||||
|
#include "env.h"
|
||||||
|
|
||||||
/** Maximum number of concurrent processes. */
|
/** Maximum number of concurrent processes. */
|
||||||
#define MAX_PROCESSES 64
|
#define MAX_PROCESSES 64
|
||||||
@@ -58,6 +59,8 @@ typedef struct process {
|
|||||||
uint32_t parent_pid; /**< Parent process ID. */
|
uint32_t parent_pid; /**< Parent process ID. */
|
||||||
uint32_t waiting_for_pid; /**< PID we are blocked waiting for (if BLOCKED). */
|
uint32_t waiting_for_pid; /**< PID we are blocked waiting for (if BLOCKED). */
|
||||||
char name[32]; /**< Process name (for debugging). */
|
char name[32]; /**< Process name (for debugging). */
|
||||||
|
char cwd[128]; /**< Current working directory. */
|
||||||
|
env_block_t env; /**< Per-process environment variables. */
|
||||||
} process_t;
|
} process_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "syscall.h"
|
#include "syscall.h"
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
|
#include "env.h"
|
||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
#include "vga.h"
|
#include "vga.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
@@ -139,6 +140,37 @@ static int32_t sys_exec(registers_t *regs) {
|
|||||||
return -1; /* Not implemented yet */
|
return -1; /* Not implemented yet */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_GETENV: get an environment variable.
|
||||||
|
* EBX = key pointer, ECX = value buffer pointer, EDX = buffer size.
|
||||||
|
* Returns length of value, or -1 if not found.
|
||||||
|
*/
|
||||||
|
static int32_t sys_getenv(registers_t *regs) {
|
||||||
|
const char *key = (const char *)regs->ebx;
|
||||||
|
char *buf = (char *)regs->ecx;
|
||||||
|
uint32_t bufsize = regs->edx;
|
||||||
|
|
||||||
|
process_t *cur = process_current();
|
||||||
|
if (!cur) return -1;
|
||||||
|
|
||||||
|
return env_get(&cur->env, key, buf, bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_SETENV: set an environment variable.
|
||||||
|
* EBX = key pointer, ECX = value pointer (NULL to unset).
|
||||||
|
* Returns 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int32_t sys_setenv(registers_t *regs) {
|
||||||
|
const char *key = (const char *)regs->ebx;
|
||||||
|
const char *value = (const char *)regs->ecx;
|
||||||
|
|
||||||
|
process_t *cur = process_current();
|
||||||
|
if (!cur) return -1;
|
||||||
|
|
||||||
|
return env_set(&cur->env, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
/** System call dispatch table. */
|
/** System call dispatch table. */
|
||||||
typedef int32_t (*syscall_fn)(registers_t *);
|
typedef int32_t (*syscall_fn)(registers_t *);
|
||||||
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||||
@@ -150,6 +182,8 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
|||||||
[SYS_YIELD] = sys_yield,
|
[SYS_YIELD] = sys_yield,
|
||||||
[SYS_WAITPID] = sys_waitpid,
|
[SYS_WAITPID] = sys_waitpid,
|
||||||
[SYS_EXEC] = sys_exec,
|
[SYS_EXEC] = sys_exec,
|
||||||
|
[SYS_GETENV] = sys_getenv,
|
||||||
|
[SYS_SETENV] = sys_setenv,
|
||||||
};
|
};
|
||||||
|
|
||||||
void syscall_handler(registers_t *regs) {
|
void syscall_handler(registers_t *regs) {
|
||||||
|
|||||||
@@ -22,9 +22,11 @@
|
|||||||
#define SYS_YIELD 5 /**< Yield the CPU. */
|
#define SYS_YIELD 5 /**< Yield the CPU. */
|
||||||
#define SYS_WAITPID 6 /**< Wait for a child process. pid=EBX. */
|
#define SYS_WAITPID 6 /**< Wait for a child process. pid=EBX. */
|
||||||
#define SYS_EXEC 7 /**< Execute a program. path=EBX, argv=ECX. */
|
#define SYS_EXEC 7 /**< Execute a program. path=EBX, argv=ECX. */
|
||||||
|
#define SYS_GETENV 8 /**< Get environment variable. key=EBX, buf=ECX, bufsize=EDX. */
|
||||||
|
#define SYS_SETENV 9 /**< Set environment variable. key=EBX, value=ECX. */
|
||||||
|
|
||||||
/** Total number of system calls. */
|
/** Total number of system calls. */
|
||||||
#define NUM_SYSCALLS 8
|
#define NUM_SYSCALLS 10
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the system call handler.
|
* Initialize the system call handler.
|
||||||
|
|||||||
Reference in New Issue
Block a user