Архитектура драйвера
Fork-процессы в VMS
С точки зрения планировщика VMS, fork-процесс представляет собой нить с укороченным контекстом. Вместо обычного дескриптора процесса (РСВ – Process Control Block) используется UCB – Unit 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 мы используем эту возможность для простой обработки ошибок: повтора операции, которая не получилась.
Для того чтобы понять, что же у нас получилось, какие возможности нам открывает такая архитектура и как ими пользоваться, нам следует сделать экскурс в одну из важных областей теории программирования.