Курсовая работа: Мониторинг виртуальной памяти в ОС Linux
size_t len;
unsigned long prot, flags;
unsigned long fd, off;
} mmap2;
……
};
}
С буфером событий связаны следующие переменные:
static struct memmon_event *events; – собственнобуфер
static int ev_start; – индекс самой старой записи в буфере
static int ev_end; – индекс последней записи
static int ev_ovf = 0; – было ли уже переполнение буфера
DECLARE_WAIT_QUEUE_HEAD (ev_waitq); – очередь ожидания (для блокирующего чтения)
spinlock_t ev_lock = SPIN_LOCK_UNLOCKED; – спин-блокировка для защиты от гонок при обращении к буферу
Пользовательские приложения запрашивают содержимое буфера событий, читая файл /proc/memmon/events . Если при открытии файла не был установлен флаг O_NONBLOCK , операция чтения по нему блокирующая – т.е., если новых данных в буфере нет, read() переводит процесс в состояние ожидания (interruptible sleep ) вызовом функции wait_event_interruptible() до получения сигнала либо появления новых данных в буфере.
Помимо open() и release(), вызываемых при открытии (создании новой структуры file ) и ее уничтожении, в file_operations данного файла определены всего 2 точки входа – read() и poll(). Обработчик poll() вызываемается, когда какой-то процесс делает вызов select() по данному файлу – ожидает, пока на нем будут доступные для чтения данные. Кроме того, в флагах структуры file вызовом nonseekable_open() сбрасывается бит, позволяющий делать вызов llseek() по файлу (т. к. данная операция лишена смысла для кольцевого буфера).
Для реализации функции read() используется абстракция под названием seq_file , предназначенная для буферизации считываемых данных. Она требует задания 4 функций – seq_start() , вызываемой при начале чтения из файла, seq_next() , вызываемой перед копированием в буфер пользователя записи об очередном событии, seq_show() , собственно осуществляющющей это копирование, и seq_stop() , вызываемой при завершении передачи данных в пользовательский буфер (когда скопировано затребованное количество данных или не осталось событий в буфере).
Рис. показывает связь между этими функциями и структурами.
При добавлении нового события в буфер происходит пробуждение все ожидающих на очереди процессов вызовом wake_up_interruptible_sync() (sync означает, что текущий процесс не будет вытеснен до разблокировки всех процессов).
2.6 Набор отслеживаемых процессов
Набор отслеживаемых процессов представляется в виде битовой карты на 65536 записей. При запуске нового процесса ядро дает ему PID, на 1 больший, чем PID предыдущего процесса. При достижении очередным процессом PID, равного 65536, новым процессам PID выделяется с единицы (т.е. с первого положительного незанятого). Лишь когда количество процессов в системе превышает эту цифру, ядро начинает выделять бОльшие номера. Данная ситуация крайне маловероятна и драйвером не обрабатывается.
Для добавления и удаления PID'ов отслеживаемых процессов создается файл /proc/memmon/watch-pids с обработчиками open(), release(), read(), write() .
Обработчик open() , в случае, если файл открыт для чтения, выделяет буфер ядра и распечатывает туда содержимое текущей битовой карты (при помощи функции bitmap_scnlistprintf() ). Попытка открыть файл одновременно на чтение и запись приводит к ошибке EINVAL .
Обработчик read() считывает запрошенный пользователем блок данных из этого буфера
Обработчик write() считывает одно число (возможно, со знаком) из пользовательского буфера. Если оно положительное, оно воспринимается как PID процесса, который необходимо добавить в битовую карту. Если отрицательное – соответствующий PID удаляется. Если 0 – битовая карта обнуляется (не отслеживается ни один процесс).
2.7 Перехват страничных ошибок и выделений страничных фреймов
Возможно осуществить перехват страничных отказов, подменив смещение обработчика исключения в IDT, однако данный метод требует повторения того же анализа сбойного адреса, который делает и стандартный системный обработчик, (файл mm/memory.c) что привело бы к падению производительности системы. В связи с этим было решено внести еще одну модификацию в ядро. При обработке страничного отказа ядро сначала определяет, относится ли сбойная страница к виртуальной памяти процесса (если нет, ему посылается сигнал SIGSEGV ), после чего вызывает функцию handle_pte_fault() . Та анализирует причину отказа и либо подгружает страницу из своп-файла / файла отображения, либо выделяет процессу новую страницу, либо посылает ему SIGSEGV (например, при попытке записи в регион памяти, доступной только для чтения), либо происходит ситуация OOM (out of memory), в результате которой сбойный процесс уничтожается с целью освобождения памяти. Модификация ядра добавляет глобальную переменную-указатель на внешнюю функцию, которую каждый раз вызывает handle_pte_fault . При инициализации драйвер заносит туда адрес своей функции mm_handle_fault() , которая, в зависимости от причины страничного отказа, заносит в буфер одно из трех событий (вместе с адресом и типом доступа – чтение либо запись):
ANON_PF – сбой при обращении к новой (еще не выделенной из физической памяти) страницы;
SWAP_PF – сбой при обращении к странице, которая находится в файле подкачки;
FILE_PF – сбой при обращении к странице, которая может быть загружена из файла, отображенного в память (например, код разделяемой библиотеки).
Алгоритм анализа причины страничного отказа продемонтсрирован на рис. 3.7.