Как каналы ввода / вывода реализованы в ядре Linux?

8

stdin, stdout, stderr - это некоторые целые числа, которые индексируют в структуре данных, которая «знает», какие каналы ввода / вывода должны использоваться для процесса. Я понимаю, что эта структура данных уникальна для каждого процесса. Являются ли каналы ввода-вывода ничем иным, как некоторыми структурами массивов данных с динамическим распределением памяти?

KawaiKx
источник
под каналами ввода / вывода вы подразумеваете потоки или каналы? Кроме того, это, вероятно, зависит от ядра. Вы, вероятно, должны спросить о конкретном ядре.
Струджи
1
@strugee Я говорю о ядре Linux. Я имел в виду потоки по каналам ввода / вывода. Как эти потоки реализованы в Linux? какой-то массив или что-то?
KawaiKx

Ответы:

14

В Unix-подобных операционных системах, стандартные ввода, вывода и ошибок потоков определены файловых дескрипторов 0, 1, 2. В Linux они видны в procфайловой системе в /proc/[pid]/fs/{0,1,2}. Эти файлы на самом деле являются символическими ссылками на псевдотерминальное устройство в /dev/ptsкаталоге.

Псевдотерминал (PTY) - это пара виртуальных устройств, главный псевдотерминал (PTM) и подчиненный псевдотерминал (PTS) (совместно именуемые псевдотерминальной парой ), которые обеспечивают канал IPC, что-то вроде двунаправленной трубы между программой, которая ожидает быть подключенным к терминальному устройству и программе драйвера, которая использует псевдотерминал для отправки ввода и получения ввода от предыдущей программы.

Ключевым моментом является то, что псевдотерминальный подчиненный выглядит как обычный терминал, например, он может переключаться между неканоническим и каноническим режимом (по умолчанию), в котором он интерпретирует определенные входные символы, например генерирует SIGINTсигнал, когда символ прерывания (обычно генерируется) нажатием Ctrl+ Cна клавиатуре) записывается в мастер псевдотерминала или вызывает read()возврат следующего, 0когда встречается символ конца файла (обычно генерируемый Ctrl+ D). Другими операциями, поддерживаемыми терминалами, является включение или выключение эха, настройка группы процессов переднего плана и т. Д.

Псевдотерминалы имеют ряд применений:

  • Они позволяют таким программам sshуправлять терминально-ориентированными программами на другом хосте, подключенном через сеть. Ориентированная на терминал программа может быть любой программой, которая обычно запускается в сеансе интерактивного терминала. Стандартный ввод, вывод и ошибка такой программы не могут быть подключены напрямую к сокету, так как сокеты не поддерживают вышеупомянутую функциональность, связанную с терминалом.

  • Они позволяют таким программам expectуправлять интерактивной терминально-ориентированной программой из сценария.

  • Они используются эмуляторами терминала, например, xtermдля обеспечения функциональности терминала.

  • Они используются такими программами, как screenмультиплексирование одного физического терминала между несколькими процессами.

  • Они используются такими программами, как scriptзапись всех входных и выходных данных во время сеанса оболочки.

PTY-файлы в стиле Unix98 , используемые в Linux, настраиваются следующим образом:

  • Программа драйвера открывает главный мультиплексор псевдотерминала dev/ptmx, на котором она принимает файловый дескриптор для PTM, и в /dev/ptsкаталоге создается устройство PTS . Каждый дескриптор файла, полученный при открытии, /dev/ptmxявляется независимым PTM со своим собственным PTS.

  • Драйвер программы вызывает fork()для создания дочернего процесса, который, в свою очередь, выполняет следующие шаги:

    • Ребенок вызывает, setsid()чтобы начать новый сеанс, из которых ребенок является лидером сеанса. Это также приводит к тому, что ребенок теряет свой контролирующий терминал .

    • Ребенок переходит к открытию устройства PTS, которое соответствует PTM, созданному программой драйвера. Поскольку дочерний объект является лидером сеанса, но не имеет управляющего терминала, PTS становится управляющим терминалом дочернего объекта.

    • Дочерний объект использует dup()для дублирования файловый дескриптор для ведомого устройства на нем стандартный ввод, вывод и ошибку.

    • Наконец, ребенок вызывает exec()запуск ориентированной на терминал программы, которая должна быть подключена к псевдотерминальному устройству.

В этот момент все, что программа драйвера записывает в PTM, отображается как входная информация для ориентированной на терминал программы в PTS, и наоборот.

При работе в каноническом режиме вход в PTS буферизуется построчно. Другими словами, как и в случае с обычными терминалами, программа, считывающая данные из PTS, получает строку ввода только тогда, когда символ PTL записывается в PTM. Когда буферная емкость исчерпана, дальнейшие write()вызовы блокируются до тех пор, пока не будут использованы некоторые входные данные.

В ядре Linux системные вызовы open(), связанные с файлами read(), write() stat()и т. Д. Реализованы на уровне виртуальной файловой системы (VFS), который обеспечивает единый интерфейс файловой системы для программ пользовательского пространства. VFS позволяет различным реализациям файловой системы сосуществовать в ядре. Когда пользовательские программы вызывают вышеупомянутые системные вызовы, VFS перенаправляет вызов соответствующей реализации файловой системы.

Устройства PTS ниже /dev/ptsуправляются реализацией devptsфайловой системы, определенной в /fs/devpts/inode.c, в то время как драйвер TTY, обеспечивающий устройство в стиле Unix98, ptmxопределен в drivers/tty/pty.c.

Буферизация между устройствами TTY и дисциплинами линий TTY , такими как псевдотерминалы, обеспечивается структурой буфера, поддерживаемой для каждого устройства tty, определенной вinclude/linux/tty.h

До версии ядра 3.7 буфер был перекидным :

#define TTY_FLIPBUF_SIZE 512

struct tty_flip_buffer {
        struct tq_struct tqueue;
        struct semaphore pty_sem;
        char             *char_buf_ptr;
        unsigned char    *flag_buf_ptr;
        int              count;
        int              buf_num;
        unsigned char    char_buf[2*TTY_FLIPBUF_SIZE];
        char             flag_buf[2*TTY_FLIPBUF_SIZE];
        unsigned char    slop[4];
};

Структура содержит хранилище, разделенное на два буфера одинакового размера. Буферы были пронумерованы 0(первая половина char_buf/flag_buf) и 1(вторая половина). Драйвер сохранил данные в буфере, указанном buf_num. Другой буфер может быть сброшен в дисциплину строки.

Буфер был «перевернут» переключением buf_numмежду 0и 1. Когда buf_numизменено, char_buf_ptrи flag_buf_ptr было установлено в начало буфера, идентифицированного buf_num, и countбыло установлено в 0.

Начиная с версии ядра 3.7, буферы переключения TTY были заменены объектами, выделенными через kmalloc()организованные в кольцах . В обычной ситуации для последовательного порта, управляемого IRQ, на типичных скоростях их поведение почти такое же, как и в случае старого откидного буфера; Два буфера в итоге распределяются, и ядро ​​циклически переключается между ними, как и раньше Однако, когда есть задержки или скорость увеличивается, новая реализация буфера работает лучше, так как буферный пул может немного увеличиться.

Томас Найман
источник
Ответы столь же знающие, как это, чрезвычайно трудно найти.
этало-когомология
-1

Из справочных страниц для любого из трех он объясняет ответ:

   Under  normal circumstances every UNIX program has three streams opened
   for it when it starts up, one for input, one for output,  and  one  for
   printing diagnostic or error messages.  These are typically attached to
   the user's terminal but might instead  refer  to  files  or
   other  devices,  depending  on what the parent process chose to set up.

   The input stream is referred to as "standard input"; the output  stream
   is  referred  to as "standard output"; and the error stream is referred
   to as "standard error".  These terms are abbreviated to form  the  sym-
   bols used to refer to these files, namely stdin, stdout, and stderr.

   Each  of these symbols is a stdio(3) macro of type pointer to FILE, and
   can be used with functions like fprintf(3) or fread(3).

   Since FILEs are a buffering wrapper around UNIX file  descriptors,  the
   same  underlying  files  may  also  be accessed using the raw UNIX file
   interface, that is, the functions like read(2) and lseek(2).

   On program startup, the integer file descriptors  associated  with  the
   streams  stdin,  stdout, and stderr are 0, 1, and 2, respectively.  The
   preprocessor symbols STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are
   defined  with  these values in <unistd.h>.
Jeight
источник
Этот ответ описывает реализацию stdin, stdoutи stderrс точки зрения библиотеки C, но вопрос явно о реализации ядра. Я попытался объяснить точку зрения ядра в своем ответе .
Томас Найман