xv6, Deep Dive — Part5

Balemarthy Vamsi Krishna
3 min readMar 27, 2023

--

Photo from Canva Pro

Let’s get into the inner core of shell’s most used function “runcmd”. Few sentences were mentioned earlier about “runcmd()” function in Part 4. Now let us look at the working of runcmd().

This is THE function that get’s called by the main function of the shell. This uses the helper functions for every kind of operation supported by the shell. Also, there are helper functions like parsecmd() and others that parse and understand the commands provided as strings.

Here is the code of the runcmd() function and detailed explanation of major parts of it.

The function “runcmd” accepts the parameter struct cmd. There are variables declared at the beginning that will be used by the helper function and runcmd later.

For example, the array of integers “int p[2]” defined is used in the creation of “pipe” when the “pipecmd” has to be executed by the shell. Rest all are pointers to the different kinds of structures which are already mentioned in the 4th part of this series

// Execute cmd. Never returns.
void runcmd(struct cmd *cmd)
{
int p[2];
struct backcmd *bcmd;
struct execcmd *ecmd;
struct listcmd *lcmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;

Next in line comes the switch that selects the kind of command to be executed after the parsing of command. Here is the explanation. But before that a sanity check to avoid empty cmd pointer and the default case of wrong operation

if(cmd == 0)
exit();

switch(cmd->type)
{
default:
panic(“runcmd”);

Let us start with EXEC case which already was mentioned as the leaf command or the last command that actually gets the results. It calls the “exec” function with the command and the parameter passed as part of command line to shell. It can be seen that the parameters passed to the exec command actually are provided of the ecmd structure members.

case EXEC:
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit();
exec(ecmd->argv[0], ecmd->argv);
printf(2, “exec %s failed\n”, ecmd->argv[0]);
break;

Now comes the REDIR command, which is the redirection command where the output of one command is passed or redirected to a file. What is significant in this is the fact that “open” call is made with the file name and mode as parameters and these come as part of the rcmd structure pointer. After the opening of file the actual command passed in the “cmd” attribute gets run.

case REDIR:
rcmd = (struct redircmd*)cmd;
close(rcmd->fd);
if(open(rcmd->file, rcmd->mode) < 0)
{
printf(2, “open %s failed\n”, rcmd->file);
exit();
}
runcmd(rcmd->cmd);
break;

The LIST command just runs the list of commands passed on the shell. The beautiful thing I found again is the usage of fork call to execute the left command and the right command being execute by the current process.

This is really clever way, and these kinds of implementations are nothing but patterns that should be learned and stored in mind.

case LIST:
lcmd = (struct listcmd*)cmd;
if(fork1() == 0)
runcmd(lcmd->left);
wait();
runcmd(lcmd->right);
break;

The PIPE command and I can’t wait to talk about it. The the array of two integers is passed to create a pipe. Fork is again called to create two child processes. In one child process, the left command is run and it’s output is “piped” through the input of the pipe. In another child process this input is provided to the command on the right. After the operation both the ends of the pipe are closed. The code is as below and this requires a bit of implementation practice using “Pipes IPC”.

case PIPE:
pcmd = (struct pipecmd*)cmd;
if(pipe(p) < 0)
panic(“pipe”);

if(fork1() == 0)
{
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
}

if(fork1() == 0)
{
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}

close(p[0]);
close(p[1]);
wait();
wait();
break;

The BACK commands just create a child process and executes the command that comes as part of it’s structure.

case BACK:
bcmd = (struct backcmd*)cmd;
if(fork1() == 0)
runcmd(bcmd->cmd);
break;
}

exit(); // This is the exit for the main child process created by shell
}

That is it.

Was this explanation helpful. Please provide comments if you have any for improvement. Help me grow and provide value, one article at a time.

--

--

Balemarthy Vamsi Krishna

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