Search code examples
clinux-kernel

Where does read(2) check the termios struct to determine that it should read STDIN in an (un)blocking fashion?


I have searched:

  • the standard library (figured the answer isn't there because there is a dead end of libc_hidden_def which is apparently something that connects to the kernel)
  • xfce4-terminal source code
  • the Linux kernel

and still failed to find the answer that will explain how read(2) knows that is has to block or not block. Yes, read(2) is not the fuction containing the answer, but it is the starting point.

Here I will draw the timeline of the problem:

  1. tcsetattr(set VMIN and VTIME to get raw mode or some other combo)
  2. read(try to read from STDIN)
  3. ???
  4. something somewhere checks the termios struct that we have set, and decides whether to keep polling STDIN until a newline comes, until x characters are inside, until x time has passed, or always return whatever is (not) inside
  5. we get the result of read()

Solution

  • Where does read(2) check the termios struct to determine that it should read STDIN in an (un)blocking fashion?

    In the Linux kernel in TTY implementation. Related documentation https://docs.kernel.org/driver-api/tty/index.html .

    First we need the macros from https://elixir.bootlin.com/linux/latest/source/include/linux/tty.h#L40 :

    #define TIME_CHAR(tty) ((tty)->termios.c_cc[VTIME])
    #define MIN_CHAR(tty) ((tty)->termios.c_cc[VMIN])
    

    Looks like VMIN and VTIME is used around here https://elixir.bootlin.com/linux/latest/source/drivers/tty/n_tty.c#L2212 :

    static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, u8 *kbuf,
                  size_t nr, void **cookie, unsigned long offset)
    {
        .......
        minimum = time = 0;
        timeout = MAX_SCHEDULE_TIMEOUT;
        if (!ldata->icanon) {
            minimum = MIN_CHAR(tty);
            if (minimum) {
                time = (HZ / 10) * TIME_CHAR(tty);
            } else {
                timeout = (HZ / 10) * TIME_CHAR(tty);
                minimum = 1;
            }
        }
        ......
                    timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,
                            timeout);
    
        ......
        if (kb - kbuf >= minimum)
            break;
        if (time)
            timeout = time;