Skip to content

Home Программирование Произвольный доступ: lseek()
Произвольный доступ: lseek()

Системный вызов iseeko позволяет произвольно перемещать текущую позицию ввода-вывода, iseekо объявлен в заголовочном файле unistd.h следующим образом:

off_t Iseek (int FD, off_t OFFSET, int WHENCE);

Тип of f_t является целым числом, размерность которого зависит от реализации. Как правило, это длинное целое (long int). fd — это файловый дескриптор, offset — требуемое смещение относительно параметра whence, который может быть представлен одной из трех констант:

  • seek_set — начало файла;
  • seek_cur — текущая позиция ввода-вывода;
  • seek_end — конец файла.

Аргумент offset может принимать отрицательные значения, смещающие текущую позицию назад от whence.

Системный вызов iseeko возвращает установленную позицию ввода-вывода относительно начала файла. В случае ошибки возвращается -1.

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

off_t pos = Iseek (fd, 0, SEEK_CUR);

Рассмотрим теперь уже знакомый нам пример, который выводит на экран "хвост" указанного файла (листинг 7.10). Для реализации этой программы будем пользоваться низкоуровневыми механизмами ввода-вывода Linux.

Программа mylseektall.с

#include <fcntl.h>
#include <unistd.h>
# include < sys/types.h>
#include <string.h>
#define BUF_SIZE 4096 char buffer[BUF_SIZE];
int main {int argc, char ** argv)
{
int fd;
off_t nback;
ssize_t nbytes;
char arg_emsg[] = "Too few arguments\n";
char file_emsg[] = "Cannot open input file\n";
char close_emsg[] = "Cannot close file\n";
if (argc < 3)
{
write (2, arg_emsg, strlen (arg_emsg));
return 1;
}
fd = open ( argv[1], 0_RDONLY); if (fd == -1)
{
write (2, file_emsg, strlen (file_emsg));
return 1;
}
nback = abs (atoi (argv[2]));
lseek (fd, 0, SEEK_END);
if (nback > lseek (fd, 0, SEEK_CUR))
lseek (fd, 0, SEEK_SET); else
lseek (fd, -nback, SEEK_END);
while ((nbytes = read (fd, buffer, BUF_SIZE)) > 0)
write (I, buffer, nbytes);
if (close (fd) == -1)
{
write (2, close_emsg, strlen (close_emsg));
return 1;
}
return 0;
}

Итак, рассмотрим листинг  по порядку. В заголовочном файле 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])) 
write (2, arg_emsg, ARKAY_SIZE (arg__emsg));
write (2, file_emsg, ARKAY_SIZE (file_emsg));
write (2, close_emsg, ARRAY_SIZE (close_emsg));

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

  • Опция -h (--help) будет выводить краткую справочную информацию по работе с программой.
  • Опция -о (--output) будет использоваться для того, чтобы перенаправить вывод в файл. Имя файла указывается в зависимом аргументе. Если опция -о не указывается, то вывод будет направляться в стандартный вывод.
  • Опция -р (--position) передает программе позицию в файле (через зависимый аргумент), откуда будет производиться чтение информации. Если опция -р не указана, то по умолчанию чтение производится с начала файла.
  • Опция -с (--count) через зависимый аргумент передает программе число байтов, которые требуется прочитать. Эта опция является обязательной, если не указан флаг -h (--help).
  • Флаг -n (--newiine) указывает программе, что вывод надо закончить переносом строки.
  • Обязательный независимый аргумент передает программе имя файла, из которого будет происходить чтение.

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

Пример реализации низкоуровневого ввода вывода 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; }

Рассмотрим все по порядку. Сначала в программу включаются заголовочные файлы, которые объявляют следующее:

  • unistd.h — объявляет системные вызовы read(), write(}, lseekO и close (), а также константы seek_set, seek_cur и seek_end;
  • fcntl.h — системный вызов open (), а также константы o_rdonly, o_wronly, o_creat и q_trunc;
  • sys/types.h — типы of f_t и size_t;
  • sys/stat.h — константы режима файла s_irusr, s_iwusr и s_irgrp;
  • getopt.h — структуру option, переменные optarg и optind, а также функцию getopt_long();
  • Stdlib.h — фуНКЦИИ abs () И atoi ().

Макрос arr_size() вычисляет на стадии компиляции размер статического массива. В функции main() сначала объявляются различные вспомогательные переменные:

  • целая переменная i —счетчик для цикла;
  • целая переменная opt — для получения и обработки значения, возвращаемого функцией getopt_long ();
  • целая переменная ifd —для хранения дескриптора входного файла;
  • целая переменная ofd — для хранения дескриптора выходного файла, который по умолчанию (если не указана опция -о) связан со стандартным выводом;
  • целая переменная nf lag — показывает, нужно ли завершать вывод символом новой строки;
  • символьная переменная ch — для ввода-вывода;
  • переменная pos — для вычисления и хранения начальной позиции чтения файла;
  • переменная count — для вычисления и хранения числа байтов, подлежащих выводу.

Строки 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 () в каждом процессе, использующем этот файл.

Комментарии (0)

RSS feed Comments

Написать комментарий

smaller | bigger

busy
 

Регистрация




Top