xv6, line by line
test yourself

Test yourself · category 19 of 20

Files, pipes, the console and the UART

How one read or write is routed by struct file, how a pipe’s two counters and two channels move bytes between processes, and how the console and the UART share one wire between processes, interrupt handlers and printk.

1warm-upChoose one

wc calls read(0, buf, 512). Inside the kernel, fileread must decide whether this read goes to a pipe, the console driver or a file on disk. What does it look at?

kernel/file.c
104// Read from file f.
105// addr is a user virtual address.
106int
107fileread(struct file *f, uint64 addr, int n)
109 int r = 0;
111 if (f->readable == 0 || n < 0)
112 return -1;
114 if (f->type == FD_PIPE) {
116 } else if (f->type == FD_DEVICE) {
117 if (f->major < 0 || f->major >= NDEV || !devsw[f->major].read)
118 return -1;
119 r = devsw[f->major].read(1, addr, n);
120 } else if (f->type == FD_INODE) {
122 if ((r = readi(f->ip, 1, addr, f->off, n)) > 0)
123 f->off += r;
125 } else {
126 panic("fileread");
127 }
129 return r;
3warm-upTrue or false, and why

True or false: after kfork, the parent and the child each have their own struct file for the console, so the child moving a file offset or closing its copy cannot affect the parent’s.

Why?

4warm-upChoose one

wc is reading from a pipe. The pipe is empty, and the last write end has been closed (writeopen == 0). What does piperead do?

kernel/pipe.c
111int
112piperead(struct pipe *pi, uint64 addr, int n)
114 int i;
115 struct proc *pr = myproc();
116 char ch;
119 while (pi->nread == pi->nwrite && pi->writeopen) { //DOC: pipe-empty
120 if (killed(pr)) {
122 return -1;
123 }
124 sleep_prepare(&pi->nread); //DOC: piperead-sleep
128 }
129 for (i = 0; i < n; i++) { //DOC: piperead-copy
130 if (pi->nread == pi->nwrite)
131 break;
133 if (copyout(pr->pagetable, pr->sz, addr + i, &ch, 1) == -1) {
134 if (i == 0)
135 i = -1;
136 break;
137 }
139 }
140 wakeup(&pi->nwrite); //DOC: piperead-wakeup
142 return i;
5warm-upClick the line

In piperead, click the line that checks whether any writer is still left on the pipe.

kernel/pipe.c
111int
112piperead(struct pipe *pi, uint64 addr, int n)
114 int i;
115 struct proc *pr = myproc();
116 char ch;
119 while (pi->nread == pi->nwrite && pi->writeopen) { //DOC: pipe-empty
120 if (killed(pr)) {
122 return -1;
123 }
124 sleep_prepare(&pi->nread); //DOC: piperead-sleep
128 }
129 for (i = 0; i < n; i++) { //DOC: piperead-copy
130 if (pi->nread == pi->nwrite)
131 break;
133 if (copyout(pr->pagetable, pr->sz, addr + i, &ch, 1) == -1) {
134 if (i == 0)
135 i = -1;
136 break;
137 }
139 }
140 wakeup(&pi->nwrite); //DOC: piperead-wakeup
142 return i;

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

6warm-upMatch the pairs

consoleintr treats some typed characters specially. Match each key with what the kernel does when it arrives.

kernel/console.c
146void
151 switch (c) {
152 case C('P'): // Print process list.
154 break;
155 case C('U'): // Kill line.
156 while (cons.e != cons.w &&
157 cons.buf[(cons.e - 1) % INPUT_BUF_SIZE] != '\n') {
160 }
161 break;
162 case C('H'): // Backspace
163 case '\x7f': // Delete key
164 if (cons.e != cons.w) {
167 }
168 break;
169 default:
170 if (c != 0 && cons.e - cons.r < INPUT_BUF_SIZE) {
171 c = (c == '\r') ? '\n' : c;
173 // echo back to the user.
176 // store for consumption by consoleread().
179 if (c == '\n' || c == C('D') || cons.e - cons.r == INPUT_BUF_SIZE) {
180 // wake up consoleread() if a whole line (or end-of-file)
181 // has arrived.
184 }
185 }
186 break;
187 }
7warm-upChoose one

When you type a key, consoleintr echoes it with consputc, which calls uartputc_sync and spins on the UART. Why does it not send the echo through uartwrite, like a process’s write does?

kernel/console.c
28//
29// send one character to the uart, but don't use
30// interrupts or sleep(). safe to be called from
31// interrupts, e.g. by printk and to echo input
32// characters.
33//
34void
37 if (c == BACKSPACE) {
38 // if the user typed backspace, overwrite with a space.
42 } else {
44 }
8warm-upChoose all that apply

A process with only descriptors 0, 1 and 2 open calls pipe(p), which succeeds. Which statements are true right afterwards?

kernel/pipe.c
22int
23pipealloc(struct file **f0, struct file **f1)
25 struct pipe *pi;
27 pi = 0;
28 *f0 = *f1 = 0;
29 if ((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0)
30 goto bad;
31 if ((pi = (struct pipe *)kalloc()) == 0)
32 goto bad;
33 pi->readopen = 1;
35 pi->nwrite = 0;
36 pi->nread = 0;
37 initlock(&pi->lock, "pipe");
38 (*f0)->type = FD_PIPE;
39 (*f0)->readable = 1;
40 (*f0)->writable = 0;
41 (*f0)->pipe = pi;
42 (*f1)->type = FD_PIPE;
43 (*f1)->readable = 0;
44 (*f1)->writable = 1;
45 (*f1)->pipe = pi;
46 return 0;
9solidType a number

cat makes one write(1, buf, 512) to the console. How many times does consolewrite call uartwrite for it?

kernel/console.c
58//
59// user write() system calls to the console go here.
60// uses sleep() and UART interrupts.
61//
62int
65 char buf[32]; // move batches from user space to uart.
66 int i = 0;
68 while (i < n) {
69 int nn = sizeof(buf);
70 if (nn > n - i)
71 nn = n - i;
72 if (either_copyin(buf, user_src, src + i, nn) == -1)
73 break;
75 i += nn;
76 }
78 return i;
decimal, 0x hex or 0b binary
10solidChoose one

When the last reference to a file goes away, fileclose copies *f into the local ff, frees the slot and releases ftable.lock, and only then calls pipeclose or iput. Why not do the cleanup while still holding ftable.lock?

kernel/file.c
58// Close file f. (Decrement ref count, close when reaches 0.)
59void
60fileclose(struct file *f)
62 struct file ff;
65 if (f->ref < 1)
66 panic("fileclose");
67 if (--f->ref > 0) {
69 return;
70 }
71 ff = *f;
72 f->ref = 0;
76 if (ff.type == FD_PIPE) {
78 } else if (ff.type == FD_INODE || ff.type == FD_DEVICE) {
82 }
11solidPut in order

In ls | wc, wc is asleep in piperead on an empty pipe. ls has written all its output and now exits. Put the events that end wc’s reading in order.

  1. pipeclose sets writeopen = 0 and calls wakeup(&pi->nread)
  2. wc returns from sleep, retakes pi->lock and re-checks the loop condition on line 119
  3. The copy loop breaks at once with i = 0, and read returns 0 to wc
  4. wc’s read loop ends and it prints its counts
  5. ls calls exit(0); kexit starts closing its open descriptors
  6. fileclose drops the write end’s ref to 0 and frees the ftable slot
12solidChoose one

nread and nwrite are never reset; they only count up, and positions are found with % PIPESIZE. What does this design buy compared with two indices that wrap back to 0 at 512?

kernel/pipe.c
11#define PIPESIZE 512
13struct pipe {
14 struct spinlock lock;
16 uint nread; // number of bytes read
17 uint nwrite; // number of bytes written
18 int readopen; // read fd is still open
19 int writeopen; // write fd is still open
20};
13solidTrue or false, and why

True or false: pipewrite calls copyin while holding the spinlock pi->lock, and this is safe in this kernel.

kernel/pipe.c
76int
77pipewrite(struct pipe *pi, uint64 addr, int n)
79 int i = 0;
80 struct proc *pr = myproc();
83 while (i < n) {
84 if (pi->readopen == 0 || killed(pr)) {
86 return -1;
87 }
88 if (pi->nwrite == pi->nread + PIPESIZE) { //DOC: pipewrite-full
94 } else {
95 char ch;
96 if (copyin(pr->pagetable, pr->sz, &ch, addr + i, 1) == -1) {
97 if (i == 0)
98 i = -1;
99 break;
100 }
102 i++;
103 }
104 }
108 return i;

Why?

14solidChoose one

cat with no arguments is in read(0, buf, 512) on the console. You type h, i, then Control-D (no Enter). What does that read return?

kernel/console.c
87int
91 int c;
92 char cbuf;
96 while (n > 0) {
97 // wait until interrupt handler has put some
98 // input into cons.buffer.
99 while (cons.r == cons.w) {
100 if (killed(myproc())) {
102 return -1;
103 }
108 }
112 if (c == C('D')) { // end-of-file
113 if (n < target) {
114 // Save ^D for next time, to make sure
115 // caller gets a 0-byte result.
117 }
118 break;
119 }
121 // copy the input byte to the user-space buffer.
123 if (either_copyout(user_dst, dst, &cbuf, 1) == -1)
124 break;
126 dst++;
127 --n;
129 if (c == '\n') {
130 // a whole line has arrived, return to
131 // the user-level read().
132 break;
133 }
134 }
137 return target - n;
15solidFill in the machine state

Hart 2 is idle in its scheduler (between intr_on and intr_off) when a UART interrupt arrives. uartintr calls consoleintr, which takes cons.lock and echoes the key. Hart 2 is now inside uartputc_sync, just after its push_off(). What is its state?

kernel/uart.c
98// write a byte to the uart without using
99// interrupts, for use by kernel printk() and
100// to echo characters. it spins waiting for the uart's
101// output register to be empty.
102void
105 if (panicking == 0)
108 if (panicked) {
109 for (;;)
110 ;
111 }
113 // wait for UART to set Transmit Holding Empty in LSR.
114 while ((ReadReg(LSR) & LSR_TX_IDLE) == 0)
115 ;
118 if (panicking == 0)
16solidChoose one

uartwrite serializes writers with tx_lock, a sleep-lock. Why would an ordinary spinlock not work here?

kernel/uart.c
76// transmit buf[] to the uart. it blocks if the
77// uart is busy, so it cannot be called from
78// interrupts, only from write() system calls.
79void
80uartwrite(char buf[], int n)
84 int i = 0;
85 while (i < n) {
89 i += 1;
90 } else {
92 }
93 }
17solidClick the line

Three harts call printk at the same moment. Click the line that keeps their messages from interleaving character by character.

kernel/printk.c
62// Print to the console.
63int
64printk(char *fmt, ...)
66 va_list ap;
67 int i, cx, c0, c1, c2;
68 char *s;
70 if (panicking == 0)
73 va_start(ap, fmt);
74 for (i = 0; (cx = fmt[i] & 0xff) != 0; i++) {
75 if (cx != '%') {
77 continue;
78 }

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

18solidDecode the bits

A program calls open("README", omode) with omode = 0x401. Using kernel/fcntl.h and sys_open, decode it and predict the result.

kernel/sysfile.c
376 if (ip->type == T_DEVICE) {
379 } else {
381 f->off = 0;
382 }
383 f->ip = ip;
387 if ((omode & O_TRUNC) && ip->type == T_FILE) {
389 }

Value: 0x401

19solidChoose all that apply

A process’s write to the console goes consolewrite → uartwrite. Which of these can happen on that path in this kernel?

kernel/uart.c
76// transmit buf[] to the uart. it blocks if the
77// uart is busy, so it cannot be called from
78// interrupts, only from write() system calls.
79void
80uartwrite(char buf[], int n)
84 int i = 0;
85 while (i < n) {
89 i += 1;
90 } else {
92 }
93 }
20deepType a number

A long program runs that never reads the console, so nobody calls consoleread. Before it started, the console input buffer was empty (r == w == e). You now type 200 ordinary letters without pressing Enter. How many of them are echoed on the screen?

kernel/console.c
169 default:
170 if (c != 0 && cons.e - cons.r < INPUT_BUF_SIZE) {
171 c = (c == '\r') ? '\n' : c;
173 // echo back to the user.
176 // store for consumption by consoleread().
179 if (c == '\n' || c == C('D') || cons.e - cons.r == INPUT_BUF_SIZE) {
180 // wake up consoleread() if a whole line (or end-of-file)
181 // has arrived.
184 }
185 }
186 break;
decimal, 0x hex or 0b binary
21deepChoose all that apply

On hart 0, cat is in uartwrite holding tx_lock, halfway through a 32-byte batch. On harts 1 and 2 other things are happening. Which of these can put bytes on the screen between two bytes of cat’s batch?

22deepTrue or false, and why

True or false: uartwrite calls sleep_prepare without holding any spinlock, so if the transmit-done interrupt arrives between the LSR check on line 87 and the sleep on line 91, the wakeup is lost and the process can sleep forever.

kernel/uart.c
79void
80uartwrite(char buf[], int n)
84 int i = 0;
85 while (i < n) {
89 i += 1;
90 } else {
92 }
93 }

Why?

23deepType a number

A program calls write(fd, buf, 10000) on a regular file, and every writei succeeds. How many file-system transactions (begin_op … end_op pairs) does filewrite run? (MAXOPBLOCKS is 10, BSIZE is 1024.)

kernel/file.c
148 } else if (f->type == FD_INODE) {
149 // write a few blocks at a time to avoid exceeding
150 // the maximum log transaction size, including
151 // i-node, indirect block, allocation blocks,
152 // and 2 blocks of slop for non-aligned writes.
153 int max = ((MAXOPBLOCKS - 1 - 1 - 2) / 2) * BSIZE;
154 int i = 0;
155 while (i < n) {
156 int n1 = n - i;
157 if (n1 > max)
162 if ((r = writei(f->ip, 1, addr + i, f->off, n1)) > 0)
163 f->off += r;
167 if (r != n1) {
168 // error from writei
169 break;
170 }
171 i += r;
172 }
173 ret = (i == n ? n : -1);
decimal, 0x hex or 0b binary
24deepChoose one

Continue from cat reading h, i, Control-D: the first read returned 2. cat writes hi and calls read(0, buf, 512) again. Why does this second read return 0 immediately instead of sleeping?

kernel/console.c
87int
91 int c;
92 char cbuf;
96 while (n > 0) {
97 // wait until interrupt handler has put some
98 // input into cons.buffer.
99 while (cons.r == cons.w) {
100 if (killed(myproc())) {
102 return -1;
103 }
108 }
112 if (c == C('D')) { // end-of-file
113 if (n < target) {
114 // Save ^D for next time, to make sure
115 // caller gets a 0-byte result.
117 }
118 break;
119 }
121 // copy the input byte to the user-space buffer.
123 if (either_copyout(user_dst, dst, &cbuf, 1) == -1)
124 break;
126 dst++;
127 --n;
129 if (c == '\n') {
130 // a whole line has arrived, return to
131 // the user-level read().
132 break;
133 }
134 }
137 return target - n;
25deepChoose one

When both ends of a pipe are closed, pipeclose runs release(&pi->lock) on line 70 and only then kfree((char *)pi) on line 71. What would go wrong if the two lines were swapped?

kernel/pipe.c
58void
59pipeclose(struct pipe *pi, int writable)
62 if (writable) {
65 } else {
66 pi->readopen = 0;
68 }
69 if (pi->readopen == 0 && pi->writeopen == 0) {
71 kfree((char *)pi);
72 } else