Курсовая работа: Мониторинг виртуальной памяти в ОС Linux
Файлы, создаваемые в /proc, представляются структурой proc_entry, содержащую поле proc_fops, куда и заносится указатель на структуру file_operations для данного файла.
Данный драйвер создает в папке /proc/memmon 2 файла – watch-pids – для добавления / удаления процессов в список отслеживаемых и events – файл, содержащий собственно лог событий.
2.4 Перехват системных вызовов
Одно из основных действий, выполняемых драйвером – перехват системных вызовов.
Это довольно небезопасный прием, так как если 2 драйвера перехватят один и тот же вызов, и будут выгружены не в том порядке, в каком загрузились, последний восстановит неверный адрес в таблице вызовов, вследствие чего произойдет сбой при следующей попытке сделать данный вызов. В связи с этим, начиная с версии 2.5, ядро более не экспортирует таблицу системных вызовов. Тем не менее, эта проблема устраняется небольшим исправлением ядра (patch), который добавляет в произвольный файл ядра следующие строки, экспортирующие из ядра данную таблицу.
extern void *sys_call_table[];
EXPORT_SYMBOL_GPL (sys_call_table);
Для перехвата системных вызовов имеются 3 таблицы указателей – оригинальных (системных) обработчиков, наших пре-обработчиков (вызываемых ДО оригинального, и принимающих аргументы) и пост-обработчиков (вызываемых ПОСЛЕ и принимающих возвращаемое значение):
void *old_sys_call [NR_syscalls];
void *sys_call_trap [NR_syscalls];
void *sys_call_exit [NR_syscalls];
Кроме того, есть общая таблица структур, каждый элемент которой описывает один из перехватываемых системных вызовов:
struct syscall_handler
{
/* Syscall nr */
int nr;
/* Pre-call & post-call handler */
void *hand1, *hand2;
};
Функцияcapture_syscalls() , вызываемаяприинициализациидрайвера, копируетэтиадресавпредыдущие 2 таблицыизаписываетвsys_call_table понужнымномерамадресуниверсальногоперехватчика – syscalls_entry , находящегосявфайлеsyscalls-entry.
Необходимость в ассемблерном коде обусловлена механизмом обработки системных вызовов в Linux. На рис. 3 показан стек на момент вызова обработчика системного вызова (замененного или стандартного). Проблема заключается в том, что некоторые стандартные обработчики требуют, чтобы стек имел именно такой вид, и если вызывать их из нового обработчика, они правильно работать не будут. В связи с этим syscalls_entry сначала вызывает пре-обработчик системного вызова, затем заменяет в стеке адрес возврата на адрес следующей инструкции и делает переход на стандартный обработчик, который получает кадр стека в изначальном виде. Затем, при возврате, мы попадаем на следующую инструкцию, которая вызывает пост-обработчик и делает перход на изначальный адрес возврата, на код в arch/i386/kernel/entry.S (точки входа всех системных вызовов в Linux). Этот адрес сохраняется внизу стека ядра, там же где хранится указатель на текущую задачу и некоторая другая служебная информация ядра. Данные действия продемонстрированы на рис. 3.3–3.5.
Данный драйвер перехватывает следующие системные вызовы:
m[un] map() – выделение / освобождение региона памяти
mremap() – перемещение региона памяти
brk() – расширение / сужение сегмента данных программы
m[un] lock[all]() – блокировка набора страниц в рабочем множестве процесса
fsync() – используется в качестве маркера в журнале событий
Для каждого из вызовов в журнал печатается имя вызова, PID вызвавшего процесса и список (с расшифровкой, там, где это имеет смысл) аргументов.
2.5 Кольцевой буфер событий
Для хранения журнала событий, таких как выделение блоков виртуальной памяти и страниц, использует кольцевой буфер, защищенный спин-блокировкой. Размер данного буфера может задаваться при загрузке драйвера в качестве его параметра, значение по умолчанию – 32 кб, минимальное – 8 кб. Память для буфера выделяется функцией kzalloc() – аналогом фукнций malloc/calloc() из стандартной библиотеки С. Передаваемый ей параметр GFP_KERNEL означает, что память выделяется для ядра (т.е. не может быть позже выгружена с диска), но не в атомарном контексте (т.е. текущий процесс может быть отложен до освобождения необходимой памяти).
Каждая запись в буфере представляет из себя следующую структуру: