06 сем Семинар 7. Системный вызов open - chrislvt/OS GitHub Wiki
Два способа заполнения структур. Мы с вами использовали один способ, но есть ещё один.
Первый способ - заполнение полей структуры (например struct file_operations). Как бы структура ни была заполнена, в первую очередь это используется в драйверах устройств. Итогом написания наших ЛР является возможность написания собственных драйверов или файловых систем, и там нужно определить свои функции, которые в ядре являются точками входа.
struct file_operations fops = {
read: device_read,
write: device_write,
open: device_open;
release: device_release
};
Следующий способ описывается стандартом C99 и этот способ предпочтителен начиная с gcc 2.95
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
Системный вызов open и его флаги.
В системном вызове open устанавливается, например, флаг READ/WRITE/APPEND.
-
Если READ, то пытаемся открыть существующий файл и прочитать информацию.
-
Если WRITE, то создаём новый файл, но у этого файла может существовать inode, так как когда используем write на уже существующий файл, то информацию, записанную в этот файл, мы теряем. Система всё равно проверяет есть inode или нет. Этот inode проверятся в кеше. Существует 2 inode - ядра и дисковый. Inode ядра находится в ядре и он нужен для ускорения доступа к информации о файлах. Дисковый inode нужен, чтобы мы могли прочитать данные из конкретного блока, потому что там присутствует информация об адресах. Всё это ищется в кеше. Если файл был создан, но мы всё равно указали write, есть флаг который при указании выдаст вопрос файл существует/заменить? да нет. Получается, что если да, то просто поменяются какие-то поля inode.
-
Если APPEND, то дописываем в существующий файл. С бинарными файлами (блочными) все по-другому, там нет append, но мы же всё равно открываем на read + write.
На системном вызове open определены флаги:
O_CREATE, O_EXCL, O_NOCTTY, O_TRUNC. На эти флаги надо обратить внимание. Вся фишка в том что существует макрос
SYSCALL_DEFINE(open, const char__user *filename, int flags, umode_t mode) //Передаётся имя системного вызова.
{
if (force_o_largefile())
flags|= O_LARGEFILE;
return do_sys_open(AT_FDCWO, filename, flags, mode);
}
SYSCALL_DEFINE3(open, ...) -> (вызывает) ksys_open(filename, flags, mode) //filename - строка - имя файла usermode (имя файла в пространстве пользователя). Очень большая работа функций ядра выполняется для копирования из пространства пользователя в пространство ядра и преобразования. //flags передаются в том же виде, в каком они определены в вызванной нами функции. Надо взять man_open перечислить флаги. //ksys_open вызывает do_sys_open.
do_sys_open вызывает функцию, которая совершает основной объем по обработке информации.
//FDCWD FILE DESCRIPTOR CURRENT WORKING DIRECTORY
static inline long do_sys_open(AT_FDCWD, filename, flags, mode)
Далее проверка является ли система 64х разрядной и флаг O_LARGEFILE. Если флаг установлен, то система 64х разрядная.
do_sys_open вызывает функцию build_open_how() и заполняет структуру open_how, затем вызывает функцию do_sys_openat2() и заполняется структура (struct open_flags). Для этого вызывается build_open_flags().
Сначала флаги приводятся к определенной форме понятной ядру, далее идет проверка флагов. Если флаги правильные и проверка успешна, то заполняется структура struct filename, то есть вызывается функция getname(filename). В ней есть функция str_copy_from_user().
Если копирование успешно, то выполняется поиск свободного файлового дескриптора fd. У каждого файла есть структура struct file, там имеется массив файловых дескрипторов, причем первые fd[0], fd[1], fd[2] открываются автоматически, а далее система в массиве ищет первый свободный файловый дескриптор. Так как отправной точкой для выполняемых действий является процесс и процесс должен получить информацию о файле, чтобы с ним работать. Мы с вами видели это на семинаре и связывали struct task_struct со структурами, с которыми работает процесс. Здесь это всё подтверждается.
Поиск свободного файлового дескриптора выполняется функцией alloc_fd(). Это действительно очень разумный способ и самый правильный способ это вот эту большую последовательность потом детализировать в основных функциях.
alloc_fd() ищет свободный файловый дескриптор и помечает как занятый. Поиск свободного файлового дескриптора является критическим действием системы, устанавливается spin_lock. В результате работы функции allock_fd проверяется получили мы файловый дескриптор или нет. Если файловый дескриптор получен, то вызывается функция set_open_fd(), которая помечает найденный файловый дескриптор как занятый и spin блокировка отпускается, заполняется структура struct file - дескриптор открытого файла - основная структура ядра. Сколько раз бы ни был открыт один и тот же файл, каждое открытие приведет к созданию структуры struct file. Выполняется это функцией do_filp_open(). Эта функция как раз вернет нам указатель на struct file (то есть создаст дескриптор открытого файла в системной таблице открытых файлов).
Далее вызывается set_nameidata. Она заполняет поля структуры struct nameidata. Эту структуру надо привести в отчете. Это очень важная структура. Первое поле которое она содержит - path.
struct nameidata
{
struct path path;
...
struct path root;
struct inode;
unsigned int flags;
...
}
Path нужен для того, чтобы найти, где располагается файл. Каждый элемент пути хранится как файл. И мы опять же используя информацию, которая хранится об этих элементах пути, можем получить доступ к соответствующему блоку на диске. Мы не должны забыть, что основное назначение файловой системы - обеспечение доступа к файлам, которые находятся во вторичной памяти.
do_filp_open должна открыть нам файл, если файл открыт успешно, то есть мы открыли файл, заполнили соответствующую структуру - после этого необходимо проинформировать файловую систему об открытии файла (fsnotify_open()). После этого вызывается fd_install(). Это в случае если удалось открыть файл.
Если файл открыть не удалось, тогда надо освободить файловый дескриптор - put_unusedfd().
Мы упоминали структуру struct filename - она промежуточная. Если мы открыли файл, получили файловый дескриптор, дескриптор открытого файла процесса, то освобождается память из-под этой структуры - вызывается функция putname. Результатом всех перечисленных действий является возврат файлового дескриптора.
Дальше по логике вещей надо рассмотреть функцию build open_flags(). Она проверяет флаги.
int build_open_flags(int flags, umode_t mode, struct open_flags *op)
Какие флаги проверяет - O_CREATE или O_TMPFILE. Затем проверяет флаг O_PATH. Затем проверяет флаг O_TRUNC. Затем проверяет флаг O_APPEND.
Если флаг O_PATH установлен - ветвится.
Заканчивается данная функция установкой op -> lookupflags.
Функция, которая возвращает struct file - ф-я path_openat(). Вызывается alloc_empty_file() и kmem_cache_alloc(). Нам это даёт более глубокое понимание того, что в системе всё кешируется. В системе существует буферизация (на самом деле в системе всё буферизуется). Основная работа системы заключается в передаче данных от внешних устройств в память и из памяти на внешние устройства. Разница между буфером и кешем - буфер хранит как бы только единственный экземпляр данных. А кеш хранит копии. Потому что задача кеша - ускорить доступ. Кешируем. path_openat() возвращает struct file*. Соответственно необходимо создать новую структуру struct file, если вызывается эта функция. Для этого вызывается функция allock_empty_file(), а в ней вызывается kmem_cach_zallog(). Структура кешируется. Если выделение структуры выполнено успешно, то начинают проверяться флаги.
В результате если файл успешно открыт, то возвращается указатель. Если файл не удалось открыть, то необходимо освободить память и вернуть ошибку.
Если установлен флаг O_CREATE - создать inode. !!!!!!!!!!!!!!!!!!! оч важно!
Функция do_last() возвращает int.
int do_last(). В do_last вызывается audit_inode. Если установлен флаг O_CREATE, то идёт работа по читателям, писателям. Устанавливается inode_lock(). Работа идёт писателя - создать. Если нет, то inode_lock_shared() и вызывается функция lookup_open().
Если установлен O_CREATE, то уведомление системы о создании файла и для снятия блокировки используется inode_unlock() иначе снимается флаг inode_unlock_shared(). Затем вызывается may_o_create().
Функция inode_lock() захватывает семафор read/write (struct rw_semaphore). То есть если надо создать inode, то система работает как писатель. Если создавать inode не надо, то будет работать как читатель.