Системни примитиви на файловата система
Ще разгледаме системните примитиви по стандарта POSIX, който, както
вече отбелязахме, е реализиран в операционните системи LINUX и MINIX.
Като цяло системните примитиви реализират услуги, които се предоставят
на потребителските програми от ядрото. В конкретния случай системните
примитиви са обръщения към програми на C в ядрото. Всички системни
примитиви са реализирани по следния общ принцип – в стандартната
библиотека на C е включена библиотечна функция за съответния системен
примитив, която в общи линии прави следното:
- преобразува аргументите;
- зарежда регистри с номер на системен примитив и аргументите;
- предизвиква програмно прекъсване чрез машинна инструкция;
- проверява за грешки и преобразува върнатите стойности.
Когато се предизвика програмно прекъсване, управлението се предава на
модул в ядрото, който обработва програмните прекъсвания и пренасочва
управлението към друг модул, който реализира съответния системен
примитив. Оттук нататък като говорим за системен примитив ще имаме
предвид съответната му библиотечна функция.
Общото правило за обработка на грешки е следното: при грешка съответната
функция връща стойност -1. За да се провери каква грешка е станала
трябва да се използва глобалната променлива errno, която съдържа код на
грешката. Кодовете на грешки са цели числа. В заглавния файл errno.h са
дефинирани мнемонични символни константи за кодовете на грешките. При
успешно завършване съответната функция връща 0, ако няма някакъв
специфичен изход.
Типично за системните примитиви е използване на производни типове
(дефинирани с конструкция typedef). Тези типове са описани в
стандартният файл types.h. Целта е създаване на преносими програми.
Системните примитиви на файловата система се делят на три групи:
- системни примитиви за манипулиране с файлове (най-вече обикновени);
- системни примитиви за манипулиране на йерархичната структура на файловата система;
- системни примитиви, реализиращи защитата на файловата система.
Системни таблици на файловата система
Системните таблици са структури, които се намират в ядрото и се използват при функциониране на файловата система.
Файлов дескриптор (file descriptor) е цяло число от 0 до N-1, където N е
максималният възможен брой отворени файлове. Той представлява
идентификатор на отворен файл, който се използва от системните примитиви
при манипулиране с файла. Свързването на файл с дескриптор се
осъществява при отварянето на файла и тази връзка се разкъсва при
затваряне на файла. Файловите дескриптори имат локално значение за
процесите – всеки процес разполага с N файлови дескриптора. Файловите
дескриптори 0, 1, 2 са свързани съответно със стандартния вход,
стандартния изход и стандартния изход за грешки.
Всъщност тези файлови дескриптори са свързани с един и същ специален файл, който представлява управляващия терминал на процеса.
При всяко отваряне на файл с него се свързва указател на текуща позиция
(file pointer, file offset) за това отваряне. Тя управлява позицията в
която се пише или от която се чете във файла. Текущата позиция е цяло
число и се измерва с отместването относно началото на файла в брой
байтове. При всяко отваряне по подразбиране текущата позиция има
стойност 0.
При всяко отваряне на файл с него се свързва и режим на отваряне.
Той определя какви операции могат да се изпълняват върху отворения файл – например само четене, само писане, четене и писане.
Режимът на отваряне няма нищо общо с кода на защита на файла.
Основните системни таблици на файловата система са три – таблица на
индексните описатели, таблица на отворените файлове и таблица на
файловите дескриптори.
Таблицата на индексните описатели съдържа по един запис за всеки отворен
файл. Този запис се създава при отваряне на нов файл и се изтрива при
затваряне на файла. Един файл може да е отворен много пъти, дори в
рамките на един процес, но за него в таблицата на индексните описатели
се поддържа само един запис.
Всеки запис в таблицата има следното съдържание:
- копие на inode на отворения файл;
- адрес на inode на отворения файл в индексната област;
- брояч на отварянията на файла;
- флагове за състоянието на inode в записа и др.
Таблицата на отворените файлове съдържа по един запис за всяко отваряне
на файл. Така за един файл може да има много записи в таблицата на
отворените файлове. Всеки запис в таблицата има следното съдържание:
- указател към запис в таблицата на индексните описатели, с който е свързан файла;
- режим на отваряне;
- текуща позиция;
- брояч на указателите, които сочат към този запис от таблиците на файловите дескритори.
Всеки процес има собствена таблица на файловите дескриптори. Всяко поле
от тази таблица съдържа указател към запис от таблицата на отворените
файлове. Всъщност файловият дескриптор представлява индекс в таблицата
на файловите дескритори.
За потребителя файлът представлява последователност от байтове.
Системните примитиви са проектирани съобразно тази структура.
Системен примитив open
Системният примитив open има следния прототип:
int open (const char *filename, int flag[, mode_t mode]).
Той може да се използва както за отваряне на съществуващ файл, така и за
създаване на нов файл. С open могат да се отварят и специални файлове,
но могат да се създават само обикновени файлове.
Аргументът filename задава името на файла – абсолютно, собствено или относително спрямо текущия каталог в текущия процес.
Аргументът flag е цяло число, което се интепретира като флагове, задаващи режима на отваряне.
Аргументът mode се използва само при създаване на файл и той задава код на защита.
Флаговете, които се поддържат чрез аргумента flag са следните:
- флагове, свързани с режима на отваряне –
O_RDONLY (само за четене),
O_WRONLY (само за писане) или
O_RDWR (за четене и за писане);
- флагове, които влияят на действието на open –
O_CREAT (създаване на нов файл, ако не съществува),
O_TRUNC (изтриване на съдържанието на файла при отваряне);
- флагове, които влияят на четенето и писането –
O_APPEND (добавяне в края на файла при режим на писане),
O_SYNC (операцията за писане е синхронна с физическото записване върху диска).
По премълчаване флагът
O_SYNC не е вдигнат и ядрото
използва така наречения механизъм на отложения запис. Възможно е
синхронизация да се постигне и чрез съответния флаг в индексния описател
на файла.
Различните флагове се обединяват с помощта на побитово “или” (|).
Алгоритъм на open:
1. По името на файла се организира търсене по каталозите и се намира индексния описател на файла.
2. Индексният описател на файла се зарежда в таблицата на индексните описатели, ако вече не е там.
3. Добавя се нов запис в таблицата на отворените файлове, в който се
зарежда режима на отваряне от аргумента flag, установява се текуща
позиция 0 и брояч на указателите 1.
4. Разпределя се първият свободен запис от таблицата на файловите
дескриптори на текущия процес и той се свързва със записа от таблицата
на отворените файлове.
При успешно завършване open връща файловият дескриптор на отворения файл. При неуспех open връща -1.
Системен примитив creat
В по-ранните UNIX системи чрез системния примитив open не може да се
създава файл. За тази цел се използва системния примитив creat, който е
запазен и в по-новите UNIX системи. Подобно на open, чрез creat могат да
се създават само обикновени файлове.
Системният примитив creat има следния прототип:
int creat (char *filename, mode_t mode).
Той създава файл с име filename и код на защита mode и след това го
отваря в режим само за писане. Ако файлът е съществувал, информацията в
него се изтрива. При успех creat връща файловият дескриптор на отворения
файл, при неуспех creat връща -1.
Действието на creat може да се моделира чрез open по следния начин:
open (filename, O_WRONLY|O_CREAT|O_TRUNC, mode)
Системен примитив close
Системният примитив close се използва за затваряне на вече отворени
файлове, т.е. за прекъсване на връзката между процеса и файл.
Той има следния прототип: int close (int fd).
Аргументът fd задава файлов дескриптор на файла, който ще се затваря.
Алгоритъм на close:
1. Намалява се с 1 броячът на указателите в записа за файла в таблицата на отворените файлове.
2. Ако новото значение на брояча е по-голямо от 0, то се освобождава
записът в таблицата на файловите дескриптори и close завършва.
3. Ако новото значение на брояча е 0, то се намалява с 1 броячът на
отварянията в записа за файла в таблицата на индексните описатели и се
освобождават записът в таблицата на отворените файлове и записът в
таблицата на файловите дескриптори.
4. Ако новото значение на брояча на отварянията е 0, то индексният
описател на файла се записва обратно на диска, ако е бил изменен и се
освобождава записът в таблицата на индексните описатели.
При успешно завършване close връща 0, иначе -1.
Системни примитиви read и write
Read е системен примитив за четене от файл. Той има следния прототип:
size_t read (int fd, word *buf, size_t count).
Аргументът fd задава файлов дескриптор на отворен файл, от който ще се
чете. Аргументът buf задава адрес в паметта, където ще се записват
прочетените данни. Аргументът count задава брой байтове, които ще се
прочетат. Четенето от файла започва от текущата позиция. При успех read
връща броя на действително прочетените байтове, който може да е по-малък
от count. При неуспех read връща -1. След изпълнението на read текущата
позиция се увеличава с броя на действително прочетените байтове. При
опит да се чете от текуща позиция след края на файла, read връща 0.
За четене от каталози read можеше да се използва в по-старите версии на
UNIX, където записите в каталога са с фиксиран размер. В по-новите
версии има специални функции за работа с каталози, които не са системни
примитиви – opendir, readdir, closedir.
Write е системен примитив за писане във файл. Той има следния прототип:
size_t write (int fd, word *buf, size_t count).
Аргументът fd задава файлов дескриптор на отворен файл, в който ще се
пише. Аргументът buf задава адрес в паметта, откъдето ще се взимат
данните за писане. Аргументът count задава брой байтове, които ще се
записват във файла. Писането във файла започва от текущата позиция. При
успех write връща броя на действително записаните байтове. При неуспех
write връща -1. След изпълнението на write текущата позиция се увеличава
с броя на действително записаните байтове. При опит да се пише от
текуща позиция след края на файла, то се увеличава размера на файла и за
него се разпределят нови блокове, ако се наложи.
В началото на изпълнението на read и write индексният описател на файла в
таблицата на индексните описатели се заключва. Отключва се в края на
изпълнението. По този начин се осъществява неделимост на операциите за
четене и писане – по време на изпълнението на read и write други процеси
нямат достъп до файла.
Системен примитив lseek
Lseek е системен примитив за позициониране във файл. Той има следния прототип:
off_t lseek (int fd, off_t offset, int flag).
Аргументът fd е файлов дескриптор на отворен файл. Lseek променя
указателя на текущата позиция в записа от таблицата на отворените
файлове, който е свързан с файловия дескриптор fd. Аргументът offset
задава отместването, с което ще се промени текущата позиция в брой
байтове. Аргументър flag определя откъде ще се отчита отместването.
Flag може да приема следните стойности (в скоби са посочени еквивалентни символни константи):
- 0 (
SEEK_SET) – отместването се отчита относно началото на файла, т.е. новата стойност на текущата позиция е offset;
- 1 (
SEEK_CUR) – отместването се отчита относно текущата позиция във файла, т.е. към текущата позиция се прибавя offset;
- 2 (
SEEK_END) – отместването се отчита относно края
на файла, т.е. новата стойност на текущата позиция е размерът на файла,
прибавен към offset.
За да се промени успешно текущата позиция, нейната нова стойност трябва
да е неотрицателно цяло число. Ако новата текуща позиция е след края на
файла, то размерът му не се променя. Lseek връща 0 при успех и -1 при
неуспех. Ще отбележим, че lseek е най-бързият системен примитив, тъй
като при него не се извършват дискови операции – променя се единствено
запис в таблицата на отворените файлове, която се намира в паметта. Чрез
lseek се осигурява произволен достъп до файла.
Системни примитиви stat и fstat
Системните примитиви stat и fstat се използват за получаване на
информация за файл. За да се използват е необходимо включването на
заглавния файл stat.h по следния начин: #include <sys/stat.h>.
Системният примитив stat има следния прототип:
int stat (const char *filename, struct stat *sbuf).
Аргументът filename задава име на файл. Информацията за файла се връща в
аргумента sbuf. Структурата stat е описана в заглавния файл stat.h и
нейните полета включват всички атрибути на файла от индексния описател,
номер на индексния описател и др. При успех stat връща 0, при неуспех
връща -1.
Системният примитив fstat има следния прототип:
int fstat (int fd, struct stat *sbuf).
Аргументът fd е файлов дескриптор на отворен файл. Подобно на stat,
информацията за файла се връща в аргумента sbuf. При успех fstat връща
0, при неуспех връща -1. Fstat има смисъл да се използва при работа с
наследени файлове. Всеки новосъздаден процес наследява таблицата с
файловите дескриптори от родителския процес. Новият процес може да
получи информация за наследените файлове чрез fstat, тъй като имената на
тези файлове са неизвестни за него.
Системен примитив dup
Системният примитив dup има следния прототип: int dup (int fd). Той се
използва за дублиране на файлов дескриптор. Аргументът fd е файлов
дескритор на отворен файл. Dup търси първият свободен файлов дескриптор в
таблицата на файловите дескриптори за съответния процес и копира в него
съдържанието на файловия дескриптор fd. Освен това, броячът на
указателите в съответния запис на таблицата на отворените файлове се
увеличава с 1. При успех dup връща новия файлов дескритор, при неуспех
връща -1. След изпълнението на dup двата файлови дескриптора ще са
свързани с един и същ запис в таблицата на отворените файлове, т.е. те
ще съответстват на един и същи отворен файл, с една и съща текуща
позиция и режим на отваряне. Dup има смислено приложение при
пренасочване на входа и изхода. Пример за пренасочване на стандартния
вход във файла с име infile:
fd = open (“infile”, O_RDONLY);
if (fd == -1)
{
printf (“Грешка!n”); exit (1);
}
close (0);
dup (fd);
close (fd);
Преписването е забранено.