| Произвольный доступ: lseek() |
|
Системный вызов iseeko позволяет произвольно перемещать текущую позицию ввода-вывода, iseekо объявлен в заголовочном файле unistd.h следующим образом: off_t Iseek (int FD, off_t OFFSET, int WHENCE); Тип of f_t является целым числом, размерность которого зависит от реализации. Как правило, это длинное целое (long int). fd — это файловый дескриптор, offset — требуемое смещение относительно параметра whence, который может быть представлен одной из трех констант:
Аргумент offset может принимать отрицательные значения, смещающие текущую позицию назад от whence. Системный вызов iseeko возвращает установленную позицию ввода-вывода относительно начала файла. В случае ошибки возвращается -1. В ядре Linux нет отдельного системного вызова, который сообщал бы текущую позицию в файле. Для этих целей применяется все тот же iseek (): off_t pos = Iseek (fd, 0, SEEK_CUR); Рассмотрим теперь уже знакомый нам пример, который выводит на экран "хвост" указанного файла (листинг 7.10). Для реализации этой программы будем пользоваться низкоуровневыми механизмами ввода-вывода Linux. Программа mylseektall.с #include <fcntl.h> Итак, рассмотрим листинг по порядку. В заголовочном файле fcntl.h объявлен системный вызов open(). Файл unisteLh нужен для read(), write(), lseek() И close(). В sys/types.h объявлены ТИПЫ off_t И ssize_t, а в заголовочном файле string.h — функция strlen (). Затем создается константа препроцессора buf_size и массив buffer, через который будут считываться и записываться данные. В языке С существует также другой подход для работы со статическими массивами. Для этого достаточно объявить следующий макрос: #define AKRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) Этот макрос вычисляет на стадии компиляции размер статического массива: fdefine AHRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) char buffer [40.96]; while ((nbytes = read (fd, buffer, ARRAY_SIZE (buffer))) write (1, buffer, nbytes); Следует учитывать, что такой "трюк" не работает с динамическими массивами. В данной программе все операции ввода-вывода осуществляются при помощи системных вызовов read о и write(), включая вывод сообщений об ошибках. Для ЭТОГО сначала создаются массивы arg_emsg, file_emsg и close_emsg. Вывод ошибок реализуется следующим образом: write (2, arg_emsg, strlen (arg_emsg)); write (2, file_emsg, strlen (arg_emsg)); write (2, close_emsg, strlen (close_emsg)); Другими словами, мы просто записываем данные необходимого размера в файл с дескриптором 2, т. е. в стандартный поток ошибок. Учитывая то, что массивы arg_emsg, ?ile_emsg И close_emsg статические, МОЖНО вообще не пользоваться функцией strlen (): fdefine ARRAY_SIZE(array)(sizeof(array)/sizeof(array[0])) Для закрепления рассмотрим еще одну программу, которая читает из файла заданное число байтов от указанной позиции. Чтобы программа была "красивее", научим ее обрабатывать опции функцией getopt_iong (). Для начала определим набор опций и аргументов.
Исходный код программы достаточно велик, но он позволяет увидеть в действии практически все базовые принципы низкоуровневого ввода-вывода. Кроме того, данная программа является еще одной полезной иллюстрацией обработки длинных опций. Пример реализации низкоуровневого ввода вывода roadblock.с #include <unistd.h> tinclude <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <getopt.h> #include <stdlib.h> #define AKR_SIZE(array) (sizeof(array)/sizeof(array[0])) int main (int argc, char ** argv) int i, opt, ifd, ofd = 1, nflag = 0; char ch; off_t pos; size_t count; char help_str[] = "Usage: readblock OPTIONS FILENAME\n" "OPTIONS:\n" "-h, —help\n" "-o, —output <filename>\n" "-p, —position <nuiriber>\n" "-C —count <number>\n" "-n, —newline\n"; char unkn_emsg[] = "Unknown error\n"; char ifile__emsg[] = "Cannot open input file\n"; char ofile_emsg[] = "Cannot open output file\n",- char close_emsg[] = "Cannot close file\n"; char lseek_emsg[] = "Cannot set I/O position\n"; char * ofname = NULL; char * pos_str = NULL; char * count_str = NULL; const struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, { "output", required_argument, NULL, 'o' }, { "position", reguired_argument, NULL, 'p' }, { "count", reguired_argument, NULL, 'с' }, { "newline", no_argument, NULL, 'n' }, { NULL, 0, NULL, 0 } while ((opt = getopt_long (argc, argv, "ho:p:c:n", long_opts, NULL)) != -1) { switch (opt) { case 'h': write (1, help_str, ARR_SIZE (help_str)); return 0; case 'о': ofname = optarg; break; case 'p': pos_str = optarg; break; case 'c': count_str = optarg; break; case 'n': nflag = 1; break; case '?': write (2, help_str, AKR_SIZE (help_str)); return 1; default: write (2, unkn_emsg, ARR_SIZE (unkn_emsg)) return 2; } } if (count_str == NULL) { write (2, help_str, ARR_SIZE (help_str)); return 3; } count = abs(atoi(count_str)); if (pos_str != NULL) pos = abs (atoi (pos_str)); else pos = 0; if (optind >= argc) { write (2, help_str, ARR_SIZE (help_str)); return 4; } if (ofname != NULL) { ofd = open (ofname, OJ/flRONLY | OjCREAT | 0_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP); /* 0640 */ if (ofd == -1) { write (2, ofile_emsg, ARR_SIZE (ofile_emsg)); return 5; } } ifd = open (argv[optind], 0_RDONLY); if (ifd == -1) { write (2, ifile_emsg, ARR_SIZE (ifile_emsg)); return 6; } if (pos > Iseek (ifd, 0, SEEI^_END)) { count = 0; } else if (lseek (ifd, pos, SEEK_SET) == -1) { write (2, lseek_emsg, ARR_SIZE (lseek_emsg)); return 7;? } for (i =0; i < count; i++) { if (read (ifd, &ch, 1) <= 0) break; write (ofd, &ch, 1); } if (nflag) write (ofd, "\n", 1); if (close (ifd) == -1) { write (2, close_emsg, ARR_SIZE (close_emsg)); return 8; } if (ofd != 1) { if {close (ofd) == -1) { write (2, close_emsg, ARR_SIZE (close_emsg)); return 9; } } return 0; } Рассмотрим все по порядку. Сначала в программу включаются заголовочные файлы, которые объявляют следующее:
Макрос arr_size() вычисляет на стадии компиляции размер статического массива. В функции main() сначала объявляются различные вспомогательные переменные:
Строки help_str, unkn_emsg, ifile_emsg, ofile_emsg, close_emsg И lseefc_emsg служат для вывода информационных сообщений. Как правило, это сообщения об ошибках. Указатели ofname, pos_str и count_str используются для получения зависимых аргументов опций -о, -р и -с соответственно. Изначально эти указатели инициализируются значением null. В этом есть определенный смысл: если, например, указатель ofname после цикла обработки опций будет по-прежнему содержать null, to это будет означать, что опция -о (--output) не указывалась. Массив структур iong_opts содержит описание длинных опций и их аргументов. Кроме того, этот массив устанавливает соответствия между длинными и короткими опциями. Так, например, длинной опции --position соответствует короткая опция -р. После всех объявлений следует цикл обработки опций, который продолжается до тех пор, пока функция getopt_iong() не вернет значение -1, символизирующее о том, что больше обрабатывать нечего. С каждым проходом цикла функция getopt__long() возвращает в переменную opt код следующей полученной опции. Если была получена неизвестная опция, то в opt помещается код символа "?" (вопросительный знак). Каждая программа может по-своему реагировать на появление неизвестной опции. Наша программа в этом случае выводит в стандартный поток ошибок краткую справку (heip_str) и завершается с кодом возврата 1. Если getopt_iong () возвращает что-то иное (блок default конструкции switch), то выводится сообщение об ошибке, и программа завершается с кодом возврата 2. Обратите внимание, что в данной программе каждая ошибка завершает программу с индивидуальным кодом возврата. В нашем случае это не просто удобная, но и необходимая мера. Обратите внимание на то, что все ощибки, связанные с неправильной передачей в программу опций или аргументов, выводят одну и ту же справочную информацию. Поэтому получение кода возврата — практически единственный способ узнать, где именно произошла ошибка. В данной программе консольный ввод-вывод реализован через системный вызов write (). Таким образом здесь продемонстрировано, что для осуществления консольного ввода-вывода можно обойтись исключительно средствами ядра Linux. Хотя это не всегда удобно и целесообразно. После цикла следует проверка того, была ли указана опция -с. В предварительном описании программы сообщалось, что эта опция является обязательной, поэтому, если переменная count_str по-прежнему содержит значение null, то программа выводит краткую инструкцию и завершается с кодом возврата 3. Если проверка была удачно пройдена, то count_str переводится в числовое значение, которое заносится в переменную count. Функция получения модуля abs () в данной программе предназначена для уменьшения числа проверок и сохранения компактности исходного кода. Будет замечательно, если вы самостоятельно добавите блок обработки отрицательного значения для count. Затем идет обработка pos_str. Если опция -р указывалась, то в переменную pos заносится свободный аргумент этой опции, приведенный функциями abs () и atoi () к целому неотрицательному значению. Если указатель pos_str по-прежнему содержит значение null (опция -р не указывалась), то переменная pos устанавливается в нулевое значение. Это говорит о том, что данные будут считываться с начала файла. Далее идет проверка наличия обязательного свободного аргумента, в который заносится имя входного файла. Если аргумент отсутствует, то в стандартный поток ошибок направляется краткая справка (help_str), и программа завершается с кодом возврата 4. После этого, если была указана опция -о, о чем свидетельствует отличное от null значение указателя ofname, предпринимается попытка открыть файл с флагами o_wronly, o_creat и o_trunc в режиме, соответствующем восьмеричным правам доступа 0640. То же самое можно было сделать при помощи системного вызова creat (). Если файл не удалось открыть, то выводится сообщение об ошибке, и программа завершается с кодом возврата 5. Потом предпринимается попытка открыть входной файл с флагом q_rdonly (только чтение). В случае неудачи выводится сообщение об ошибке, и программа завершается с кодом возврата 6. Если входной файл был успешно открыт, то программа переходит к установке текущей позиции ввода-вывода для этого файла относительно его начала. Здесь учитывается, что указанное значение pos может превышать размер файла. В этом случае переменная count обнуляется, как бы символизируя о том, что из указанной позиции нечего выводить. Если же значение переменной pos не превышает размера файла, то выполняется попытка установки текущей позиции ввода-вывода на указанное число байтов относительно начала файла (seek_set). Если что-то пошло не так, то в стандартный поток ошибок выводится сообщение (help_str), и программа завершается с кодом возврата 7. Наконец, когда все предварительные проверки успешно пройдены, программа посимвольно считывает указанный блок входного файла и записывает каждый символ в выходной файл. Естественно, это не самый оптимальный вариант. Попробуйте самостоятельно проделать то же самое, но с использованием механизма буферизации. Если программе был передан флаг -п, то в выходной файл заносится символ переноса строки. Эта опция полезна, когда вывод происходит на экран: очередное приглашение командной строки в этом случае не прилипает к "хвосту" вывода программы, а появляется на привычном месте. Теперь, когда все прочитано и записано, необходимо закрыть файлы. Если входной файл по каким-либо причинам не удалось закрыть, то выводится сообщение об ошибке, и программа завершается с кодом возврата 8. Чтобы закрыть выходной файл, нужно сначала убедиться, что он не является стандартным выводом. Если произошла ошибка закрытия выходного файла, то выводится соответствующее сообщение, и программа завершается с кодом возврата 9. Внимательный читатель заметит, что выходной файл можно и не проверять на соответствие значению по умолчанию, поскольку закрытие стандартного вывода перед выходом из программы не вызывает никаких негативных последствий. Вспомните, что говорилось ранее: чтобы реально закрыть файл, необходимо вызвать close () в каждом процессе, использующем этот файл.
Related Articles
Set as favorite
Bookmark
Email This
Hits: 296 Комментарии (0)RSS feed CommentsНаписать комментарий |