Linux – Why does 8250 UART driver not wake up TTY if more than 256 characters are pending

consolehistorylinuxserial-consoletty

What is the motivation of this if-condition in void serial8250_tx_chars(struct uart_8250_port *up)?

if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
    uart_write_wakeup(port);

It has been there ever since Linux 1.1.13 (May 1994) and repeats in most UART drivers.

Background: customized Linux 3.4.91, embedded system on ARMv7, UART port 0 is configured for 38400 baud, 16 byte FIFO for i/o. None of this can be changed in our setup.

When printf-ing very heavily on the console via UART, the internal 4kB-buffer (UART_XMIT_SIZE) fills up and then stalls the user-space process until the buffer is emptied (which takes one second at 38400 baud!). Then this behavior repeats. This is because function n_tty_write() goes to sleep when the buffer is full, and is not woken up for a long time because of the questionable condition above.

I would find it more natural and efficient if this check was simply removed. Then the printfs would fill up the buffer as quickly as they can, and then continue at the speed at which the buffer is being emptied, rather than the burst-processing I am observing.

It works fine in my environment but surely I am missing or misunderstanding something. There must be a reason for the current implementation. Are there any side effects if I remove that condition?

As a side-question: are there configuration options to tune this behavior, e.g. to have printf always return immediately and discard the output if the buffer is full?

Best Answer

It's an efficiency measure. The CPU runs so much faster than the serial port that if the kernel let the userspace process run every time there was a little bit of room in the buffer, it would end up making a trip to userspace and back for every single byte of data. That's very wasteful of CPU time:

$ time dd if=/dev/zero of=/dev/null bs=1 count=10000000
10000000+0 records in
10000000+0 records out
10000000 bytes (10 MB, 9.5 MiB) copied, 5.95145 s, 1.7 MB/s

real    0m5.954s
user    0m1.960s
sys     0m3.992s

$ time dd if=/dev/zero of=/dev/null bs=1000 count=10000
10000+0 records in
10000+0 records out
10000000 bytes (10 MB, 9.5 MiB) copied, 0.011041 s, 906 MB/s

real    0m0.014s
user    0m0.000s
sys     0m0.012s

The above test isn't even reading and writing a real device: the whole time difference is how often the system is bouncing between userspace and kernelspace.

If userspace doesn't want to be held up, it can use non-blocking I/O, or it can check use a select() call to see if there's room to write to the device... and if there's not, it can dump the remainder into a buffer of its own and continue processing. Admittedly, that does complicate things, since now you have a buffer that you have to flush... but if you're using stdio, that's generally true anyway.

Related Question