#include "kernel.h"
/*
* This program simulates user-space and kernel-space code, by running two
* separate processes (and thus creating two separate address spaces). The
* user process will make system calls to the kernel process, which will
* perform the desired operations.
*
* Look at
* - user.c to see the code that makes system calls to the kernel.
* - kernel.c to see the code that handles the system calls.
*
* These are really the only two files you need to look at to understand the
* system call mechanism. The rest of the files are just support code.
*
* Behind the scenes, the user and kernel processes communicate over a socket
* pair. The user process sends a message to the kernel process, which reads
* the message, performs the desired operation, and sends a response back to
* the user process. There are also special requests that allow the kernel
* to request reads and writes from the user process's address space.
*/
int
main(void)
{
fprintf(stderr, "- kern: Kernel-space booting.\n");
kmain();
fprintf(stderr, "- kern: Kernel-space shutting down.\n");
return 0;
}
#include "kernel.h"
#define SIMULATED_PID 42
#define SYS_PRINT_LIMIT 4096 /* Maximum number of characters to print */
int
sys_print(const_userptr_t str, int *len_printed)
{
char kernel_buf[SYS_PRINT_LIMIT];
int err;
err = copyinstr(str, kernel_buf, sizeof(kernel_buf), NULL);
if (err == 0) {
fputs(kernel_buf, stdout);
*len_printed = strlen(kernel_buf);
return 0;
} else {
return err;
}
}
int
sys_get_pid(int *pid)
{
*pid = SIMULATED_PID;
return 0;
}
int
sys_exit(int code)
{
kprintf("User process exited with code %d\n", code);
/* Terminate the user process */
kill(user_pid, SIGTERM);
return 0;
}
void
handle_syscall(struct syscall_args *args, struct syscall_result *result)
{
int err, retval;
kprintf("Handling syscall %d\n", args->num);
switch (args->num) {
case SYS_PRINT:
err =
sys_print((const_userptr_t) args->args[0], (int *) &retval);
break;
case SYS_GET_PID:
err = sys_get_pid((int *) &retval);
break;
case SYS_EXIT:
err = sys_exit(args->args[0]);
user_connected = false;
return;
default:
kprintf("Unknown syscall number: %d\n", args->num);
err = ENOSYS;
retval = -1;
}
retval = err ? -1 : retval;
result->err = err;
result->ret_val = retval;
kprintf("Syscall %d returning %d, err=%d\n", args->num, retval, err);
}
#include "user.h"
/*
* Other than debugging with uprintf, the user program should never directly
* make system calls on the host system. Instead, it should make system calls
* to the kernel process, which will perform the desired operations.
*/
#define BIG_STRING_SIZE 1500
int
main(void)
{
long ret;
int exit_code = 0;
char big_string[BIG_STRING_SIZE];
for (int i = 0; i < BIG_STRING_SIZE; i++) {
big_string[i] = 'A' + (i % 26);
}
big_string[BIG_STRING_SIZE-1] = '\0';
big_string[BIG_STRING_SIZE-2] = '\n';
uprintf("User process started up.\n");
ret = system_call(SYS_PRINT, (intptr_t) "Hello, Kernel!\n", 0, 0, 0);
uprintf("SYS_PRINT returned: %ld\n", ret);
ret = system_call(SYS_PRINT, (intptr_t) big_string, 0, 0, 0);
uprintf("SYS_PRINT returned: %ld (%s)\n", ret, strerror(errno));
ret = system_call(SYS_PRINT, 0, 0, 0, 0);
uprintf("Errorneous SYS_PRINT returned: %ld (%s)\n", ret,
strerror(errno));
ret = system_call(SYS_GET_PID, 0, 0, 0, 0);
uprintf("SYS_GET_PID returned: %ld\n", ret);
uprintf("Calling SYS_EXIT\n");
system_call(SYS_EXIT, exit_code, 0, 0, 0);
uprintf("This should not be printed\n");
return 0; /* This should never be reached */
}
#ifndef COMMON_H
#define COMMON_H
#include <sys/types.h>
#include <stdint.h>
#define MAX_DATA_SIZE 1024
enum syscall_num { SYS_PRINT = 1, SYS_GET_PID, SYS_EXIT };
enum kernel_request {
KREQ_READ_MEM = 1,
KREQ_WRITE_MEM,
KREQ_READ_STRING,
KREQ_SYSCALL_RESULT
};
struct syscall_args {
enum syscall_num num;
intptr_t args[4];
};
struct syscall_result {
int err;
long ret_val;
};
struct memory_area {
void *start;
size_t size;
};
struct kernel_message {
enum kernel_request req;
union {
struct syscall_result result;
struct memory_area mem_area;
};
// void *addr;
// size_t size;
// long data;
};
struct user_message {
int err;
char data[MAX_DATA_SIZE];
};
#endif /* COMMON_H */
#ifndef KERNEL_H
#define KERNEL_H
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <assert.h>
#include "common.h"
/*
* Define userptr_t as a pointer to a one-byte struct, so it won't mix
* with other pointers. [Copied from OS/161.]
*/
struct __userptr {
char _dummy;
};
typedef struct __userptr *userptr_t;
typedef const struct __userptr *const_userptr_t;
int copyin(const_userptr_t usersrc, void *dest, size_t len);
int copyout(const void *src, userptr_t userdest, size_t len);
int copyinstr(const_userptr_t usersrc, char *dest, size_t len, size_t *got);
int copyoutstr(const char *src, userptr_t userdest, size_t len, size_t *got);
void handle_syscall(struct syscall_args *args, struct syscall_result *result);
#define kprintf(...) fprintf(stderr, "- kern: " __VA_ARGS__)
#define kmalloc malloc
#define kfree free
void syscall_responder(void);
void kmain(void);
extern bool user_connected; /* Is the user process connected? */
extern int user_pid; /* Actual system PID of the user process */
#endif /* KERNEL_H */
#ifndef USER_H
#define USER_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>
#include "common.h"
long system_call(enum syscall_num num, intptr_t arg1, intptr_t arg2,
intptr_t arg3, intptr_t arg4);
#define uprintf(...) fprintf(stderr, "- user: " __VA_ARGS__)
#define main user_main
#endif /* USER_H */
#include "kernel.h"
#ifndef KERNEL_LOG_INTERNALS
/* Unless KERNEL_LOG_INTERNALS is defined, we don't want to see the
* internal logging messages. */
#undef kprintf
#define kprintf(...)
#endif
static int user_sock; /* Global socket for communication with user process */
int user_pid; /* actual PID of the user process */
bool user_connected = false;
static int
copyin_prim(const_userptr_t usersrc, void *dest, size_t len)
{
struct kernel_message kmsg;
struct user_message umsg;
kmsg.req = KREQ_READ_MEM;
kmsg.mem_area.start = (void *) usersrc;
kmsg.mem_area.size = len;
if (send(user_sock, &kmsg, sizeof(kmsg), 0) == -1) {
perror("send");
return ENOTCONN;
}
if (recv(user_sock, &umsg, sizeof(umsg), 0) == -1) {
perror("recv");
return ENOTCONN;
}
if (umsg.err) {
kprintf("Failed to read memory from user space\n");
return umsg.err;
}
memcpy(dest, umsg.data, len);
return 0;
}
/* While copyin_prim can only handle data up to MAX_DATA_SIZE, copyin can handle
* arbitrary data sizes by breaking it up into chunks. */
int
copyin(const_userptr_t usersrc, void *dest, size_t len)
{
int err;
size_t chunk_size;
size_t remaining = len;
char *dest_ptr = dest;
while (remaining > 0) {
chunk_size =
remaining > MAX_DATA_SIZE ? MAX_DATA_SIZE : remaining;
err = copyin_prim(usersrc, dest_ptr, chunk_size);
if (err) {
return err;
}
usersrc += chunk_size;
dest_ptr += chunk_size;
remaining -= chunk_size;
}
return 0;
}
static int
copyout_prim(const void *src, userptr_t userdest, size_t len)
{
struct kernel_message kmsg;
struct user_message umsg;
kmsg.req = KREQ_WRITE_MEM;
kmsg.mem_area.start = userdest;
kmsg.mem_area.size = len;
if (send(user_sock, &kmsg, sizeof(kmsg), 0) == -1) {
perror("send");
return ENOTCONN;
}
if (send(user_sock, src, len, 0) == -1) {
perror("send");
return ENOTCONN;
}
if (recv(user_sock, &umsg, sizeof(umsg), 0) == -1) {
perror("recv");
return ENOTCONN;
}
if (umsg.err) {
kprintf("Failed to write memory to user space\n");
return umsg.err;
}
return 0;
}
int
copyout(const void *src, userptr_t userdest, size_t len)
{
int err;
size_t chunk_size;
size_t remaining = len;
const char *src_ptr = src;
while (remaining > 0) {
chunk_size =
remaining > MAX_DATA_SIZE ? MAX_DATA_SIZE : remaining;
err = copyout_prim(src_ptr, userdest, chunk_size);
if (err) {
return err;
}
userdest += chunk_size;
src_ptr += chunk_size;
remaining -= chunk_size;
}
return 0;
}
static int
copyinstr_prim(const_userptr_t usersrc, char *dest, size_t max_len, size_t *got)
{
struct kernel_message kmsg;
struct user_message umsg;
char *endptr;
kmsg.req = KREQ_READ_STRING;
kmsg.mem_area.start = (void *) usersrc;
kmsg.mem_area.size = max_len;
if (send(user_sock, &kmsg, sizeof(kmsg), 0) == -1) {
perror("send");
return ENOTCONN;
}
if (recv(user_sock, &umsg, sizeof(umsg), 0) == -1) {
perror("recv");
return ENOTCONN;
}
if (umsg.err == ENAMETOOLONG) {
kprintf(
"String from user space is too long, partially copied\n");
memcpy(dest, umsg.data, max_len);
if (got != NULL) {
*got = max_len;
}
return umsg.err;
} else if (umsg.err) {
kprintf("Failed to read string from user space\n");
return umsg.err;
}
endptr = stpncpy(dest, umsg.data, max_len);
if (endptr - dest == max_len) {
kprintf("Protocol error: string from user space not "
"null-terminated\n");
return EPROTO;
}
if (got != NULL) {
*got = endptr - dest + 1; /* includes the null terminator */
}
return 0;
}
/* While copyinstr_prim can only handle data up to MAX_DATA_SIZE, copyinstr can
* handle arbitrary data sizes by breaking it up into chunks. Here, we know if
* we need to copy more if copyinstr_prim returns ENAMETOOLONG. */
int
copyinstr(const_userptr_t usersrc, char *dest, size_t max_len, size_t *got)
{
int err;
size_t chunk_size;
size_t remaining = max_len;
char *dest_ptr = dest;
size_t total_copied = 0;
while (remaining > 0) {
chunk_size =
remaining > MAX_DATA_SIZE ? MAX_DATA_SIZE : remaining;
err =
copyinstr_prim(usersrc, dest_ptr, chunk_size, &chunk_size);
total_copied += chunk_size;
if (err != ENAMETOOLONG) {
break;
}
assert(chunk_size == MAX_DATA_SIZE);
usersrc += chunk_size;
dest_ptr += chunk_size;
remaining -= chunk_size;
}
if (got != NULL) {
*got = total_copied;
}
return err;
}
int
copyoutstr(const char *src, userptr_t userdest, size_t len, size_t *got)
{
int err;
size_t actual_len = strlen(src) + 1;
if (actual_len > len) {
return ENAMETOOLONG;
}
err = copyout(src, userdest, actual_len);
if (got != NULL && err == 0) {
*got = actual_len;
}
return err;
}
void
syscall_responder(void)
{
struct syscall_args args;
struct syscall_result result;
struct kernel_message kmsg;
ssize_t n;
while (user_connected) {
n = recv(user_sock, &args, sizeof(args), 0);
if (n <= 0) {
if (n == 0) {
printf("User process has terminated.\n");
} else {
perror("recv");
}
break;
}
kprintf("Received syscall request: %d\n", args.num);
handle_syscall(&args, &result);
if (result.err == ENOTCONN) {
user_connected = false;
}
if (!user_connected) {
break;
}
kmsg.req = KREQ_SYSCALL_RESULT;
kmsg.result = result;
kprintf("Sending syscall result\n");
if (send(user_sock, &kmsg, sizeof(kmsg), 0) == -1) {
perror("send");
break;
}
}
}
void user_main(void);
void
kmain(void)
{
int socks[2];
pid_t pid;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) {
perror("socketpair");
exit(1);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
/* Child process */
close(socks[0]);
dup2(socks[1], STDIN_FILENO);
dup2(socks[1], STDOUT_FILENO);
close(socks[1]);
// execl("./user", "user", NULL);
// perror("execl");
user_main();
exit(1);
}
/* Parent process */
close(socks[1]);
user_sock = socks[0];
user_pid = pid;
user_connected = true;
syscall_responder();
close(user_sock);
wait(NULL);
}
#include "user.h"
#ifndef USER_LOG_INTERNALS
/* Unless USER_LOG_INTERNALS is defined, we don't want to see the
* internal logging messages. */
#undef uprintf
#define uprintf(...)
#endif
/* Fault handling, if memory access fails, we need to return EFAULT to the
* kernel process */
jmp_buf bail_out;
static void
sigsegv_handler(int signo)
{
longjmp(bail_out, 1);
}
const char *request_names[] = {[KREQ_READ_MEM] = "KREQ_READ_MEM",
[KREQ_WRITE_MEM] = "KREQ_WRITE_MEM",
[KREQ_READ_STRING] = "KREQ_READ_STRING",
[KREQ_SYSCALL_RESULT] = "KREQ_SYSCALL_RESULT"};
const char *
request_name(enum kernel_request req)
{
if (req < KREQ_READ_MEM || req > KREQ_SYSCALL_RESULT) {
return "Unknown request";
}
return request_names[req];
}
long
system_call(enum syscall_num num, intptr_t arg1, intptr_t arg2, intptr_t arg3,
intptr_t arg4)
{
struct syscall_args args;
struct kernel_message kmsg;
struct user_message umsg;
ssize_t n;
char *endptr;
bool done = false;
args.num = num;
args.args[0] = arg1;
args.args[1] = arg2;
args.args[2] = arg3;
args.args[3] = arg4;
uprintf("Sending syscall %d\n", num);
write(STDOUT_FILENO, &args, sizeof(args));
/* Install signal handler for SIGSEGV and SIGBUS */
signal(SIGSEGV, sigsegv_handler);
signal(SIGBUS, sigsegv_handler);
while (!done) {
if (setjmp(bail_out) != 0) {
uprintf("Memory access failed!\n");
umsg.err = EFAULT;
write(STDOUT_FILENO, &umsg, sizeof(umsg));
continue;
}
uprintf("Waiting for kernel message...\n");
n = read(STDIN_FILENO, &kmsg, sizeof(kmsg));
if (n <= 0) {
perror("read");
exit(1);
}
uprintf("Got kernel message, size: %zd, %s\n", n,
request_name(kmsg.req));
assert(kmsg.req == KREQ_SYSCALL_RESULT ||
kmsg.mem_area.size <= MAX_DATA_SIZE);
switch (kmsg.req) {
case KREQ_READ_MEM:
uprintf("Copying memory to kernel space\n");
memcpy(umsg.data, kmsg.mem_area.start,
kmsg.mem_area.size);
umsg.err = 0;
write(STDOUT_FILENO, &umsg, sizeof(umsg));
break;
case KREQ_WRITE_MEM:
uprintf("Copying memory from kernel space\n");
// Receive another message with the data payload
n = read(STDIN_FILENO, kmsg.mem_area.start,
kmsg.mem_area.size);
if (n <= 0) {
perror("read");
exit(1);
}
umsg.err = 0;
write(STDOUT_FILENO, &umsg, sizeof(umsg));
break;
case KREQ_READ_STRING:
uprintf("Copying string to kernel space\n");
// strncpy(umsg.data, kmsg.addr, kmsg.size);
endptr = stpncpy(umsg.data, kmsg.mem_area.start,
kmsg.mem_area.size);
umsg.err = (endptr - umsg.data < kmsg.mem_area.size)
? 0
: ENAMETOOLONG;
write(STDOUT_FILENO, &umsg, sizeof(umsg));
break;
case KREQ_SYSCALL_RESULT:
uprintf("Got syscall result\n");
done = true;
break;
default:
uprintf("Unknown kernel request: %d\n", kmsg.req);
umsg.err = 0;
write(STDOUT_FILENO, &umsg, sizeof(umsg));
}
}
/* Restore default signal handler */
signal(SIGSEGV, SIG_DFL);
signal(SIGBUS, SIG_DFL);
if (kmsg.result.err == 0) {
errno = 0;
return kmsg.result.ret_val;
} else {
errno = kmsg.result.err;
return -1;
}
}