mprotect()#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
static int alloc_size;
static char* memory;
void segv_handler(int signal_number) {
printf("memory accessed!\n");
mprotect(memory, alloc_size, PROT_READ | PROT_WRITE);
}
int main() {
int fd;
struct sigaction sa;
/* Назначение функции segv_handler() обработчиком сигнала
SIGSEGV. */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &segv_handler;
sigaction(SIGSEGV, &sa, NULL);
/* Выделение одной страницы путем отображения в памяти файла
/dev/zero. Сначала память доступна только для записи. */
alloc_size = getpagesize();
fd = open("/dev/zero", O_RDONLY);
memory =
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 done\n");
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(). В отличие от стандартного системного вызова эта функция может принимать дробное число секунд и возобновлять операцию ожидания в случае прерывания по сигналу.
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.