Марк Митчелл - Программирование для Linux. Профессиональный подход
mmap(NULL, alloc_size, PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
/* Запись на страницу для получения ее копии в частное
использование. */
memory[0] = 0;
/* Запрет на запись в память. */
mprotect(memory, alloc_size, PROT_NONE);
/* Попытка записи в память. */
memory[0] = 1;
/* Удаление памяти. */
printf("all donen");
munmap(memory, alloc_size);
return 0;
}
Программа работает по следующей схеме.
1. Задается обработчик сигнала SIGSEGV.
2. Файл /dev/zero отображается в памяти, из которой выделяется одна страница. В эту страницу записывается инициализирующее значение, благодаря чему программе предоставляется частная копия страницы.
3. Программа защищает память, вызывая функцию mprotect() с флагом PROT_NONE.
4. Когда программа впоследствии обращается к памяти, Linux посылает ей сигнал SIGSEGV, который обрабатывается в функции segv_handler(). Обработчик сигнала отменяет защиту памяти, разрешая выполнить операцию записи.
5. Программа удаляет область память с помощью функции munmap().
8.10. Функция nanosleep(): высокоточная пауза
Функция nanosleep() является более точной версией стандартной функции sleep(), принимая указатель на структуру типа timespec, где время задается с точностью до наносекунды, а не секунды. Правда, особенности работы ОС Linux таковы, что реальная точность оказывается равной 10 мс, но это все равно выше, чем в функции sleep(). Функцию nanosleep() можно использовать в приложениях, где требуется запускать различные операции с короткими интервалами между ними.
В структуре timespec имеются два поля:
■ tv_sес — целое число секунд;
■ tv_nsec — дополнительное число миллисекунд (должно быть меньше, чем 109).
Работа функции nanosleep(), как и функции sleep(), прерывается при получении сигнала. При этом функция возвращает значение -1, а в переменную errno записывается код EINTR. Но у функции nanosleep() есть важное преимущество. Она принимает дополнительный аргумент — еще один указатель на структуру timespec, в которую (если указатель не равен NULL) заносится величина оставшегося интервала времени (т.е. разница между запрашиваемым и прошедшим промежутками времени). Благодаря этому можно легко возобновлять прерванные операции ожидания.
В листинге 8.8 показана альтернативная реализация функции sleep(). В отличие от стандартного системного вызова эта функция может принимать дробное число секунд и возобновлять операцию ожидания в случае прерывания по сигналу.
Листинг 8.8. (better_sleep.c) Высокоточная реализация функции sleep()#include <errno.h>
#include <time.h>
int better_sleep(double sleep_time) {
struct timespec tv;
/* Заполнение структуры timespec на основании указанного числа
секунд. */
tv.tv_sec = (time_t)sleep_time;
/* добавление неучтенных выше наносекунд. */
tv.tv_nsec = (long)((sleep_time - tv.tv_sec) * 1e+9);
while (1) {
/* Пауза, длительность которой указана в переменной tv.
В случае прерывания по сигналу величина оставшегося
промежутка времени заносится обратно в переменную tv. */
int rval = nanosleep(&tv, &tv);
if (rval == 0)
/* пауза успешно окончена. */
return 0;
else if (errno == EINTR)
/* Прерывание по сигналу. Повторная попытка. */
continue;
else
/* Какая-то другая ошибка. */
return rval;
}
return 0;
}
8.11. Функция readlink(): чтение символических ссылок
Функция readlink() определяет адресата символической ссылки. Она принимает три аргумента: путь к символической ссылке, буфер для записи адресата и длина буфера. Как ни странно, путевое имя, помещаемое в буфер, не завершается нулевым символом. Но поскольку в третьем аргументе возвращается длина буфера, добавить этот символ несложно.
Если первый аргумент не является символической ссылкой, функция readlink() возвращает -1, а в переменную errno записывается константа EINVAL.
Программа, представленная в листинге 8.9, показывает адресата символической ссылки, заданной в командной строке.
Листинг 8.9. (print-symlink.с) Отображение адресата символической ссылки#include «errno.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
char target_path[256];
char* link_path = argv[1];
/* Попытка чтения адресата символической ссылки. */
int len =
readlink(link_path, target_path, sizeof(target_path));
if (len == -1) {
/* Функция завершилась ошибкой. */
if (errno == EINVAL)
/* Это не символическая ссылка. */
fprintf(stderr, "%s is not a symbolic linkn", link_path);
else
/* Произошла какая-то другая ошибка. */
perror("readlink");
return 1;
} else {
/* Завершаем путевое имя нулевым символом. */
target_path[len] = ' ';
/* Выводим результат. */
printf("%sn", target_path);
return 0;
}
}
Ниже показано, как создать символическую ссылку и проверить ее с помощью программы print-symlink:
% ln -s /usr/bin/wc my_link
% ./print-symlink my_link
/usr/bin/wc
8.12. Функция sendfile(): быстрая передача данных
Функция sendfile() — это эффективный механизм копирования данных из одного файлового дескриптора в другой. Дескрипторам могут соответствовать дисковые файлы, сокеты или устройства.
Обычно цикл копирования реализуется следующим образом. Программа выделяет буфер фиксированного размера, перемещает в него данные из исходного дескриптора, затем записывает содержимое буфера во второй дескриптор и повторяет описанную процедуру до тех пор, пока не будут скопированы все данные. Такая схема неэффективна как с точки зрения времени, так и с точки зрения затрат памяти, поскольку выделяется дополнительный буфер и над его содержимым выполняются операции копирования.
Функция sendfile() устраняет потребность в создании промежуточного буфера. Ей передаются дескриптор для записи, дескриптор для чтения, указатель на переменную смещения и число копируемых данных. Переменная смещения определяет позицию входного файла, с которой начинается копирование (0 — это начало файла). После окончания копирования переменная будет содержать смещение конца блока. Функция sendfile() объявлена в файле <sys/sendfile.h>.
Программа, показанная в листинге 8.10, представляет собой простую, но очень эффективную реализацию механизма файлового копирования. Она принимает в командной строке два имени файла и копирует содержимое первого файла во второй. Размер исходного файла определяется с помощью функции fstat().
Листинг 8.10. (сору.с) Копирование файла с помощью функции sendfile()#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
int read_fd;
int write_fd;
struct stat stat_buf;
off_t offset = 0;
/* Открытие входного файла. */
read_fd = open(argv[1], O_RDONLY);
/* Определение размера входного файла. */
fstat(read_fd, &stat_buf);
/* Открытие выходного файла для записи. */
write_fd =
open(argv[2], O_WRONLY | O_CREAT, stat_buf.st_mode);
/* Передача данных из одного файла в другой. */
sendfile(write_fd, read_fd, &offset, stat_buf.st_size);
/* Закрытие файлов. */
close(read_fd);
close(write_fd);
return 0;
}
Функция sendfile() часто используется для повышения эффективности копирования. Она широко применяется Web-серверами и сетевыми демонами, предоставляющими файлы по сети клиентским программам. Запрос обычно поступает через сокет. Серверная программа открывает локальный дисковый файл, извлекает из него данные и записывает их в сокет. Благодаря функции sendfile() эта операция существенно ускоряется.
8.13. Функция setitimer(): задание интервальных таймеров
Функция setitimer() является обобщением системного вызова alarm(). Она планирует доставку сигнала по истечении заданного промежутка времени.