lock.l_len = (size_t) SIZE;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\twrite:%s locked\n", myname);
sprintf(buffer, "%s #%02d", myname, trial);
printf ("\twrite:%s \"%s\"\n", myname, buffer);
lseek (fd, (off_t) OFFSET, SEEK_SET);
write (fd, buffer, SIZE);
sleep (PAUSE);
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\twrite:%s unlocked\n", myname);
trial++;
}
void readAccess(){
flock_t lock;
printf("Read:%s #%d\n", myname, trial);
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = (off_t) OFFSET;
lock.l_len = (size_t) SIZE;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\tread:%s locked\n", myname);
lseek(fd, (off_t) OFFSET, SEEK_SET);
read (fd, buffer, SIZE);
printf("\tcontents:%s \"%*.*s\"\n", myname, SIZE, SIZE, buffer);
sleep (PAUSE);
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\tread:%s unlocked\n", myname);
trial++;
}
А. Богатырев, 1992-95 - 244 - Си в UNIX
Исследуя выдачу этой программы, вы можете обнаружить, что READ-области могут перекры-
ваться; но что никогда не перекрываются области READ и WRITE ни в какой комбинации.
Если идет чтение процессом A - то запись процессом B дождется разблокировки A (чтение
- не будет дожидаться). Если идет запись процессом A - то и чтение процессом B и
запись процессом B дождутся разблокировки A.
6.9.2.
UNIX SVR4 имеет еще один интерфейс для блокировки файлов: функцию lockf.
#include
int lockf(int fd, int operation, size_t size);
Операция operation:
F_ULOCK
Разблокировать указанный сегмент файла (это может снимать один или несколько
замков).
F_LOCK
F_TLOCK
Установить замок. При этом, если уже имеется чужой замок на запрашиваемую
область, F_LOCK блокирует процесс, F_TLOCK - просто выдает ошибку (функция возв-
ращает -1, errno устанавливается в EAGAIN).
- Ожидание отпирания/запирания замка может быть прервано сигналом.
- Замок устанавливается следующим образом: от текущей позиции указателя чтения-
записи в файле fd (что не похоже на fcntl, где позиция задается явно как пара-
метр в структуре); длиной size. Отрицательное значение size означает отсчет от
текущей позиции к началу файла. Нулевое значение - означает "от текущей позиции
до конца файла". При этом "конец файла" понимается именно как конец, а не как
текущий размер файла. Если файл изменит размер, запертая область все равно
будет простираться до конца файла (уже нового).
- Замки, установленные процессом, автоматически отпираются при завершении про-
цесса.
F_TEST
Проверить наличие замка. Функция возвращает 0, если замка нет; -1 в противном
случае (заперто).
Если устанавливается замок, перекрывающийся с уже установленным, то замки объединя-
ются.
было: ___________#######____######__________
запрошено:______________##########______________
стало: ___________#################__________
Если снимается замок с области, покрывающей только часть заблокированной прежде,
остаток области остается как отдельный замок.
было: ___________#################__________
запрошено:______________XXXXXXXXXX______________
стало: ___________###__________####__________
6.10. Файлы устройств.
Пространство дисковой памяти может состоять из нескольких файловых систем (в
дальнейшем FS), т.е. логических и/или физических дисков. Каждая файловая система
имеет древовидную логическую структуру (каталоги, подкаталоги и файлы) и имеет свой
корневой каталог. Файлы в каждой FS имеют свои собственные I-узлы и собственную их
нумерацию с 1. В начале каждой FS зарезервированы:
А. Богатырев, 1992-95 - 245 - Си в UNIX
- блок для загрузчика - программы, вызываемой аппаратно при включении машины (заг-
рузчик записывает с диска в память машины программу /boot, которая в свою оче-
редь загружает в память ядро /unix);
- суперблок - блок заголовка файловой системы, хранящий размер файловой системы (в
блоках), размер блока (512, 1024, ...), количество I-узлов, начало списка сво-
бодных блоков, и другие сведения об FS;
- некоторая непрерывная область диска для хранения I-узлов - "I-файл".
Файловые системы объединяются в единую древовидную иерархию операцией монтирования -
подключения корня файловой системы к какому-то из каталогов-"листьев" дерева другой
FS.
Файлы в объединенной иерархии адресуются при помощи двух способов:
- имен, задающих путь в дереве каталогов:
/usr/abs/bin/hackIt
bin/hackIt
./../../bin/vi
(этот способ предназначен для программ, пользующихся файлами, а также пользова-
телей);
- внутренних адресов, используемых программами ядра и некоторыми системными прог-
раммами.
Поскольку в каждой FS имеется собственная нумерация I-узлов, то файл в объединенной
иерархии должен адресоваться ДВУМЯ параметрами:
- номером (кодом) устройства, содержащего файловую систему, в которой находится
искомый файл: dev_t i_dev;
- номером I-узла файла в этой файловой системе: ino_t i_number;
Преобразование имени файла в объединенной файловой иерархии в такую адресную пару
выполняет в ядре уже упоминавшаяся выше функция namei (при помощи просмотра катало-
гов):
struct inode *ip = namei(...);
Создаваемая ею копия I-узла в памяти ядра содержит поля i_dev и i_number (которые на
самом диске не хранятся!).
Рассмотрим некоторые алгоритмы работы ядра с файлами. Ниже они приведены чисто
схематично и в сильном упрощении. Форматы вызова (и оформление) функций не соот-
ветствуют форматам, используемым на самом деле в ядре; верны лишь названия функций.
Опущены проверки на корректность, подсчет ссылок на структуры file и inode, блоки-
ровка I-узлов и кэш-буферов от одновременного доступа, и многое другое.
Пусть мы хотим открыть файл для чтения и прочитать из него некоторую информацию.
Вызовы открытия и закрытия файла имеют схему (часть ее будет объяснена позже):
#include
#include
#include
int fd_read = open(имяФайла, O_RDONLY){
int fd; struct inode *ip; struct file *fp; dev_t dev;
u_error = 0; /* errno в программе */
// Найти файл по имени. Создается копия I-узла в памяти:
ip = namei(имяФайла, LOOKUP);
// namei может выдать ошибку, если нет такого файла
if(u_error) return(-1); // ошибка
// Выделяется структура "открытый файл":
fp = falloc(ip, FREAD);
// fp->f_flag = FREAD; открыт на чтение
А. Богатырев, 1992-95 - 246 - Си в UNIX
// fp->f_offset = 0; RWptr
// fp->f_inode = ip; ссылка на I-узел
// Выделить новый дескриптор
for(fd=0; fd < NOFILE; fd++)
if(u_ofile[fd] == NULL ) // свободен
goto done;
u_error = EMFILE; return (-1);
done:
u_ofile[fd] = fp;
// Если это устройство - инициализировать его.
// Это функция openi(ip, fp->f_flag);
dev = ip->i_rdev;
if((ip->i_mode & IFMT) == IFCHR)
(*cdevsw[major(dev)].d_open)(minor(dev),fp->f_flag);
else if((ip->i_mode & IFMT) == IFBLK)
(*bdevsw[major(dev)].d_open)(minor(dev),fp->f_flag);
return fd; // через u_rval1
}
close(fd){
struct file *fp = u_ofile[fd];
struct inode *ip = fp->f_inode;
dev_t dev = ip->i_rdev;
if((ip->i_mode & IFMT) == IFCHR)
(*cdevsw[major(dev)].d_close)(minor(dev),fp->f_flag);
else if((ip->i_mode & IFMT) == IFBLK)
(*bdevsw[major(dev)].d_close)(minor(dev),fp->f_flag);
u_ofile[fd] = NULL;
// и удалить ненужные структуры из ядра.
}
Теперь рассмотрим функцию преобразования логических блоков файла в номера физических
блоков в файловой системе. Для этого преобразования в I-узле файла содержится таблица
адресов блоков. Она устроена довольно сложно - ее начало находится в узле, а продол-
жение - в нескольких блоках в самой файловой системе (устройство это можно увидеть в
примере "Фрагментированность файловой системы" в приложении). Мы для простоты будем
предполагать, что это просто линейный массив i_addr[], в котором n-ому логическому
блоку файла отвечает bno-тый физический блок файловой системы:
bno = ip->i_addr[n];
Если файл является интерфейсом устройства, то этот файл не хранит информации в логи-
ческой файловой системе. Поэтому у устройств нет таблицы адресов блоков. Вместо
этого, поле i_addr[0] используется для хранения кода устройства, к которому приводит
этот специальный файл. Это поле носит название i_rdev, т.е. как бы сделано
#define i_rdev i_addr[0]
(на самом деле используется union). Устройства бывают байто-ориентированные, обмен с
которыми производится по одному байту (как с терминалом или с коммуникационным пор-
том); и блочно-ориентированные, обмен с которыми возможен только большими порциями -
блоками (пример - диск). То, что файл является устройством, помечено в поле тип
файла
ip->i_mode & IFMT
А. Богатырев, 1992-95 - 247 - Си в UNIX
одним из значений: IFCHR - байтовое; или IFBLK - блочное. Алгоритм вычисления номера
блока:
ushort u_pboff; // смещение от начала блока
ushort u_pbsize; // сколько байт надо использовать
// ushort - это unsigned short, смотри
// daddr_t - это long (disk address)
daddr_t bmap(struct inode *ip,
off_t offset, unsigned count){
int sz, rem;
// вычислить логический номер блока по позиции RWptr.
// BSIZE - это размер блока файловой системы,
// эта константа определена в
daddr_t bno = offset / BSIZE;
// если BSIZE == 1 Кб, то можно offset >> 10
u_pboff = offset % BSIZE;
// это можно записать как offset & 01777
sz = BSIZE - u_pboff;
// столько байт надо взять из этого блока,
// начиная с позиции u_pboff.
if(count < sz) sz = count;
u_pbsize = sz;
Если файл представляет собой устройство, то трансляция логических блоков в физические
не производится - устройство представляет собой "сырой" диск без файлов и каталогов,
т.е. обращение происходит сразу по физическому номеру блока:
if((ip->i_mode & IFMT) == IFBLK) // block device
return bno; // raw disk
// иначе провести пересчет:
rem = ip->i_size /*длина файла*/ - offset;
// это остаток файла.
if( rem < 0 ) rem = 0;
// файл короче, чем заказано нами:
if( rem < sz ) sz = rem;
if((u_pbsize = sz) == 0) return (-1); // EOF
// и, собственно, замена логич. номера на физич.
return ip->i_addr[bno];
}
Теперь рассмотрим алгоритм read. Параметры, начинающиеся с u_..., на самом деле пере-
даются как статические через вспомогательные переменные в u-area процесса.
read(int fd, char *u_base, unsigned u_count){
unsigned srccount = u_count;
struct file *fp = u_ofile[fd];