Eliminate race condition in console_print_stats() found by Sorav's
[pintos-anon] / src / lib / kernel / console.c
1 #include <console.h>
2 #include <stdarg.h>
3 #include <stdio.h>
4 #include "devices/serial.h"
5 #include "devices/vga.h"
6 #include "threads/interrupt.h"
7 #include "threads/synch.h"
8
9 static void vprintf_helper (char, void *);
10 static void put_char_unlocked (uint8_t c);
11
12 /* The console lock.
13    Both the vga and serial layers do their own locking, so it's
14    safe to call them at any time.
15    But this lock is useful to prevent simultaneous printf() calls
16    from mixing their output, which looks confusing. */
17 static struct lock console_lock;
18
19 /* It's possible, if you add enough debug output to Pintos, to
20    try to recursively grab console_lock from a single thread.  As
21    a real example, I added a printf() call to palloc_free().
22    Here's a real backtrace that resulted:
23
24    lock_console()
25    vprintf()
26    printf()             - palloc() tries to grab the lock again
27    palloc_free()        
28    schedule_tail()      - another thread dying as we switch threads
29    schedule()
30    thread_yield()
31    intr_handler()       - timer interrupt
32    intr_set_level()
33    serial_putc()
34    put_char_unlocked()
35    putbuf()
36    sys_write()          - one process writing to the console
37    syscall_handler()
38    intr_handler()
39
40    This kind of thing is very difficult to debug, so we avoid the
41    problem by simulating a recursive lock with a depth
42    counter. */
43 static int console_lock_depth;
44
45 /* Number of characters written to console. */
46 static int64_t write_cnt;
47
48 /* Initializes the console. */
49 void
50 console_init (void) 
51 {
52   lock_init (&console_lock);
53 }
54
55 /* Acquires the console lock. */
56 static void
57 acquire_console (void) 
58 {
59   if (!intr_context ()) 
60     {
61       if (lock_held_by_current_thread (&console_lock)) 
62         console_lock_depth++; 
63       else
64         lock_acquire (&console_lock); 
65     }
66 }
67
68 /* Releases the console lock. */
69 static void
70 release_console (void) 
71 {
72   if (!intr_context ()) 
73     {
74       if (console_lock_depth > 0)
75         console_lock_depth--;
76       else
77         lock_release (&console_lock); 
78     }
79 }
80
81 /* Returns true if the current thread has the console lock,
82    false otherwise. */
83 static bool
84 console_locked_by_current_thread (void) 
85 {
86   return intr_context () || lock_held_by_current_thread (&console_lock);
87 }
88
89 /* Prints console statistics. */
90 void
91 console_print_stats (void) 
92 {
93   acquire_console ();
94   printf ("Console: %lld characters output\n", write_cnt);
95   release_console ();
96 }
97
98 /* The standard vprintf() function,
99    which is like printf() but uses a va_list.
100    Writes its output to both vga display and serial port. */
101 int
102 vprintf (const char *format, va_list args) 
103 {
104   int char_cnt = 0;
105
106   acquire_console ();
107   __vprintf (format, args, vprintf_helper, &char_cnt);
108   release_console ();
109
110   return char_cnt;
111 }
112
113 /* Writes string S to the console, followed by a new-line
114    character. */
115 int
116 puts (const char *s) 
117 {
118   acquire_console ();
119   while (*s != '\0')
120     put_char_unlocked (*s++);
121   put_char_unlocked ('\n');
122   release_console ();
123
124   return 0;
125 }
126
127 /* Writes the N characters in BUFFER to the console. */
128 void
129 putbuf (const char *buffer, size_t n) 
130 {
131   acquire_console ();
132   while (n-- > 0)
133     put_char_unlocked (*buffer++);
134   release_console ();
135 }
136
137 /* Writes C to the vga display and serial port. */
138 int
139 putchar (int c) 
140 {
141   acquire_console ();
142   put_char_unlocked (c);
143   release_console ();
144   
145   return c;
146 }
147 \f
148 /* Helper function for vprintf(). */
149 static void
150 vprintf_helper (char c, void *char_cnt_) 
151 {
152   int *char_cnt = char_cnt_;
153   (*char_cnt)++;
154   put_char_unlocked (c);
155 }
156
157 /* Writes C to the vga display and serial port.
158    The caller has already acquired the console lock if
159    appropriate. */
160 static void
161 put_char_unlocked (uint8_t c) 
162 {
163   ASSERT (console_locked_by_current_thread ());
164   write_cnt++;
165   serial_putc (c);
166   vga_putc (c);
167 }