Write OS kernel from scratch – run shell


Series catalog

Shell command line

This is the last article in this series. Adding a user interface shell to this OS is the most basic classic textbook project in Linux programming. You can also find many small tutorials on the Internet. There is no waste of time here. Just show the core part:

void print_shell() {
  printf("bash> ");
while (1) {
  while (1) {
    int32 c = read_char();
    if (c == '\n') {
    } else if (c < 128) {

Shell is essentially just a shell. Just like its name, it provides a command-line interface to interact with users, waiting for users to input characters and print them back; Once the user presses the Enter key, it indicates that the previously entered command line needs to be run, which is displayed in therun_programFunction:

void run_program() {
  // Parse cmd and get program and args.
  // ..
  // (fork + exec) new prgoram.
  int32 pid = fork();
  if (pid < 0) {
    printf("fork failed");
  } else if (pid > 0) {
    // parent
    int32 status;
    wait(pid, &status);
  } else {
    // child
    int32 ret = exec(program, args_index, (char**)args);

Here, first parse the command line string entered by the user just before hitting enter to parse the executable program name and parameters. Then there is the classicfork + execCombine and run this program. The program name and parameters are passed to theexecHandler function of system callprocess_exec, where the user executable is read from disk and executed. The program name entered on the command line here is very simple, and there is no concept of path, because we use naive_ FS has only one layer structure, and all files are at the top level, so you can use the file name directly.

The parent process after fork will call wait system call blocking and wait for the end of child. I haven’t expanded the system calls of wait and exit in detail in this series. Readers can read the source code by themselves.

Kernel startup task

Let’s take a look at the process of starting the kernel, what tasks are started in the background, and how to finally enter the shell interface. The code in this section issrc/task/scheduler.cYes.

Start firstKernel main process / thread, it is the most primitive ancestral process and can do these things:

  • Create kernel resource cleanup threadkernel_clean_thread, this is a background thread. I use it to do the final recycling of process / thread resources. It usually sleeps and wakes up only when process / thread dies and needs to be cleaned up;
  • Create init process and threadkernel_init_thread, it will become the first user process to run the user programinit; stayinitIn the program, I createdshellProcess, and theninitThe process enters blocking; In the actual Linux system, the realinitThe process should also be used as a background task, specifically responsible for waiting to take over and reclaim all orphan processes(Orphan Process), I won’t realize it here. Interested students can check the information and learn it;
  • After the above two tasks are completed, the original thread becomescpu_idleThread, the so-called CPU idle is an instructionhlt, it will run only when the system really does not have any tasks to run, and it will make the CPU enter a low-power running state;

Of course, the above is only my personal implementation of starting the kernel task, which is somewhat similar to but not completely consistent with Linux; This is actually very casual. After all, this is just a toy OS written by ourselves. The way of Linux is not a standard answer. We only need to make all important tasks of the system run successfully and schedule them.