6.828-Hw2-Shell

Homework: shell

Executing simple commands

Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
case ' ':
ecmd = (struct execcmd *)cmd;
if (ecmd->argv[0] == 0)
_exit(0);
// Check if the file is executeable in pwd
if (access(ecmd->argv[0], X_OK) == 0)
{
execv(ecmd->argv[0], ecmd->argv);
}
else
{
// Check if the file is executeable in /bin/
// no support for PATHs
const char *binPath = "/bin/";
int pathLen = strlen(binPath) + strlen(ecmd->argv[0]);
char *path = (char *)malloc((pathLen + 1) * sizeof(char));
strcpy(path, binPath);
strcat(path, ecmd->argv[0]);
if (access(path, X_OK) == 0)
{
execv(path, ecmd->argv);
}
else
{
fprintf(stderr, "shell: command <%s> not found\n", ecmd->argv[0]);
}
}
break;

I/O redirection

Implementation

Reading xv6-book is helpful.

File descriptors and fork interact to make I/O redirection easy to implement. Fork copies the parent’s file descriptor table along with its memory, so that the child starts with exactly the same open files as the parent. The system call exec replaces the calling process’s memory but preserves its file table. This behavior allows the shell to implement I/O redirection by forking, reopening chosen file descriptors, and then execing the new program.

By making fork and exec separate, Unix can implement interactive commands like redirection and pipes easily.

Internally, the xv6 kernel uses the file descriptor as an index into a per-process table, so that every process has a private space of file descriptors starting at zero. By convention, a process reads from file descriptor 0 (standard input), writes output to file descriptor 1 (standard output), and writes error messages to file descriptor 2 (standard error). As we will see, the shell exploits the convention to implement I/O redirection and pipelines. The shell ensures that it always has three file descriptors open (8707), which are by default file descriptors for the console.

……

The close system call releases a file descriptor, making it free for reuse by a future open, pipe, or dup system call (see below). A newly allocated file descriptor is always the lowest-numbered unused descriptor of the current process.

So that’s why we close first in the following code.

For convenience’s sake, just use 0777 as the mode in open()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
case '>':
rcmd = (struct redircmd *)cmd;
close(rcmd->fd); // in this case it's 1
if (open(rcmd->file, rcmd->flags, 0777) < 0)
{
fprintf(stderr, "open %s failed!\n", rcmd->file);
exit(0);
}
runcmd(rcmd->cmd);
break;

case '<':
rcmd = (struct redircmd *)cmd;
close(rcmd->fd); // in this case it's 0
if (open(rcmd->file, rcmd->flags, 0777) < 0)
{
fprintf(stderr, "open %s failed!\n", rcmd->file);
exit(0);
}
runcmd(rcmd->cmd);
break;

Implement pipes

Implementation

pipe() creates a pipe, a unidirectional data channel that can be
used for interprocess communication. The array pipefd is used to
return two file descriptors referring to the ends of the pipe.
pipefd[0] refers to the read end of the pipe. pipefd[1] refers
to the write end of the pipe. Data written to the write end of
the pipe is buffered by the kernel until it is read from the read
end of the pipe.

The dup() system call allocates a new file descriptor that refers
to the same open file description as the descriptor oldfd. (For
an explanation of open file descriptions, see open(2).) The new
file descriptor number is guaranteed to be the lowest-numbered
file descriptor that was unused in the calling process.

The dup2() system call performs the same task as dup(), but
instead of using the lowest-numbered unused file descriptor, it
uses the file descriptor number specified in newfd. In other
words, the file descriptor newfd is adjusted so that it now
refers to the same open file description as oldfd.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
case '|':
pcmd = (struct pipecmd *)cmd;
if(pipe(p) == -1){
fprintf(stderr, "create pipe failed!\n");
exit(0);
}
// Run two commands respectively in two child process
if(fork()==0){
// Make child's stdout refer to where p[1] refers to (Write end)
dup2(p[1],STDOUT_FILENO);
// close child's p[]
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
}
if(fork()==0){
// Same, but Read end
dup2(p[0],STDIN_FILENO);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}
close(p[0]);
close(p[1]);
wait(&r);
wait(&r);
break;
Author

BakaFT

Posted on

2021-11-04

Updated on

2023-12-28

Licensed under

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×