Write OS kernel from scratch-load executable program


Series catalog

exec system call

With the foreshadowing of the previous articles about system calls and file systems, this article is already ready exec exec . After it is called in the process, it will read the given executable file, and then overwrite the current process operation with the program in this file. This is actually the completion of starting a new program from the disk. the process of.

Prepare user program

First, we need to prepare several user programs and write them into the naive_fs disk image we customized in the previous article. I created a user directory in the project, and added user/src directory to store the source files of the user program, and user/prog to store the executable binary after compilation and linking. For example, we can simply write a user program:

int main(int argc, char** argv) {
    while (1) {}

It is very simple but an endless loop. Of course, you can also write a print function program, but you need to implement printing first. Note that this is user-mode printing. You must let the kernel provide a print function system call, such as print , for users to call. I will call this function encapsulated in the src/common/stdio.c of printf functions it uses the underlying print system call. This is similar to the C standard library, it will be linked to the user program binary.

For example, I wrote a user program hello , which used the printing function:

#include "common/common.h"
#include "common/stdio.h"
#include "syscall/syscall.h"

int main(uint32 argc, char* argv[]) {
  printf("start user app: hello\n");
  printf("argc = %d\n", argc);
  for (uint32 i = 0; i < argc; i++) {
    printf("argv[%d] = %s\n", i, argv[i]);

  return 0;

After compiling and linking to generate user binary programs, you can use the disk_image_writer function mentioned in the previous article to write them into the disk image.

Load the program and exec

Next, we can implement the exec system call. The implementation part in the kernel is the process_exec function.

The first is to read the binary file. The file system interface implemented in the previous article is used here:

// Get elf binary file stat.
file_stat_t stat;
if (stat_file(path, &stat) != 0) {
  monitor_printf("Command %s not found\n", path);
  return -1;

// Read elf binary file.
uint32 size = stat.size;
char* read_buffer = (char*)kmalloc(size);
if (read_file(path, read_buffer, 0, size) != size) {
  monitor_printf("Failed to load cmd %s\n", path);
  return -1;

Then it is to parse the elf file. This loaded into the kernel . Here we rewrite it in C language. It looks a little clearer. The code is in src/elf/elf.c , it will elf Each section of the program is loaded into the memory, and then the entry address of the program is returned.

The next step is to discard all resources of the original process, because exec is occupying, the original program will be completely replaced. There are two main tasks here:

  • Clean up all threads of the original process, except for the current thread;
  • Release all virtual memory in the original user space;

Then create a new thread and start it with the entry function of the new elf binary we just loaded as entry. This new thread is the main thread where the new program starts to run.

As for the current thread of execution, at process_exec schedule_thread_exit function will be called directly to die.


This article is relatively simple, mainly because we have done all the preparatory work very well, and exec is just a splicing work. However, there are some details that should be paid attention to. For example, the parameters of exec need to be copied to the kernel in advance, and then the virtual memory in the user space is released, because the parameters passed in were originally stored in the user space. Once the memory is released, it will be later These parameters can no longer be accessed.

阅读 378

naive programmer

511 声望
90 粉丝
0 条评论

naive programmer

511 声望
90 粉丝