Приложение B. Обзор исходного текста ядра Linux. В этой главе мы пытаемся по порядку об'яснить исходный текст ядра Linux, пытаясь помочь читателям понять, как структурирован исходный текст, и как описаны соответственные части Linux.Мы преследуем цель близко познакомить начинающего программиста на языке Си с общим устройством Linux. В качестве отправной точки обзора возьмем загрузку системы. Для осознания этого материала требуются хорошее знание языка Си и практически полное представление о концепциях UN*X и архитектуре ПК. В этой главе, однако, вы не встретите текстов на Си, а увидите лишь ссылки на истинный текст. Подробная информация о ядре находится в предыдущих главах, здесь же мы можем увидеть лишь обзорный материал. Любой путь, встречающийся в этой главе, относится к основному кодовому дереву каталогов, обычно это /usr/src/linux. ****** * Большинство информации, представленной в главе, взята из Linux 1.0, однако * здесь встречаются ссылки на более поздние версии. Любой параграф в * этой главе, выделенный так же, как этот, означает описание изменений * в ядре по отношению к Linux 1.0. Если в параграфе нет подобных ссылок, * это означает, что исходный текст не претерпел изменения в версиях 1.0.9 - 1.1.76. * Иногда выделенные места появляются в тексте в качестве ссылок на исходный текст. ****** B.1. Загрузка системы. Во время загрузки ПК процессор 80x86 запускается в режиме реального времени и запускает код ROM-BIOS по адресу 0xFFFF0. PS BIOS проводит тестирование системы и инициализирует вектор прерывания на 0-й физический адрес. После этого она загружает сектор загрузочного устройства по адресу 0x7C00 и обращается по этому адресу. Это устройство обычно представляет собой жесткий диск или накопитель в дисководе. Данная выкладка сильно упрощена, однако она дает представление о инициализации ядра. Основная (первая) часть ядра Linux была написана на ассемблере 8086 (boot/bootsect.s). Во время запуска она помещает себя по абсолютному адресу 0x90000, считывая следующие 2Кб кода с загрузочного устройства по адресу 0x90200 и часть ядра по адресу 0x10000. Во время загрузки системы появляется сообщение "Loading...". Далее контроль передается коду в boot/Setup.S (другой исходник режима реального времени на ассемблере). Установленная часть определяет остальные компоненты системы и тип карты vga. Если нужно, она может дать право выбора видеорежима. Затем она переносит всю систему с адреса 0x10000 по адресу 0x1000, включает защищенный режим и обращается к остальной части системы (по адресу 0x1000). Следующим шагом является распаковка ядра. Код по адресу 0x1000 берется из zBoot/head.S, которая устанавливает регистры и вызывает decompress_kernel(), которая создает zBoot/inflate.c, zBoot/unzip.c и zBoot/misc.c. Распакованная информация помещается по адресу 0x1000000 (1Мб), поэтому Linux не может быть запущена на компьютере с ОЗУ, меньшим 1Мб. ********* * Сокрытие ядра в файле gzip делается Makefile и утилитами в каталоге zBoot. * Среди них есть занимательные программы. * * Ядро версии 1.1.75 помещает каталоги boot и zBoot в arch/i386/boot. Это * изменение позволяет ядру подстраиваться под разные архитектуры. ********* Распакованный код запускается по адресу 0x1010000, где делаются все 32- битные установки: загружаются IDT, GDF и LTD, производятся установки процессору и сопроцессору, устанавливаются страницы и вызывается подпрограмма start_kernel. Исходные тексты предыдущих операций находятся в boot/head.S. Это наиболее изощренный код во всем ядре. Помните, что в случае возникновения ошибки во время выполнения предыдущих шагов компьютер остановит работу. ОС не может справляться с ошибками, если она не полностью установлена. start_kernel помещается в init/main и никогда не прекращает работу. Единственное, что до этого момента написано на Си - это управление прерываниями и системный вызов enter/leave (однако и здесь большинство макросов нвписано на ассемблере). В.2 После обработки самых тонких вопросов, start_kernel инициализирует все по отдельности части ядра. - Устанавливает границы памяти и вызывает paging_init(). - Устанавливает каналы IRQ и планирование. - Грамматически разбирает командную строку. - Если надо, распределяет памть под буффер. - Устанавливает драйвера устройств и буферизацию диска, также как и други неосновные компоненты. - Определяет циклическую задержку. - Проверяет работает-ли прерывание 16 с сопроцессором. После этого ядро готово к move_to_user_mode() (перемещение в пользовательский режим. Затем 0-й процесс, так называемая идеальная задача, продолжает функционировать в бесконечном цикле. Процесс init петаетса запустить /exec/init, или /bin/init, или /sbin/init. Если ни один из перечисленных методов не запускается, система запускает "/bin/sh /etc/rc" и ведает основную оболочку на первый терминал. Эта процедура была написана в Linux 0.01, когда ОС состояла из одного ядра и не поддерживала операцию login. После запуска функцией exec() программы init с одной из стандартных позиций (предположим что мы находимся в одной из них), ядро не контролирует процесс работы программы. Его ролью в этот момент становится поддерживать процессы с помощью системных вызовов и обслуживать асинхронные события, такие как прерывания аппаратного обеспечения. Многозадачность также устанавливается до этого, поэтому управлением доступа задач с помощью fork() и login занимается программа init. Данный обзор рассмотрит обслуживание ядром асинхронные события, также подробно как размещение информации и организацию кода. В.3 Как ядро рассматривает процесс. С точки зрения ядра процесс есть ни что иное, как запись в таблице процессов. Таблица процессов - одна из важнейших структур данных внутри системы совместно с таблицей распределения памяти и механизмом кэширования буфера. Особое место в таблице процессов занимает довольно об'емная структура task_structure, определенная в include/linux/sched.h. Внутри структуры task_struct содержится информация как высокого, так и низкого уровня - от некоторых регистров аппаратного обеспечения до inode работающей директории процесса. Таблица процессов является одновременно массивом и двусвязным списком в виде дерева. Физическое описание представляет собой статический массив указателей, длина которого NR_TASKS, является константой, определенной в include/linux/tasks.h, так что размер структуры может быть переопределен лишь на определенной зарезервированной странице. Структура списка определена двумя указателями next_task и prev_task, а структура дерева общеизвестна и мы не будем на ней здесь останавливаться. Вы можете изменить величину NR_TASKS со 128, как установлено по умолчанию, однако вам придется перекомпилировать все исходные файлы измененные при этом. После загрузки ядро работает от имени какого-либо процесса, используя глобальную переменную current и указатель на структуру task_struct для запуска прцессов. current изменятся только планировщиком в kernel/sched.c. Когда надо закрыть все процессы, используется макрос for_each_task. Это намного быстрее нежели послеовательное считывание массива, когда система загружена неполностью. Процесс работает одновременно и в режиме ядра, и в режиме пользователя. Оновная часть процесса запускается в пользовательском режиме, системные вызовы запускаются в режиме ядра. Стеки, используемые работающими в разных режимах процессами, различны - определенный сегментный стек используется в пользовательском режиме, а режим ядра использует стек определенной величины. Стековая страница ядра никогда не своппится, так как она должна быть доступна в любое время работы системы. Системные вызовы внутри ядра существуют как функции на языке Си и их имена начинаются с префикса "sys_". Системный вызов burnout, к примеру, содержится в ядре в качествефункции Sys_burnout(). ********* * Механизм обработки системных вызовов описан в главе 3 этой книги. * Просмотр for_each_task и SET_LINKS в include/linux/shed.h может помочь * вам в понимании структуры списка и дерева в таблице процессов. ********* B.4. Создание и удаление процесса. Система unix создает процесс с помощью системного вызова fork(), удаление процесса может осуществляться с помощью exit() или с помощью передачи ядру сигнала. Описание этих функций в Linux расположено в kernel/fork.c и в kernel/exit.c. Разветвление процессов устроено довольно просто, так как файл fork.c небольшой и хорошо читаемый. его главная задача - заполнение структуры данных нового процесса. Здесь представлены основные шаги процесса заполнения, исключая заполнение полей: - Получение пустой страницы для помещения туда task_struct; - Нахождение пустого слота для процесса (find_empty_process()); - Получение другой пустой страницы для kernel_stack_page; - Копирование LDT родителя наследнику; - Засылка копии информации из mmap родителю; sys_fork() управляет дескрипторами и inode файлов. ************ * В ядре версии 1.0 предлагается весьма несовершенная поддержка наследования * и системный вызов fork() хорошо демонстрирует это. ************ Выход из программы осуществляется в системе изощренным методом, так как каждый родительский процесс получает информацию ото всех своих существующих наследников. Кроме того, процесс может завершиться при kill() (уничтожении) другого процесса (позаимствовано из UN*X). Файл exit.c содержит sys_kill(), различные версии sys_wait() и sys_exit(). Текст exit.c не описывается здесь - он неинтересен. Он оперирует большим количеством инструментов выхода из системы в рабочем состоянии. Стандарт POSIX управляет сигналами. B.5. Запуск программы. После разделения (fork()) запускаются две одинаковые программы. Одна из них обычно запускает (exec()) другую. Системный вызов exec() должен создать двоичный образ запускаемого файла, загрузить и запустить его. Слово "загрузить" не обязательно означает "запись в память двоичный образ", так как Linux поддерживает загрузку по требуемым частям. Описание вызова exec() в Linux поддерживает разные форматы двоичного кода. Это содержится в структуре linux_binfmt, которая устанавливает два указателя на функции: один - на функцию запускаемого кода, второй - на загрузку библиотеки, каждый двоичный формат может представлять и запускаемые файлы, и библиотеки. Загрузка нужных библиотек описана в том же исходном файле, что и exec(), но они позволяют подключать себя функции exec(). Системы UN*X позволяют программисту работать с шестью различными версиями функции exec(). Одна из них может быть описана, как библиотечная функция. Также ядро Linux подключает отдельно функцию execve(). Она исполняет достаточно простую задачу - чтение заголовка запускаемого файла и попытку запустить его. Если первые два байта "#!", делается грамматический разбор и включается интерпретатор, иначе последовательно применяются другие двоичные форматы. Родной формат Linux поддерживается прямо внутри fs/exec.c вместе с соответствующими функциями load_aout_binary и load_aout_library. Что касается двоичных кодов, функция, загружаемая как запускаемый файл a.out, заканчивает работу после mmap() дискового файла, или после вызова read_exec(). Формальный метод, используемый Linux, требует загружаемый механизм для обнаружения ошибок в программных страницах, когда к ним открывается доступ, тогда как более новый метод используется, когда в основной файловой системе не подчеркивается распределение памяти (к примеру, файловая система "msdos"). ************ * После версии 1.1 в ядра включались переделанные файловые системы msdos, * поддерживающие mmap(). Более того, структура linux_binfmt как список * для поддержки новых двоичных форматов в качестве модулей ядра. В итоге * структура была расширена для доступа к подпрограммам конвертации форматов. ************ B.6. Доступные файловые системы. Всем известно, что файловая система - основной ресурс системы UN*X, настолько основной и общей, что она должна иметь удобное сокращение имени. Далее в тексте будем называть файловую систему "фс". Я предполагаю, что читатель уже знаком с с основными концепциями фс UNIX - разрешение доступа, inode, superblock, mounting и umounting. Эти концепции об'яснены в других книгах по UNIX, так что я не буду повторяться, а остановлюсь на особых компонентах Linux. Первые UNIX-системы поддерживали одну файловую систему, структуру, которая была занесена прямо в ядро. На данный момент используется нестандартный интерфейс для создания коммуникации между ядром и файловой системой в порядке непринужденного обмена информацией между архитектурами. Сам Linux поддерживает стандартный метод обмена информацией между ядром и каждым модулем. Этот метод называется VFS. Текст файловой системы тем самым разбивается на две части: верхняя часть, связанная с распределением таблиц ядра и структур данных, и нижняя часть, созданная для установки функций, зависящих от фс и вызываемых через структуры данных VFS. Весь материал, не зависящий от фс, хранится в файлах fs/*.c. Они выполняют следующие операции: - Управление кешированием буфера; - Ответ на системные вызовы fcntl() и ioctl() (fcntl.c и ioctl.c); - Распределение pipe и fifo на inode и буферах (fifo.c и pipe.c); - Управление файловыми и inode таблицами (file_table.c и inode.c); - Закрытие и открытие файлов и записей (locks.c); - Распределение имен в inode (namei.c, open.c); - Описание функции select() (select.c); - Информационная база (stat.c); - mounting и umounting фс (super.c); - Запуск (exec()) запускаемых файлов и загрузка библиотек (exec.c); - Загрузка различных двоичных форматов (bin_fmt*.c, как описано выше). Интерфейс VFS содержит набор операций высокого уровня, запускаемых независимым от фс кодом, и представляется нужном формате для фс. Наиболее важными структурами являются file_operations и inode_operations, однако, они далеко не единственны. Все они описаны в include/linux/fs.h. Отправной точкой в ядре в обращении к файловой системе является структура file_system_type. Массив file_system_types помещен в fs/filesystems.c, и во время запуска mount на него происходит ссылка. Затем функция read_super для соответствующего типа фс заполняет элемент структуры struct super_block,который, в свою очередь, заносит информацию в struct super_operations и в struct type_sb_info. Создатель устанавливает указатели на главные операции для данного типа фс, последняя также указывает специальную информацию для типа файловой системы. ********** * Массив типов файловой системы помещен в скомпанованный список для организации * новых типов фс как модулей ядра. Функция, делающая это - (un-)register_filesystem, * описана в fs/super.c. ********** B.7. Краткий обзор сущности типа файловой системы. Роль типа файловой системы состоит в выполнении низкоуровневых задач, используемых для распределения высокоуровневых операций VFS на физических устройствах. Интерфейс VFS достаточно универсален для поддержки обеих встроенных фс UN*X и более экзотичных, таких, как msdos и umsdos. Каждая фс создается из следующих компонентов, принадлежащих ее собственным каталогам: - Запись в массиве file_streams[] (fs/filesystems.c); - Суперблочный файл include (include/linux/type_fs_sb.h); - inode include файл (include/linux/type_fs_i.h); - Основной include файл (include/linux/type_fs.h); - Две строки #include внутри include/linux/fs.h, также как и запись в структуры super_block и inode. Собственная директория типа фс содержит исходные тексты, обладающие inode и осуществляющие управление обработкой информации. ********* * Глава про фс proc в этой книге содержит все подробности о низкоуровневом коде * и интерфейсе VFS для этого типа фс. Исходный текст в fs/procfs достаточно * доступен после прочтения этой главы. ********* Разберем внутреннее устройство механизма VFS и фс minix в качестве примера. Я выбрал в качестве примера minix, так как она небольшая, но полная, кроме того, все фс linux берут начало от minix. Читателю предлагается разобрать в качестве упражнения тип ext2, встречающийся в инсталляции Linux. Во время поддержки системой фс minix minix_read_super заполняет структуру super_block информацией, полученной с поддерживаемого устройства. Поле s_op структуры содержит указатель на minix_sops, используемый основным кодом фс для быстрого выполнения операции суперблока. Соединение новой поддерживаемой фс с системой основывается на изменении следующих компонент (помещение sb в super_block и dir_i в место обращения): - sb->s_mounted указывает на inode корневого каталога поддерживаемой фс (MINIX_ROOT_INO); - dir_i->i_mount, содержащий sb->s_mounted; - sb->s_covered, содержащий dir_i; Umount происходит с помощью do_umount, включающим запуск minix_put_super. Когда разрешен доступ к файлу, minix_read_inode заполняет общую системную inode структуру полями из minix_inode. Поле inode->i_op заполняется, исходя из inode->i_mode и отвечает за все будующие операции над файлом. Описание исходных текстов функций minix вы можете найти в fs/minix/inode.c. Структура inode_operations используется для засылки inode операций в специальные функции ядра; первая запись в структуре - указатель на file_operations, которая информационно эквивалентна i_op. Фс minix позволяет выбрать три образца наборов inode - операций (для каталогов, для файлов и для скомпанованных символов) и два образца установки file_operations. Операции над каталогами (одна minix_readdir) могут быть найдены в fs/minix/dir.c, операции над файлами - в fs/minix/file.c, и операции над скомпанованными символами - в fs/minix/symlink.c. Остальная часть каталога minix предназначена для следующих задач: - bitmap.c управляет распределением и очисткой inode и блоков (ext2 имеет два разных файла). - fsynk.c ответственнен за системные вызовы fsync() - управление прямыми, непрямыми и сдвоенными непрямыми блоками. (Я надеюсь, вы имеете о них представление из UN*X). - namei.c включает в себя inode-операции, связанные с именами, такие как создание и удаление node, переименование, компановка. - truncate.c выполняет усечение файлов. B.8. Пультовый драйвер. Будучи драйвером ввода/вывода в большинстве компонентов Linux, пультовый драйвер заслуживает внимания. Исходный текст имеет такое же отношение к управлению, как и любой другой символьный драйвер, находящийся в /drivers/chart, и мы будем мы будем использовать эту директорию при ссылке на имена файлов. Инициализация управления происходит с помощью функции tty_init() в tty_io.c. Эта функция предназначена для получения основных номеров устройств и вызова инициализации каждого установленного устройства. con_init() - одна из функций, относящихся к управляющему драйверу, инициализирующая его, находится в console.c. ********** * Инициализация управляющего устройства сильно изменилась после выпуска версии * 1.1, была убрана из tty_init() и вызывается прямо из../../main.c. Виртуальные * пульты на данный момент динамически распределяемы, и в них изменена большая * часть исходного текста. ********** B.8.1. Как файловые операции посылаются пульту. Этот параграф довольно низкого уровня, и может быть исключена из прочитываемого. Доступ к устройству UN*X осуществляется через файловую систему. Этот параграф описывает все шаги файла устройства к функциям пульта. Кроме того, эта информация взята из исходных текстов версии 1.1.73, и может отличаться от исходников 1.0. Когда открывается inode устройства, запускается функция chrdev_open() ( или blkdev_open(), мы будем рассматривать символьные устройства). Эта функция полна компонентами структуры def_chr_fops, на которую ссылаются chrdev_inode_operations, используемой всеми типами фс. chrdev_open заботится о спецификации операций над устройством,помещая собственную таблицу file_operations в текущий flip и вызывая специфицированную open(). Специфицированные таблицы устройства содержатся в массиве chrdevs[], индексированном по основным номерам устройств и заполняемом тем же ../../fs/devices.c. Если мы рассматриваем tty устройство (нужен ли нам в таком случае пульт ?), мы переходим к драйверам tty, чьи функции находятся в tty_io.c, индексированные tty_fops. tty_open() вызывает init_dev(), которая выделяет любую структуру данных, нужную устройству, базируемую на подномере устройства. Подномер также используется для поиска фактического драйвера для устройства, который был зарегистрирован через tty_register_driver(). Драйвер в таком случае представляет собой иную структуру, используемую для определения подмодулей, таких, как file_ops; он напямую связан с записью и контролем над устройством. Последняя структура, используемая в управлении tty, это линейная дисциплина, описываемая позже. Линейная дисциплина для пульта (или любого другого устройства tty) устанавливается с помощью функции initialize_tty_struct(), запускаемой init_dev. Все, что мы рассматривали в этом параграфе, не зависит от самих драйверов. Только специальная пультовая часть, расположенная в console.c, регистрирует свой собственный драйвер во время работы con_init(). В целом, линейная дисциплина также не зависит от устройства. ************* * Структура tty_driver полностью определена внутри. * Предыдущая информация была взята из исходного текста версии 1.1.73. Он не * похож на используемое вами ядро. ************* B.8.2. Передача информации пульту. Когда происходит запись в пультовое устройство, вызывается функция con_write(). Эта функция управляет всеми контрольными символами и esc-последовательностями, используемыми для поддержки приложений, связанных со всем управлением экрана. Эти esc-последовательности определены в коммуникационной подпрограмме vt102. Это означает, что вы должны установить TERM=vt102, когда вы хотите передать информацию не Linux-овскому host адаптеру, однако для локальных целей лучше устанавливать TEMP=console, так как пульт Linux позволяет оптимально установить vt102. con_write() на большую часть состоит из вложенных установок переключателей, используемых для посимвольной интерпритации esc-последовательностей. В нормальном режиме, символ выводитсяя на экран, будучи записанным в видео память, используя определенные атрибуты. Внутри console.c, все поля структуры struct vc становятся доступными лишь через макросы, так что любая ссылка на attr (к примеру), на самом деле приходится на поле в структуре vc_cons[currcons], до тех пор пока система не перестает ссылаться на номер данного пульта. ************ * В новых ядрах, vc_cons представляет собой массив указателей, содержание * которых распределено в памяти kmalloc(). Использование макросов * сильно упростило изменения, так как в переписывании нуждалась лишь * небольшая часть кода. ************ Непосредственное распределение памяти пульта на экран осуществляется функциями set_scrmem() (копирование информации из буфера пульта в видео память% и get_scrmem() (копирование информации обратно в буфер). Личный буфер конкретного пульта физически расположен прямо в видео RAM, для уменьшения количества передач информации. Это означает, что функции get- и set- stream() являются static(статическими) для console.c и вызываются только во время переключения пульта. B.8.3 Чтение из пульта. Чтение из пульта устроено через линейную дисциплину. По умолчанию Linux пользуется линейной дисциплиной tty_ldisc_N_TTY. Линейная дисциплина - это метод разбора компонентов передаваемых в строке. Она является еще одной таблицей функций, которая работает при чтении устройства. С помощью флагов termios, линейная дисциплина контролирует ввод из tty в режимах raw,cbreak и cooked, а также работу функциий select(), ioctl() и подобных. Функция чтения в линейной дисциплине называется read_chan(). Она осуществляет чтение из буфера tty в зависимости от того, что он представляет. Причина по которым символы передаются через tty обусловлена асинхронными прерываниями аппаратного обеспечения. ************** * Линейная дисциплина N_TTY находится в том-же tty_io.c, более * поздние ядра также подключают исходник n_tty.c ************** Самым низкоуровневым передатчиком информации пульту, является менеджер клавиатуры, описанный в keyboard.c, в функции keyboard_interrupt(). B.8.4 Управление клавиатурой. Управление клавиатурой реализовано крайне необдуманно. В keyboard.c определены все десятичные значения различных кодов клавиатуры различных производителей. В keyboard.c истинный хакер не найдет для себя никакой полезной информации. ************** * Читателям действительно интересующихся образом клавиатуры * в Linux, советую просматривать файл keyboard.c с конца, так как * подробности управления на низком уровне могут находится лишь в * первой половине файла. ************** B.8.5 Переключение пультов. Текущий пульт переключается через запуск функции change_console(), которая переопределяет размер tty_io.c, осуществляемый либо keyboard.c, либо vt.c (Пользователь переключает их нажатием клавиши, программа вызовом ioctl()) Переключение происходит в два этапа, и функция complete_change_console() отвечает за второй. Разрыв переключения обусловлен окончанием работы задачи, с предварительным сообщением об этом процессу контролируемому покилаемую нами tty. Если пульт не подчиняется контролирующему процессу, функция change_console() вызывает complete_change_console() самостоятельно. Для смены графического пульта на текстовый нужен процесс конверции, при этом отдельный сервер может продолжать работать с графическим пультом. B.8.6 Механизм выбора пульта. "selection" - это устройство службы вырезания и копирования для текстовых пультов. Этот механизм поддерживается процессом пользовательского уровня который может быть запущен selection или gpm. Программа пользовательского уровня использует ioctl() в работе с пультом, для сообщения ядру точного места подсветки текста на экране. Затем выбранный текст помещается в буфер. Этот буфер статически определен в console.c. Копирование текста связывается с обычным помещением символов из буфера в входную очередь tty. Весь механизм выбора защищен #ifdef, так что пользователь может запретить его во время сохранения конфигурации ядра в несколько килобайт памяти. Выбор является низко уровневым методом, поэтому он его деятельность не доступна другим процессам. Это означает, что большинство прстейших операций #ifdef удаляющих выделение текста в любом случае изменяется. *********** * В версиях Linux код прцесса выбора не улучшился со времени создания. * Единственное изменение произошло после внедрения динамического * буфера вместо статического, делающее ядро меньше на 4 Кб. *********** B.8.7 Контроль над вводом-выводом устройства (ioctl()). Системный вызов ioctl(), является отправной точкой пользовательских процессов, контролирующих поведение файла устройства. Управление передачи контроля находится в ../../fs/ioctl.c, где расположен sys_ioctl(). Стандартные запросы на передачу контроля удовлетворяются прямо здесь, иные запросы, связанные с файлами довлетворяются с помощью file_ioctl() (находится в том-же исходнике), до тех пор пока следующий запрос не обратится к особой функции ioctl() устройства. Информация о контроле над пультовыми устройствами находится в vt.c, так как пультовое устройство удовлетворяет ioctl - запросы функцией vt_ioctl(). ********** * Вышеописанная информация взята из версии 1.1.7x. Ядро 1.0 не * имела таблицы драйверов, и vt_ioctl() находился прямо в * таблице file_operations(). ********** В серии 1.1.7x обозначены следующие вещи: tty_ioctl.c описывает только запросы на линейные дисциплины (за исключением функции n_tty_ioctl(), являющейся единственной функцией n_tty вне n_tty.c), в то время как поле file_operations указывает на tty_ioctl() в tty_io.c. Если номер запроса не не определяется в tty_ioctl(), он передается в tty->driver.ioctl или в случае провала в tty->ldisc.ioctl. Материал по линейным дисциплинам находится в tty_ioctl.c, в то время как информация о пультовых драйверах в vt.c. В Ядре 1.0, tty_ioctl() находится в tty_ioctl.c и указывает на него общая file_operations. Нераспознанные запросы проходят через определенный контроль или через код линейной дисциплины похожий на версию 1.1.7x. Помните что в обоих случаях запрос TIOCLINUX не зависит от устройства. Это говорит о том, что выбор пульта может быть установлен ioctlом любого tty. Вы можете встреть множество разнообразных устройств, относящихся к пультовому устройству, и лучший способ познать их - изучить исходный текст vt.c.