|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Глава 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.
Для получения сведений о других опциях, связанных с расширениями функции 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, которая объявляется, как
Выполните упражнение 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— массиву нуль-терминированных строк. Время и датаПрограмме часто полезно иметь возможность определить время и дату. Возможно, она хочет зарегистрировать длительность собственного выполнения или ей нужно изменять свое поведение в определенные моменты времени. Например, игра может отказываться запускаться в рабочие часы или программа резервного копирования по расписанию хочет дождаться ранних часов, прежде чем начать резервное копирование в автоматическом режиме. Примечание Время задается с помощью типа time_t. Это целочисленный тип, достаточный для хранения дат и времени в секундах. В Linux-подобных системах это тип long integer(длинное целое), определенный вместе с функциями, предназначенными для обработки значений времени, в заголовочном файле time.h. Примечание #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_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
Таким образом, обычная дата, такая же, как полученная из программы 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 = ×truct; 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, но возвращенный результат — открытый низкоуровневый дескриптор файла. Примечание Информация о пользователеВсе программы в ОС 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
В некоторых системах 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); Подробную информацию об идентификаторах группы и эффективных идентификаторах пользователей следует искать на страницах интерактивного справочного руководства системы, хотя, быть может, вы решите, что вам вообще не следует манипулировать ими. Примечание Информация о компьютереПрограмма может установить некоторые подробные сведения о компьютере, на котором выполняется, так же, как она определяет информацию о пользователе. Подобную информацию предоставляет команда 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
В случае успешного завершения функция 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. Учтите, что формат возвращаемых строк зависит от реализации, например, строка с версией системы содержит дату компиляции ядра. Примечание Уникальный идентификатор каждого рабочего компьютера можно получить с помощью функции 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_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
Функция 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
Существует множество других ограничений, полезных приложению, поэтому следует ознакомиться с заголовочными файлами установленной у вас версии системы. Примечание В заголовочном файле 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
Структура timevalопределена в файле sys/time.h и содержит поля tv_secи tv_usec, представляющие секунды и микросекунды соответственно. Время ЦП, потребляемое программой, делится на время пользователя (время, затраченное самой программой на выполнение собственных инструкций) и системное время (время ЦП, потребляемое операционной системой в интересах программы, т.е. время, затраченное на системные вызовы, выполняющие ввод и вывод или другие системные функции). Функция getrusageзаписывает данные о времени ЦП в структуру rusage, на которую указывает параметр r_usage. Параметр whoможет быть задан одной из констант, приведенных в табл. 4.10. Таблица 4.10
Мы будем обсуждать дочерние процессы и приоритеты задач в главе 11, но для полноты картины мы здесь упоминаем об их причастности к потреблению системных ресурсов. Пока достаточно сказать, что у каждой выполняющейся программы есть ассоциированный с ней приоритет, и чем выше приоритет программы, тем больше ей выделяется доступного времени ЦП. Примечание Приложения могут определять и изменять свои (и чужие) приоритеты с помощью функций getpriorityи setpriority. Процесс, исследуемый или изменяемый с помощью этих функций, может быть задан идентификатором процесса, группы или пользователя. Параметр whichописывает, как следует интерпретировать параметр who(табл. 4.11). Таблица 4.11
Итак, для определения приоритета текущего процесса вы можете выполнить следующий вызов: 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
Определенный выше тип rlim_t— целочисленный тип, применяемый для описания уровней ресурсов. Обычно мягкое ограничение — это рекомендуемое ограничение, которое не следует превышать; нарушение этой рекомендации может вызвать возврат ошибок из библиотечных функций. При превышении жесткого ограничения система может попытаться завершить программу, отправив ей сигнал, например, сигнал SIGXCPUпри превышении ограничения на потребляемое время ЦП и сигнал SIGSEGVпри превышении ограничения на объем данных. В программе можно самостоятельно задать для любых значений собственные мягкие ограничения, не превышающие жесткого ограничения. Допустимо уменьшение жесткого ограничения. Увеличить его может только программа, выполняющаяся с правами суперпользователя. Ограничить можно ряд системных ресурсов. Эти ограничения описаны в параметре resourceфункций rlimitи определены в файле sys/resource.h, как показано в табл. 4.13. Таблица 4.13
В упражнении 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завершается неудачно, поскольку не может создать такой большой временный файл. Примечание В приведенном примере сообщение об ошибке "Error writing to temporary file" ("Ошибка записи во временный файл") не выводится. Это происходит потому, что некоторые системы (например, Linux 2.2 и более поздние версии) завершают выполнение программы при превышении ограничения ресурса. Делается это с помощью отправки сигнала SIGXFSZ. В главе 11 вы узнаете больше о сигналах и способах их применения. Другие системы, соответствующие стандарту POSIX, заставляют функцию, превысившую ограничение, вернуть ошибку. РезюмеВ этой главе вы посмотрели на окружение системы Linux и познакомились с условиями выполнения программ. Вы узнали об аргументах командной строки и переменных окружения — и те, и другие могут применяться для изменения стандартного поведения программы и предоставляют подходящие программные опции. Вы увидели, как программа может воспользоваться библиотечными функциями для обработки значений даты и времени и получить сведения о себе, пользователе и компьютере, на котором она выполняется. Программы в ОС Linux, как правило, должны совместно использовать дорогостоящие ресурсы, поэтому в данной главе рассматриваются способы определения имеющихся ресурсов и управления ими. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|