Ядро ОС Linux

         

Информационные переменные.


host - индекс массива scsi_hosts.

target - cодержит ID цели команды SCSI. Эта информация важна в случае поддержки целью многозадачности.

cmnd - массив байт, содержащий текущую команду SCSI. Эти байты посылаются цели посте строки COMMAND. cmnd[0] - код команды SCSI. Макро COMMAND_SIZE, определенный в scsi.h используется для определения длины команды.

result - код результата запроса SCSI. Cм. 2.7.7.2.5 для более подробной информации об этой переменной. Она должна быть верно установлена до возврата низкоуровневых подпрограмм.



Name


name содержит указатель на краткое описание host адаптера SCSI.



Can_queue


can_queue содержит число невыполненных команд, которые может выполнить главный адаптер. В случае, если ваш драйвер поддерживает слово RESELECTION и использует прерывания, этой переменной присваивается значение 1.



Список Разветвления - компановки. (Scatter-gather)


use_sg содержит количество кусков обрабатываемых scatter-gather. Если use_sg = 0, тогда request_buffer указывает на буфер данных команды SCSI, и размер буфера содержится в request_bufferlen. В ином случае request_buffer указывает на массив структур scatterlist и use_sg идентифицирует количество структур в массиве. Использование request_buffer довольно тяжело.

Каждый элемент массива scatterlist содержит компоненты address и length. Если флаг unchecked_isa_dma в структуре scsi_Host установлен в 1, адрес гарантированно попадает в область первых 16Мб физической памяти. Одной SCSI командой можно в таком случае передать большое количество информации, при этом длина большого куска равна сумме длин всех малых.

typedef struct scsi_cmnd { int host; unsigned char target; lun; index; struct scsi_cmnd *next, *prev; unsigned char cmnd[10]; unsigned request_bufflen; void *request_buffer; unsigned char data_cmnd[10]; unsigned short use_sg; unsigned short sglist_len; unsigned bufflen; void *buffer; struct request request; unsigned char sense_buffer[16]; int retries; int allowed; int timeout_per_command, timeout-total, timeout; unsigned char internal_timeout; unsigned flags; void (*scsi_done)(struct scsi_cmnd *); void (*done)(struct scsi_cmnd *); Scsi_Pointer Scp; unsigned char *host_schribble; int result; } Scsi_Cmnd; Рис. 1.6: Структура Scsi_Cmnd.



This_id


Большинство главных адаптеров имеют особые, приписанные им SCSI ID. Эти SCSI ID, обычно равные 6 или 7, используются для реализации RESELECTION. this_id содержит SCSI ID адаптера. Если адаптеру не соответствует ID, этой переменной присваивается значение -1 (RESELECTION в таком случае не поддерживается).



Sg_tablesize




Высокоуровневый код поддерживает метод "scatter-gather" (компановка - раз'единение) повышения эффективности обмена информацией с помощью комбинирования многих маленьких запросов в несколько больших. Так как большинство накопителей SCSI форматированы с прослойкой 1:1, что означает, что все сектора на одной дорожке располагаются последовательно, время, требуемое для выполнения слов ARBITRATION и SELECTION, не превышает времени чередования секторов.Так что за один оборот диска может сработать лишь один процесс, что приводит к скорости передачи 50Кб в секунду, в то время, как метод "scatter-gather" дает скорость около 500Кб в секунду.

sg_tablesize содержит максимально возможное число запросов в списке метода компановки-раз'единения. Если драйвер не поддерживает метод "scatter-gather", этой переменной присваивается значение SG_NONE. Если драйвер поддерживает неограниченное число групповых запросов, эта переменная принимает значение SG_ALL. В некоторых драйверах это число ограничивается предельным значением sg_tablesize, поддерживаемым адаптером. Некоторые адаптеры Adaptec требуют значение не более 16.



Cmd_per_lun


SCSI стандарт поддерживает понятие "компановка команд". Компановка команд позволяет нескольким командам выстраиваться в порядке очередности к подаче на одно устройство. Эта переменная равна 1 в случае поддержки компановки команд. Однако на данный момент высокоуровневый код SCSI не использует преимуществ, предоставляемых этой возможностью.

Скомпанованные команды имеют фундаментальные отличия от команд одиночных (что описывается в переменной can_queue). Скомпанованные команды всегда предназначаются одной и той же цели и не обязательно используют слово RESELECTION. Также компанованные команды исключают слова ARBITRATION, SELECTION и MESSAGE OUT после прохождения первой установленной в списке. В то же время одиночные команды могут посылаться на контролируемую цель и требуют слова ARBITRATION, SELECTION, MESSAGE OUT и RESELECTION.



Present


Бит present устанавливается в случае обнаружения устройства.



Unchecked_isa_dma


Некоторые host - адаптеры используют доступ к указанной памяти (Direct Memory Acess(DMA)) для чтения и записи блочной информации прямо в основаную память компьютера. Linux - система виртуальной памяти,имеющая возможность использовать более 16Мб физической памяти. На машинах с шиной ISA DMA ограничен шестнадцатью Мб физической памяти.

Если установлен бит unchecked_isa_dma, высокоуровневый код будет поддерживать информационный буфер адресацией ниже 16Мб физической памяти. Драйверы, не используюшие DMA, устанавливают бит в 0. Драйверы, работающие с шиной EISA, всегда устанавливают этот бит также в 0, так как машины с EISA не позволяют доступа к DMA.



Файл blk.h


Вначале текста вашего дpайвеpа после описания.h файлов вы должны написать две стpоки:

#define MAJOR_NR DEVICE MAJOR #include

где DEVICE_MAJOR - основной номеp вашего устpойства.drivres/block/blk.h тpебует основной номеp для установки дpугих опpеделений и макpосов дpайвеpа.

Тепеpь вам нужно изменить файл blk.h.После #ifdef MAJOR_NR есть часть пpогpаммы в котоpой опpеделены некотоpые основные номеpа, защищенные

#elif (MAJOR_NR = DEVICE_MAJOR).

В конце списка вы запишете раздел для вашего драйвера :

#define DEVICE_NAME "device" #define DEVICE_REQUEST do_dev_request #define DEVICE_ON( device ) /* usully blank, see below */ #define DEVICE_OFF( device ) /* usully blank, see below */ #define DEVICE_NR( device ) (MINOR(device))

DEVICE_NAME - имя устройства.В качестве примера посмотрите предыдущие записи в blk.h.

DEVICE_REQUEST - ваша "strategy routine", которая будет осуществлять ввод/вывод в вашем устройстве.См 2.5.3 для более полного изучения.

DEVICE_ON и DEVICE_OFF - для устройств, которые включаются/выключаются во время работы.

DEVICE_NR(device) - используется для определения номера физического устройства с помощью подномера устройства. В частности, драйвер hd, в то время как второй жесткий диск работает с подномером 64, DEVICE_NR(device) определяется (MINOR(device) >> 6).

Если ваш драйвер управляется прерываниями, также установить

#define DEVICE_INTR do_dev

что автоматически становится переменной и используется даже в blk.h, в основном макросами SET_INTR и CLEAR_INTR.

Также вы можете присовокупить такие определения :

#define DEVICE_TIMEOUT DEV_TIMER #define TIMEOUT_VALUE n,

где n - число тиков часов (в Linux/386 - сотые секунды )для паузы в случае незапуска прерывания. Это делается для того,чтобы драйвер не ждал прерывания, которое может никогда не случиться. Если вы делаете эти установки, они автоматически используются

SET_INTR для установки драйвера в положение ожидания. Конечно, в таком случае ваш драйвер должен будет иметь возможность отмены ожидания.



Функция lseek().


Функция вызывается, когда в специальном файле, представляющем устройство, появляется системный вызов lseek().Это функция перехода текущей позиции на заданное смещение.Ей задается четыре аргумента :

struct inode * inode - Указатель на структуру inode для этого устройства. struct file * file - Указатель на файловую структуру для данного устройства. off_t offset - Смещение от ! origin !. int origin 0 = смещение от начала. 1 = смещение от текущей позиции. 2 = смещение от конца.

lseek() возвращает -errno в случае ошибки или положительное смещение после выполнения.

Если lseek() отсутствует, ядро автоматически изменяет элемент file -> f_pos.При origin = 2 в случае file -> f_inode = NULL ему присваивается значение -EINVAL,иначе file -> fpos принимает значение file -> f_inode -> i_size + offset.Поэтому в случае возврата ошибки устройства системным вызовом lseek() вы должны использовать функцию lseek для определения этой ошибки.



Переменные в структуре Scsi_Host.


В общем случае переменные в структуре Scsi_Host не используются до вызова функции detect(), так как некоторым переменным может присваиваться значение лишь во время определения (обнаружения) адаптера. Это происходит в случае, если драйвер может управлять несколькими устройствами с похожими свойствами, так что некоторые параметры структуры зависят от обнаруженного адаптера.



Термины SCSI.


"SCSI bus" - протокол обмена информацией с подключенными внешними устройствами SCSI. Одиночный обмен инициатора("initiator") с целью("target") может содержать до 8 слов ("phases"). Эти слова определяются целью (т.е. жестким диском). Текущее слово может быть определено путем просмотра пяти сигналов SCSI bus так, как это показано в таблице 1.1.

Некоторые контроллеры (в частности, недорогой контроллер Seagate) требуют переделки сигналов, переданных SCSI bus, другие автоматически используют эти низкоуровневые сигналы. Каждое из 8 слов будет подробно описано.

-SEL -BSY -MSG -C/D -I/O PHASE HI HI ? ? ? BUS FREE HI LO ? ? ? ARBITRATION I I&T ? ? ? SELECTION T I&T ? ? ? RESELECTION HI LO HI HI HI DATA OUT HI LO HI HI LO DATA IN HI LO HI LO HI COMMAND HI LO HI LO LO STATUS HI LO LO LO HI MESSAGE OUT HI LO LO LO LO MESSAGE IN I = сигнал инициатора; T = сигнал цели; ? = HI или LO Таблица 1.1. Определение слов SCSI Bus.

Слово BUS FREE

Определяет SCSI bus как незанятый.

Слово ARBITRATION

Подается в случае, если устройство SCSI пытается установить контроль над SCSI bus.В этот момент устройство вносит свой SCSI ID в DATA BUS (установки SCSI bus).Например, если ID = 2, устройство задает дате 0x04. В случае попытки обращения нескольких устройств одновременно, над целью устанавливает контроль устройство с наиболее высоким ID.Слово ARBITRATION использовалось также в стандарте SCSI-1.

Слово SELECTION

После установки контроля устройство, ставшее инициатором, заносит в дату протокола передачи SCSI ID цели. Если цель обнаруживается, она определяется, как занятая с помощью строки -BSY. Эта строка остается активной все то время, пока цель соединена с инициатором.

Слово RESELECTION

Протокол SCSI позволяет устройству отключаться от протокола передачи во время работы запроса. Когда устройство готово к продолжению обмена, оно вновь подключается к адаптеру. Слово RESELECTION идентично слову SELECTION, за исключением того, что оно используется отключенной целью для подключения к исходному инициатору. Драйверы, не поддерживающие RESELECTION, не имеют возможности раз'единения с целью SCSI. Однако RESELECTION поддерживается почти всеми драйверами, так что многозадачные многозадачные устройства SCSI выполнять одновременно несколько задач, что уменьшает время обмена при запросах ввода/вывода.

Слово COMMAND


После этого слова отинициатора к цели может передаваться 6-ти, 10-ти и 12-ти байтная команда.
Слова DATA OUT и DATA IN
После этих слов осуществляется непосредственная передача информации между целью и инициатором. В случае DATA OUT, например, информация передается от адаптера к диску. DATA IN в таком случае осуществляет обратную передачу. Если команда SCSI требует передачи информации, слово не используется.
Слово STATUS
Это слово задается после завершения всех команд и дает возможность послать инициатору статусный байт. Существует 9 вариантов статусного байта (таблица 1.2). Заметим, что так как для статусного кода используются биты 1-5, статусный байт перед использованием маскируется 0x3e. Значения важнейших статусных кодов:
GOOD - операция выполнена успешно. CHECK CONDITION - сообщение о случившейся ошибке.Команда REQUEST SENSE может быть использована для получения более подробной информации об ошибке. BUSY - устройство не может выполнить комаду. Это может случиться во время самотестирования или сразу после включения устройства.
Слова MESSAGE OUT и MESSAGE IN
Дополнительная информация передается между инициатором и целью. Этой информацией может быть статус посторонней команды или запрос
Value Status 0x00 GOOD 0x02 CHECK CONDITION 0x04 CONDITION MET 0x08 BUSY 0x10 INTERMEDIATE 0x14 INTERMEDIATE-CONDITION MET 0x18 RESERVATION CONFLICT 0x22 COMMAND TERMINATED 0x28 QUEUE FULL (После наложения маски 0x3e) Таблица 1.2. Статусные коды SCSI.
для смены протокола. Слова MESSAGE OUT и MESSAGE IN могут неоднократно встречаться во время одной передачи.Если во время передачи доступно использование RESELECTION, драйвер должен поддерживать также слова SAVE DATA POINTERS, RESTORE POINTERS и DISCONNECT (сохранение и загрузка указателей, раз'единение). В SCSI-2 не все драйверы сохраняют указатели перед раз'единением.

Усложненный механизм заморозки.


Если механизм sleep_on()/wake_up() в Linux не удовлетворяет вашим требованиям, вы можете усовершенствовать его. В качестве примера тому можете посмотреть серийный драйвер устройства (/kernel/chr_drv/serial.c), функцию block_til_ready(), которая представляет собой несколько измененные add_wait_queue() и schedule()



Запрос IRQ.


После определения detect() должен запросить канал DMA и пириоритет прерывания. Всего существует 16 приоритетов, называемых IRQ - от 0 до 15. Ядро поддерживает два метода установки обработчика IRQ: irqaction() и request_irq().

Функция request_irq() запрашивает два аргумента: номер IRQ и указатель на подпрограмму-обработчика. Часто устанавливаются параметры структуры sigaction с использованием irqaction(). Текст request_irq() показан на рисунке 1.3.

Определение функции irqaction():

int irqaction( unsigned int irq, struct sigaction *new)

где первый параметр, irq, номер запрошенного IRQ, второй, new, структура, определение которой показано на рис. 1.4.

int request_irq( unsigned int irq, void (*handler)( int )) { struct sigaction sa; sa.sa_handler = handler; sa.sa_flags = 0; sa.sa_mask = 0; sa.sa_restorer = NULL; return irqaction( lrq, &sa ); } Рис. 1.3: Функция request-irq().

struct sigaction { __sighandler_t sa_handler; sigset_t sa_mask; int sa_flags; void (*sa_restorer) (void); }; Рис. 1.4: Структура sigaction

sa_handler в этой структуре указывает на подпрограмму обработчика прерываний, определяемую

void fdomain_16x0_intr( int irq )

где irq - номер IRQ, указывающий обработчику на пробуждение.

Переменная sa_mask используется как глобальный флаг подпрограммы irqaction().

Переменная sa_flags может быть установлена либо в 0, либо в SA_INTERRUPT. Если выбран 0, обработчик прерываний запускается при разрешенных посторонних прерываниях и возвращает значение через сигнальные функции обработчика. Эта установка используется для низких IRQ, таких, как таймер и клавиатура.

SA_INTEERUPT используется при больших ("быстрых") IRQ, например, при использовании упраляемых прерываниями драйверов жестких дисков. В последнем случае обработчик вызывается с запрещенными прерываниями.

Переменная sa_restorer в данный момент не задействована и традиционно установлена в NULL.

Функции request_irq() и irqaction() будут возвращать нуль, если IRQ успешно поставлен в соответствие определенному обработчику прерываний. Ненулевые возвращаемые значения могут быть следующими:

EINVAL Запрошенный IRQ больше 15, или обработчику прерываний был подан указатель на NULL. EBUSY Запрошенный IRQ уже занят другим обработчиком прерываний. Эта ситуация не возникает в случае использования panic().

Ядро использует Intel "распределение" для установки IRQ, запрашиваемых функцией irqaction().



Detect()


Единственный аргумент функции detect() - "главный номер"(host number), индекс к переменным Scsi_hosts (массив типа struct Scsi_Host). Функция detect() возвращает ненулевое значение в случае обнаружения адаптера и нулевое в обратном случае.

Определение главного (host) адаптера должно производиться очень аккуратно. Обычно процесс начинается с просмотра области ROM в поисках "описания BIOS" главного адаптера.

В PS/AT и совместимых компьютерах адресное пространство с адреса 0xc0000 по 0xfffff полностью распределено. Видео-BIOS компьютера расположена начиная с адреса 0xc0000, BIOS жесткого диска, если таковой существует, начинается с адреса 0xc8000. Во время загрузки PS/AT - совместимых компьютеров каждый 2-х килобайтный блок с адреса 0xc0000 до 0xf8000 проверяется на 2-х байтовую запись 0x55aa, которая свидетельствует о существовании расширенного BIOS.

Описание BIOS обычно содержит серию из нескольких байт, идентифицирующих BIOS. Future Domain Bios, например, имеет описание:

FUTURE DOMAIN CORP. (C) 1986 - 1990 1800 - V2.07/28/89

Оно начинается с пятого байта от начала блока BIOS.

После обнаружения описания BIOS можно оттестировать функциональные качества адаптера особыми способами. Так как описания BIOS жестко закодированы в ядре, смена BIOS может привести драйвер к сбою. У пользователей адаптера SCSI исключительно в Linux может возникнуть желание отключить BIOS для ускорения начальной загрузки. По этим причинам должен существовать альтернативный метод определения адаптера.

Обычно каждый адаптер имеет несколько адресов ввода/вывода, использующихся для обеспечения связи. Иногда эти адреса жестко определены в драйвере, заставляя пользователей Linux, имеющих подобный адаптер, использовать определенную установку адресов. Другие драйверы сами определяют эти адреса, просматривая все возможные.

Обычно адаптер позволяет использовать 3 - 4 набора, руководствуясь переключателями на карте.

После определения адресов портов ввода/вывода адаптер может сам заявлять о себе. Эти тесты особенны для каждого адаптера, но имеют общие методы определения основного адреса BIOS (который затем может быть сравнен с адресм BIOS, найденным во время поиска определения BIOS)для проверки уникального номера, присущего карте. На машинах с шиной MCA каждому типу карты дается уникальный номер, благодаря которому ни один посторонний производитель не может использовать некоторые адаптеры. Future Domain, например, используют эту технологию на машинах ISA.



Указатель scsi_done().


Указатель должен быть установлен на функцию done() в функции queuecommand(). Других использований этому указатенлю не предусмотрено.



Указатель host_scribble


Код высокого уровня поддерживает пару функций распределения памяти - scsi_malloc() и scsi_free(), которые гарантируют возврат физической памяти из первых 16Мб. Эта память также подходит для использования DMA.

Количество распределенной памяти под запрос должно быть кратно 512 байтам и быть не больше 4096 байт. Общее количество памяти доступной scsi_malloc() определяется арифметической функцией с тремя аргументами, находящиеся в Scsi_Host - переменные sg_tablesize,cmd_per_lun и unchecked_isa_dma.

Указатель host_scribble указывает на область досупной памяти выделенной scsi_malloc(). Драйвер SCSI низкого уровня обладает возможностью управления этим указателем и соответствующей ему памяти, а также возможностью очистки ненужной информации в памяти.



Запрос канала DMA.


Некоторые адаптеры SCSI используют DMA лдя помещения больших информационных блоков в памяти. Так как процессор не управляет передачей информации в блоки DMA, передача осуществляется быстрее передачи, контролируемой процессором и позволяет последнему работать в это время над другой задачей.

Адаптеры используют определенные каналы DMA. Эти каналы определяются функцией detect() и запрашиваются ядром с помощью request_dma(). Эта функция получает номер канала DMA как свой единственный параметр и возвращает нуль, если канал DMA успешно подключен. Другие возможные возвращаемые значения:

EINVAL Запрошенный канал DMA имеет номер больше 7. EBUSY Запрошенный канал DMA уже используется. Этой ситуация может привести к неудовлетворения запроса SCSI. В этом случае также можно использовать panic().



Info()


Функция info() возвращает указатель на статическую область, содержащую описание драйвера низкого уровня. Это описание содержится в переменной-указателе name и выводится во время загрузки.



Структура Scsi_Pointer.


Переменная SCp, структура типа Scsi_Pointer, описана на рисунке ниже. Переменные этой структуры могут быть использованы любыми средствами в драйверах низкого уровня. Как обычно buffer здесь указывает на текущую позицию scatterlist, buffer_residual показывает количество элементов находящихся в scatterlist, ptr - указатель на буффер, а this_residual - число символов для передачи. Некоторые host адаптеры требуют эту информацию, некоторые игнорируют ее.

Второй набор переменных содержит информацию о статусе SCSI, различные указатели и флаги.

typedef struct scsi_pointer { char *ptr; int this_residual; struct scatterlist *buffer; int buffers_residual; volatile int Status; volatile int Message; volatile int have_data_in; volatile int sent_command; volatile int phase; }Scsi_Pointer;



Queuecommand()


Функция queuecommand() осуществляет запуск команды SCSI адаптером, затем завершает работу. По завершению команды вызывается функция done() с указателем на структуру Scsi_Cmnd в качестве параметра. Это позволяет команде SCSI запуститься в режиме прерывания. Перед завершением работы функция queuecommand() должна выполнить следующие операции:

Сохранить указатель на структуру Scsi_Cmnd. Сохранить указатель на функцию done() в качестве поля Scsi_done() в структуре Scsi_Cmnd. См. раздел 2.7.7.2.5 для более подробной информации. Установить специальные переменные в Scsi_Cmnd, требуемые драйвером. Запустить команду SCSI. Для расширенных host-адаптеров это может быть простейшая засылка команды в "mailbox" host-адаптера. Для менее "мудрых" адаптеров используется сначала слово ARBITRATION.

Функция queuecommand() вызывается лишь в случае ненулевой переменной can_queue (см. 2.7.7.1.2). В ином случае для всех запросов используется функция command(). В случае успеха функция queuecommand() возвращает 0. (Высокоуровневый код SCSI игнорирует это возвращаемое значение).



Done()


Функция done() вызывается после завершения команды SCSI. Единственный параметр, этой функции - указатель на структуру Scsi_Cmnd, используемую прежде функцией queuecommand(). Перед вызовом функции done() должна быть правильно установлена переменная result. Она имеет тип 32-битного целого, каждый байт которого имеет свое значение:

Байт 0 - Содержит код SCSI STATUS, как описано в 2.7.2.1.

1 - Содерит SCSI MESSAGE, как описано в 2.7.2.1

2 - Содержит возвращаемый код host адаптера. Этим кодам присваивается значения в scsi.h:

DID_OK Ошибок не обнаружено

DID_NO_CONNECT SCSI SELECTION не может передаться из-за отсутствия устройства по указанному адресу.

DID_BUS_BUSY Ошибка SCSI ARBITRATION

DID_TIME_OUT Произошла приостановка работы процесса по неизвестной причине, возможно во время SELECTION или в ожидании RESELECTION.

DID_BAD_TARGET SCSI ID цели такой-же как ID адаптера

DID_ABORT Высоко-уровневый код вызывает низко-уровневую функцию abort().

DID_PARITY Ошибка SCSI PARITY

DID_ERROR Ошибка, не поддающаяся распознанию (к примеру ошибка самого адаптера)

DID_RESET Высоко-уровневый код вызывает низко-уровневую функцию reset()

DID_BAD_INTR Возникновение непредвиденного прерывания, которым не возожно управлять.

Возврат DID_BUS_BUSY будет пытаться запустить команду еще раз, в то время как DID_NO_CONNECT сбросит команду.

Байт 3 Этот байт предназначен для возвращения кода высокого уровня и устанавливается низким уровнем в 0.

В настоящий момент драйвера низкого уровня не описывают сообщения об ошибках, поэтому легче всего для вас найти их определения в scsi.c вместо того чтобы исследовать существующие драйвера.



Command()


Функция command() запускает команду SCSI и возвращается после ее завершения. Когда был создан оригинал кода SCSI, в нем не осуществлялась поддержка драйверов управляемых прерываниями. Старые драйвера менее эфеективны чем созданные на данный момент драйвера управляемые прерываниями, но более просты в написании. Для новых драйверов эта функция заменена на queuecommand(), как описано в следующей программе:

ststic volatile int internal_done_flag = 0; static volatile int internal_done_errcode = 0; static void internal_done(Scsi_Cmnd *SCpnt); { internal_done_errcode = SCpnt->result; ++internal_done_flag; } int aha1542_command(Scsi_Cmnd *SCpnt) { aha1542_queuecommand (SCpnt, internal_done ); while(!internal_done_flag); internal_done_flag = 0; return internal_done_errcode; }

Возвращаемое значение - то же, что и в переменной result в структуре Scsi_Cmnd. См 2.7.7.2.5 и 2.7.8.



Abort()


Высокоуровневый код SCSI управляет всеми преостановками. Это освобождает драйвер низкого уровня от распределения времени между запросами на периоды исполнения для различных устройств (преостановка работы стримера может быть на много дольше, нежели преостановка жесткого диска).

Функция abort() используется отключения запроса текущей команды SCSI определенной указателем Scsi_Cmnd. После установки переменной result в структуре Scsi_Cmnd функция abort() возвращает нулевое значение.

Если code, второй параметр функции abort(), равен нулю, тогда result устанавливается в DID_ABORT. В ином случае result равн code (обычно это DID_TIM_OUT и DID_RESET).

На данный момент ни один из драйверов низкого уровня не может правильно отключать комманды SCSI. Инициатор должен запрашивать словом MESSEGE OUT цель, для решения этой задачи. Затем инициатор посылает ABORT цели.



Reset()


Функция reset() служит для выгрузки шины SCSI. После выгрузки ни комманда SCSI не будет выполняться, возвращая код DID_RESET.

В настоящий момент ни один из драйверов низкого уровня не может правильно пользоваться этой операцией. Для правильной выгрузки инициатор запрашивает (посылая -ATN) MESSAGE OUT, и подает цели команду BUS DEVICE RESET. Можно также дать команду SCSI RESET, спослав -RST, заставляющую все цели отключиться.

После выгрузки будет полезно удалить также протокол связи.



Slave_attach()


Функция на данный момент не описана. Используется для установки связи между host адаптером и целью. Связь подразумевает обмен парой SYNCHRONOUS DATA TRANSFER REQUEST между целью и инитатором. Обмен возникает при условиях:

Устройство SCSI поддерживающее обмен не соединяется с устройством, после получения сигнала отбоя (RESET). Устройство SCSI также не может быть соединено с другим в случае если оно получило сообщени BUS DEVICE RESET.



Bios_param()


Linux поддерживает систему деления жеского диска MS-DOS. Каждый диск содержит "таблицу частей" в которой определено как диск разбит на логические диски. Обработка информации в таблице требует знания о размере диска в циллиндрах, головках и секторах. Диски SCSI скрывают свои физические параметры и логически представляются списком секторов.

Для получения совместимости с MS-DOS, host адаптер SCSI "лжет" о своих физических параметрах. Так что вместо параметров физических устройство SCSI посавляет "логические параметры".

Linux нуждается в определении "логических параметров" для правильного изменеия таблицы. В сущности метода конвертации логических параметров в физические не существует. Функция bios_param() представляет собой осуществление доступа к параметрам.

Параметр size содержит размер диска в секторах. Некоторые host адаптеры располагают формулой для подсчета логических параметров исходя из этой цифры, иным приходится хранить информацию в таблицах доступных драйверу.

Для обеспечения этого доступа, параметр dev хранит информацию о номере устройства. Два макроса описанные в linux/fs.h осуществляют определение этого значения: MAJOR(dev) - основного номера устройства и MINOR(dev) - определение подномера. Это те-же номера, используемые при выполнении стандартной команды Linux mknod, служащей для создания устройства в каталоге /dev. Параметр info указывает на массив целых, заполняемый функцией bios_param() до возвращения:

info[0] Количество головок

info[1] Количество секторов на циллиндр

info[2] Количество циллиндров

Информация в info является "логиескими парвметрами" устройстваб, используемые методами MS-DOS как физические.



Функции read() и write().


Функции read() и write() осуществляют обмен информацией с устройством, посылая на него строку символов.Если функции read() и write() отсутствуют в структуре file_operatios, определенной в ядре, то в случае символьного устройства одноименные вызовы будут возвращать -EINVAL.В случае блочных устройств функции не определяются, так как VFS будет общаться с устройством через механизм обработки буфера, вызывающий "strategy routine". См. 2.5.2 для более подробного изучения устройства механизма работы с буфером.

Функции read() и write() используют следующие аргументы :

struct inode * inode
- Указатель на структуру inode специального файла устройства, доступного для использования непосредственно пользователем. В частности, вы можете найти подномер файла при помощи конструкции unsigned int minor = MINOR(inode -> i_rdev); Определение макроса MINOR находится в < linux/fs.h >, так же, как и масса других нужных определений. Для получения более подробной информации см. fs.h. Более подробное описание представлено в 2.6. Для определения типа файла может быть использована inode -> i_mode. struct file * file
- Указатель на файловую структуру этого устройства. char * buf
- Буфер символов для чтения и записи. Он расположен в пространстве памяти пользователя, и доступ к нему осуществляется с помощью макросов get_fs*(), put_fs*() и memcpu*fs(), описанных в 2.6. Пространство памяти пользователя не доступно во время прерывания, так что если ваш драйвер управляется прерываниями, вам придется списывать содержание буфера в очередь (queue). int count
- Число символов, записанных или читаемых из buf. count - размер буфера, так что с помощью него легко определить последний символ buf, даже если буфер не заканчивается NULL.



Опознание комплектующих PS.


![Вам следует изучить текст подпрограмм genhel.c и include для понимания их использования.]



Рабочие области.


В зависимости от возможостей и требований host адаптера, список scatter- gather может управляться различными способами. Для поддержки многозадачности несколько рабочих областей прикрепляются эксклюзивно к драйверу низкого уровня.



Функция readdir().


Еще один элемент структуры file_operations, используемый для описания файловых систем так же, как драйверы устройств. Функция не нуждается в предопределении. Ядро возвращает -ENOTDIR в случае вызова readdir() из специального файла устройства.



Функция select().


Функция select() полезна в основном в работе с символьными устройствами. Обычно она используется для многократного чтения без использования последовательного вызова функций. Приложение делает системный вызов select(), задавая ему список дескрипторов файлов, затем ядро сообщает программе, при просмотре какого дескриптора она была активизирована. Также select() иногда используется как таймер. Однако функция select() в драйвере устройства не вызывается непосредственно системным вызовом, так что file_operations select() выполняет небольшое количество примитивных операций. Ее аргументы:

struct inode * inode
- Указатель на структуру inode устройства. struct file * file
- Указатель на файловую структуру устройства.

int sel_type
- Тип совершаемого действия
SEL_IN - чтение
SEL_OUT - запись
SEL_EX - удаление

select_table * wait
- Если wait = NULL, функция select() проверяет, готово ли устройство, и возвращается в случае отсутствия готовности. Если wait не равен NULL, select() замораживает процесс и ждет, пока устройство не будет готово. Функция select_wait() делает то же, что и select() при wait = NULL.



Функция ioctl().


Функция ioctl() осуществляет функцию передачи контроля ввода/вывода. Структура вашей функции должна быть следующей: первичная проверка ошибок, затем переключение, дающее вам право контролировать все ioctl. Номер ioctl находится в аргументе cmd, аргумент контролируемой команды находится в arg. Для работы с ioctl() вы должны иметь подробное представление о контроле над вводом/выводом. Если вы сомневаетесь в правильности использования ioctl(), спросите кого-нибудь, так как эта функция в текущий момент может оказаться ненужной. Так как ioctl() является частью интерфейса драйверов, вам придется уделить ей внимание.

struct inode * inode
- Указатель на inode структуру данного устройства;

struct file * file
- Указатель на файловую структуру устройства; unsigned int cmd
- Команда, над которой осуществляется контроль; unsigned int arg
- Это аргумент для команды, определяется пользователем. В случае, если он вида (void *), он может быть использован как указатель на область пользователя, обычно находящуюся в регистре fs. Возвращаемое значение :
-errno в случае ошибки, все другие значения определяются пользователем.

Если слот ioctl() в file_operations не заполнен, VFS возвращает значение -EINVAL, однако в любом другом случае, кесли cmd принимант одно из значений - FIOCLEX, FIONCLEX,FIONBIO, FIOASYNC, будет происходить следующее:

FIOCLEX 0x5451
Устанавливает бит "закрытие для запуска" FIONCLEX 0x5450
Очищает бит "закрытие для запуска" FIONBIO 0x5421
Если аргумент не равен 0, устанавливает O_NONBLOCK, иначе очищает O_NONBLOCK. FIOASYNC 0x5421
Если аргумент не равен 0, устанавливает O_SYNC, иначе очищает O_SYNC. Пока еще не описано, но для полноты вставлено в ядро.

Помните, что вам надо учитывать эти четыре номера при написании своих ioctl(), так как они могут быть несовместимы между собой, откуда в программе может возникнуть тяжело обнаруживаемая ошибка.



Функция mmap().


struct inode *inode
- Указатель на inode

struct file *file
- Указатель на файловую структуру

unsigned long addr
- Начальный адрес блока, используемого mmap()

size_t len - Общая длина блока.

int prot - Принимает значения:
PROT_READ читаемый кусок
PROT_WRITE перезаписываемый кусок
PROT_EXEC кусок, доступный для запуска
PROT_NONE недоступный кусок

unsigned long off
- Внутрифайловое смещение, от которого производится перестановка. Этот адрес будет переставлен на адрес addr.

[В описании распределения памяти описано, как функции интерфейса Менеджера виртуальной памяти могут быть использованы mmap().]



Функции open() и release().


struct inode *inode
- Указатель на inode

struct file *file
- Указатель на файловую структуру

Функция вызывается после открытия специальных файлов устройств. Она является механизмом слежения за последовательностью выполняемых действий. Если устройством пользуется лишь один процесс, функция open() закроет устройство любым доступным в данный момент способом, обычно устанавливая нужный бит в положение "занято". Если процесс уже использует устройство (бит уже установлен), open() возвращает -EBUSY.

Если же устройство необходимо нескольким процессам, эта функция обладает возможностью любой очередности.

Если устройство не существует, open() вернет -ENODEV.

Функция release() вызывается лишь тогда, когда процесс закрывает последний файловый дескриптор. release() может переустанавливать бит "занято". После вызова release(), вы можете очистить куски выделенной kmalloc() памятью под очереди процессов.



Функция init().


Эта функция не входит в file_operations но вам придется использовать ее, так как именно она регистрирует file_operations с содержащейся там VFS - - без нее запросы на драйвер будут находится в беспорядочном состоянии. Эта функция запускается во время загрузки и самоконфигурирования ядра. init() получает переменную с адресом конца используемой памяти. Затем она обнаруживает все устройства, выделяет память, исходя из их общего числа, сохраняет полезные адреса и возвращает новый адрес конца используемой памяти. Функцию init() вы должны вызывать из определенного места. Для символьных устройств это /kernel/cdr_dev/mem.c. В общем случае функции надо задавать лишь переменную memory_start.

Во время работы функции init(), она регистрирует ваш драйвер с помощью регистрирующих функций. Для символьных устройств это register_chrdev(). register_chrdev использует три аргумента :

int major - основной номер устройства.

srtring name - имя устройства.

адрес #DEVICE#_fops структуры file_operations.

После окончания работы функции, файлы становятся доступными для VFS, и она по надобности переключает устройство с одного вызова на другой.

Функция init() обычно выводит сведения о найденном аппаратном обеспечении и информацию о драйвере.Это делается с использованием функции printk().



A.1. Аннотированная библиография.


Эта аннотированная библиография охватывает книги по теории операционных систем так же, как и по разным видам программирования в среде UNIX. Указанная цена может быть точной, а может и нет, но будет вполне преемлема для правительственной работы. [Если у вас есть книга, которая, по-вашему, подходит для библиографии, напишите ее краткий обзор и пошлите необходимую информацию (заголовок,автор, издательство, ISBN и приблизительная цена) и обзор по адресу johnson@sunsite.unc.edu]. Эта версия постепенно отходит, в то время как появляется настоящая библиография.

Заглавие: The Design of the UNIX Operating System Автор: Maurice J. Bach Издательство: Prentice Hall, 1986 ISBN: 0-13-201799-7 Приближенная цена: $65.00

Это одна из книг, которые Linus использовал при разработке Linux. Это описание структур данных, используемых в ядре System V. Множество имен важных функций в исходных текстах Linux пришли из этой книги, и названы по алгоритмам, представленным в ней. Например, если вы не можете догадаться, что делают функции getblk(), brelse(), bread(), breada() и bwrite(), глава 3 об'яснит это очень хорошо.

В то время как большинство алгоритмов схожи или одинаковы, стоит отметить несколько различий:

Буферный кеш Linux динамически перераспределяется под другой размер, так что алгоритм для действий по получению новых буферов немного другой. Поэтому об'яснение getblk(), приведенное выше, отличается от getblk() в Linux. Linux не использует потоков в текущий момент, и, если потоки реализуются для Linux, желательно, чтобы они имели другую семантику. Семантика и структура вызовов драйверов устройств другая. Концепция сходна, и еще стоит прочитать главу по драйверам устройств, но для подробностей по драйверам лучшая ссылка - The Linux Kernel Hackers' Gude. Алгоритмы распределения памяти немного отличны.

Есть и другие маленькие отличия, но хорошее понимание этой книги поможет вам разобрать исходные тексты Linux.

Заглавие: Advanced Programming in the UNIX Environment Автор: W. Richard Stevens Издательство: Addison Wesley, 1992 ISBN: 0-201-56317-7 Приближенная цена: $50.00


Этот замечательный томик охватывает все тонкости, которые вы действительно должны знать, чтобы писать настоящие UN*X программы. Он включает обсуждение различных стандартов релизации UN*X, включая POSIX, X/Open XPG3и FIPS, и концентрируется на двух реализациях, SVR4 и предварительный выпуск 4.4 BSD, который упоминается в книге, как 4.3 + BSD.

Заглавие: Advanced 80386 Programming Techniques Автор: James L. turley Издательство: Osborne McGraw-Hill, 1988 ISBN: 0-07-881342-5 Прибл. Цена: $22.95

Эта книга достаточно хорошо охватывает 80386, не затрагивая других аппаратных средств. В книгу включены примеры кода. Охвачены все главные возможности, также как и множество основных понятий. Книга включает следующие главы: Основы, Сегментация памяти, Уровни привелегий, Замещение страниц, Многозадачность, Связь между задачами, Обработка сбоев и прерываний, Эмуляция 80286, Эмуляция 8086, Отладка, Математический процессор 80387, Сброс и реальный режим, Аппаратное обеспечение, и несколько приложений включая таблицы управления памятью в качестве справочника.

У автора хороший стиль изложения: если у вас технический склад ума, вы найдете захватывающим чтение этой книги. Сильная сторона этой книги в том что автор не обьясняет ни как делать что либо под DOS, ни как обращаться с конкретной аппаратурой. Фактически единственное место где он упоминает DOS и PC-совместимое аппаратное обеспечение это во введении где он обещает больше не упоминать о них. Заглавие: The C programming Language, second edition. Автор: Brian W. Kernighan and Dennis M. Ritchie Издательство: Prentice Hall, 1988 ISBN: 0-13-110362-8 Прибл. Цена: $35.00

Библия по программированию на Си. Включает учебник по Си, справочник по UN*X интерфейсу, справочник по Си и справочник по стандартным библиотекам. Программируете на Си, купите эту книгу. Это просто. Заглавие: Operating Systems: Design and Implementation Автор: Andrew S. Tanenbaum Издательство: Prentice Hall, 1987 ISBN: 0-13-637406-9 Прибл. Цена: $50.00

В то время как эта книга имеет немного упрощенное описание некоторых тем и опускает некоторые важные моменты, она дает достаточно четкое представление о том что надо сделать чтобы написать операционную систему. Пол книги занимает исходный код клона UN*X называемого Minix, который основывается на микроядре, в отличии от Linux, который славится монолитным дизайном. Было сказано что Minix показывает возможность написания UN*X, основанного на микроядре, но не обьясняется в достаточной степени зачем нужно это делать.

Певоначально предполагалось что Linux будет доступной заменой для Minix фактически он первоначально должен был быть совместимым на уровне двоичного кода с Minix-386. Minix-386 был средой разработки под которой был раскручен Linux. В Linux нет кода Minix, но признаки этого наследия проявляются в таких вещах как файловая система Minix в Linux.

На ранних порах существования Linux, Эндрю Таненбаум начал жаркую войну с Linus по поводу разработки ОС, которая была интересной, если не поучительной.

Однако эта книга может оказаться стоящй для тех кто ищет доступного обьяснения основ ОС, так как в изложении Таненбаума они остаются наиболее понятными (и более занимательными, если вы не хотите скучать).

К сжалению упор делается на основы, в то время как такие вещи как виртуальная память не охвачены вообще. Заглавие: Modern operating systems Автор: Andrew S. Tanenbaum Издательство: Prentice Hall, 1992 ISBN: 0-13-588187-0 Прибл. Цена: $51.75



первая половина книги это перепечатка более ранней Operating systems, но эта книга включает некоторые вещи не раскрытые в ранней, включая такие вещи как виртуальная память. Minix не упоминается, но есть обзор MS-DOS и нескольких других распостраненных систем. Эта книга вероятно более полезна для тех кто зочет углубить свои знания, чем более ранняя книга Таненбаума Operating systems: Design and Implementation. Причину этого можно видеть в заголовке.. Однако что делает DOS в книге по СОВРЕМЕННЫМ операционным системам многие не могут понять. Заглавие: operating Systems Автор: William Stallings Издательство: Macmillan, 1992 (800-548-9939) ISBN: 0-02-415481-4 Прибл. Цена: $??.??

Наиболее полнай текст по операционным системам, эта книга дает более глубокий подход к темам раскрытым в книге Таненбаума, и охватывает больше тем, имеет живой стиль мзложения. Эта книга охватывает все главные темы которые понадобятся вам для написания операционной системы, и делает это очень доступным образом. Автор использует примеры из трех главных систем сравнивая и противопоставляя их: UN*X, OS/2, и MVS. В каждом разделе эти системы используются для разяснения пунктов и приведения примеров реализации.

Темы охваченные в Operating Systems включают Нити (Связи), системы рельного времени, Планировка в Мультипроцессорах, распределенные системы, миграция процессов, и Безопасность, также как и стандартные темы как планировка управление памятью. Раздел по распределенной обработке похоже вполне современен, и я нахожу его очень полезным. Заглавие: UNIX Network programming Автор: W. Richard Stevens Издательство: Prentice Hall, 1990 ISBN: 0-13-949876-1 Прибл. Цена: $48.75

Эта книга охватывает несколько видов работы в сетях под UN*X, и содержит очень полезные справки по формам сетевой обработки которые она непосредственно не охватывает. Она охватывает TCP/IP и XNS особенно полно, и довольно исчерпывающе описывает как работают все вызовы. В ней также есть описание и пример кода использующего TLI System V, и достаточно полное описание IPC System V. Книга содержит много примеров исходного кода и много полезных процедур. Один из примеров это код реализующий используемые семафоры, основанный на частично- фрагментированной реализации которая применяется в System V. Заглавие: Programming in the UNIX environment Автор: Brian W. Kernighan and Robert Pike Издательство: Prentice Hall, 1984 ISBN: 0-13-937699 (hardcover) 0-13-937681-X (paperback) Прибл. Цена: $??.?? Нет Аннотации. Заглавие: Writing UNIX Device drivers Автор: George Pajari Издательство: Addison Wesley, 1992 ISBN: 0-201-52374-4 Прибл. Цена: $32.95



Эта книга написана президентом и основателем Driver Design Labs, компании специалзирующейся на разработке драйверов устройств для UN*X. Эта книга отличное введение в порой суровый мир разработки драйверов устройств. Сначало кратко обсуждаются четыре основные типа драйверов (символьные, блочные, tty, STREAMS). Приведено множество полных примеров драйверов устройств разных типов, начиная с простейших и с растущей сложностью. Все примеры драйверов работают под UN*X на PC-совместимой аппаратуре. Включены следующие главы:

Character Drivers I: A Test Data Generator Character Drivers II:

An A/D Converter Character Drivers III: A Line Printer Block Drivers I:

A Test Data Generator Block Drivers II:

A RAM Disk Driver Block Drivers III: A SCSI Disk Driver Character

Drivers IV: The Raw Disk Driver Terminal Drivers I: The COM1 Port

Character Drivers V: A Tape Drive STREAMS Drivers I:

A Loop-Back Driver STREAMS Drivers II: The COM1 Port (Revisited) Driver

Installation Zen and the Art of Device Driver Writting.

Хотя множество вызовов используемых в этой книге Linux- не совместимы, присутствует общая идея, и большинство решений отображаются непосредственно в Linux.


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. Данный обзор рассмотрит обслуживание ядром асинхронные события, также подробно как размещение информации и организацию кода.


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.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.

|



B.8. Пультовый драйвер.


Будучи драйвером ввода/вывода в большинстве компонентов Linux, пультовый драйвер заслуживает внимания. Исходный текст имеет такое же отношение к управлению, как и любой другой символьный драйвер, находящийся в /drivers/chart, и мы будем мы будем использовать эту директорию при ссылке на имена файлов.

Инициализация управления происходит с помощью функции tty_init() в tty_io.c. Эта функция предназначена для получения основных номеров устройств и вызова инициализации каждого установленного устройства. con_init() - одна из функций, относящихся к управляющему драйверу, инициализирующая его, находится в console.c.

Инициализация управляющего устройства сильно изменилась после выпуска версии 1.1, была убрана из tty_init() и вызывается прямо из../../main.c. Виртуальные пульты на данный момент динамически распределяемы, и в них изменена большая часть исходного текста.



Что поддеpживет 386 пpоцессоp?


386 пpоцессоp pазделяет события на два класса: пpеpывания и исключения. Оба типа событий пpедназначены для ускоpения пpоцесса пеpеключения между задачами. Пpеpывания могу случаться в любое вpемя pаботы пpогpаммы, так как являются pевкцией на сигналы аппаpатного обеспечения. Исключения вызываются опpеделенными пpогpамными инстpукциями.

386 пpоцессоp pаспознает два типа пpеpываний: маскиpуемые и немаскиpуемые. Также опpеделяются два типа исключений: опpеделяемые пpоцессоpом и пpогpамные исключения.

Каждое исключение и пpеpывание имеет свой номеp, котоpый в литеpатуpе называется вектоpом. Hемаскиpуемым пpеpываниям и исключениям опpеделяемым пpоцессоpом пpиписываются вектоpа с 0-го по 32, включительно. Вектоpа маскиpуемых пpеpываний опpеделяются аппаpатным обеспечением. Внешние пpеpывания помещают вектоp в шину во вpемя цикла опpеделения пpеpывания. Любой вектоp входящий в диапазон от 32 до 255 может быть использован маскиpуемым пpеpыванием или пpогpамиpуемым исключением. См pис 4.1 для пpосмотpа всех возможных пpеpываний и исключений:

0 ошибка деления 1 исключение отладки 2 немаскиpуемое пpеpывание 3 контpольная точка (breakpoint) 4 пеpеполнение пpи вводе 5 достижение гpаницы 6 невеpный код опеpации 7 недоступен сопpоцессоp 8 двойная ошибка 9 пеpезапущен сегмент сопpоцессоpа 10 непpавильный сегмент метки задачи 11 отсутствие сегмента 12 неиспpавность стека 13 общая защита 14 неиспpавность стpаницы 15 не используется 16 ошибка сопpоцессоpа 17-31 не используются 32-255 маскиpуемые пpеpывания

Рис 4.1: Значения пpеpываний и исключений.

ВЫСШИЙHеиспpавности включающие в себя неиспpавность отладчика

Hеиспpавность инстpукций INTO, INT n, INT 3.

Отладка неиспpавностей этих инстpукций.

Отладка последующих инстpукций.

Hемаскиpуемое пpеpывание HИЗШИЙПpеpывание INTR

Рис 4.2: Пpиоpитет пpеpываний и исключений.



Что такое SCSI ?


Вступление к стандартному описанию SCSI-2 дает подробнейшее определение Small Computer System Interfase (Интерфейс Малых Компьютерных Систем) и об'ясняет, как SCSI-2 соотносится с SCSI-1 и CCS.

Протокол SCSI создан для обеспечения эффективного обмена информацией с несколькими устройствами ( до 8 ) на нескольких адаптерах. Данные могут передаваться асинхронно со скоростью, определяемой характеристиками устройства и длиной кабеля.

Синхронный обмен информацией может поддерживать скорость до 10 млн. передач в секунду. при использовании 32-битных шин скорость увеличивается до 40Мб в секунду.

SCSI-2 содержит команды для магнитных, оптических дисков, стримеров, принтеров, процессоров, CD-ROMов, сканеров и коммуникационных устройств.

В 1985 году первый стандарт SCSI стал национальным Американским Стандартом, и несколько производителей обратились к группе разработчиков X3T9.2 с с пожеланием расширить стандарт SCSI для использования полнодоступных устройств.

В процессе расширения SCSI группа X3T9.2 разработала пакет, названный Common Comand SET (CCS - "общий набор команд") и создала несколько программных продуктов, базирующихся на этом интерфейсе.

Параллельно этому группа занялась созданием расширенного станарта SCSI, названного SCSI-2. Он содержал в себе результаты разработок CCS с возможностью их использования различными устройствами. Также он включал в себя команды кеширования и другие не менее важные функции. Так как SCSI-2 был лишь более качественной расширенной копией стандарта SCSI-1, он обладал высокой степенью совместимости с устройствами SCSI-1.



Данные управления памятью в таблице процессов


Ниже приводится краткое описание некоторых данных, содержащихся в таблице процессов, которые используются для управления памятью: [Это должно быть документировано значительно лучше. Необходима значительно большая детализация]

Ограничения на память процесса: ulong start_code, end_code, end_data, brk, start_stack; Определение нарушения страницы: ulong min_flt, maj_flt, cmin_flt, cmaj_flt; Локальная таблица дескриптора: struct desc_struct ltd[32] представляет собой локальную таблицу дескриптора задачи. rss количество резидентных страниц. swappable: если - 0, тогда страницы процесса не замещаются. kernel_stack_page: указатель на страницу, размещенную при распараллеливании. saved_kernel_stack: V86 режим работы. struct tss

Сегмент стека esp0 указатель на стек ядра (kernel_stack_page)

ss0 сегмент стека ядра (0х10)

esp1 = ss1 = esp2 = ss2 = 0

неиспользуемые привилегированные уровни. Секторы сегмента: ds = es = fs = gs = ss = 0x17, cs = 0x0f все указатели на сегменты в текущем ltd[]. cr3: указывает на директорию страниц для данного процесса. ltd: _LTD(n) селектор для LTD текущей задачи.