xv6, Deep Dive — Part3

Balemarthy Vamsi Krishna
3 min readMar 23, 2023

--

Photo from Canva Pro

Welcome to the 3rd article in the series of articles about xv6 operating system. Here is the link to the first article and second article if you missed them.

The story intensifies from here. In the second part we have seen the internals of the init process, which is the first process that gets created by the kernel. Mostly the process id (PID) for the init process will be 1. Also, it was mentioned in second article that init process will create the shell using with the user can interact with the kernel and get the job of interest to be done by it.

Here in this article and few following articles the mystery behind the “shell” will be unraveled. One will find different ways, most of them being trivial, implementing a shell if one can search in GitHub. But the implementation in xv6 is not just a toy program or a trivial program. Also, it is not a full-blown implementation like famous shells like “Bash” or “Ksh” commonly found in GNU/Linux distributions. It is just enough to make the interested person get enough knowledge about shell and it’s intricacies.

Let’s dive in. As usual the code will be interleaved with the explanation. The source code of shell — sh.c present in xv6 is being provided below for easy reference.

xv6-public/sh.c at master · mit-pdos/xv6-public · GitHub

Currently the code stands at 493 lines of code and hence I would like to conclude the articles about shell in 5 or less articles. Let us begin with the small and sweet “main” function.

The first part of the main ensures that the shell is provided with all the file descriptors opened for it. The stdin, stdout, and stderr.

int main(void)
{
static char buf[100];
int fd;

// Ensure that three file descriptors are open.
while((fd = open(“console”, O_RDWR)) >= 0)
{
if(fd >= 3)
{
close(fd);
break;
}
}

After the aquisition of the 3 important file descriptors, the shell enters listening mode or “accepting the commands” mode using the api “getcmd”.

A special of “chdir- changing directory” is handled by restricting the operation only from the parent and not by the child. Post that check the code continues the operation of “getcmd” present inside while instead of returning. The shell exits after the running of the command.

In the while loop, the shell “gets” the command that is entered by the user.

// Read and run input commands.
while(getcmd(buf, sizeof(buf)) >= 0)
{
if(buf[0] == ‘c’ && buf[1] == ‘d’ && buf[2] == ‘ ‘)
{
// Chdir must be called by the parent, not the child.
buf[strlen(buf)-1] = 0; // chop \n
if(chdir(buf+3) < 0)
printf(2, “cannot cd %s\n”, buf+3);
continue;
} // This is closing bracke for the “if” condition above and not while

The magic done by the shell happens now. “fork” system call is called and, in the child, process the complete command given by the user is executed.

This means the parent process which is the “shell” in this case is always up and running.

runcmd and parsecmd are the two functions that will be discussed in detail in next articles. Suffice to say, that the child process handles the command and waits for the completion of the command execution.

if(fork1() == 0)
runcmd(parsecmd(buf));
wait();
} // This is the end of while mentioned above
exit();
}

That is it for this article.

I await your comments and suggestions for the improvement. Meet you in the next article.

Until then, bye and happy learning to you.

--

--

Balemarthy Vamsi Krishna

Early career embedded software mentor. Talks and writes about interviews, resume building, technical branding, growth and hike