Запись в файл: системный вызов write ()



 

Для записи данных в файл используется системный вызов write(). Ниже представлен его прототип.

ssize_t write (int fd, const void * buffer, size_t count);

Как видите, прототип write () отличается от read () только спецификатором const во втором аргументе. В принципе write () выполняет процедуру, обратную read (): записывает count байтов из буфера buffer в файл с дескриптором fd, возвращая количество записанных байтов или -1 в случае ошибки. Так просто, что можно сразу переходить к примеру. За основу возьмем программу myread 1 из предыдущего раздела.

/* rw.c */

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h> /* read(), write(), close() */

#include <fcntl.h> /* open(), O_RDONLY */

#include <sys/stat.h> /* S_IRUSR */

#include <sys/types.h> /* mode_t.*/

#define BUFFER_SIZE 64

int main (int argc, char ** argv)

{

int fd;

ssize_t read_bytes;

ssize_t written_bytes;

char buffer[BUFFER_SIZE];

fd = open (argv[1], 0_RDONLY);

if (fd<0)

fprintf (stderr, "Cannot open file\n");

exit(1);

while ((read_bytes = read (fd, buffer, BUFFER_SIZE)) > 0)

{

/* 1 == stdout */

written_bytes = write (1, buffer, read_bytes);

if(written_bytes!=read_bytes) ,

{

fprintf (stderr, "Cannot write\n");

exit(1);

}

}

if (read_bytes < 0)

{

fprintf (stderr, "myread: Cannot read file\n");

exit (1);

}

close ( fd );

exit (0);

}

В этом примере нам уже не надо изощряться в попытках вставить нуль-терминатор в строку для записи, поскольку системный вызов write () не запишет большее количество байт, чем мы ему указали. В данном случае для демонстрации write () мы просто записывали данные в файл с дескриптором 1, то есть в стандартный вывод. Но прежде, чем переходить к чтению следующего раздела, попробуйте самостоятельно записать что-нибудь (при помощи write (), естественно) в обычный файл. Когда будете открывать файл для записи, обратите пожалуйста внимание на флаги O _ TRUNC , O _ CREAT и O _ APPEND. Подумайте, все ли флаги сочетаются между собой по смыслу.

 

 

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

 

Как уже говорилось, с каждым открытым файлом связано число, указывающее на текущую позицию чтения-записи. При открытии файла позиция равна нулю. Каждый вызов read () или write () увеличивает текущую позицию на значение, равное числу прочитанных или записанных байт. Благодаря этому механизму, каждый повторный вызов read () читает следующие данные, и каждый повторный write () записывает данные в продолжение предыдущих, а не затирает старые. Такой механизм последовательного доступа очень удобен, однако иногда требуется получить произвольный доступ к содержимому файла, чтобы, например, прочитать или записать файл заново.

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

off _ t lseek ( int fd , ott _ t offset , int against );

Первый аргумент, как всегда, - файловый дескриптор. Второй аргумент - смещение, как положительное (вперед), так и отрицательное (назад). Третий аргумент обычно передается в виде одной из трех констант SEEK _ SET, SEEK _ CUR и SEEK _ END, которые показывают, от какого места отсчитывается смещение. SEEK _ SET - означает начало файла, SEEK _ CUR - текущая позиция, SEEK _ END - конец файла. Рассмотрим следующие вызовы:

l seek (fd, 0, SEEK _ SET );

lseek ( fd , 20, SEEK _ CUR );

lseek ( fd , -10, SEEK _ END );

Первый вызов устанавливает текущую позицию в начало файла. Второй вызов смещает позицию вперед на 20 байт. В третьем случае текущая позиция перемещается на 10 байт назад относительно конца файла.

В случае удачного завершения, lseek () возвращает значение установленной "новой" позиции относительно начала файла. В случае ошибки возвращается -1.

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

/* draw . c */

# include < stdlib . h >

#include <stdio.h>

#include <unistd.h>

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <string.h> /* memset() */

#define N_ROWS 15 /* Image height */

#define N_COLS 40 /* Image width */

#define FG_CHAR '0' /* Foreground character */

#define IMG_FN "image" /* Image filename */

#define N_MIN(A,B) ((A)<(B)?(A):(B))

#define N_MAX(A,B) ((A)>(B)?(A):(B))

static char buffer[N_COLS];

void init_draw (int fd)

{

ssize_t bytes_written = 0;

memset (buffer,'', N_COLS);

buffer [N_COLS] = '\n';

while (bytes_written < (N_ROWS * (N_COLS+1)))

bytes_written += write (fd, buffer, N_COLS+1);

}

void draw_point (int fd, int x, int y)

{

char ch = FG_CHAR;

lseek (fd, у * (N_COLS+1) + x, SEEK_SET);

write (fd, &ch, 1);

}

void draw_hline (int fd, int y, int x1, int x2)

{

size_t bytes_write = abs (x2-x1) + 1;

memset (buffer, FG_CHAR, bytes_write);

lseek (fd, y * (N_COLS+1) + N_MIN (x1, x2), SEEK_SET);

write (fd, buffer, bytes_write);

}

void draw_vline (int fd, int x, int y1, int y2)

{

inti=N_MIN(yl,y2);

while (i <= N_MAX(y2, y1)) draw_point (fd, x, i++);

int main (void)

{

int a, b, c, i = 0;

char ch;

int fd = open (IMG_FN, O_WRONLY | O_CREAT | O_TRUNC, 0644);

if(fd<0)

{

fprintf (stderr, "Cannot open file\n");

exit(1);

}

init_draw (fd);

char * icode[] = { "v 1 1 11", "v 11 7 11", "v 14 5 11",

"v 18 6 11", "v 21 5 10", "v 25 5 10", "v 29 5 6", "v 33 5 6",

"v 29 10 11", "v 33 10 11", "h 11 1 8","h 5 16 17",

"h 11 22 24", "p 11 5 0", "p 15 6 0", " р 26 11 0", " р 30 7 0",

"p 32 7 0", "p 31 8 0", "p 30 9 0", "p 32 9 0", NULL };

while (icode[i]!= NULL)

{

sscanf(icode[i], "%c %d %d %d", &ch, &a, &b, &c);

switch (ch) {

case 'v': draw_vline (fd, a, b, c); break;

case 'h': draw_hline (fd, a, b, c); break;

case 'p': draw_point (fd, a, b); break;

default: abort();

}

i++;

} close (fd);

exit (0);

Теперь разберемся, как работает эта программа. Изначально "полотно" заполняется пробелами. Функция init _ draw () построчно записывает в файл пробелы, чтобы получился "холст размером N _ ROWS на N _ COLS. Массив строк icode в функции main () - это набор команд рисования. Команда начинается с одной из трех литер: 'v' - нарисовать вертикальную линию, 'h' -нарисовать горизонтальную линию, 'р' - нарисовать точку. После каждой такой литеры следуют три числа. В случае вертикальной линии первое число - фиксированная координата X, а два других числа - это начальная и конечная координаты Y. В случае горизонтальной линии фиксируется координата Y (первое число). Два остальных числа - начальная координата Х и конечная координата X. При рисовании точки используются только два первых числа: координата Х и координата Y. Итак, функция draw _ vline () рисует вертикальную линию, функция draw _ hline () рисует горизонтальную линию, a draw _ point () рисует точку.

Функция init _ draw () пишет в файл N _ ROWS строк, каждая из которых содержит N _ COLS пробелов, заканчивающихся переводом строки. Это процедура подготовки "холста".

Функция draw _ point () вычисляет позицию (исходя из значений координат), перемещает туда текущую позицию ввода-вывода файла, и записывает в эту позицию символ (FG _ CHAR), которым мы рисуем "картину".

Функция draw _ hline () заполняет часть строки символами FG _ CHAR. Так получается горизонтальная линия. Функция draw _ vline () работает иначе. Чтобы записать вертикальную линию, нужно записывать по одному символу и каждый раз "перескакивать" на следующую строку. Эта функция работает медленнее, чем draw _ hline (), но иначе мы не можем.

Полученное изображение записывается в файл image. Будьте внимательны: разгрузить исходный код, из программы исключены многие проверки (read (), write (), диапазон координат и проч.). Попробуйте включить эти проверки самостоятельно.

 

 


Дата добавления: 2019-03-09; просмотров: 298; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!