Главная страница
Навигация по странице:

  • 8.6. Пример. Печать каталогов

  • 8.6. Пример. Печать каталогов 233

  • 8.7. Пример. Распределитель памяти

  • 236 _ Глава 8. Интерфейс с системой

  • 238 _ Глава 8. Интерфейс с системой UNIX

  • 8.7. Пример. Распределитель памяти 239

  • 240 Глава 8. Интерфейс с системой

  • Б. Керриган, Д. Ритчи Язык программирования C. Б. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003


    Скачать 31.48 Mb.
    НазваниеБ. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003
    АнкорБ. Керриган, Д. Ритчи Язык программирования C.pdf
    Дата06.04.2017
    Размер31.48 Mb.
    Формат файлаpdf
    Имя файлаБ. Керриган, Д. Ритчи Язык программирования C.pdf
    ТипКнига
    #4546
    страница17 из 28
    1   ...   13   14   15   16   17   18   19   20   ...   28
    Глава 8. Интерфейс с системой UNIX
    name. Структура, описывающая возвращаемое функцией stat значение,
    находится в h> и выглядит примерно так:
    struct stat {
    dev ino short short short short time_t time_t st_dev;
    st_ino;
    st_uid;
    st_gid;
    st_rdev;
    st_size;
    /•
    /
    /
    /
    информация из возвращаемая stat */
    устройство */
    номер inode */
    режимные биты */
    число связей с файлом */
    имя пользователя-собственника */
    имя группы собственника */
    для специальных файлов */
    размер файла в символах */
    время последнего использования */
    время последней модификации */
    время последнего изменения inode */
    Большинство этих значений объясняется в комментариях. Типы, подоб- ные dev_t и ino_t, определены в файле h>, который тоже нуж- но включить посредством
    Элемент содержит набор флажков, составляющих дополнительную информацию о файле. Определения флажков также содержатся нам потребуется только та его часть, которая имеет дело с типом файла:
    S_IFMT 0160000 0040000 /*
    S_IFCHR 0020000 /*
    S_IFBLK 0060000 /*
    S_IFREG 0100000
    тип файла */
    каталог */
    символьно-ориентированный */
    */
    обычный */
    Теперь мы готовы приступить к написанию программы f size. Если ре- жимные биты полученные от stat, указывают, что файл не яв- ляется каталогом, то можно взять его размер (st_size) и напечатать. Од- нако если файл - каталог, то мы должны обработать все его файлы, каж- дый из которых в свою очередь может быть каталогом. Обработка катало- га - процесс рекурсивный.
    Программа m a i n просматривает параметры командной строки, переда- вая каждый аргумент функции f
    «include ,

    «include
    /* флажки чтения и записи */
    /* определения типов */

    8.6. Пример. Печать каталогов
    /* структура, возвращаемая stat */
    void fsize(char *);
    /* печатает размеры файлов */
    main(int argc, char
    {
    if
    == 1) /* по умолчанию берется текущий каталог */
    else while
    > 0)
    fsize(*++argv);
    return 0;
    >
    Функция f size печатает размер файла. Однако, если файл - каталог,
    она сначала вызывает di чтобы обработать все его файлы. Обрати- те внимание на то, как используются имена флажков S_IFMT и S_IFDIR
    из h> при проверке, является ли файл каталогом. Здесь нужны скобки, поскольку приоритет оператора & ниже приоритета оператора ==.
    int stat(char *, struct stat *);
    void dirwalk(char *, void
    *));
    /*
    печатает размер файла "name" */
    void
    {
    struct stat if
    -1) {
    fprintf(stderr, "fsize: нет доступа к name);
    return;
    }
    if
    &
    == S_IFDIR)
    fsize);
    }
    Функция di rwalk - это универсальная программа, применяющая не- которую функцию к каждому файлу каталога. Она открывает каталог,
    с помощью цикла перебирает содержащиеся в нем файлы, применяя к каж- дому из них указанную функцию, затем закрывает каталог и осуществля- ет возврат. Так как fsize вызывает rwalk на каждом каталоге, в этих двух функциях заложена косвенная рекурсия.

    232 _ Глава 8. Интерфейс с системой UNIX
    MAX_PATH 1024
    /* dirwalk: применяет ко всем файлам из dir */
    void
    *dir, void (*fcn)(char *))
    {
    char
    *dp;
    DIR
    if ((dfd =
    == NULL) {
    fprintf(stderr, "dirwalk: не могу открыть dir);
    return;
    }
    while
    =
    != NULL) {
    if
    == 0
    == 0)
    continue; /* пропустить себя и родителя */
    if
    >
    "dirwalk: слишком длинное имя dir, dp->name);
    else {
    dir,
    closedir(dfd);
    }
    Каждый вызов readdi возвращает указатель на информацию о следующем файле или NULL, если все файлы обработаны. Любой каталог всегда хра- нит в себе информацию о себе самом в файле под именем и о своем родителе в файле под именем их нужно пропустить, иначе программа зациклится. Обратите внимание: код программы этого уровня не зависит от того, как форматированы каталоги. Следующий шаг — представить ми- нимальные версии opendi r, readdi r и closedi r для некоторой конкретной системы. Здесь приведены программы для систем Version 7 и System V
    UNIX. Они используют информацию о каталоге, хранящуюся в за- головочном файле h>, который выглядит следующим образом:
    tfifndef DIRSIZ
    DIRSIZ 14
    struct direct /* элемент каталога */
    {
    ino_t
    /* номер inode */

    8.6. Пример. Печать каталогов 233
    char
    /* длинное имя не имеет
    */
    Некоторые версии системы допускают более длинные имена и имеют бо- лее сложную структуру каталога.
    Тип ino_t задан с помощью typedef и описывает индекс списка узлов mode. В системе, которой пользуемся мы, этот тип есть unsigned но в других системах он может быть иным, поэтому его лучше определять через
    Полный набор "системных" типов находится в h>.
    Функция opendir открывает каталог, проверяет, является ли он дей- ствительно каталогом (в данном случае это делается с помощью систем- ного вызова f stat, который аналогичен stat, но применяется к дескрип- тору файла), запрашивает пространство для структуры каталога и запи- сывает информацию.
    int fd, struct stat *);
    /* opendir: открывает каталог для вызовов
    */
    DIR *opendir(char int fd;
    struct stat stbuf;
    DIR *dp;
    if
    =
    0_RDONLY,
    == -1
    fstat(fd,
    == -1
    &
    (dp = (DIR
    == NULL)
    return NULL;
    dp->fd = fd;
    return dp;
    Функция closedi закрывает каталог и освобождает пространство.
    /* closedir: закрывает каталог, открытый opendir */
    void
    *dp)
    if
    (dp)
    {

    234 _ Глава 8.
    с системой UNIX
    Наконец, readdi г с помощью read читает каждый элемент каталога. Если некий элемент каталога в данный момент не используется (соответству- ющий ему файл был удален), то номер узла mode у него равен нулю, и дан- ная позиция пропускается. В противном случае номер mode и имя раз- мещаются в статической (static) структуре, и указатель на нее выдается в качестве результата. При каждом следующем обращении новая инфор- мация занимает место предыдущей.
    /* место расположения структуры каталога */
    /*
    последовательно читает элементы каталога */
    Dirent *readdir(DIR *dp)
    {
    struct direct
    /* структура каталога на данной системе */
    static d; /* возвращает унифицированную структуру */
    while
    (char *)
    ==
    {
    if
    == 0) /* пустой элемент, не используется */
    continue;
    d.ino = dirbuf
    DIRSIZ);
    =
    /* завершающий
    */
    return &d;
    }
    return NULL;
    Хотя программа fsize - довольно специализированная, она иллюс- трирует два важных факта. Первый: многие программы не являются "си- стемными"; они просто используют информацию, которую хранит опера- ционная система. Для таких программ существенно то, что представле- ние информации сосредоточено исключительно в стандартных заголовоч- ных файлах. Программы включают эти а не держат объявления в себе. Второе наблюдение заключается в том, что при старании систем- но-зависимым объектам можно создать интерфейсы, которые сами не бу- дут системно-зависимыми. Хорошие тому примеры - функции стандарт- ной библиотеки.
    Упражнение 8.5. Модифицируйте таким образом, чтобы можно было печатать остальную информацию, содержащуюся в узле mode,

    8.7. Пример. Распределитель памяти
    235
    8.7. Пример. Распределитель памяти
    В главе 5 был описан простой распределитель памяти, основанный на принципе стека. Версия, которую мы напишем здесь, не имеет ограни- чений: вызовы и
    могут выполняться в любом порядке; malloc делает запрос в операционную систему на выделение памяти тогда, когда она требуется. Эти программы иллюстрируют приемы, позволяющие по- лучать машинно-зависимый код сравнительно машинно-независимым способом, и, кроме того, они могут служить примером применения таких средств языка, как структуры, объединения И
    Никакого ранее скомпилированного массива фиксированного раз- мера, из которого выделяются куски памяти, не будет. Функция malloc запрашивает память у операционной системы по мере надобности. По- скольку и другие действия программы могут вызывать запросы памяти,
    которые удовлетворяются независимо от этого распределителя памяти,
    пространство, которым заведует malloc, не обязательно представляет со- бой связный кусок памяти. Поэтому свободная память хранится в виде списка блоков. Каждый блок содержит размер, указатель на следующий блок и само пространство. Блоки в списке хранятся в порядке возраста- ния адресов памяти, при этом последний блок (с самым большим адре- сом) указывает на первый.
    список ков
    X

    Зс зан зан
    Ц
    зан свободное пространство, находящееся в распоряжении функции malloc выделенное функцией malloc пространство
    (занято)
    пространство, не находящееся в распоряжении функции malloc
    При возникновении запроса на память просматривается список сво- бодных блоков, пока не обнаружится достаточно большой блок. Такой алгоритм называется "поиском первого подходящего" в алгоритма "поиска наилучшего подходящего", который ищет наименьший блок из числа удовлетворяющих запросу. Если размер блока в точности

    236 _ Глава 8. Интерфейс с системой UNIX
    соответствует требованиям, то такой блок исключается из списка и отда- ется в пользование. Если размер блока больше, чем требуется, от него от- резается нужная часть - она отдается пользователю, а ненужная оставля- ется в списке свободных блоков. Если блока достаточного размера не ока- залось, то у операционной системы запрашивается еще один большой ку- сок памяти, который присоединяется к списку свободных блоков.
    Процедура освобождения сопряжена с прохождением по списку сво- бодных блоков, поскольку нужно найти подходящее место для освобож- даемого блока. Если подлежащий освобождению блок примыкает с ка- кой-то стороны к одному из свободных блоков, то он объединяется с ним в один блок большего размера, чтобы по возможности уменьшить раздроб- ленность (фрагментацию) памяти. Выполнение проверки, примыкают ли блоки друг к другу, не составляет труда, поскольку список свободных блоков всегда упорядочен по возрастанию адресов.
    Существует проблема, о которой мы уже упоминали в главе 5, состоя- щая в том, что память, выдаваемая функцией должна быть соот- ветствующим образом выровнена с учетом объектов, которые будут в ней храниться. Хотя машины и отличаются друг от друга, но для каждой из них существует тип, предъявляющий самые большие требования на вы- равнивание, и, если по некоему адресу допускается размещение объекта этого типа, то по нему можно разместить и объекты всех других типов.
    На некоторых машинах таким самым "требовательным" типом является double, на других это может быть int или long.
    Свободный блок содержит указатель на следующий блок в списке, свой размер и собственно свободное пространство. Указатель и размер представ- ляют собой управляющую информацию и образуют так называемый "заго- ловок". Чтобы упростить выравнивание, все блоки создаются кратными размеру заголовка, а заголовок соответствующим образом выравнивается.
    Этого можно достичь, сконструировав объединение, которое будет со- держать соответствующую заголовку структуру и самый требовательный в отношении выравнивания тип. Для конкретности мы выбрали тип long.
    typedef long Align; /* для выравнивания по границе long */
    union header { /* заголовок блока: */
    struct {
    union header *ptr; /*
    в списке свободных */
    unsigned size; /* размер этого блока */
    } s;
    Align x; /* принудительное выравнивание блока */
    typedef union header Header;

    8.7. Пример. Распределитель памяти 237
    Поле A l i g n нигде не используется; оно необходимо только для того, что- бы каждый заголовок был выровнен по самому "худшему" варианту гра- ницы.
    Затребованное число символов округляется в ос до целого числа единиц памяти размером в заголовок (именно это число и записывается в поле
    (размер) в заголовке); кроме того, в блок входит еще одна еди- ница памяти - сам заголовок. Указатель, возвращаемый функцией указывает на свободное а не на заголовок. Со свободным пространством пользователь может делать что угодно, но, если он будет писать что-либо за его пределами, то, вероятно, список разрушится.
    указатель на следующий свободный блок размер блок, возвращаемый функцией malloc
    Поскольку память, управляемая функцией malloc, не обладает связно- стью, размеры блоков нельзя вычислить по указателям, и поэтому без поля,
    хранящего размер, нам не обойтись.
    Для организации начала работы используется переменная base. Если f геер есть NULL (как это бывает при первом обращении к malloc), создает- ся "вырожденный" список свободного пространства; он содержит один блок нулевого размера с указателем на самого себя. Поиск свободного блока подходящего размера начинается с этого указателя (f т. е. с по- следнего найденного блока; такая стратегия помогает поддерживать спи- сок однородным. Если найденный блок окажется слишком большим,
    пользователю будет отдана его хвостовая часть; при этом потребуется только уточнить его размер в заголовке найденного свободного блока.
    В любом случае возвращаемый пользователю указатель является адре- сом свободного пространства, размещающегося в блоке непосредственно за заголовком.
    static Header base; /* пустой список для нач. запуска */
    static Header
    = NULL; /* начало в списке своб. блоков */
    /* malloc: универсальный распределитель памяти */
    void nbytes)
    {
    Header
    *prevp;
    Header unsigned nunits;

    238 _ Глава 8. Интерфейс с системой UNIX
    nunits = (nbytes +
    - 1) /
    + 1;
    if ((prevp = freep) == NULL) { /* списка памяти еще нет */
    = freep = prevp =
    = 0;
    }
    for (p =
    ; prevp = p, p = p->s.ptr) {
    if
    >= nunits) { /* достаточно большой */
    if
    == nunits) /* точно нужного размера */
    prevp->s.ptr =
    else { /* отрезаем
    */
    -= nunits;
    p +=
    = nunits;
    . . freep = prevp;
    return (void
    }
    if (p == freep) /* прошли полный цикл по списку */
    = raorecore(nunits)) == NULL)
    return NULL; /* больше памяти нет */
    Функция mo reco re получает память от операционной системы. Детали того, как это делается, могут не совпадать в различных системах. Так как запрос памяти у системы - сравнительно дорогая операция, мы бы не хо- тели для этого каждый раз обращаться к
    Поэтому используется функция которая запрашивает не менее единиц памяти;
    этот больший кусок памяти будет "нарезаться" потом по мере надобнос- ти. После установки в поле размера соответствующего значения функ- ция mo reco re вызывает функцию f и тем самым включает полученный кусок в список свободных областей памяти.
    NALLOC 1024 /* миним. число единиц памяти для запроса */
    /* morecore: запрашивает у системы дополнительную память */
    static Header *
    nu)
    {
    char *cp,
    Header *up;
    if (nu < NALLOC)
    nu NALLOC;

    8.7. Пример. Распределитель памяти 239
    ср =
    *
    if (cp == (char *) -1) /* больше памяти нет */
    return NULL;
    up = (Header *) cp;
    = nu;
    free((void return freep;
    >
    Системный вызов sbrk(n) в возвращает указатель на п байт па- мяти или если требуемого пространства не оказалось, хотя было бы лучше, если бы в последнем случае он возвращал NULL. Константу -1 не- обходимо привести ктипу char *, чтобы ее можно было сравнить с возвра- щаемым значением. Это еще один пример того, как операция приведения типа делает функцию относительно независимой от конкретного пред- ставления указателей на различных машинах. Есть, однако, одна "некор- ректность", состоящая в том, что сравниваются указатели на различные блоки, выдаваемые функцией sbrk. Такое сравнение не гарантировано стандартом, который позволяет сравнивать указатели лишь в пределах одного и того же массива. Таким образом, эта версия malloc только на тех машинах, в которых допускается сравнение любых указателей.
    В заключение рассмотрим функцию f
    Она просматривает список свободной памяти, начиная с f reep, чтобы подыскать место для вставля- емого блока. Искомое место может оказаться или между блоками, или в начале списка, или в его конце. В любом случае, если подлежащий ос- вобождению блок примыкает к соседнему блоку, он объединяется с ним в один блок. О чем еще осталось позаботиться, - так это о том, чтобы ука- затели указывали в нужные места и размеры блоков были правильными.
    /* free: включает блок в список свободной памяти */
    void free(void *ap)
    {
    Header *bp, *p;
    bp = (Header *)ap -1; /* указатель на заголовок блока */
    for
    > p && bp < p->s.ptr); p =
    if (p >= p->s.ptr
    (bp > p bp <
    break; /* освобождаем блок в начале или в конце */
    if (bp +
    == p->s.ptr) { /* слить с верхним */
    +=
    /* соседом */
    bp->s.ptr =
    } else

    240 Глава 8. Интерфейс с системой UNIX
    bp->s.
    = p->s.
    if (p
    == bp) { /* слить с нижним соседом */
    +=
    p->s.
    =
    } else
    = bp;
    freep = p;
    }
    Хотя выделение памяти по своей сути - машинно-зависимая пробле- ма, с ней можно справиться, что и иллюстрирует приведенная програм- ма, в которой машинная зависимость упрятана в очень маленькой ее час- ти. Что касается проблемы мы разрешили ее с помощью typedef и union (предполагается, что дает подходящий в смысле вы- равнивания указатель). Операции приведения типов позволяют нам сде- лать явными преобразования типов и даже справиться с плохо спроекти- рованным интерфейсом системы. Несмотря на то, что наши рассуждения касались распределения памяти, этот общий подход применим и в других ситуациях.
    Упражнение 8.6. Стандартная функция c a l l o c ( n , size) возвращает указатель на п элементов памяти размера size, заполненных нулями.
    Напишите свой вариант пользуясь функцией ос или модифицируя последнюю.
    Упражнение 8.7. Функция допускает любой размер, никак не про- веряя его на правдоподобие; f гее предполагает, что размер освобождаемого блока — правильный. Усовершенствуйте эти программы таким образом,
    чтобы они более тщательно контролировали ошибки.
    Упражнение 8.8. Напишите программу освобождающую произвольный блок р, состоящий из п символов, путем включения в список свободной памяти, поддерживаемый функциями malloc и
    С помощью bf пользователь должен иметь возможность в любое время добавить в список свободной памяти статический или внешний массив.

    Приложение А
    1   ...   13   14   15   16   17   18   19   20   ...   28


    написать администратору сайта