xv6, line by line
test yourself

Test yourself · category 20 of 20

User space: ulib, usys and the shell

How a user program starts, calls the kernel through the usys stubs and the small ulib/printf/malloc library, and how the shell parses a command line and wires pipes and redirections with fork, close, dup and exec.

1warm-upChoose one

This is the whole user-side write function, generated by user/usys.pl. How do the descriptor, buffer address and byte count reach the kernel?

user/usys.S
28.global write
30 li a7, SYS_write
31 ecall
32 ret
2warm-upType a number

When cat calls write(1, buf, n), what number is in register a7 at the ecall?

user/usys.S
28.global write
30 li a7, SYS_write
31 ecall
32 ret
decimal, 0x hex or 0b binary
3warm-upTrue or false, and why

True or false: if a user program’s main returns 3 without calling exit, the process crashes, because main returns to an invalid address.

user/ulib.c
8//
9// wrapper so that it's OK if main() does not call exit().
10//
11void
12start(int argc, char **argv)
14 int r;
15 extern int main(int argc, char **argv);

Why?

4warm-upChoose one

user/user.ld has no ENTRY line, and the Makefile passes no -e option when it links _cat. Where does cat start executing after kexec?

user/user.ld
1OUTPUT_ARCH( "riscv" )
3SECTIONS
4{
5 . = 0x0;
6
7 .text : {
8 *(.text .text.*)
9 }
5warm-upType a number

init executes printf("init: starting sh\n"). How many write system calls does that one call make?

user/printf.c
9static void
10putc(int fd, char c)
12 write(fd, &c, 1);
decimal, 0x hex or 0b binary
6warm-upChoose one

The shell handles cd itself instead of forking a child to run it like every other command. Why?

user/sh.c
159 // Read and run input commands.
160 while (getcmd(buf, sizeof(buf)) >= 0) {
161 char *cmd = buf;
162 while (*cmd == ' ' || *cmd == '\t')
163 cmd++;
164 if (*cmd == '\n') // is a blank command
165 continue;
166 if (cmd[0] == 'c' && cmd[1] == 'd' && cmd[2] == ' ') {
167 // Chdir must be called by the parent, not the child.
168 cmd[strlen(cmd) - 1] = 0; // chop \n
169 if (chdir(cmd + 3) < 0)
170 fprintf(2, "cannot cd %s\n", cmd + 3);
171 } else {
172 if (fork1() == 0)
174 wait(0);
175 }
176 }
7warm-upMatch the pairs

Match each piece of shell syntax with the node that the parser in user/sh.c builds for it.

8warm-upChoose all that apply

Every user program except forktest is linked with ULIB = ulib.o usys.o printf.o umalloc.o. Which of these are therefore part of _cat?

9solidClick the line

For ls | wc, click the line that makes ls’s standard output (descriptor 1) refer to the pipe.

user/sh.c
101 case PIPE:
102 pcmd = (struct pipecmd *)cmd;
103 if (pipe(p) < 0)
104 panic("pipe");
105 if (fork1() == 0) {
107 dup(p[1]);
108 close(p[0]);
109 close(p[1]);
111 }
112 if (fork1() == 0) {
114 dup(p[0]);
115 close(p[0]);
116 close(p[1]);
118 }
119 close(p[0]);
120 close(p[1]);
121 wait(0);
122 wait(0);
123 break;

Your pick: none yet (click a line in the code)

10solidPut in order

You type ls and press Enter. Put these steps in the order they happen in the shell and its child.

  1. The child’s parsecmd builds the tree, and nulterminate writes \0s into its copy of buf
  2. fork1 creates a child; the shell goes on to wait
  3. gets reads the line one byte per read, stopping at the newline
  4. main checks whether the line starts with cd
  5. runcmd reaches the EXEC case and calls exec
  6. getcmd writes $ to descriptor 2
11solidChoose one

In the right child (the future wc) someone deletes line 116, close(p[1]). You run ls | wc. What happens?

user/sh.c
101 case PIPE:
102 pcmd = (struct pipecmd *)cmd;
103 if (pipe(p) < 0)
104 panic("pipe");
105 if (fork1() == 0) {
107 dup(p[1]);
108 close(p[0]);
109 close(p[1]);
111 }
112 if (fork1() == 0) {
114 dup(p[0]);
115 close(p[0]);
116 close(p[1]);
118 }
119 close(p[0]);
120 close(p[1]);
121 wait(0);
122 wait(0);
123 break;
12solidDecode the bits

While parsing echo hi > out, parseredirs builds a redircmd whose mode field is 0x601. Decode it with kernel/fcntl.h.

user/sh.c
390 switch (tok) {
391 case '<':
393 break;
394 case '>':
396 break;
397 case '+': // >>
399 break;
400 }

Value: 0x601

13solidTrue or false, and why

True or false: in the REDIR case, runcmd tells open which descriptor number the file should get (rcmd->fd).

user/sh.c
83 case REDIR:
84 rcmd = (struct redircmd *)cmd;
86 if (open(rcmd->file, rcmd->mode) < 0) {
87 fprintf(2, "open %s failed\n", rcmd->file);
88 exit(1);
89 }
91 break;

Why?

14solidChoose one

Why does the shell parse the command line in the child after fork1(), rather than parsing first and forking afterwards?

user/sh.c
171 } else {
172 if (fork1() == 0)
174 wait(0);
175 }
15solidChoose one

After parsing ls | wc, where do the strings ecmd->argv[0] of the two exec nodes point?

user/sh.c
421struct cmd *
422parseexec(char **ps, char *es)
424 char *q, *eq;
425 int tok, argc;
426 struct execcmd *cmd;
427 struct cmd *ret;
429 if (peek(ps, es, "("))
430 return parseblock(ps, es);
433 cmd = (struct execcmd *)ret;
435 argc = 0;
437 while (!peek(ps, es, "|)&;")) {
438 if ((tok = gettoken(ps, es, &q, &eq)) == 0)
439 break;
440 if (tok != 'a')
441 panic("syntax");
445 if (argc >= MAXARGS)
446 panic("too many args");
448 }
449 cmd->argv[argc] = 0;
450 cmd->eargv[argc] = 0;
451 return ret;
16solidType a number

A freshly exec’d program’s first malloc call is malloc(100). How many bytes does malloc (through morecore) ask the kernel for with sbrk? A Header is 16 bytes.

user/umalloc.c
46static Header *
49 char *p;
52 if (nu < 4096)
53 nu = 4096;
54 p = sbrk(nu * sizeof(Header));
55 if (p == SBRK_ERROR)
56 return 0;
57 hp = (Header *)p;
58 hp->s.size = nu;
59 free((void *)(hp + 1));
60 return freep;
63void *
69 nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
70 if ((prevp = freep) == 0) {
72 base.s.size = 0;
73 }
74 for (p = prevp->s.ptr;; prevp = p, p = p->s.ptr) {
75 if (p->s.size >= nunits) {
76 if (p->s.size == nunits)
77 prevp->s.ptr = p->s.ptr;
78 else {
80 p += p->s.size;
82 }
84 return (void *)(p + 1);
85 }
86 if (p == freep)
87 if ((p = morecore(nunits)) == 0)
88 return 0;
89 }
decimal, 0x hex or 0b binary
17solidFill in the machine state

cat is in its write stub (user/usys.S:31) and is about to execute ecall; the trap has not happened yet. What is the state of the hart running it?

user/usys.S
28.global write
30 li a7, SYS_write
31 ecall
32 ret
18solidChoose all that apply

Which of these user library functions can make a system call?

21deepChoose one

You type echo hi &. The shell prints the next prompt without waiting for echo. Which process eventually calls wait and frees echo’s process slot?

user/sh.c
125 case BACK:
126 bcmd = (struct backcmd *)cmd;
127 if (fork1() == 0)
129 break;
130 }
131 exit(0);
22deepChoose one

The file out contains 50 bytes. You type cat < nosuchfile > out, and nosuchfile does not exist. What happens?

user/sh.c
380struct cmd *
381parseredirs(struct cmd *cmd, char **ps, char *es)
383 int tok;
384 char *q, *eq;
386 while (peek(ps, es, "<>")) {
387 tok = gettoken(ps, es, 0, 0);
388 if (gettoken(ps, es, &q, &eq) != 'a')
389 panic("missing file for redirection");
390 switch (tok) {
391 case '<':
393 break;
394 case '>':
396 break;
397 case '+': // >>
399 break;
400 }
401 }
402 return cmd;
23deepChoose one

char buf[] = "abcde"; then memmove(buf + 1, buf, 4);. Which branch of memmove runs, and what does buf hold afterwards?

user/ulib.c
112void *
113memmove(void *vdst, const void *vsrc, int n)
115 char *dst;
116 const char *src;
120 if (src > dst) {
121 while (n-- > 0)
122 *dst++ = *src++;
123 } else {
124 dst += n;
125 src += n;
126 while (n-- > 0)
127 *--dst = *--src;
128 }
129 return vdst;
24deepChoose all that apply

The shell’s child calls exec("echo", argv) for the line echo hi there. When echo’s start executes its first instruction, which statements are true?

kernel/exec.c
96 sp = sz;
99 // Copy argument strings into new stack, remember their
100 // addresses in ustack[].
101 for (argc = 0; argv[argc]; argc++) {
102 sp -= strlen(argv[argc]) + 1;
103 sp -= sp % 16; // riscv sp must be 16-byte aligned
105 goto bad;
106 if (copyout(pagetable, sz, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
107 goto bad;
109 }
112 // push a copy of ustack[], the array of argv[] pointers.
113 sp -= (argc + 1) * sizeof(uint64);
114 sp -= sp % 16;
116 goto bad;
117 if (copyout(pagetable, sz, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) <
118 0)
119 goto bad;
121 // a0 and a1 contain arguments to user main(argc, argv)
122 // argc is returned via the system call return
123 // value, which goes in a0.
126 // Save program name for debugging.
127 for (last = s = path; *s; s++)
128 if (*s == '/')
129 last = s + 1;
130 safestrcpy(p->name, last, sizeof(p->name));
132 // Commit to the user image.
135 p->sz = sz;
136 p->trapframe->epc = elf.entry; // initial program counter = ulib.c:start()
137 p->trapframe->sp = sp; // initial stack pointer
140 return argc; // this ends up in a0, the first argument to main(argc, argv)
25deepChoose one

user/user.ld places .text at address 0, and kexec maps it with PTE_R, PTE_X and PTE_U. A buggy user program executes int x = *(int *)0;. What happens?

user/user.ld
1OUTPUT_ARCH( "riscv" )
3SECTIONS
4{
5 . = 0x0;
6
7 .text : {
8 *(.text .text.*)
9 }