open и release

Предыдущая  Содержание  Следующая V*D*V

Теперь, после быстрого взгляда на поля, мы начнём использовать их в реальных функциях scull.

Метод open

Чтобы сделать любые инициализации при подготовке к поздним операциям для драйвера, предусмотрен метод open. В большинстве драйверов open должен выполнять следующие задачи:

 

Проверку зависимых от устройства ошибок (таких, как "устройство не готово" или аналогичных проблем с оборудованием);

Проинициализировать устройство, если оно открывается в первый раз;

Обновить в случае необходимости указатель f_op;

Создать и заполнить любые структуры данных для размещения в filp->private_data;

 

Первым делом, однако, обычно определяется, какое устройство открывается в настоящий момент. Вспомните прототип метода open:

 

int (*open)(struct inode *inode, struct file *filp);

 

Аргумент inode имеет информацию, которая нам необходима, в форме его поля i_cdev, в котором содержится структура cdev, которую мы создали раньше. Единственной проблемой является то, что обычно мы не хотим саму структуру cdev, мы хотим структуру scull_dev, которая содержит эту структуру cdev. Язык Си позволяет программистам использовать всевозможные трюки, чтобы выполнить такое преобразование, однако, программирование таких трюков чревато ошибками и приводит к коду, который труден для чтения и понимания другими. К счастью, в данном случае программисты ядра сделали сложную работу за нас, в виде макроса container_of, определённого в <linux/kernel.h>:

 

container_of(pointer, container_type, container_field);

 

Этот макрос получает указатель на поле под названием container_field, находящееся внутри структуры типа container_type, и возвращает указатель на эту структуру. В scull_open этот макрос используется, чтобы найти соответствующую структуру устройства:

 

struct scull_dev *dev; /* информация об устройстве */

 

dev = container_of(inode->i_cdev, struct scull_dev, cdev);

filp->private_data = dev; /* для других методов */

 

Как только он нашел структуру scull_dev, для облегчения доступа в будущем scull сохраняет указатель на неё в поле private_data структуры file.

 

Другим способом идентификации открываемого устройства является поиск младшего номера, сохранённого в структуре inode. Если вы регистрируете устройство с register_chrdev, вы должны использовать эту технику. Обязательно используйте iminor, чтобы получить младший номер из структуры inode, а также убедиться, что он соответствует устройству, которое ваш драйвер действительно готов обслужить.

 

(Немного упрощённый) код для scull_open:

 

int scull_open(struct inode *inode, struct file *filp)

{

    struct scull_dev *dev; /* информация об устройстве */

 

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);

    filp->private_data = dev; /* для других методов */

 

    /* теперь установим в 0 длину устройства, если открытие было только для записи */

    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {

        scull_trim(dev); /* игнорирование ошибок */

    }

    return 0; /* успешно */

}

 

Код выглядит очень небольшим, поскольку не делает какого-либо обращения к определённому устройству, когда вызывается open. В этом нет необходимости, потому что устройство scull по дизайну является глобальным и стойким. В частности, нет никаких действий, таких как "инициализация устройств при первом открытии", поэтому мы не храним счётчик открытий для устройств scull.

 

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

 

Мы увидим позже, как работает настоящая инициализация, когда посмотрим на код других вариантов scull.

Метод  release

Роль метода release обратна open. Иногда вы обнаружите, что реализация метода называется device_close вместо device_release. В любом случае, метод устройства должен выполнять следующие задачи:

 

Освободить всё, что open разместил в filp->private_data;

Выключить устройство при последнем закрытии;

 

Базовая форма scull не работает с аппаратурой, которую надо выключать, так что код необходим минимальный: (* Другие разновидности устройства закрываются другими функциями, потому что scull_open заменяет различные filp->f_op для каждого устройства. Мы будем обсуждать их при знакомстве с каждой разновидностью.)

 

int scull_release(struct inode *inode, struct file *filp)

{

    return 0;

}

 

Вы можете быть удивлены, что происходит, когда файл устройства закрывается большее число раз, чем открывается. В конце концов, системные вызовы dup и fork создают копии открытых файлов без вызова open; каждая из этих копий затем закрывается при завершении программы. Например, большинство программ не открывают свой файл stdin (или устройство), но все они в конечном итоге его закрывают. Как драйвер узнает, когда открытый файл устройства действительно был закрыт?

 

Ответ прост: не каждый системный вызов close вызывает метод release. Только вызовы, которые действительно освобождают структуру данных устройства, запускают этот метод, отсюда его название. Ядро хранит счётчик, сколько раз используется структура file. Ни fork, ни dup не создают новую структуру file (только open делает это); они просто увеличивают счётчик в существующей структуре. Системный вызов call запускает метод release только тогда, когда счётчик для структуры file падает до 0, что происходит, когда структура уничтожена. Эта взаимосвязь между методом release и системным вызовом close гарантирует, что ваш драйвер видит только один вызов release для каждого open.

 

Обратите внимание, что метод flush вызывается каждый раз, когда приложение вызывает close. Однако, очень немногие драйверы реализуют flush, потому что обычно нечего делать во время закрытия, пока не вызван release.

 

Как вы можете себе представить, предыдущее обсуждение применяется даже тогда, когда приложение завершается без явного закрытия открытых файлов: ядро автоматически закрывает все файлы во время завершения процесса внутренне с помощью системного вызова close.

Предыдущая  Содержание  Следующая