Иллюстрированный самоучитель по теории операционных систем

Архитектура драйвера

Fork-процессы в VMS

С точки зрения планировщика VMS, fork-процесс представляет собой нить с укороченным контекстом. Вместо обычного дескриптора процесса (РСВProcess Control Block) используется UCBUnit Control Block, блок управления устройством. Укорочение заключается в том, что эта нить может работать только с одним банком виртуальной памяти из трех, имеющихся у процессора VAX, а именно с системным (полный список банков памяти VAX приведен в Главе 5); таким образом, при переключении контекста задействуется меньше регистров диспетчера памяти. Fork-процесс имеет более высокий приоритет, чем пользовательские процессы, и может быть вытеснен только более приоритетным fork-процессом и обработчиком прерывания.

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

Пример 10.3. Более сложный драйвер контроллера гибкого диска.

/* Обработчики прерываний в зависимости от состояния
*/ void schedule_seek (fdd__struct *fdd)
if (!motor_speed_pk (fdd)) {
fdd › handler = schedule_seek;
retry_spinup (); }
if (fdd › current_track!= CALCULATEJTRACK (fdd › f ile)) fdd › handler = schedule_command;
seek_head(fdd, CALCULATE_TRACK (f ile) }; } else
/* Мы уже на нужной дорожке */ schedule operation (fdd);
void schedule_operation(fdd_struct *fdd) {
if (fdd › current_track!= CALCULATEJTRACK(fdd › file))
{ fdd › handler = schedule_operation; retry_seek(fdd); return; }
switch(fdd › operation) (case FDD_WRITE:
fdd › handler = handle_dma_write_interrupt;
setup_fdd_dma(fdd › fdd_buffer+fdd › bytes__xfered, fdd › copy_size)
I issue_write_coromand (fdd); break; case FDD_READ:
fdd › handler = handle_dma_read_interrupt;
setup_fdd_dma (fdd › fdd_buf fer-t-fdd › bytes_xfered, fdd › copy_size)
issue_read_command (fdd);
break; /* Здесь же мы должны обрабатывать другие команды,
требующие предварительного SEEK */
void handle_dma_write_interrupt (fdd_struct *fdd)
(/* Увеличить fdd › bytes_xfered на количество фактически
переданных символов * /
if (буфер полон/пуст)
/* Здесь мы не можем передавать данные из пользовательского
адресного пространства. Надо будить основную нить * /
wake_up_interruptible (&fdd › fdd_wait_queue); else {
fdd › handler = handle__dma__write_interrupt;
setup_fdd__dma (fdd › fdd_buf fer+fdd › bytes_xfered, fdd › copy_size)
issue_write_corranand(fdd);
/* Основная нить драйвера */
static int fdd_write (struct inode * inode, struct file * file,
char * buf, int count) (
/* Получить идентификатор устройства:
*/ unsigned int minor = MINOR (inode › i_rdev);
/* Обратите внимание, что почти все переменные основной нити
"переехали" в описатель состояния устройства */
/* Найти блок переменных состояния устройства
*/ struct fdd struct *fdd = &fdd table [minor];
fdd › total_bytes_written = 0; fdd › operation = FDD_WRITE;
do { fdd › copy_size = (count <= FDD_BUFFER_SIZE?
count: FDD_BOFFER_SIZE);
/* Передать данные из пользовательского контекста
*/ memcpy_fromfs(fdd › fdd_buffer, buf, copy_size);
if (!motor_5peed_ok()) (
fdd › handler = schedule_seek;
turn_motor_on(fdd); } else
schedule_seek(fdd);
current › timeout = jiffies + FDD_INTERRUPT__TIMEOUT;
inte.rruptible_sleep_on(&fdd › fdd_wait_queue);
if (current › signal & ~current › blocked) { if (fdd › total_bytes_written+fdd › bytes__written)'
return fdd › total_bytes_written+fdd › bytes_written; else
return – EINTR; /* Ничего не было записано,
системный вызов был прерван, требуется повторная попытка */
fdd › total_bytes_written += fdd › bytes_written;
fdd › buf += fdd › bytes_written; count – = fdd › bytes_written;
} while (count > 0); return total bytes written;
static struct tq_struct floppy_tq;
/* Обработчик прерывания */ static void fdd interrupt(int irq)
truct fdcl struct *fdd = &fdd_table [fdd_irq [irq] ];
Af (fdd › ha!,;;ier!= NULL) {
void (Chandler)(int irq, fdd_struct * fdd);
f]_0ppy_tq .routine = (void *)(void *) fdd › handler;
floppy tq.parameter = (void *)fdd;
fdd › handler=NULL;
queue_task(sfloppy_tq, &tq_immediate); } else
{ /* He наше прерывание? */
}
}

Видно, что теперь наш драйвер представляет собой последовательность функций, вызываемых обработчиком прерываний. Обратите внимание, что если мы торопимся, очередную функцию можно вызывать и непосредственно в обработчике, а не создавать для нее fork-процесс посредством queue_task. Но самое главное, на что нам следует обратить внимание – последовательность этих функций не задана жестко: каждая из функций сама определяет, какую операцию вызывать следующей. В том числе, она может решить, что следующая операция может состоять в вызове той же самой функции. В примере 10.3 мы используем эту возможность для простой обработки ошибок: повтора операции, которая не получилась.

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

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.