Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Богатырев А. Весь текст 1009.15 Kb

Хрестоматия по программированию на Си в Unix

Предыдущая страница Следующая страница
1 ... 42 43 44 45 46 47 48  49 50 51 52 53 54 55 ... 87
        struct   inode *ip = fp->f_inode;
        struct   buf   *bp;
        daddr_t         bno; // очередной блок файла

        // dev - устройство,
        // интерфейсом которого является файл-устройство,
        // или на котором расположен обычный файл.
        dev_t dev = (ip->i_mode & (IFCHR|IFBLK)) ?

А. Богатырев, 1992-95                  - 248 -                              Си в UNIX

              ip->i_rdev : ip->i_dev;

        switch( ip->i_mode & IFMT ){

        case IFCHR:  // байто-ориентированное устройство
          (*cdevsw[major(dev)].d_read)(minor(dev));
          // прочие параметры передаются через u-area
          break;

        case IFREG:  // обычный файл
        case IFDIR:  // каталог
        case IFBLK:  // блочно-ориентированное устройство
          do{
             bno = bmap(ip, fp->f_offset /*RWptr*/, u_count);
             if(u_pbsize==0 || (long)bno < 0) break; // EOF
             bp  = bread(dev, bno);  // block read

             iomove(bp->b_addr + u_pboff, u_pbsize, B_READ);

Функция iomove копирует данные

    bp->b_addr[ u_pboff..u_pboff+u_pbsize-1 ]

из адресного пространства ядра (из буфера в ядре) в адресное пространство процесса по
адресам

    u_base[ 0..u_pbsize-1 ]

то есть пересылает u_pbsize байт между ядром и процессом (u_base  попадает  в  iomove
через  статическую  переменную).  При записи вызовом write(), iomove с флагом B_WRITE
производит обратное копирование - из памяти процесса в память ядра. Продолжим:

             // продвинуть счетчики и указатели:
             u_count      -= u_pbsize;
             u_base       += u_pbsize;
             fp->f_offset += u_pbsize;  // RWptr
          } while( u_count != 0 );
          break;
        ...
        return( srccount - u_count );
    } // end read

Теперь обсудим некоторые места этого алгоритма.  Сначала  посмотрим,  как  происходит
обращение  к  байтовому устройству.  Вместо адресов блоков мы получаем код устройства
i_rdev.  Коды устройств в UNIX (тип dev_t) представляют собой пару двух чисел,  назы-
ваемых мажор и минор, хранимых в старшем и младшем байтах кода устройства:

    #define major(dev)  ((dev >> 8) & 0x7F)
    #define minor(dev)  ( dev       & 0xFF)

Мажор обозначает тип устройства (диск, терминал, и.т.п.) и приводит к одному из драй-
веров  (если  у  нас  есть  8 терминалов, то их обслуживает один и тот же драйвер); а
минор обозначает номер устройства данного типа (... каждый из терминалов имеет миноры
0..7).  Миноры обычно служат индексами в некоторой таблице структур внутри выбранного
драйвера.  Мажор же служит индексом в переключательной таблице устройств.   При  этом
блочно-ориентированные  устройства  выбираются  в  одной таблице - bdevsw[], а байто-
ориентированные - в другой  -  cdevsw[]  (см.  ;  имена  таблиц  означают
block/character  device  switch).   Каждая  строка  таблицы  содержит адреса функций,
выполняющих некоторые предопределенные операции способом,  зависимым  от  устройства.
Сами  эти  функции  реализованы  в  драйверах устройств.  Аргументом для этих функций
обычно служит  минор  устройства,  к  которому  производится  обращение.   Функция  в

А. Богатырев, 1992-95                  - 249 -                              Си в UNIX

драйвере  использует  этот  минор  как  индекс для выбора конкретного экземпляра уст-
ройства данного типа; как индекс в массиве управляющих структур  (содержащих  текущее
состояние,  режимы  работы,  адреса функций прерываний, адреса очередей данных и.т.п.
каждого конкретного устройства) для данного типа устройств. Эти управляющие структуры
различны для разных типов устройств (и их драйверов).
     Каждая строка переключательной таблицы содержит адреса функций, выполняющих опе-
рации  open,  close,  read, write, ioctl, select.  open служит для инициализации уст-
ройства при первом его открытии (++ip->i_count==1) - например, для включения  мотора;
close  -  для  выключения  при последнем закрытии (--ip->i_count==0).  У блочных уст-
ройств поля для read и write объединены в функцию strategy, вызываемую  с  параметром
B_READ  или B_WRITE.  Вызов ioctl предназначен для управления параметрами работы уст-
ройства.  Операция select - для опроса: есть ли поступившие в устройство данные (нап-
ример,  есть ли в clist-е ввода с клавиатуры байты? см. главу "Экранные библиотеки").
Вызов select применим только к некоторым байтоориентированным устройствам  и  сетевым
портам  (socket-ам).   Если  данное  устройство не умеет выполнять такую операцию, то
есть запрос к этой операции должен вернуть в  программу  ошибку  (например,  операция
read  неприменима  к  принтеру), то в переключательной таблице содержится специальное
имя функции nodev; если же операция допустима, но является фиктивной (как  write  для
/dev/null)  -  имя  nulldev.  Обе эти функции-заглушки представляют собой "пустышки":
{}.
     Теперь обратимся к блочно-ориентированным устройствам.  UNIX  использует  внутри
ядра дополнительную буферизацию при обменах с такими  устройствами[*].   Использованная
нами  выше функция bp=bread(dev,bno); производит чтение физического блока номер bno с
устройства dev.  Эта операция обращается к драйверу конкретного устройства и вызывает
чтение  блока  в  некоторую  область  памяти в ядре ОС: в один из кэш-буферов (cache,
"запасать").  Заголовки кэш-буферов (struct buf) организованы в список и  имеют  поля
(см. файл ):
b_dev
     код устройства, с которого прочитан блок;
b_blkno
     номер физического блока, хранящегося в буфере в данный момент;
b_flags
     флаги блока (см. ниже);
b_addr
     адрес участка памяти (как правило в самом ядре), в котором собственно и хранится
     содержимое блока.

Буферизация блоков позволяет системе экономить число обращений к диску.  При  обраще-
нии  к  bread()  сначала происходит поиск блока (dev,bno) в таблице кэш-буферов. Если
блок уже был ранее прочитан в кэш, то обращения  к  диску  не  происходит,  поскольку
копия  содержимого  дискового  блока уже есть в памяти ядра.  Если же блока еще нет в
кэш-буферах, то в ядре выделяется чистый буфер, в заголовке ему прописываются  нужные
значения полей b_dev и b_blkno, и блок считывается в буфер с диска вызовом функции

    bp->b_flags |= B_READ;  // род работы: прочитать
    (*bdevsw[major(dev)].d_startegy)(bp);
    // bno и минор - берутся из полей *bp

из драйвера конкретного устройства.
     Когда мы что-то изменяем в файле вызовом write(), то  изменения  на  самом  деле
происходят в кэш-буферах в памяти ядра, а не сразу на диске.  При записи в блок буфер
помечается как измененный:

    b_flags |= B_DELWRI;  // отложенная запись

____________________
   [*] Следует отличать эту системную буферизацию от буферизации при помощи  библиотеки
stdio.   Библиотека  создает буфер в самом процессе, тогда как системные вызовы имеют
буфера внутри ядра.

А. Богатырев, 1992-95                  - 250 -                              Си в UNIX

и на диск немедленно не записывается.  Измененные буфера  физически  записываются  на
диск в таких случаях:
-    Был сделан системный вызов sync();
-    Ядру не хватает кэш-буферов (их число ограничено). Тогда самый старый  буфер  (к
     которому  дольше  всего  не  было  обращений) записывается на диск и после этого
     используется для другого блока.
-    Файловая система была отмонтирована вызовом umount;

Понятно, что не измененные блоки обратно на диск из буферов не записываются (т.к.  на
диске  и  так  содержатся  те же самые данные).  Даже если файл уже закрыт close, его
блоки могут быть еще не записаны на диск - запись произойдет лишь  при  вызове  sync.
Это  означает,  что  измененные  блоки записываются на диск "массированно" - по многу
блоков, но не очень часто, что позволяет оптимизировать и саму запись на диск: сорти-
ровкой блоков можно достичь минимизации перемещения магнитных головок над диском.
     Отслеживание самых "старых" буферов  происходит  за  счет  реорганизации  списка
заголовков  кэш-буферов.  В большом упрощении это можно представить так: как только к
блоку происходит обращение, соответствующий заголовок переставляется в начало списка.
В  итоге  самый  "пассивный" блок оказывается в хвосте - он то и переиспользуется при
нужде.
     "Подвисание" файлов в памяти ядра значительно  ускоряет  работу  программ,  т.к.
работа с памятью гораздо быстрее, чем с диском. Если блок надо считать/записать, а он
уже есть в кэше, то реального обращения к диску не происходит.  Зато,  если  случится
сбой питания (или кто-то неаккуратно выключит машину), а некоторые буфера еще не были
сброшены на диск - то часть изменений в файлах будет  потеряна.   Для  принудительной
записи всех измененных кэш-буферов на диск существует сисвызов "синхронизации" содер-
жимого дисков и памяти

    sync();  // synchronize

Вызов sync делается раз в 30  секунд  специальным  служебным  процессом  /etc/update,
запускаемым  при  загрузке  системы.  Для работы с файлами, которые должны гарантиро-
ванно быть корректными на диске, используется открытие файла

    fd = open( имя, O_RDWR | O_SYNC);

которое означает, что при каждом write блок из кэш-буфера немедленно записывается  на
диск.  Это делает работу надежнее, но существенно медленнее.
     Специальные файлы устройств не  могут  быть  созданы  вызовом  creat,  создающим
только обычные файлы.  Файлы устройств создаются вызовом  mknod:

    #include 
    dev_t dev = makedev(major, minor);
                    /* (major << 8) | minor */
    mknod( имяФайла, кодыДоступа|тип, dev);

где dev - пара (мажор,минор) создаваемого устройства; кодыДоступа -  коды  доступа  к
файлу (0777)[**]; тип - это одна из констант S_IFIFO, S_IFCHR, S_IFBLK из  include-файла
.
     mknod доступен для выполнения только суперпользователю  (за  исключением  случая
S_IFIFO).  Если бы это было не так, то можно было бы создать файл устройства, связан-
ный с существующим диском, и читать информацию с него напрямую,  в  обход  механизмов
логической файловой системы и защиты файлов кодами доступа.
     Можно создать файл устройства с мажором и/или минором,  не  отвечающим  никакому
реальному  устройству  (нет такого драйвера или минор слишком велик).  Открытие таких
____________________
   [**] Обычно к блочным устройствам (дискам) доступ разрешается  только  суперпользова-
телю,  в противном случае можно прочитать с "сырого" диска (в обход механизмов файло-
вой системы) физические блоки любого файла и весь механизм защиты окажется неработаю-
щим.

А. Богатырев, 1992-95                  - 251 -                              Си в UNIX

устройств выдает код ошибки ENODEV.
     Из нашей программы мы можем вызовом stat() узнать  код  устройства,  на  котором
расположен файл.  Он будет содержаться в поле dev_t st_dev; а если файл является спе-
циальным файлом (интерфейсом драйвера устройства), то  код  самого  этого  устройства
можно узнать из поля dev_t st_rdev; Рассмотрим пример, который выясняет, относятся ли
два имени к одному и тому же файлу:

    #include 
    #include 
    void main(ac, av) char *av[]; {
      struct stat st1, st2; int eq;
      if(ac != 3) exit(13);
      stat(av[1], &st1); stat(av[2], &st2);
      if(eq =
        (st1.st_ino == st2.st_ino && /* номера I-узлов */
         st1.st_dev == st2.st_dev))  /* коды устройств */
    printf("%s и %s - два имени одного файла\n",av[1],av[2]);
      exit( !eq );
    }

Наконец, вернемся к склейке нескольких файловых систем в одну объединенную иерархию:

          ino=2
          *------      корневая файловая система
         / \    /\     на диске /dev/hd0
        /  /\    /\
             \
              *-/mnt/hd1
              :
              * ino=2    FS на диске /dev/hd1
             / \         (removable FS)
            /\  \
Предыдущая страница Следующая страница
1 ... 42 43 44 45 46 47 48  49 50 51 52 53 54 55 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама