• Аргументы программы
  • getopt
  • getopt_long
  • Переменные окружения
  • Применение переменных окружения
  • Переменная environ
  • Время и дата
  • Временные файлы
  • Информация о пользователе
  • Информация о компьютере
  • Ведение системных журналов
  • Ресурсы и ограничения
  • Резюме 
  • Глава 4

    Окружение Linux

    Когда вы пишете программу для ОС Linux (или UNIX и UNIX-подобных систем), следует принимать во внимание то, что программа будет выполняться в многозадачной среде или многозадачном окружении. Это означает, что много программ будет выполняться одновременно и совместно использовать ресурсы компьютера, такие как память, дисковое пространство и циклы центрального процессора. Может даже существовать несколько экземпляров одной и той же программы, выполняющихся одновременно. Важно, чтобы эти программы не мешали друг другу, знали о своем окружении и могли действовать надлежащим образом, избегая конфликтов, таких как попытка писать в один и тот же файл одновременно с другой программой.

    В этой главе рассматривается окружение, в котором действуют программы, как они его используют для получения информации об условиях функционирования и как пользователи программ могут изменять их поведение. В частности, в данной главе рассматриваются следующие темы:

    □ передача аргументов в программы;

    □ переменные окружения;

    □ определение текущего времени;

    □ временные файлы;

    □ получение информации о пользователе и рабочем компьютере;

    □ формирование и настройка регистрируемых сообщений;

    □ выявление ограничений, накладываемых системой.

    Аргументы программы

    Когда в ОС Linux или UNIX выполняется программа на языке С, она начинается с функции

    main
    . В таких программах функция
    main
    объявляется следующим образом:

    int main(int argc, char *argv[])

    Здесь

    argc
    — это счетчик аргументов программы, a
    argv
    — массив символьных строк, представляющих сами аргументы.

    Вы можете встретить программы на языке С для ОС Linux, просто объявляющие функцию

    main
    как

    main()

    Этот вариант тоже работает, поскольку по умолчанию возвращаемому функцией значению будет назначен тип

    int
    , а формальные параметры, которые в функции не применяются, не нуждаются в объявлении. Параметры
    argc
    и
    argv
    остаются на своем месте, но если вы не объявляете их, то и не можете их использовать.

    Каждый раз, когда операционная система запускает новую программу, параметры

    argc
    и
    argv
    устанавливаются и передаются функции
    main
    . Обычно эти параметры предоставляются другой программой, часто командной оболочкой, которая запросила у операционной системы запуск новой программы. Оболочка принимает заданную командную строку, разбивает её на отдельные слова и использует их для заполнения массива
    argv
    . Помните о том, что до установки параметров
    argc
    и
    argv
    командная оболочка Linux обычно выполняет раскрытие метасимволов в аргументах, содержащих имена файлов, в то время как оболочка MS-DOS рассчитывает на то, что программы примут аргументы с метасимволами и выполнят собственную постановку.

    Например, если мы дадим командной оболочке следующую команду:

    $ myprog left right 'and center'

    программа myprog запустит функцию

    main
    с приведенными далее параметрами.

    argc: 4

    argv: {"myprog", "left", "right", "and center"}

    Обратите внимание на то, что аргумент-счётчик содержит имя программы и в массив

    argv
    оно включено как первый элемент
    argv[0]
    . Поскольку в команде оболочки мы применили кавычки, четвертый аргумент представляет собой строку, содержащую пробелы.

    Вам все это знакомо, если вы программировали на языке С стандарта ISO/ANSI, Аргументы функции

    main
    соответствуют позиционным параметрам в сценариях командной оболочки:
    $0
    ,
    $1
    и т.д. Язык ISO/ANSI С заявляет, что функция
    main
    должна возвращать значение типа
    int
    , спецификация X/Open содержит явное объявление, данное ранее.

    Аргументы командной строки удобны для передачи данных программам. Например, вы можете применить их в приложениях баз данных для передачи имени базы данных, которую хотите использовать, что позволит вам применить одну и ту же программу для работы с несколькими базами данных. Многие утилиты также используют аргументы командной строки для изменения собственного поведения или установки опций. Вы обычно задаете эти так называемые флаги или переключатели с помощью аргументов командной строки, начинающихся со знака "дефис". Например, программа

    sort
    принимает переключатель для изменения обычного порядка сортировки на обратный:

    $ sort -r файл

    Опции командной строки используются очень широко, и согласованное их применение будет реальной помощью тем, кто станет использовать вашу программу. В прошлом у каждой утилиты был свой подход к формированию опций командной строки, что приводило к некоторой путанице. Например, взгляните на то, каким способом приведенные далее команды принимают параметры:

    $ tar cvfB /tmp/file.tar 1024

    dd if=/dev/fd0 of=/trap/file.dd bs=18k

    $ ps ax

    $ gcc --help

    $ ls -lstr

    $ ls -l -s -t -r

    Мы рекомендуем в ваших приложениях все переключатели командной строки начинать с дефиса и делать их односимвольными, состоящими из одной буквы или цифры. При необходимости опции, не содержащие последующих аргументов, могут группироваться вместе после общего дефиса. Таким образом, два только что приведенных примера с командой

    ls
    соответствуют нашим рекомендациям. За каждой опцией может следовать любое необходимое ей значение как отдельный аргумент. Пример с программой
    dd
    нарушает наше правило, поскольку использует многосимвольные опции, которые начинаются совсем не с дефисов (
    if=/dev/fd0
    ): в примере с программой
    tar
    опции полностью оторваны от своих значений! Целесообразно добавлять более длинные и информативные имена переключателей как альтернативу односимвольных вариантов и использовать двойной дефис для их выделения. Таким образом, у нас могут быть два варианта опции получения помощи:
    -h
    и
    --help
    .

    Еще один недостаток некоторых программ — создание опции

    +x
    (например) для выполнения функции, противоположной
    . В главе 2 мы применяли команду
    set -о xtrace
    для включения отслеживания действий командной оболочки и команду
    set +о xtrace
    для выключения этого режима.

    Вы, вероятно, можете сказать, что запомнить порядок и назначение всех этих программных опций достаточно трудно без необходимости освоения вызывающих идиосинкразию форматов. Часто единственный выход — применение опции

    -h
    (от англ. help) или страниц интерактивного справочного руководства (
    man
    ), если программист предоставил одну из этих возможностей. Чуть позже в этой главе мы покажем, что функция
    getopt
    предоставляет изящное решение этих проблем. А сейчас, тем не менее, в упражнении 4.1 давайте посмотрим, как передаются аргументы программы.

    Упражнение 4.1. Аргументы программы

    Далее приведена программа args.c, проверяющая собственные аргументы.

    #include <stdio.h>

    #include <stdlib.h>


    int main(int argc, char *argv[]) {

     int arg;

     for (arg = 0; arg < argc; arg++) {

      if (argv[arg][0] == '-')

    printf("option: %s\n", argv[arg]+1);

      else

       printf("argument %d: %s\n", arg, argv[arg]);

     }

     exit(0);

    }

    Когда вы выполните эту программу, она просто выведет свои аргументы и определит опции. Суть в том, что программа принимает строковый аргумент и необязательный аргумент с именем файла, вводимый опцией

    -f
    . Могут быть определены и другие опции.

    $ ./args -i -lr 'hi there' -f fred.c

    argument 0: ./args

    option: i

    option: lr

    argument 3: hi there option: f

    argument 5: fred.с

    Как это работает

    Программа просто использует аргумент-счетчик

    argc
    для задания цикла, просматривающего все аргументы программы. Она находит опции поиском начального дефиса.

    В данном примере, если мы предполагаем, что доступны опции

    -l
    и
    -r
    , то упускаем тот факт, что группа
    -lr
    , возможно, должна интерпретироваться так же, как
    -l
    и
    -r
    .

    В стандарте X/Open (который можно найти по адресу http://opengroup.org/) определено стандартное применение опций командной строки (Utility Syntax Guidelines, руководство по синтаксису утилит) и стандартный программный интерфейс для представления переключателей командной строки в программах на языке С: функция

    getopt
    .

    getopt

    Для того чтобы вам легче было следовать правилам, приведенным в этих руководствах, ОС Linux предлагает очень простое в применении средство

    getopt
    , поддерживающее использование опций со значениями и без них.

    #include <unistd.h>

    int getopt(int argc, char *const argv[], const char *optstring);

    extern char *optarg;

    extern int optind, opterr, optopt;

    Функция

    getopt
    принимает параметры
    argc
    и
    argv
    в том виде, в каком они передаются функции
    main
    в программе, и строку спецификатора опций, которая сообщает
    getopt
    , какие опции определены для программы и есть ли у них связанные с ними значения.
    optstring
    — это просто список символов, каждый из которых представляет односимвольную опцию. Если за символом следует двоеточие, это означает, что у опции есть ассоциированное значение, которое будет принято как следующий аргумент. Команда
    getopt
    оболочки bash выполняет аналогичную функцию.

    Например, для обработки предыдущего примера можно было бы применить следующий вызов:

    getopt(argc, argv, "if:lr");

    В нем учтены простые опции

    -i
    ,
    -l
    ,
    -r
    и
    -f
    , за которыми последует аргумент с именем файла. Вызов команды с теми же параметрами, но указанными в другом порядке, изменит поведение. Вы сможете попробовать сделать это, когда получите пример кода из упражнения 4.2.

    Результат, возвращаемый функцией

    getopt
    , — символ следующей опции, хранящийся в массиве
    argv
    (если он есть). Вызывайте
    getopt
    повторно для поочередного получения каждой опции. Функция ведет себя следующим образом.

    □ Если опция принимает значение, на него указывает внешняя переменная

    optarg
    .

    □ Функция

    getopt
    вернет -1, когда не останется опций для обработки. Специальный аргумент 
    --
    заставит
    getopt
    прекратить перебор опций.

    □ Функция

    getopt
    вернет
    ?
    , если есть нераспознанная опция, которую она сохранит во внешней переменной
    optopt
    .

    □ Если опции требуется значение (например, в нашем примере опции

    -f
    ) и не задана никакая величина,
    getopt
    обычно возвращает
    ?
    . Если поместить двоеточие как первый символ в строке опций, при отсутствии заданной величины функция
    getopt
    вернет
    :
    вместо
    ?
    .

    Во внешней переменной

    optind
    хранится номер следующего обрабатываемого аргумента. Функция
    getopt
    использует ее, чтобы знать, как далеко она продвинулась. Программы редко нуждаются в установке этой переменной. Когда все аргументы с опциями обработаны, переменная
    optind
    указывает, где в конце массива argv можно найти оставшиеся аргументы. 

    Некоторые версии функции

    getopt
    прекратят выполнение при обнаружении первого аргумента не опции, вернув значение -1 и установив переменную
    optind
    . Другие, например предлагаемые в ОС Linux, могут обрабатывать опции, где бы они ни встретились в аргументах программы. Учтите, что в данном случае
    getopt
    фактически перепишет массив
    argv
    так, что все аргументы не опции будут собраны вместе, начиная с элемента массива
    argv[optind]
    . В случае версии GNU функции
    getopt
    ее поведение определяется переменной окружения
    POSIXLY_CORRECT
    . Если переменная установлена,
    getopt
    остановится на первом аргументе не опции. Кроме того, некоторые реализации
    getopt
    выводят сообщения об ошибке для незнакомых опций. Имейте в виду, что в стандарте POSIX написано о том, что если переменная
    opterr
    не равна нулю, функция
    getopt
    выведет сообщение об ошибке в
    stderr
    .

    Итак, выполните упражнение 4.2.

    Упражнение 4.2. Функция
    getopt
     

    В этом упражнении вы используете функцию getopt; назовите новую программу argopt.c.

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>


    int main(int argc, char *argv[]) {

     int opt;

     while ((opt = getopt(argc, argv, ":if:lr")) != -1) {

      switch(opt) {

      case 'i':

      case 'l':

      case 'r':

       printf("option: %c\n", opt);

       break;

      case 'f':

       printf("filename: %s\n", optarg);

       break;

      case ':':

       printf("option needs a value\n");

       break;

      case '?':

       printf("unknown option: %c\n", optopt);

       break;

      }

     }

     for (; optind < argc; optind++)

      printf("argument: %s\n", argv[optind]);

     exit(0);

    }

    Теперь, когда вы выполните программу, то увидите, что все аргументы командной строки обрабатываются автоматически:

    $ ./argopt -i -lr 'hi there' -f fred.с -q

    option: i

    option: l

    option: r

    filename: fred.c

    unknown option: q

    argument: hi there

    Как это работает

    Программа многократно вызывает функцию

    getopt
    для обработки аргументов-опций до тех пор, пока не останется ни одного, в этот момент
    getopt
    вернет -1. Для каждой опции выбирается подходящее действие, включая обработку неизвестных опций и пропущенных значений. Если у вас другая версия
    getopt
    , то вы получите вывод, слегка отличающийся от показанного, — особенно сообщения об ошибках — но смысл будет понятен.

    Когда все опции обработаны, программа просто выводит оставшиеся аргументы, как и раньше, но начиная с номера, хранящегося в переменной

    optind
    .

    getopt_long

    Многие приложения Linux принимают более информативные аргументы, чем использованные в предыдущем примере односимвольные опции. Библиотека С проекта GNU содержит версию функции

    getopt
    , названную
    getopt_long
    , которая принимает так называемые длинные аргументы, которые вводятся с помощью двойного дефиса.

    Рассмотрим упражнение 4.3.

    Упражнение 4.3. Функция
    getopt_long

    Примените функцию

    getopt_long
    для создания новой версии примера программы, которая может вызываться с использованием длинных эквивалентов опций, например, следующих:

    $ ./longopt --initialize --list 'hi there' --file fred.c -q

    option: i

    option: l

    filename: fred.c

    ./longopt: invalid option --q

    unknown option: q

    argument: hi there

    На самом деле и новые длинные опции, и исходные односимвольные можно смешивать. Длинным опциям также можно давать сокращенные названия, но они

    должны отличаться от односимвольных опций. Длинные опции с аргументом можно задавать как единый аргумент в виде --опция= значение, как показано далее:

    $ ./longopt --init -l --file=fred.с 'hi there'

    option: i

    option: l

    filename: fred.с

    argument: hi there

    Далее приведена новая программа longopt.c, полученная из программы argopt.c с изменениями, обеспечивающими поддержку длинных опций, которые в тексте программы выделены цветом.

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>


    #define _GNU_SOURCE

    #include <getopt.h>


    int main(int argc, char *argv[]) {

     int opt;

     struct option_longopts[] = {

      {"initialize", 0. NULL, 'i'},

      {"file" 1, NULL, 'f'},

      {"list", 0, NULL, 'l'},

     {0, 0, 0, 0}};

     while ((opt = getopt_long(argc, argv, ":if:lr, longopts, NULL)) != -1) {

      switch(opt) {

      case 'i':

      case 'l':

      case 'r':

       printf("option: %c\n", opt);

       break;

      case 'f':

       printf("filename: %s\n", optarg);

       break;

      case ':':

       printf("option needs a value\n");

       break;

      case '?':

       printf("unknown option: %c\n", optopt);

       break;

      }

     }

     for (; optind < argc; optind++)

      printf("argument: %s\n", argv[optind]);

     exit(0);

    }

    Как это работает

    Функция

    getopt_long
    принимает два дополнительных параметра по сравнению с функцией
    getopt
    . Первый из них — массив структур, описывающий длинные опции и сообщающий функции
    getopt_long
    способ их обработки. Второй дополнительный параметр — адрес переменной, которая может использоваться как вариант
    optind
    , предназначенный для длинных опций; для каждой распознанной длинной опции ее номер в массиве длинных опций может быть записан в эту переменную. В данном примере вам не нужна эта информация, поэтому вы используете
    NULL
    в качестве значения второго дополнительного параметра.

    Массив длинных опций состоит из ряда структур типа

    struct option
    , в каждой из которых описано требуемое поведение длинной опции. Массив должен заканчиваться структурой, содержащей все нули.

    Структура длинной опции определена в заголовочном файле getopt.h и должна подключаться с помощью константы

    _GNU_SOURCE
    , определенной для того, чтобы разрешить использование функции
    getopt_long
    .

    struct option {

     const char *name;

     int has_arg;

     int *flag;

     int val;

    };

    Элементы структуры описаны в табл. 4.1.


    Таблица 4.1.

    Параметр опции Описание
    name
    Название длинной опции. Сокращения будут приниматься до тех пор, пока они не создадут путаницы при определении названий других опций
    has_arg
    Принимает ли эта опция аргумент. Задайте 0 для опций без аргументов, 1 для опций, у которых должно быть значение, и 2 для опций с необязательным аргументом
    flag
    Задайте
    NULL
    , чтобы
    getopt_long
    вернула при обнаружении данной опции значение, заданное в
    val
    . В противном случае
    getopt_long
    возвращает 0 и записывает значение
    val
    в переменную, на которую указывает
    flag
    val
    Значение
    getopt_long
    для данной опции, предназначенное для возврата

    Для получения сведений о других опциях, связанных с расширениями функции

    getopt
    в проекте GNU и родственных функциях, см. страницы интерактивного справочного руководства к функции
    getopt
    .

    Переменные окружения

    Мы обсуждали переменные окружения в главе 2. Это переменные, которые могут использоваться для управления поведением сценариев командной оболочки и других программ. Вы также можете применять их для настройки пользовательской среды. Например, у каждого пользователя есть переменная окружения

    HOME
    , определяющая его исходный каталог, стандартное место старта его или ее сеанса. Как вы видели, просмотреть переменные окружения можно из строки приглашения командной оболочки:

    $ echo $НOМЕ

    /home/neil

    Вы также можете воспользоваться командой оболочки

    set
    для получения списка всех переменных окружения.

    В спецификации UNIX определено множество стандартных переменных окружения, применяемых для самых разных целей, включая тип терминала, имена редакторов, установленных по умолчанию, названия часовых поясов и т.д. Программа на языке С может получить доступ к переменным окружения с помощью функций

    putenv
    и
    getenv
    .

    #include <stdlib.h>

    char *getenv(const char *name);

    int putenv(const char *string);

    Окружение состоит из строк вида

    имя=значение
    . Функция
    getenv
    ищет в окружении строку с заданным именем и возвращает значение, ассоциированное с этим именем. Она вернет
    NULL
    , если требуемая переменная не существует. Если переменная есть, но ее значение не задано, функция
    getenv
    завершится успешно и вернет пустую строку, в которой первый байт равен
    NULL
    . Строка, возвращаемая
    getenv
    , хранится в статической памяти, принадлежащей функции, поэтому для ее дальнейшего использования вы должны скопировать эту строку в другую, поскольку она может быть перезаписана при последующих вызовах функции
    getenv

    Функция

    putenv
    принимает строку вида
    имя=значение
    и добавляет ее в текущее окружение. Она даст сбой и вернет -1, если не сможет расширить окружение из-за нехватки свободной памяти. Когда это произойдет, переменной
    errno
    будет присвоено значение
    ENOMEM
    .

    В упражнении 4.4 вы напишeте программу для вывода значения любой выбранной вами переменной окружения. У вас также будет возможность задать значение, если вы укажете второй аргумент программы.

    Упражнение 4.4. Функции
    getenv
    и
    putenv

    1. Первые несколько строк после объявления функции

    main
    гарантируют корректный вызов программы environ.c с только одним или двумя аргументами:

    #include <stdlib.h>

    #include <stdio.h>

    #include <string.h>


    int main(int argc, char *argv[]) {

     char *var, *value;

     if (argc == 1 || argc > 3) {

      fprintf(stderr, "usage: environ var [value]\n");

      exit(1);

     }

    2. Сделав это, вы извлекаете значение переменной из окружения с помощью функции

    getenv
    :

     var = argv[1];

     value = getenv(var);

     if (value)

      printf("Variable %s has value %s\n", var, value);

     else

      printf("Variable %s has no value\n", var);

    3. Далее проверьте, был ли при вызове программы указан второй параметр. Если был, вы задаете значение этого аргумента, конструируя строку вида

    имя=значение
    и затем вызывая функцию
    putenv
    :

     if (argc == 3) {

      char *string;

      value = argv[2];

      string = malloc(strlen(var)+strlen(value)+2);

      if (!string} {

       fprintf(stderr, "out of memory\n");

       exit(1);

      }

      strcpy(string, var);

      strcat(string, "=");

      strcat(string, value);

      printf("Calling putenv with: %s\n", string);

      if (putenv(string) != 0) {

       fprintf(stderr, "putenv failed\n");

       free(string);

       exit(1);

      }

    4. В заключение вы узнаете новое значение переменной, вызвав функцию getenv еще раз:

      value = getenv(var);

      if (value)

       printf("New value of %s is %s\n", var, value);

      else

       printf("New value of %s is null??\n", var);

     }

     exit(0);

    }

    Когда вы выполните эту программу, то сможете увидеть и задать переменные окружения:

    $ ./environ НОМЕ

    Variable HOME has value /home/neil

    $ ./environ FRED

    Variable FRED has no value

    $ ./environ FRED hello

    Variable FRED has no value

    Calling putenv with: FRED=hello

    New value of FRED is hello

    $ ./environ FRED

    Variable FRED has no value

    Обратите внимание на то, что окружение локально по отношению к программе. Изменения, которые вы делаете в программе, не отражаются вне ее, поскольку значения переменных не передаются из дочернего процесса (вашей программы) в родительский (командную оболочку).

    Применение переменных окружения

    Программы часто применяют переменные окружения для изменения способа своей работы. Пользователи могут задать значения этих переменных окружения либо в их стандартном окружении с помощью файла .profile, читаемого их регистрационной командной оболочкой, использующей специальный файл запуска (rc) оболочки, либо заданием переменных в командной строке командной оболочки. Например,

    $ ./environ FRED

    Variable FRED has no value

    $ FRED=hello ./environ FRED

    Variable FRED has value hello

    Командная оболочка принимает начальные присвоения значений переменным как временные изменения переменных окружения. Во второй части предыдущего примера программа

    environ
    выполняется в окружении, где у переменной
    FRED
    есть значение.

    Например, в будущей версии приложения, управляющего базой данных компакт-дисков, вы сможете изменить переменную окружения, скажем

    CDDB
    , обозначающую базу данных, которую нужно использовать. Каждый пользователь затем сможет задать собственное значение по умолчанию или применить команду оболочки для задания значения при очередном выполнении приложения:

    $ CDDB=mycds; export CDDB

    $ cdapp

    или

    $ CDDB=mycds cdapp

    Примечание

    Переменные окружения — противоречивое благо, и их следует применять с осторожностью. Эти переменные более "закрыты" от пользователя, чем опции командной строки, и это может затруднить отладку. По смыслу переменные окружения подобны глобальным переменным, поэтому они могут изменять поведение программы, что порой приводит к неожиданным результатам.

    Переменная environ

    Как вы уже знаете, окружение программы формируется из строк вида имя=значение. Этот массив строк становится доступен программе непосредственно из переменной environ, которая объявляется, как

    #include <stdlib.h>

    extern char **environ;

    Выполните упражнение 4.5.

    Упражнение 4.5. Переменная
    environ

    Далее приведена программа showenv.c, использующая переменную environ для вывода переменных окружения.

    #include <stdlib.h>

    #include <stdio.h>


    extern char **environ;


    int main() {

     char **env = environ;

     while (*env) {

      printf("%s\n", *env);

      env++;

     }

     exit(0);

    }

    Когда вы выполните программу в системе Linux, то получите нечто, похожее на следующий вывод, который немного сокращен. Количество, порядок отображения и значения этих переменных зависят от версии операционной системы, применяемой командной оболочки и настроек пользователя в момент выполнения программы.

    $ ./showenv

    HOSTNAME=tilde.provider.com

    LOGNAME=neil

    MAIL=/var/spool/mail/neil

    TERM=xterm

    HOSTTYPE=i386

    PATH=/usr/local/bin:/bin:/usr/bin:

    HOME=/usr/neil

    LS_OPTIONS=-N --color=tty -T 0

    SHELL=/bin/bash

    OSTYPE=Linux

    ...

    Как это работает

    Для вывода всего окружения программа в цикле обращается к переменной

    environ
    — массиву нуль-терминированных строк.

    Время и дата

    Программе часто полезно иметь возможность определить время и дату. Возможно, она хочет зарегистрировать длительность собственного выполнения или ей нужно изменять свое поведение в определенные моменты времени. Например, игра может отказываться запускаться в рабочие часы или программа резервного копирования по расписанию хочет дождаться ранних часов, прежде чем начать резервное копирование в автоматическом режиме.

    Примечание

    Во всех системах UNIX применяется одна и та же точка отсчета времени и дат: полночь по Гринвичу (GMT) на 1 января 1970 г. Это "начало эпохи UNIX", и ОС Linux — не исключение. Время в системе Linux измеряется в секундах, начиная с этого момента времени. Такой способ обработки аналогичен принятому в системе MS-DOS за исключением того, что эпоха MS-DOS началась в 1980 г. В других системах применяют точки отсчета иных эпох.

    Время задается с помощью типа

    time_t
    . Это целочисленный тип, достаточный для хранения дат и времени в секундах. В Linux-подобных системах это тип
    long integer
    (длинное целое), определенный вместе с функциями, предназначенными для обработки значений времени, в заголовочном файле time.h.

    Примечание

    Не думайте, что для хранения времени достаточно 32 битов. В системах UNIX и Linux, использующих 32-разрядный тип

    time_t
    , временное значение "будет превышено" в 2038 г. Мы надеемся, что к тому времени системы перейдут на тип
    time_t
    , содержащий более 32 битов. Недавнее широкое внедрение 64-разрядных процессоров превращает это практически в неизбежность.

    #include <time.h>

    time_t time(time_t *tloc);

    Вы можете найти низкоуровневое значение времени, вызвав функцию

    time
    , которая вернет количество секунд с начала эпохи (упражнение 4.6). Она также запишет возвращаемое значение по адресу памяти, на который указывает параметр
    tloc
    , если он — непустой указатель.

    Упражнение 4. Функция
    time

    Далее для демонстрации функции time приведена простая программа envtime.c.

    #include <time.h>

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>


    int main() {

     int i;

     time_t the_time;

     for (i = 1; i <= 10; i++) {

      the_time = time((time_t *)0);

      printf("The time is %ld\n", the_time);

      sleep(2);

     }

     exit(0);

    }

    Когда вы запустите программу, она будет выводить низкоуровневое значение времени каждые 2 секунды в течение 20 секунд.

    $ ./anytime

    The time is 1179643852

    The time is 1179643854

    The time is 1179643856

    The time is 1179643858

    The time is 1179643860

    The time is 1179643862

    The time is 1179643864

    The time is 1179643866

    The time is 1179643868

    The time is 1179643870

    Как это работает

    Программа вызывает функцию

    time
    с пустым указателем в качестве аргумента, которая возвращает время и дату как количество секунд. Программа засыпает на две секунды и повторяет вызов time в целом 10 раз.

    Использование времени и даты в виде количества секунд, прошедших с начала 1970 г., может быть полезно для измерения длительности чего-либо. Вы сможете сосчитать простую разность значений, полученных из двух вызовов функции

    time
    . Однако комитет, разрабатывавший стандарт языка ISO/ANSI С, в своих решениях не указал, что тип
    time_t
    будет применяться для определения произвольных интервалов времени в секундах, поэтому была придумана функция
    difftime
    , которая вычисляет разность в секундах между двумя значениями типа
    time_t
    и возвращает ее как величину типа
    double
    :

    #include <time.h>

    double difftime(time_t time1, time_t time2);

    Функция

    difftime
    вычисляет разницу между двумя временными значениями и возвращает величину, эквивалентную выражению
    время1–время2
    ,
    как число с плавающей точкой. В ОС Linux значение, возвращаемое функцией
    time
    , — это количество секунд, которое может обрабатываться, но для максимальной переносимости следует применять функцию
    difftime
    .

    Для представления времени и даты в более осмысленном (с человеческой точки зрения) виде мы должны преобразовать значение времени в понятные время и дату. Для этого существуют стандартные функции.

    Функция

    gmtime
    подразделяет низкоуровневое значение времени на структуру, содержащую более привычные поля:

    #include <time.h>

    struct tm *gmtime(const time_t timeval)

    В структуре

    tm
    , как минимум, определены элементы, перечисленные в табл. 4.2.


    Таблица 4.2

    Элемент
    tm
    Описание
    int tm_sec
    Секунды, 0–61
    int tm_min
    Минуты, 0–59
    int tm_hour
    Часы, 0–23
    int tm_mday
    День в месяце, 1–31
    int tm_mon
    Месяц в году, 0–11 (January (январь) соответствует 0)
    int tm_year
    Годы, начиная с 1900 г.
    int tm_wday
    День недели, 0–6 (Sunday (воскресенье) соответствует 0)
    int tm_yday
    День в году, 0–365
    int tm_isdst
    Действующее летнее время

    Диапазон элемента

    tm_sec
    допускает появление время от времени корректировочной секунды или удвоенной корректировочной секунды.

    Выполните упражнение 4.7.

    Упражнение 4.7. Функция
    gmtime

    Далее приведена программа gmtime.с, выводящая текущие время и дату с помощью структуры

    tm
    и функции
    gmtime
    .

    #include <time.h>

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     struct tm *tm_ptr;

     time_t the_time;

     (void)time(&the_time);

     tm_ptr = gmtime(&the_time);

     printf("Raw time is %ld\n", the_time);

     printf("gmtime gives:\n");

     printf("date: %02d/%02d/%02d\n",

    tm_ptr->tm_year, tm_ptr->tm_mon+1, tm_ptr->tm_mday);

     printf("time: %02d:%02d:%02d\n",

      tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec);

     exit(0);

    }

    Выполнив эту программу, вы получите хорошее соответствие текущим времени и дате:

    $ ./gmtime; date

    Raw time is 1179644196

    gmtime gives:

    date: 107/05/20

    time: 06:56:36

    Sun May 20 07:56:37 BST 2007

    Как это работает

    Программа вызывает функцию

    time
    для получения машинного представления значения времени и затем вызывает функцию gmtime для преобразования его в структуру с удобными для восприятия значениями времени и даты. Она выводит на экран полученные значения с помощью функции
    printf
    . Строго говоря, выводить необработанное значение времени таким способом не следует, потому что наличие типа длинного целого не гарантировано во всех системах. Если сразу же после вызова функции gmtime выполнить команду date, можно сравнить оба вывода.

    Но здесь у вас возникнет небольшая проблема. Если вы запустите эту программу в часовом поясе, отличном от Greenwich Mean Time (время по Гринвичу) или у вас действует летнее время, как у нас, вы заметите, что время (и, возможно, дата) неправильное. Все дело в том, что функция

    gmtime
    возвращает время по Гринвичу (теперь называемое Universal Coordinated Time (всеобщее скоординированное время) или UTC). Системы Linux и UNIX поступают так для синхронизации всех программ и систем в мире. Файлы, созданные в один и тот же момент в разных часовых поясах, будут отображаться с одинаковым временем создания. Для того чтобы посмотреть местное время, следует применять функцию
    localtime
    .

    #include <time.h>

    struct tm *localtime(const time_t *timeval);

    Функция

    localtime
    идентична функции
    gmtime
    за исключением того, что она возвращает структуру, содержащую значения с поправками на местный часовой пояс и действующее летнее время. Если вы выполните программу
    gmtime
    , но замените все вызовы функции
    gmtime
    на вызовы
    localtime
    , в отчете программы вы увидите правильные время и дату.

    Для преобразования разделенной на элементы структуры tm в общее внутреннее значение времени можно применить функцию

    mktime
    :

    #include <time.h>

    time_t mktime(struct tm *timeptr);

    Функция

    mktime
    вернет -1, если структура не может быть представлена как значение типа
    time_t
    .

    Для вывода программой

    date
    "дружественных" (в противоположность машинному) времени и даты можно воспользоваться функциями
    asctime
    и
    ctime
    :

    #include <time.h>

    char *asctime(const struct tm *timeptr);

    char *ctime(const time_t *timeval);

    Функция

    asctime
    возвращает строку, представляющую время и дату, заданные
    tm
    -структурой
    timeptr
    . У возвращаемой строки формат, подобный приведенному далее:

    Sun Jun  9 12:34:56 2007\n\0

    У нее всегда фиксированный формат длиной 26 символов. Функция

    ctime
    эквивалентна следующему вызову:

    asctime(localtime(timeval))

    Она принимает необработанное машинное значение времени и преобразует его в местное время.

    А теперь выполните упражнение 4.8.

    Упражнение 4.8. Функция
    ctime

    В этом примере благодаря приведенному далее программному коду вы увидите функцию

    ctime
    в действии.

    #include <time.h>

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     time_t timeval;

     (void)time(&timeval);

     printf ("The date is: %s", ctime(&timeval));

     exit(0);

    }

    Откомпилируйте и затем запустите на выполнение ctime.c, и вы увидите нечто похожее на приведенные далее строки:

    $ ./ctime

    The date is: Sat Jun 9 08:02:08 2007.

    Как это работает

    Программа ctime.c вызывает функцию

    time
    для получения машинного значения времени и дает возможность функции
    ctime
    выполнить всю тяжелую работу по преобразованию этого значения в удобочитаемую строку, которую потом и выводит на экран.

    Для лучшего управления точным форматированием времени и даты ОС Linux и современные UNIX-подобные системы предоставляют функцию

    strftime
    . Она довольно похожа на функцию
    sprintf
    для дат и времени и действует аналогичным образом:

    #include <time.h>

    size_t strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr);

    Функция

    strftime
    форматирует время и дату, представленные в структуре
    tm
    , на которую указывает параметр,
    timeptr
    , и помещает результат в строку
    s
    . Эта строка задается длиной
    maxsize
    (как минимум) символов. Строка
    format
    применяется для управления символами, записываемыми в строку. Как и в функции
    printf
    , она содержит обычные символы, которые будут переданы в строку, и спецификаторы преобразований для форматирования элементов времени и даты. В табл. 4.3 перечислены используемые спецификаторы преобразований.


    Таблица 4.3

    Спецификатор преобразования Описание
    %a
    Сокращенное название дня недели
    Полное название дня недели
    %b
    Сокращенное название месяца
    %B
    Полное название месяца
    %c
    Дата и время
    %d
    День месяца, 01–31
    %H
    Час, 00–23
    %I
    Час по 12-часовой шкале, 01–12
    %j
    День в году, 001–366
    %m
    Номер месяца в году, 01–12
    %M
    Минуты, 00–59
    %p
    a.m. (до полудня) или p.m. (после полудня)
    %S
    Секунды, 00–59
    %u
    Номер дня недели, 1–7 (1 соответствует понедельнику)
    %U
    Номер недели в году, 01–53 (воскресенье — первый день недели)
    %V
    Номер недели в году, 01–53 (понедельник — первый день недели)
    %w
    Номер дня недели, 0–6 (0 соответствует воскресенью)
    %x
    Дата в региональном формате
    %X
    Время в региональном формате
    %y
    Номер года, меньший 1900
    %Y
    Год
    %Z
    Название часового пояса
    %%
    Символ
    %

    Таким образом, обычная дата, такая же, как полученная из программы date, соответствует следующей строке формата функции

    strftime
    :

    "%a %b %d %Н: %М: %S %Y"

    Для облегчения чтения дат можно использовать функцию

    strptime
    , принимающую строку с датой и временем и формирующую структуру
    tm
    с теми же датой и временем:

    #include <time.h>

    char *strptime(const char *buf, const char *format, struct tm *timeptr);

    Строка

    format
    конструируется точно так же, как одноименная строка функции
    strftime
    . Функций
    strptime
    действует аналогично функции
    sscanf
    : она сканирует строку в поиске опознаваемых полей и записывает их в переменные. В данном случае это элементы структуры
    tm
    , которая заполняется в соответствии со строкой
    format
    . Однако спецификаторы преобразований для
    strptime
    немного мягче спецификаторов функции
    strftime
    . Так, в функции
    strptime
    разрешены как сокращенные, так и полные названия дней и месяцев. Любое из этих представлений будет соответствовать спецификатору %a функции
    strptime
    . Кроме того, в то время как функция
    strftime
    для представления чисел, меньших 10, всегда применяет ведущие нули,
    strptime
    считает их необязательными.

    Функция

    strptime
    возвращает указатель на символ, следующий за последним, обработанным в процессе преобразования. Если она встречает символы, которые не могут быть преобразованы, в этой точке преобразование просто прекращается. Для того чтобы убедиться в том, что в структуру
    tm
    записаны значимые данные, вызывающей программе следует проверять, достаточно ли символов строки принято и обработано.

    Рассмотрим работу функций на примере (упражнение 4.9).

    Упражнение 4.9. Функции
    strftime
    и
    strptime

    Обратите внимание на выбор спецификаторов преобразований, использованных в следующей программе:

    #include <time.h>

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     struct tm *tm_ptr, timestruct;

     time_t the_time;

     char buf[256];

     char *result;

     (void)time(&the_time);

     tm_ptr = localtime(&the_time);

     strftime(buf, 256, "%A %d %B, %I:%S %p", tm_ptr);

     printf("strftime gives: %s\n", buf);

     strcpy(buf, "Thu 26 July 2007, 17:53 will do fine");

     printf("calling strptime with: %s\n", buf);

     tm_ptr = &timestruct;

     result = strptime(buf, "%a %d %b %Y, %R", tm_ptr);

     printf("strptime consumed up to: %s\n", result);

     printf("strptime gives:\n");

     printf ("date: %02d/%02d/%02d\n",

      tm_ptr->tm_year % 100, tm_ptr->tm_mon+1, tm_ptr->tm_mday);

     printf("time: %02d:%02d\n",

      tm_ptr->tm_hour, tm->ptr->tm_min);

     exit(0);

    }

    Когда вы откомпилируете и выполните программу strftime.c, то получите следующий результат:

    $ ./strftime

    strftime gives: Saturday 09 June, 08:16 AM

    calling strptime with: Thu 26 July 2007, 17:53 will do fine

    strptime concurred up to: will do fine

    strptime gives:

    date: 07/07/26

    time: 17:53

    Как это работает

    Программа strftime получает текущее местное время с помощью вызовов функций

    time
    и
    localtime
    . Затем она преобразует его в удобочитаемую форму с помощью функции
    strftime
    с подходящим аргументом форматирования. Для демонстрации применения функции
    strptime
    программа задает строку, содержащую дату и время, затем вызывает strptime для извлечения необработанных значений времени и даты и выводит их на экран. Спецификатор преобразования
    %R
    функции
    strptime
    — это сокращенное обозначение комбинации
    %Н:%M
    .

    Важно отметить, что для успешного просмотра даты функции strptime необходима точная строка формата. Обычно она не может точно обработать даты, считываемые из строк, введенных пользователями, до тех пор, пока не будет строго выверен формат.

    Возможно, при компиляции программы strftime.c вы получите предупреждение компилятора. Причина в том, что по умолчанию в библиотеке GNU не объявлена функция

    strptime
    . Для устранения проблемы следует явно запросить средства стандарта X/Open, добавив следующую строку перед заголовочным файлом time.h:

    #define _XOPEN_SOURCE

    Временные файлы

    Зачастую программы нуждаются в возможности использования временного хранилища в виде файлов. В них могут храниться промежуточные результаты вычислений, или резервные копии файлов, сделанные перед выполнением критических операций. Например, приложение для работы с базой данных может применять временный файл при удалении записей. В файле собираются элементы базы данных, нуждающиеся в сохранении, и затем в конце процесса временный файл становится новой базой данных, а исходная база данных удаляется.

    У столь популярных временных файлов есть скрытый недостаток. Вы должны следить за тем, чтобы приложения выбирали уникальное имя для временного файла. Если это условие не соблюдается, могут возникнуть проблемы. Поскольку ОС Linux — многозадачная система, другая программа может выбрать то же самое имя, и обе будут мешать друг другу.

    Уникальное имя файла генерируется с помощью функции

    tmpnam
    :

    #include <stdio.h>

    char *tmpnam(char *s);

    Функция

    tmpnam
    возвращает допустимое имя файла, не совпадающее с именем любого из существующих файлов. Если строка
    s
    не равна
    NULL
    , в нее будет записано имя файла. Последующие вызовы функции tmpnam будут перезаписывать статическую память, используемую для возвращаемых значений, поэтому важно применять строковый параметр, если функция должна вызываться многократно. Длина строки полагается равной, как минимум,
    L_tmpnam
    (обычно около 20) символам. Функция
    tmpnam
    может вызываться в одной программе до
    TMP_MAX
    (не менее нескольких тысяч) раз, и каждый раз она будет генерировать уникальное имя файла.

    Если, временный файл предполагается использовать немедленно, вы можете одновременно назвать его и открыть с помощью функции

    tmpfile
    . Это важно, т.к. другая программа может создать файл с именем таким же, как значение, возвращенное функцией
    tmpnam
    . Функция
    tmpfile
    устраняет эту проблему полностью.

    #include <stdio.h>

    FILE* tmpfile(void);

    Функция tmpfile возвращает указатель потока, ссылающийся на уникальный временный файл. Файл открыт для чтения и записи (с помощью

    fopen
    с флагом
    w+
    ) и будет автоматически удален, когда закроются все ссылки на него.

    В случае возникновения ошибки

    tmpfile
    вернет указатель
    NULL
    и задаст значение переменной
    errno
    .

    Давайте посмотрим эти две функции в действии:

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     char tmpname[L_tmpnam];

     char* filename;

     FILE *tmpfp;

     filename = tmpnam(tmpname);

     printf("Temporary file name is: %s\n", filename);

     tmpfp = tmpfile();

     if (tmpfp) printf("Opened a temporary file OK\n");

     else perror("tmpfile");

     exit(0);

    }

    Когда вы откомпилируете и выполните программу tmpnam.с, то увидите уникальное имя файла, сгенерированное функцией

    tmpnam
    :

    $ ./tmpnam

    Temporary file name is: /tmp/file2S64zc

    Opened a temporary file OK

    Как это работает

    Программа вызывает функцию

    tmpnam
    для генерации уникального имени временного файла. Если вы хотите его использовать, нужно быстро его открыть, чтобы минимизировать риск того, что другая программа откроет файл с тем же именем. Вызов функции
    tmpfile
    одновременно создает и открывает временный файл, тем самым устраняя этот риск. При компиляции программы, использующей функцию
    tmpnam
    , компилятор GNU С может вывести предупреждение о применении этой функции.

    В некоторых версиях UNIX предлагается другой способ генерации имен временных файлов — с помощью функций

    mktemp
    и
    mkstemp
    . Они поддерживаются и ОС Linux и аналогичны функции
    tmpnam
    за исключением того, что вы должны задать шаблон имени временного файла, который предоставляет некоторые дополнительные возможности управления местом хранения и именем файла.

    #include <stdlib.h>

    char *mktemp(char *template);

    int mkstemp(char *template);

    Функция mktemp создает уникальное имя файла на основе заданного шаблона

    template
    . Аргумент
    template
    должен быть строкой с шестью завершающими символами
    Х
    .
    mktemp
    заменяет эти символы
    Х
    уникальной комбинацией символов, допустимых в именах файлов. Она возвращает указатель на сгенерированную строку или
    NULL
    при невозможности сформировать уникальное имя.

    Функция

    mkstemp
    аналогична функции
    tmpfile
    : она создает и открывает временный файл. Имя файла генерируется так же, как в функции
    mktemp
    , но возвращенный результат — открытый низкоуровневый дескриптор файла.

    Примечание

    В ваших собственных программах следует всегда применять функции "создать и открыть"

    tmpfile
    и
    mkstemp
    вместо функций
    tmpnam
    и
    mktemp
    .

    Информация о пользователе

    Все программы в ОС Linux за исключением программы init, запускаются другими программами или пользователями. В главе 11 вы узнаете больше о взаимодействии выполняющихся программ или процессов. Пользователи чаще всего запускают программы из командной оболочки, реагирующей на их команды. Вы видели, что в программе можно определить собственное окружение, просматривая переменные окружения и читая системные часы. Программа может также выяснить данные о пользователе, применяющем ее.

    Когда пользователь регистрируется в системе Linux, у него или у нее есть имя пользователя и пароль. После того как эти данные проверены, пользователю предоставляется командная оболочка. В системе у пользователя также есть уникальный идентификатор пользователя, называемый UID (user identifier). Каждая программа, выполняемая Linux, запускается от имени пользователя и имеет связанный с ней UID.

    Вы можете настроить выполнение программ так, как будто они запускаются другим пользователем. Если у программы есть свой набор прав доступа для UID, она будет выполняться от имени владельца исполняемого файла. Когда выполнена команда su, программа действует так, как будто она запущена суперпользователем. Затем она проверяет право доступа пользователя, изменяет UID на идентификатор назначенной учетной записи и запускает регистрационную командную оболочку данной учетной записи. Этот прием позволяет программе выполняться от имени другого пользователя и часто используется системными администраторами для выполнения задач технического обслуживания системы.

    Поскольку UID — это ключевой параметр для идентификации пользователя, начнем с него.

    У UID есть свои тип

    uid_t
    , определенный в файле sys/types.h. Обычно это короткое целое (small integer). Одни идентификаторы пользователя заранее определены системой, другие создаются системным администратором, когда новые пользователи становятся известны системе. Как правило, идентификаторы пользователей имеют значения, большие 100.

    #include <sys/types.h>

    #include <unistd.h>

    uid_t getuid (void);

    char *getlogin(void);

    Функция

    getuid
    возвращает UID, с которым связана программа. Обычно это UID пользователя, запустившего программу.

    Функция

    getlogin
    возвращает регистрационное имя, ассоциированное с текущим пользователем.

    Системный файл /etc/passwd содержит базу данных, имеющую дело с учетными записями пользователей. Он состоит из строк по одной на каждого пользователя, в каждую строку включены имя пользователя, зашифрованный пароль, идентификатор пользователя (UID), идентификатор группы (GID), полное имя, исходный каталог и командная оболочка, запускаемая по умолчанию. Далее приведен пример такой строки:

    neil:zBqxfqedfpk:500:100:Neil Matthew:/home/neil:/bin/bash

    Если вы пишете программу, которая определяет UID пользователя, запустившего ее, то можете расширить ее возможности и заглянуть в файл passwd для выяснения регистрационного имени пользователя и его полного имени. Мы не рекомендуем делать это, потому что современные UNIX-подобные системы уходят от применения файлов учетных записей пользователей для повышения безопасности системы. Многие системы, включая Linux, имеют возможность использовать файлы теневых паролей (shadow password), совсем не содержащие пригодной информации о зашифрованных паролях (она часто хранится в файле /etc/shadow, которые обычные пользователи не могут читать). По этой причине определен ряд функций для предоставления эффективного программного интерфейса, позволяющего получать эту пользовательскую информацию.

    #include <sys/types.h>

    #include <pwd.h>

    struct passwd *getpwuid(uid_t uid);

    struct passwd *getpwnam(const char *name);

    Структура базы данных учетных записей пользователей

    passwd
    определена в файле pwd.h и включает элементы, перечисленные в табл. 4.4.


    Таблица 4.4

    Элемент
    passwd
    Описание
    char *pw_name
    Регистрационное имя пользователя
    uid_t pw_uid
    Номер UID
    gid_t pw_gid
    Номер GID
    char *pw_dir
    Исходный каталог пользователя
    char *pw_gecos
    Полное имя пользователя
    char *pw_shell
    Командная оболочка пользователя, запускаемая по умолчанию

    В некоторых системах UNIX может использоваться другое имя для поля с полным именем пользователя: в одних системах это

    pw_gecos
    , как в ОС Linux, в других —
    pw_comment
    . Это означает, что мы не можем рекомендовать его использование. Обе функции (и
    getpwuid
    , и
    getpwnam
    ) возвращают указатель на структуру
    passwd
    , соответствующую пользователю. Пользователь идентифицируется по UID в функции
    getpwuid
    и по регистрационному имени в функции
    getpwnam
    . В случае ошибки обе функции вернут пустой указатель и установят переменную
    errno
    .

    Выполните упражнение 4.11.

    Упражнение 4.11. Информации о пользователе

    В этом упражнении показана программа user.c, извлекающая некоторую информацию о пользователе из базы данных учетных записей.

    #include <sys/types.h>

    #include <pwd.h>

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>


    int main() {

     uid_t uid;

     gid_t gid;

     struct passwd *pw;

     uid = getuid();

     gid = getgid();

     printf("User is %s\n", getlogin());

     printf("User IDs: uid=%d, gid=%d\n", uid, gid);

     pw = getpwuid(uid);

     printf(

      "UID passwd entry:\n name=%s, uid=%d, gid=%d, home=%s, shell=%s\n",

      pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir, pw->pw_shell);

     pw = getpwnam("root");

     printf("root passwd entry:\n");

     printf("name=%s, uid=%d, gid=%d, home=%s, shell=%s\n",

      pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir, pw->pw_shell);

     exit(0);

    }

    Программа предоставит следующий вывод, который может слегка отличаться в разных версиях Linux и UNIX:

    $ ./user

    User is neil

    User IDs: uid=1000, gid=100

    UID passwd entry:

    name=neil, uid=1000, gid=100, home=/home/neil, shell=/bin/bash

    root passwd entry:

    name=root, uid=0, gid=0, home=/root, shell=/bin/bash

    Как это работает

    Эта программа вызывает функцию

    getuid
    для получения UID текущего пользователя, Этот UID применяется в функции
    getpwuid
    для получения подробной информации из файла учетных записей пользователей. В качестве альтернативы мы показываем, как для извлечения информации о пользователе можно задать в функции
    getpwnam
    имя пользователя
    root
    .

    Примечание

    В исходном коде Linux вы сможете найти в команде

    id
    еще один пример-использования функции
    getuid
    .

    Для просмотра всех данных файла учетных записей пользователей можно воспользоваться функцией

    getpwent
    . Она последовательно выбирает строки файла.

    #include <pwd.h>

    #include <sys/types.h>

    void endpwent(void);

    struct passwd *getpwent(void);

    void setpwent(void);

    Функция

    getpwent
    возвращает поочередно информацию о каждом пользователе. Когда не остается ни одного, она возвращает пустой указатель. Для прекращения обработки файла, когда просмотрено достаточно элементов, вы можете применить функцию
    endpwent
    . Функция
    setpwent
    переустанавливает позицию указателя в файле учетных записей пользователей для начала нового просмотра при следующем вызове функции
    getpwent
    . Эти функции действуют так же, как функции просмотра каталога
    opendir
    ,
    readdir
    и
    closedir
    , обсуждавшиеся в главе 3.

    Идентификаторы пользователя и группы (эффективный или действующий и реальный) можно получить с помощью других реже используемых функций:

    #include <sys/types.h>

    #include <unistd.h>

    uid_t geteuid(void);

    gid_t getgid(void);

    gid_t getegid(void);

    int setuid(uid_t uid);

    int setgid(gid_t gid);

    Подробную информацию об идентификаторах группы и эффективных идентификаторах пользователей следует искать на страницах интерактивного справочного руководства системы, хотя, быть может, вы решите, что вам вообще не следует манипулировать ими.

    Примечание

    Только суперпользователь может вызывать функции

    setuid
    и
    setgid
    .

    Информация о компьютере

    Программа может установить некоторые подробные сведения о компьютере, на котором выполняется, так же, как она определяет информацию о пользователе. Подобную информацию предоставляет команда

    uname
    , в программе на языке С можно использовать для получения этих данных одноименный системный вызов — прочтите о нем в разделе системных вызовов интерактивного справочного руководства (раздел 2) с помощью команды
    man 2 uname
    .

    Сведения о рабочем компьютере могут оказаться полезными в ряде ситуаций. Вы можете захотеть настроить поведение программы в зависимости от сетевого имени машины, на которой она выполняется, скажем, на студенческом компьютере или машине администратора. Для соблюдения лицензионных соглашений вам может потребоваться ограничить выполнение программы одной машиной. Все это означает, что вам нужен способ определения компьютера, на котором выполняется программа.

    Если в системе установлены сетевые компоненты, вы очень легко можете получить сетевое имя компьютера с помощью функции

    gethostname
    :

    #include <unistd.h>

    int gethostname(char *name, size_t namelen);

    Эта функция записывает сетевое имя машины в строку

    name
    . Предполагается, что длина строки, как минимум,
    namelen
    символов. Функция
    gethostname
    возвращает 0 в случае успешного завершения и -1 в противном случае.

    Более подробную информацию о рабочем компьютере можно получить с помощью системного вызова

    uname
    .

    #include <sys/utsname.h>

    int uname(struct utsname *name);

    Функция uname записывает информацию о компьютере в структуру, на которую указывает параметр name. Структура типа utsname, определенная в файле sys/utsname.h, обязательно должна включать элементы, перечисленные в табл. 4.5.


    Таблица 4.5

    Элемент структуры
    utsname
    Описание
    char sysname[]
    Имя операционной системы
    char nodename[]
    Имя компьютера
    char release[]
    Номер выпуска (релиза) системы
    char version[]
    Номер версии системы
    char machine[]
    Аппаратный тип

    В случае успешного завершения функция

    uname
    возвращает неотрицательное целое и в противном случае с установленной переменной
    errno
    для обозначения любой возникшей ошибки.

    Выполните упражнение 4.12.

    Упражнение 4.12. Информации о компьютере

    Далее приведена программа hostget.c, извлекающая некоторые сведения о рабочем компьютере.

    #include <sys/utsname.h>

    #include <unistd.h>

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     char computer[256];

     struct utsname uts;

     if (gethostname(computer, 255) != 0 || uname(&uts) < 0) {

      fprintf(stderr, "Could not get host information\n");

      exit(1);

     }

     printf("Computer host name is %s\n", computer);

     printf("System is %s on %s hardware\n", uts.sysname, uts.machine);

     printf("Nodename is %s\n", uts.nodename);

     printf("Version is %s, %s\n", uts.release, uts.version);

     exit(0);

    }

    Она отобразит следующие зависящие от ОС Linux данные. Если ваша машина включена в сеть, то вы увидите расширенное имя компьютера, включающее обозначение сети:

    $ ./hostget

    Computer host name is suse103

    System is Linux on i686 hardware

    Nodename is suse103

    Version is 2.6.20.2-2-default, #1 SMP Fri Mar 9 21:54:10 UTC 2007

    Как это работает

    Эта программа вызывает функцию

    gethostname
    для получения имени рабочего компьютера. В приведенном примере это имя —
    suse103
    . Более подробную информацию об этом компьютере на базе Intel Pentium 4 с ОС Linux возвращает системный вызов
    uname
    . Учтите, что формат возвращаемых строк зависит от реализации, например, строка с версией системы содержит дату компиляции ядра.

    Примечание

    Другой пример применения функции

    uname
    вы можете найти в исходном коде Linux для команды
    uname
    , которая использует эту функцию.

    Уникальный идентификатор каждого рабочего компьютера можно получить с помощью функции

    gethostid
    .

    #include <unistd.h>

    long gethostid(void);

    Функция

    gethostid
    предназначена для возврата уникального значения, характеризующего рабочий компьютер. Менеджеры, следящие за соблюдением лицензионных соглашений, применяют ее для того, чтобы обеспечить функционирование программного обеспечения только на машинах с действующими лицензиями. На рабочих станциях Sun она возвращает номер, установленный в постоянной памяти во время изготовления компьютера и, таким образом, уникальный для системного оборудования. Другие системы, например Linux, возвращают значение на базе интернет-адреса машины, обычно не слишком безопасного для проверки лицензионных прав.

    Ведение системных журналов

    Многие приложения нуждаются в регистрации своей деятельности. Системные программы очень часто выводят сообщения на консоль или записывают их в регистрационный системный журнал. В этих сообщениях могут регистрироваться ошибки, предупреждения или более общая информация о состоянии системы. Например, программа su может зафиксировать тот факт, что пользователь пытался получить привилегии супер пользователя и потерпел неудачу.

    Очень часто зарегистрированные сообщения записываются в системные файлы в каталоге, предоставляемом для этой цели. Это может быть каталог /usr/admor/var/log. При типичной установке ОС Linux все системные сообщения содержатся в файле /var/log/messages, в файл /var/log/mail включены другие регистрируемые сообщения от почтовой системы, а в файле /var/log/debug могут храниться отладочные сообщения. Проверить конфигурацию своей системы можно в файле /etc/syslog.conf или /etc/syslog-ng/syslog-ng.conf в зависимости от версии Linux.

    Далее приведены некоторые примеры зарегистрированных сообщений.

    Mar 2 6 18:25:51 suse103 ifstatus: eth0 device: Advanced Micro Devices [AMD] 79c970 [PCnet32 LANCE] (rev 10)

    Mar 26 18:25:51 suse103 ifstatus: eth0 configuration: eth-id-00:0c:29:0e:91:72

    ...

    May 20 06:56:56 suse103 SuSEfirewall2: Setting up rules from /etc/sysconfig/SuSEfirewall2

    ...

    May 20 06:56:57 suse103 SuSEfirewall2: batch committing

    ...

    May 20 06:56:57 suse103 SuSEfirewall2: Firewall rules successfully set

    ...

    Jun 9 09:11:14 suse103 su: (to root) neil on /dev/pts/18 09:50:35

    В этом выводе показаны виды регистрируемых сообщений. Несколько первых отправлены непосредственно ядром Linux во время его загрузки и обнаружения установленного оборудования. Брандмауэр сообщает о своей перенастройке. И наконец, программа

    su
    извещает о том, что доступ с учетной записью суперпользователя получен пользователем neil.

    Примечание

    Для просмотра регистрируемых сообщений вы можете запросить права суперпользователя.

    Некоторые системы UNIX не предоставляют файлов с удобными для чтения сообщениями, но они снабжают администраторов средствами для чтения базы данных системных событий. См. подробности в системной документации.

    Несмотря на то, что формат и хранение системных сообщений могут отличаться, метод формирования сообщений стандартный. В спецификации UNIX представлен доступный всем программам интерфейс формирования регистрируемых сообщений с помощью функции

    syslog
    .

    #include <syslog.h>

    void syslog(int priority, const char *message, arguments...);

    Функция syslog посылает регистрируемое сообщение средству ведения системного журнала (logging facility). У каждого сообщения есть аргумент

    priority
    , полученный поразрядной операцией
    OR
    из степени важности сообщения (severity level) и типа программы, формирующей сообщение (facility value). Степень важности определяет необходимые действия, а тип программы фиксирует инициатора сообщения.

    Типы программ (из файла syslog.h) включают константу

    LOG_USER
    , применяемую для обозначения сообщения, пришедшего из приложения пользователя (по умолчанию), и константы
    LOG_LOCAL0
    ,
    LOG_LOCAL1
    , ...,
    LOG_LOCAL7
    , зарезервированные для локального администратора.

    В табл. 4.6 перечислены степени важности сообщений в порядке убывания приоритета.


    Таблица 4.6

    Приоритет Описание
    LOG_EMERG
    Кризисная ситуация
    LOG_ALERT
    Проблема с высоким приоритетом, например, повреждение базы данных
    LOG_CRIT
    Критическая ошибка, например, повреждение оборудования
    LOG_ERR
    Ошибки
    LOG_WARNING
    Предупреждение
    LOG_NOTICE
    Особые обстоятельства, требующие повышенного внимания
    LOG_INFO
    Информационные сообщения
    LOG_DEBUG
    Отладочные сообщения

    В зависимости от настройки системы сообщения типа

    LOG_EMER
    могут пересылаться всем пользователям системы, сообщения
    LOG_ALERT
    могут отправляться по электронной почте администратору, сообщения
    LOG_DEBUG
    могут игнорироваться, а сообщения других типов могут записываться в файл. Вы можете написать программу, которая применяет средство регистрации сообщений, просто вызывая функцию
    syslog
    , когда вы хотите создать регистрируемое сообщение.

    У сообщения, создаваемого

    syslog
    , есть заголовок и тело сообщения. Заголовок создается из индикатора типа программы, формирующей сообщение, и даты и времени. Тело сообщения создается из параметра
    message
    , передаваемого функции
    syslog
    , который действует как строка
    format
    функции
    printf
    . Остальные аргументы
    syslog
    используются в соответствии со спецификаторами преобразований в стиле функции
    printf
    , заданными в строке
    message
    . Дополнительно может применяться спецификатор
    %m
    для включения строки сообщения об ошибке, ассоциированной с текущим значением переменной
    errno
    . Эта возможность может оказаться полезной для регистрации сообщений об ошибках.

    Выполните упражнение 4.13.

    Упражнение 4.13. Применение функции
    syslog

    В этой программе осуществляется попытка открыть несуществующий файл.

    #include <syslog.h>

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     FILE *f;

     f = fopen("not_here", "r");

     if (!f) syslog(LOG_ERR|LOG_USER, "oops - %m\n");

     exit(0);

    }

    Когда вы откомпилируете и выполните программу syslog.с, то не увидите никакого вывода, но в конце файла /var/log/messages теперь содержится следующая строка:

    Jun 9 09:24:50 suse103 syslog: oops — No such file or directory

    Как это работает

    В данной программе вы пытаетесь открыть файл, которого нет. Когда попытка заканчивается неудачно, вы вызываете функцию

    syslog
    для записи случившегося в системный журнал.

    Обратите внимание на то, что регистрируемое сообщение не указывает, какая программа вызвала средство регистрации; оно просто констатирует тот факт, что была вызвана функция

    syslog
    с сообщением. Спецификатор преобразования
    %m
    был заменен описанием ошибки, в данном случае сообщающим об отсутствии файла. Это гораздо полезнее, чем простой отчет, содержащий внутренний номер ошибки.

    В файле syslog.h определены и другие функции, применяемые для изменения поведения средств ведения системных журналов.

    К ним относятся следующие функции:

    #include <syslog.h> void closelog(void);

    void openlog(const char *ident, int logopt, int facility);

    int setlogmask(int maskpri);

    Вы можете изменить способ представления ваших регистрируемых сообщений, вызвав функцию

    openlog
    . Это позволит задать строку
    ident
    , которая будет добавляться к вашим регистрируемым сообщениям. Вы можете применять ее для индикации программы, создавшей сообщение. Параметр
    facility
    записывает текущий принятый по умолчанию тип программы, формирующей сообщение, который будет использоваться в последующих вызовах
    syslog
    . По умолчанию устанавливается значение
    LOG_USER
    . Параметр
    logopt
    настраивает поведение будущих вызовов функции
    syslog
    . Он представляет собой результат поразрядной операции
    OR
    нулевого или большего числа параметров, приведенных в табл. 4.7.


    Таблица 4.7

    Параметр
    logopt
    Описание
    LOG_PID
    Включает в сообщения идентификатор процесса, уникальный номер, выделяемый системой каждому процессу
    LOG_CONS
    Посылает сообщения на консоль, если они не могут быть записаны
    LOG_ODELAY
    Открывает средство регистрации сообщений при первом вызове функции
    syslog
    LOG_NDELAY
    Открывает средство регистрации сообщений немедленно, не дожидаясь первого регистрируемого сообщения

    Функция

    openlog
    выделит и откроет дескриптор файла, который будет применяться для записи в программе ведения системного журнала. Вы сможете закрыть его, вызвав функцию
    closelog
    . Имейте в виду, что вам не нужно вызывать функцию
    openlog
    перед вызовом
    syslog
    , потому что последняя при необходимости самостоятельно откроет средство ведения системного журнала.

    Вы можете управлять приоритетом регистрируемых вами сообщений с помощью установки маски регистрации, используя функцию

    setlogmask
    . Все будущие вызовы
    syslog
    с разными приоритетами, не заданными в маске регистрации, будут отброшены, таким образом, вы сможете, например, использовать маску для отключения сообщений типа
    LOG_DEBUG
    без необходимости изменять тело программы.

    Вы можете создать маску для регистрируемых сообщений, используя значение

    LOG_MASK(priority)
    , создающее маску только для одного приоритета, или значение
    LOG_UPTO(priority)
    , создающее маску для всех приоритетов вплоть до заданного.

    Выполните упражнение 4.14.

    Упражнение 4.14. Маска регистрации (
    logmask
    )

    В этом примере вы увидите

    logmask
    в действии.

    #include <syslog.h>

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>


    int main() {

     int logmask;

     openlog("logmask", LOG_PID|LOG_CONS, LOG_USER);

     syslog(LOG_INFO, "informative message, pid = %d", getpid());

     syslog(LOG_DEBUG, "debug message, should appear");

     logmask = setlogmask(LOG_UPTO(LOG_NOTICE));

     syslog(LOG_DEBUG, "debug message, should not appear");

    exit(0);

    }

    Программа logmask.c ничего не выводит, но в типичной системе Linux вы увидите в файле /var/log/messages, ближе к концу, следующую строку:

    Jun 9 09:28:52 suse103 logmask[19339] : informative message, pid = 19339

    Файл, настроенный на получение регистрируемых сообщений об отладке (в зависимости от настройки регистрации, это чаще всего файл /var/log/debug или иногда файл /var/log/messages), должен содержать следующую строку:

    Jun 9 09:28:52 susel03 logmask[19339]: debug message, should appear

    Как это работает

    Программа инициализирует средство ведения системного журнала, названное logmask, и запрашивает включение идентификатора процесса в регистрируемые сообщения. Информирующее сообщение записывается в файл /var/log/messages, а отладочное сообщение — в файл /var/log/debug. Второе отладочное сообщение не появляется, потому что вы вызвали функцию

    setlogmask
    с игнорированием всех сообщений с приоритетом ниже
    LOG_NOTICE
    . (Учтите, что этот метод не работает в ранних вариантах ядра Linux.)

    Если в установленную у вас систему не включена регистрация отладочных сообщений или она настроена иначе, отладочные сообщения могут не появляться. Для разблокирования всех отладочных сообщений и для получения подробностей настройки см. системную документацию, посвященную функции

    syslog
    или
    syslog-ng
    .

    Программа logmask.c также использует функцию

    getpid
    , которая, наряду с тесно связанной с ней функцией
    getppid
    , определена следующим образом:

    #include <sys/types.h>

    #include <unistd.h>

    pid_t getpid(void);pid_t getppid(void);

    Функции возвращают идентификаторы вызвавшего и родительского процессов. Дополнительную информацию об идентификаторах процессов (PID) см. в главе 11.

    Ресурсы и ограничения

    Программы, выполняющиеся в системе Linux, зависят от ограниченности ресурсов. Это могут быть физические ограничения, накладываемые оборудованием (например, памятью), ограничения, связанные с системной политикой (например, разрешенное время процессора) или ограничения реализации (такие как размер типа

    integer
    или максимально допустимое количество символов в имени файла). В спецификацию UNIX включены некоторые из этих ограничений, которые может определять приложение. Дальнейшее обсуждение ограничений и последствия их нарушений см. в главе 7.

    В заголовочном файле limits.h определены многие именованные константы, представляющие ограничения, налагаемые операционной системой (табл. 4.8).


    Таблица 4.8

    Ограничительная константа Назначение
    NAME_MAX
    Максимальное число символов в имени файла
    CHAR_BIT
    Количество разрядов в значении типа
    char
    CHAR_MAX
    Максимальное значение типа
    char
    INT_MAX
    Максимальное значение типа
    int

    Существует множество других ограничений, полезных приложению, поэтому следует ознакомиться с заголовочными файлами установленной у вас версии системы.

    Примечание

    Имейте в виду, что константа

    NAME_MAX
    зависит от файловой системы. Для разработки легко переносимого кода следует применять функцию pathconf. Дополнительную информацию о ней см. на страницах интерактивного справочного руководства.

    В заголовочном файле sys/resource.h представлены определения операций над ресурсами. К ним относятся функции для считывания и установки предельных значений для разрешенного размера программы, приоритета выполнения и файловых ресурсов.

    #include <sys/resource.h>

    int getpriority(int which, id_t who);

    int setpriority(int which, id_t who, int priority);

    int getrlimit(int resource, struct rlimit *r_limit);

    int setrlimit(int resource, const struct rlimit *r_limit);

    int getrusage(int who, struct rusage *r_usage);

    Здесь

    id_t
    — это целочисленный тип, применяемый для идентификаторов пользователя и группы. Структура
    rusage
    , указанная в файле sys/resource.h, используется для определения времени центрального процессора (ЦП), затраченного текущей программой. Она должна содержать, как минимум, два элемента (табл. 4.9).


    Таблица 4.9

    Элемент структуры
    rusage
    Описание
    struct timeval ru_utime
    Время, использованное пользователем
    struct timeval ru_stime
    Время, использованное системой

    Структура

    timeval
    определена в файле sys/time.h и содержит поля
    tv_sec
    и
    tv_usec
    , представляющие секунды и микросекунды соответственно.

    Время ЦП, потребляемое программой, делится на время пользователя (время, затраченное самой программой на выполнение собственных инструкций) и системное время (время ЦП, потребляемое операционной системой в интересах программы, т.е. время, затраченное на системные вызовы, выполняющие ввод и вывод или другие системные функции).

    Функция

    getrusage
    записывает данные о времени ЦП в структуру
    rusage
    , на которую указывает параметр
    r_usage
    . Параметр
    who
    может быть задан одной из констант, приведенных в табл. 4.10.


    Таблица 4.10

    Константа
    who
    Описание
    RUSAGE_SELF
    Возвращает данные о потреблении только для текущей программы
    RUSAGE_CHILDREN
    Возвращает данные о потреблении и для дочерних процессов

    Мы будем обсуждать дочерние процессы и приоритеты задач в главе 11, но для полноты картины мы здесь упоминаем об их причастности к потреблению системных ресурсов. Пока достаточно сказать, что у каждой выполняющейся программы есть ассоциированный с ней приоритет, и чем выше приоритет программы, тем больше ей выделяется доступного времени ЦП.

    Примечание

    Обычные пользователи могут только снижать приоритеты своих программ, а не повышать их.

    Приложения могут определять и изменять свои (и чужие) приоритеты с помощью функций

    getpriority
    и
    setpriority
    . Процесс, исследуемый или изменяемый с помощью этих функций, может быть задан идентификатором процесса, группы или пользователя. Параметр
    which
    описывает, как следует интерпретировать параметр
    who
    (табл. 4.11).


    Таблица 4.11

    Параметр
    which
    Описание
    PRIO_PROCESS
    who
    — идентификатор процесса
    PRIO_PGRP
    who
    — идентификатор группы
    PRIO_USER
    who
    — идентификатор пользователя

    Итак, для определения приоритета текущего процесса вы можете выполнить следующий вызов:

    priority = getpriority(PRIO_PROCESS, getpid());

    Функция

    setpriority
    позволяет задать новый приоритет, если это возможно.

    По умолчанию приоритет равен 0. Положительные значения приоритета применяются для фоновых задач, которые выполняются, только когда нет задачи с более высоким приоритетом, готовой к выполнению. Отрицательные значения приоритета заставляют программу работать интенсивнее, выделяя большие доли доступного времени ЦП. Диапазон допустимых приоритетов — от -20 до +20. Часто это приводит к путанице, поскольку, чем выше числовое значение, тем ниже приоритет выполнения.

    Функция

    getpriority
    возвращает установленный приоритет в случае успешного завершения или -1 с переменной
    errno
    , указывающей на ошибку. Поскольку значение -1 само по себе обозначает допустимый приоритет, переменную
    errno
    перед вызовом функции
    getpriority
    следует приравнять нулю и при возврате из функции проверить, осталась ли она нулевой. Функция
    setpriority
    возвращает 0 в случае успешного завершения и -1 в противном случае.

    Предельные величины, заданные для системных ресурсов, можно прочитать и установить с помощью функций

    getrlimit
    и
    setrlimit
    . Обе они для описания ограничений ресурсов используют структуру общего назначения
    rlimit
    . Она определена в файле sys/resource.h и содержит элементы, перечисленные в табл. 4.12.


    Таблица 4.12

    Элемент
    rlimit
    Описание
    rlim_t rlim_cur
    Текущее, мягкое ограничение
    rlim_t rlim_max
    Жесткое ограничение

    Определенный выше тип

    rlim_t
    — целочисленный тип, применяемый для описания уровней ресурсов. Обычно мягкое ограничение — это рекомендуемое ограничение, которое не следует превышать; нарушение этой рекомендации может вызвать возврат ошибок из библиотечных функций. При превышении жесткого ограничения система может попытаться завершить программу, отправив ей сигнал, например, сигнал
    SIGXCPU
    при превышении ограничения на потребляемое время ЦП и сигнал
    SIGSEGV
    при превышении ограничения на объем данных. В программе можно самостоятельно задать для любых значений собственные мягкие ограничения, не превышающие жесткого ограничения. Допустимо уменьшение жесткого ограничения. Увеличить его может только программа, выполняющаяся с правами суперпользователя.

    Ограничить можно ряд системных ресурсов. Эти ограничения описаны в параметре

    resource
    функций
    rlimit
    и определены в файле sys/resource.h, как показано в табл. 4.13.


    Таблица 4.13

    Параметр
    resource
    Описание
    RLIMIT_CORE
    Ограничение размера файла дампа ядра, в байтах
    RLIMIT_CPU
    Ограничение времени ЦП, в секундах
    RLIMIT_DATA
    Ограничение размера сегмента
    data()
    , в байтах
    RLIMIT_FSIZE
    Ограничение размера файла, в байтах
    RLIMIT_NOFILE
    Ограничение количества открытых файлов
    RLIMIT_STACK
    Ограничение размера стека, в байтах
    RLIMIT_AS
    Ограничение доступного адресного пространства (стек и данные), в байтах

    В упражнении 4.15 показана программа limits.c, имитирующая типичное приложение. Она также задает и нарушает ограничения ресурсов.

    Упражнение 4.16. Ограничения ресурсов

    1. Включите заголовочные файлы для всех функций, которые вы собираетесь применять в данной программе:

    #include <sys/types.h> \

    #include <sys/resource.h>

    #include <sys/time.h>

    #include <unistd.h>

    #include <stdio.h>

    #include <stdlib.h>

    #include <math.h>

    2. Функция типа

    void
    записывает 10 000 раз строку во временный файл и затем выполняет некоторые арифметические вычисления для загрузки ЦП:

    void work() {

     FILE *f;

     int i;

     double x = 4.5;

     f = tmpfile();

     for (i = 0; i < 10000; i++) {

      fprintf(f, "Do some output\n");

      if (ferror(f)) {

       fprintf(stderr, "Error writing to temporary file\n");

       exit(1);

      }

     }

     for (i = 0; i < 1000000; i++) x = log(x*x + 3.21);

    }

    3. Функция

    main
    вызывает функцию
    work
    , а затем применяет функцию getrusage для определения времени ЦП, использованного
    work
    . Эта информация выводится на экран:

    int main() {

     struct rusage r_usage;

     struct rlimit r_limit;

     int priority;

     work();

     getrusage(RUSAGE_SELF, &r_usage);

     printf("CPU usage: User = %ld.%06ld, System = %ld.%06ld\n",

      r_usage.ru_utime.tvsec, rusage.ru_utime.tv_usec,

      r_usage.ru_stime.tv_sec, r_usage.ru_stime.tv_usec);

    4. Далее она вызывает функции

    getpriority
    и
    getrlimit
    для выяснения текущего приоритета и ограничений на размер файла соответственно:

     priority = getpriority(PRIO_PROCESS, getpid());

     printf("Current priority = %d\n", priority);

     getrlimit(RLIMIT_FSIZE, &r_limit);

     printf("Current FSIZE limit: soft = %ld, hard = %ld\n",

      r_limi t.rlim_cur, r_limit.rlim_max);

    5. В заключение задайте ограничение размера файла с помощью функции

    setrlimit
    и снова вызовите функцию
    work
    , которая завершится с ошибкой, т.к. попытается создать слишком большой файл:

     r_limit.rlim_cur = 2048;

     r_limit.rlim_max = 4096;

     printf("Setting a 2K file size limit\n");

     setrlimit(RLIMIT_FS1ZE, &r_limit);

     work();

     exit(0);

    }

    Выполнив эту программу, вы сможете увидеть, сколько затрачено времени ЦП, и текущий приоритет, с которым программа выполняется. После того как будет задан предельный размер файла, программа не сможет записать во временный файл более 2048 байтов.

    $ cc -о limits limits.с -lm

    $ ./limits

    CPU usage: User = 0.140008, System = 0.020001

    Current priority = 0

    Current FSIZE limit: soft = -1, hard = -1

    Setting a 2K file size limit

    File size limit exceeded

    Вы можете изменить приоритет программы, запустив ее с помощью команды

    nice
    . Далее показано, как меняется приоритет на значение +10, и в результате программа выполняется немного дольше.

    $ nice ./limits

    CPU usage: User = 0.152009, System = 0.020001

    Current priority = 10

    Current FSIZE limit: soft = -1, hard = -1 

    Setting a 2K file size limit

    File size limit exceeded

    Как это работает

    Программа limits вызывает функцию

    work
    для имитации операций типичной программы. Она выполняет некоторые вычисления и формирует вывод, в данном случае около 150 Кбайт записывается во временный файл. Программа вызывает функции управления ресурсами для выяснения своего приоритета и ограничений на размер файла. В данном случае ограничения размеров файлов не заданы, поэтому можно создавать файл любого размера (если позволяет дисковое пространство). Затем программа задает свое ограничение размера файла, равное примерно 2 Кбайт, и снова пытается выполнить некоторые действия. На этот раз функция
    work
    завершается неудачно, поскольку не может создать такой большой временный файл.

    Примечание

    Ограничения можно также наложить на программу, выполняющуюся в отдельной командной оболочке с помощью команды

    ulimit
    оболочки bash.

    В приведенном примере сообщение об ошибке "Error writing to temporary file" ("Ошибка записи во временный файл") не выводится. Это происходит потому, что некоторые системы (например, Linux 2.2 и более поздние версии) завершают выполнение программы при превышении ограничения ресурса. Делается это с помощью отправки сигнала

    SIGXFSZ
    . В главе 11 вы узнаете больше о сигналах и способах их применения. Другие системы, соответствующие стандарту POSIX, заставляют функцию, превысившую ограничение, вернуть ошибку.

    Резюме 

    В этой главе вы посмотрели на окружение системы Linux и познакомились с условиями выполнения программ. Вы узнали об аргументах командной строки и переменных окружения — и те, и другие могут применяться для изменения стандартного поведения программы и предоставляют подходящие программные опции.

    Вы увидели, как программа может воспользоваться библиотечными функциями для обработки значений даты и времени и получить сведения о себе, пользователе и компьютере, на котором она выполняется.

    Программы в ОС Linux, как правило, должны совместно использовать дорогостоящие ресурсы, поэтому в данной главе рассматриваются способы определения имеющихся ресурсов и управления ими. 








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх