Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Набор отслеживаемых процессов

 

Набор отслеживаемых процессов представляется в виде битовой карты на 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 – битовая карта обнуляется (не отслеживается ни один процесс).

 

Перехват страничных ошибок и выделений страничных фреймов

 

Возможно осуществить перехват страничных отказов, подменив смещение обработчика исключения в 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.

 

Синхронизация

 

Так как доступ к кольцевому буферу происходит из различных процессов, необходимо какое-то средство предохранения от «гонок» (race conditions). Ядро Linux предоставляет целый набор примитивов синхронизации. Однако, т. к. блокировка выполняется в том числе и в атомарном контексте, т.е. когда текущий процесс не может быть переведен в режим ожидания (например, при помещении события из обработчика прерывания), единственный подходящий примитив – спин-блокировка (spin-lock), т.е. простая бинарная переменная (со значением 0 либо 1) и активным ожиданием на ней. Захват спин-блокировки происходит при начале операции чтения буфера событий и перед добавлением в буфер нового события. Освобождение – по завершении чтения и добавления события, а так же перед переводом текущего процесса в режим ожидании в случае, когда новых данных в буфере нет.

 

 


Технологический раздел

Язык и средства программирования

 

Ядро Linux написано на языке С с небольшим количеством кода на ассемблере, и его система сборки, вообще говоря, поддерживает только данные языки для использования в модулях. Выбор из них был сделан в пользу С по причине значительно большей структурированности написанного на нем кода. Однако, небольшой объем кода драйвера пришлось написать на ассемблере, в силу чего он не является платформенно-независмым и привязан к архитектуре x86.

Для сборки драйвера используется сборочная система ядра make/Kbuild и компилятор С из gcc (GNU Compiler Collection). Особенностью ядра Linux является отсутствие совместимости на бинарном уровне, совместимость присутствует лишь на уровне исходных текстов, вследствие чего все модули и само ядро должны быть собраны одной и той же версией одного и того же компилятора. Само ядро изначально написано для компиляции посредством gcc – основного компилятора С в GNU‑системах, поэтому он же должен использоваться и при компиляции модулей для него.

 

Компиляция драйвера

 

Сначала необходимо применить исправление к ядру и перекомпилировать его. Это делается командами:

# cd /usr/src/linux

# patch – p0 < memmon.patch

# make

# make install INSTALL_PATH=/boot modules_install

# lilo

# reboot

 

После этого возможна сама сборка драйвера. Для этого из папки с его исходниками надо выполнить команду $ make.

 

Работа с драйвером

 

Для загрузки драйвера необходимо выполнить команду

# insmod procmon.ko

Можно указать размер буфера событий:

# insmod procmon.ko buflen=65536

Добавить процесс с PID = 3000 к отслеживаемым:

$ echo 3000 > /proc/memmon/watch-pids

Удалить его:

$ echo -3000 > /proc/memmon/watch-pids

Добавить процесс с именем test:

$ echo `pgrep test` > /proc/memmon/watch-pids

Удалить все процессы:

$ echo 0 > /proc/memmon/watch-pids

Просмотреть буфер событий:

$ cat /proc/memmon/events

Выгрузка драйвера делается командой

# rmmod procmon.ko

(возможно, только если файлы драйвера никем не открыты)

 


Экспериментальный раздел

 

С целью изучения особенностей выделения памяти в Linux был проведен ряд экспериментов с выделением памяти, ее чтением / записью и освобождением.

1) Запрашиваем небольшое количество (3–4) страниц памяти, обращаемся ко всей памяти в этом диапазоне, затем освобождаем ее.

Журнал событий:

29305: anon page @b7ee1fa0 (R)

29305: fsync(0)

29305: anon page @b7f22d4e (R) – страничные отказы в сегменте кода libc.so

29305: anon page @b7ee2000 (R)

29305: anon page @b7e85180 (R)

29305: anon page @b7e86330 (R)

29305: anon page @b7f1f680 (R)

29305: anon page @b7ef6470 (R)

29305: anon page @b7e83140 (R)

29305: anon page @b7e82370 (R)

29305: anon page @b7e87de0 (R)

29305: brk(00000000)

29305: brk -> 134520832 (0804a000)

29305: brk(0806e000)

29305: brk -> 134668288 (0806e000) – malloc выделил 144 кб

29305: anon page @0804a004 (W) – здесь malloc заносит метки в начало и конец выделенного региона

29305: anon page @0804d00c (W)

29305: fsync(1)

29305: anon page @0804b000 (R) – сбои при обращении к страницам из цикла

29305: anon page @0804c000 (R)

29305: fsync(2)

29305: brk(0806b000) – free возвращает часть памяти

29305: brk -> 134656000 (0806b000)

29305: fsync(0) – `дальнейшие циклы уже не выделяют памяти

29305: fsync(1)

29305: fsync(2)

В приведенном примере видно, что при выделении 12К памяти malloc() выделяет изначально гораздо бОльший объем (144К), однако реально эти страницы из физической памяти не выделяеются, и при обращениях к ним происходят страничные отказы. В первую и последнюю страницу выделенной секции malloc заносит свои метки (сбой происходит на операции записи).

2) Выделяем подряд блоки по 4 страницы (не освобождая), обращаясь ко всем страницам:

21049: brk(00000000)

21049: brk -> (0804a000)

21049: brk(0806e000)

21049: brk -> (0806e000) – выделение первого блока, malloc выделяет 144 кб

21049: anon page @0804a004 (W)

21049: anon page @0804d00c (W) – запись меток

21049: fsync(1)

21049: anon page @0804b000 (R)

21049: anon page @0804c000 (R) – обращение к выделенной области

21049: fsync(2)

21049: fsync(0)

21049: anon page @08050014 (W) – выделение следующих 12 кб (запись метки)

21049: fsync(1)

21049: anon page @0804e000 (R)

21049: anon page @0804f000 (R) – обращения к выделенной области

21049: fsync(2)

21049: brk(0808f000) – очередной вызов malloc(), расширяем сегмент данных

21049: brk -> 134803456 (0808f000)

21049: anon page @0806e064 (W)

21049: fsync(1)

21049: fsync -> -22 (ffffffea)

21049: anon page @0806c000 (R)

21049: anon page @0806d000 (R)

Таким образом, видно, что malloc выделяет память блоками по 128 с небольшим кб при помощи вызова brk(). Рассмотрим, что происходит при увеличении размера запроса.

3) Запрашиваем последовательно 4, 8, 12, 16К, и т.д., обращаясь в цикле ко всем страницам. При этом, как только размер выделения превышает 128К, malloc выделяет память уже не из области сегмента данных программы (расширяемого вызововм brk()), а при помощи mmap(), после чего free() его освобождает последующим munmap():

789: mmap (00000000, 139264, rw-, PRIVATE | ANON, fd -1, @f019a000)

789: mmap -> -1210519552 (b7d8f000)

789: anon page @b7d8f004 (W)

789: fsync(1)

789: anon page @b7d90000 (R)

789: anon page @b7db0000 (R)

789: fsync(2)

789: munmap (b7d8f000, 139264)

789: munmap -> 0 (00000000)

При этом выделяется немного боьше памяти, чем было запрошено, и каждый раз она вся освобождается (т.е. следующие запросы опять приведут к выделениям). Опять-таки, страницы изначально возвращаются невыделенными.

3) Третий пример запрашивает память куда большими блоками, увеличивающимися по 100 Мб. При отключенном overcommit'e память быстро заканчивается, и очередной mmap возвращает ENOMEM:

1536: mmap (00000000, 629149696, rw-, PRIVATE | ANON)

1536: mmap -> -12 (fffffff4)

1536: anon page @b7e06de0 (R)

1536: brk(00000000)

1536: brk -> 134520832 (0804a000)

1536: brk(2d86b000)

1536: brk -> 134520832 (0804a000)

1536: mmap (00000000, 629280768, rw-, PRIVATE | ANON)

1536: mmap -> -12 (fffffff4)

1536: mmap (00000000, 2097152, rw-, PRIVATE | ANON)

1536: mmap -> -1212555264 (b7b9e000)

1536: munmap (b7b9e000, 401408)

1536: munmap -> 0 (00000000)

1536: munmap (b7d00000, 647168)

1536: munmap -> 0 (00000000)

1536: anon page @b7c00008 (W)

1536: mmap (00000000, 629149696, rw-, PRIVATE | ANON)

1536: mmap -> -12 (fffffff4)

Библиотека libc при этом пытается сначала выделить ту же память при помощи вызова brk(), затем снова при помощи вызова mmap(), но уже несколько меньший объем, однако и эти вызовы проходят неудачно.

4) Включим overcommit. Теперь, пока программа не обращается ко всем страницам из выделенного региона, а лишь к некоторым (не расходуя при этом всю физическую память), выполнение проходит нормально, и вызов mmap() успешно выделяет блоки памяти, значительно превыщающие объем свободной оперативной памяти (порядка 600 M):

2515: mmap (00000000, 996151296, rw-, PRIVATE | ANON)

2515: mmap -> 2089299968 (7c883000)

2515: anon page @7c883004 (W)

2515: fsync(1)

2515: anon page @8b603008 (R)

2515: anon page @9a383008 (R)

2515: anon page @a9103008 (R)

2515: fsync(2)

2515: munmap (7c883000, 996151296)

2515: munmap -> 0 (00000000)

В данном примере вызов mmap() выделяет 900М, из которых мы обращаемся лишь к четырем страницам.

5) При попытке попытаться обратиться ко всем страницам из выделенного региона, очень скоро память исчерпывается и возникает ситуация, называемая OOM – Out Of Memory. В этом случае ядро вызывает функцию oom_kill(), которая выбирает процесс-жертву для уничтожения, основываясь на расходах памяти, времени работы, приоритете и т.п., и убивает выбранный процесс, с целью освободить память. При этом в журнал отладочных сообщений ядра выдается уведомление о том, что сработал oom_kill:

kernel: kwin invoked oom-killer: gfp_mask=0x201d2, order=0, oomkillad

6) Включим теперь файл подкачки. В случае, если объем выделяемого региона превышает размер доступной физической памяти, но меньше суммарного размера ее и файла подкачки, страницы из региона сначала выделяются по мере обращения к ним, затем старые начинают выгружаться в swap‑файл, после чего на втором цикле считывания происходит их подкачка оттуда:

19225: anon page @b7418bb0 (R)

19225: anon page @b7602893 (R)

19225: swapfile page @0ae1507c (R)

19225: swapfile page @0d8cb0e6 (R)

19225: swapfile page @0af146b0 (R)

7) Системные вызовы mlock()/mlockall() делают невыгружаемой определенную страницу виртуальной памяти процесса или все его страницы – текущие и / или выделенные в будущем, в зависимости от передаваемых в вызов mlockall() флагов.

Сделаем в начале выполнения программы невыгружемыми все страницы, выделяемые в будущем, после чего выделяем блоки по 12 кб (не освобождая):

13749: brk(00000000)

13749: brk -> 134520832 (0804a000)

13749: brk(0806e000)

13749: anon page @0804a000 (W)

13749: anon page @0806c000 (W)

13749: anon page @0806d000 (W)

13749: brk -> 134668288 (0806e000)

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

Таким образом, можно сделать следующие выводы:

1. При выделении памяти ядро не выделяет сразу все физические страницы (если в mmap() не был передан флаг MAP POPULATE или страницы процесса не были заблокированы в физической памяти вызовами mlock[all]() – в этих случаях страницы всего региона подгружаются сразу)

2. Для выделения небольших объемов памяти библиотека libc расширяет сегмент данных программы вызовом brk(), для запросов, бОльших 128К – использует mmap().

Overcommit является довольно мощным средством, позволяющим выделять гораздо больше виртуальной памяти, чем доступно на самом деле, при условии использования лишь доступного ее объема (данная возможность может быть полезна для различных научно-инженерных приоложений). Однако, в случае, если реально затребованная память превысит суммарноый объем доступной и файла подкачки, возникнет критическая ситуация нехватки памяти. Заключение

В рамках данной работы были исследованы вопросы, связанные с разработкой драйверов под OS Linux, работой ядра Linux с виртуальной памятью и перехватом системных вызовов.

Драйвер может быть загружен и выгружен без перезагрузки системы. Он не влияет на работу других устройств и всей системы в целом, и не приводит к ощутимым задержкам в работе.

Было произведено исследование механизма выделения памяти в ядре Linux и библиотеке libс, исследована технология overcommit.

Возможность отследить операции процессов в памятью зачастую может быть весьма полезной при отладке программ, мониторинге или настройке системы (например, для подбора оптимальных параметров сервера, расходующего большой объем памяти).

 


Список использованной литературы

 

1. Jonathan Corbet. Linux Device Drivers, 3rd Edition.

2. Роберт Лав. Разработка ядра Linux, 2-е издание.

3. Peter Salzman. The Linux Kernel Module Programming Guide, 3rd Edition.

4. Клаудия Родригес. Азбука ядра Linux.

5. Исходники ядра и документация к ним.

 


Приложения

Код драйвера

Mmon.c

/*

* Main module and entry point of memmon.

*/

 

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

#include <linux/proc_fs.h>

 

#include «common.h»

#include «watch-pids.h»

#include «mm-fault.h»

#include «events.h»

#include «syscalls.h»

 

/*** Internal data ***/

 

/*

* procfs directory entry

*/

struct proc_dir_entry *procdir = NULL;

 

/*

* Init entry point

*/

static int __init init(void)

{

int ret = – EBUSY;

 

procdir = proc_mkdir (PROCDIR, NULL);

if (! procdir)

goto out;

if (! init_watch_pids())

goto out_procdir;

if (! init_events())

goto out_watch_pids;

if (! capture_syscalls())

goto out_events;

capture_mmfault();

 

return 0;

 

out_events:

fini_events();

out_watch_pids:

fini_watch_pids();

out_procdir:

remove_proc_entry (PROCDIR, NULL);

out:

return ret;

}

 

module_init(init);

 

/*

* Exit point

*/

static void __exit exit(void)

{

release_mmfault();

restore_syscalls();

fini_events();

fini_watch_pids();

remove_proc_entry (PROCDIR, NULL);

}

 

module_exit(exit);

 

/*** Module info ***/

 

MODULE_LICENSE («GPL»);

MODULE_AUTHOR («Ivan Korotkov»);

MODULE_DESCRIPTION («Linux Virtual Memory Monitor»);

 

Events.h

 

/*

* Events ringbuffer.

*/

 

#ifndef MEMMON_EVENTS_H

#define MEMMON_EVENTS_H

 

/* Filename in procfs directory */

#define EVENTS_ENTRY «events»

 

/* Types of events */

enum memmon_event_type

{

NOTUSED = 0, /* to prevent treating zero-filled region as event struct */

MMAP2,

MUNMAP,

MREMAP,

MLOCK,

MUNLOCK,

MLOCKALL,

MUNLOCKALL,

BRK,

FSYNC,

ANON_PF,

SWAP_PF,

FILE_PF,

SYSCALLRET

};

 

/*

* Struct describing each event

*/

struct memmon_event

{

/* Type */

enum memmon_event_type type;

 

/* Caller PID */

pid_t pid;

 

/* Per-type data */

union

{

struct

{

void __user *start;

size_t len;

} munmap;

 

struct

{

void __user *start;

size_t len;

unsigned long prot, flags;

unsigned long fd, off;

} mmap2;

 

struct

{

void __user *start[2];

size_t len[2];

unsigned flags;

} mremap;

 

struct

{

void __user *start;

size_t len;

} mlock, munlock;

 

struct

{

unsigned long flags;

} mlockall;

 

struct

{

void __user *addr;

} brk;

 

struct

{

int fd;

} fsync;

 

struct

{

void __user *addr;

int write;

} pagefault;

 

struct

{

char *callname;

long ret;

} callret;

};

};

 

#define NEVENTS (EVENTS_BUFLEN/sizeof (struct memmon_event))

 

/*

* Initializes event ringbuffer & creates /proc entry

*/

int init_events(void);

 

/*

* Destroys ringbuffer & removes /proc entry

*/

void fini_events(void);

 

/*

* Adds events to ringbuffer tail

*/

void put_event (const struct memmon_event *ev);

 

#endif // MEMMON_EVENTS_H

 

Events.c

/*

* Events ringbuffer.

*/

 

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

#include <linux/seq_file.h>

#include <linux/proc_fs.h>

#include <linux/poll.h>

#include <linux/mman.h>

 

#include «common.h»

#include «events.h»

 

/*** Forward declarations ***/

 

static int events_open (struct inode *i, struct file *filp);

static unsigned events_poll (struct file *filp, struct poll_table_struct *pt);

 

static void *events_seqstart (struct seq_file *m, loff_t *pos);

static void events_seqstop (struct seq_file *m, void *p);

static void *events_seqnext (struct seq_file *m, void *p, loff_t *pos);

static int events_seqprint (struct seq_file *m, void *p);

 

/* Default ringbuffer size */

#define EVENTS_BUFLEN (32*1024)

 

/* Min ringbuffer size */

#define MIN_EVENTS_BUFLEN (8*1024)

 

/*** Module parameters ***/

 

/* Actual ringbuffer size */

static int buflen = EVENTS_BUFLEN;

module_param (buflen, int, 0444);

 

/*** File operations ***/

 

static const struct file_operations events_fops =

{

owner = THIS_MODULE,

open = events_open,

read = seq_read,

release = seq_release,

poll = events_poll

};

 

static const struct seq_operations events_seqop =

{

start = events_seqstart,

stop = events_seqstop,

next = events_seqnext,

show = events_seqprint

};

 

/*** Internal data ***/

 

/* Ringbuffer */

static struct memmon_event *events;

 

/* Last entry left in ringbuffer

* (where 1st read should begin) */

static int ev_start;

 

/* Current write position */

static int ev_end;

 

/* Whether there was ringbuffer overflow */

static int ev_ovf = 0;

 

DECLARE_WAIT_QUEUE_HEAD (ev_waitq);

spinlock_t ev_lock = SPIN_LOCK_UNLOCKED;

 

/* Damn seq_file doesn't update file pos when we return NULL iterator,

* so we first return this one and then NULL on next seqnext() call */

static void *dummy_ptr = &dummy_ptr;

 

/*** Entry points ***/

 

/*

* open() handler

*/

static int events_open (struct inode *i, struct file *filp)

{

int ret;

 

/*

* Ringbuffer is not seekable

*/

nonseekable_open (i, filp);

 

/*

* Open seq_file and set its initial pos

*/

ret = seq_open (filp, &events_seqop);

if (! ret)

{

struct seq_file *m = filp->private_data;

m->private = filp;

m->index = ev_start;

}

 

return ret;

}

 

/*

* poll/epoll() handler

*/

static unsigned events_poll (struct file *filp, struct poll_table_struct *pt)

{

struct seq_file *m = filp->private_data;

unsigned mask = 0;

 

spin_lock (&ev_lock);

 

poll_wait (filp, &ev_waitq, pt);

/*

* The only poll event we can trigger is normal read event

*/

if (m->index!= ev_end)

mask = POLLIN | POLLRDNORM;

 

spin_unlock (&ev_lock);

 

return mask;

}

 

/*

* Called by seq_file within read() request

*/

static void *events_seqstart (struct seq_file *m, loff_t *pos)

{

struct file *filp = m->private;

 

spin_lock (&ev_lock);

 

/*

* Wait for data become available

*/

while (*pos == (loff_t) ev_end)

{

void *err = NULL;

 

/* Can't schedule while atomic */

spin_unlock (&ev_lock);

 

if (filp->f_flags & O_NONBLOCK)

err = ERR_PTR(-EAGAIN);

else if (wait_event_interruptible (ev_waitq, *pos!= (loff_t) ev_end))

err = ERR_PTR(-ERESTARTSYS);

 

/*

* There IS a slim chance, that we loose waiting condition

* between awakening and acquiring spinlock – hence while() loop

*/

 

spin_lock (&ev_lock);

 

if (err)

return err;

}

 

return events + *pos;

}

 

/*

* Finish read() request

*/

static void events_seqstop (struct seq_file *m, void *p)

{

spin_unlock (&ev_lock);

}

 

/*

* Iterate to next event

*/

static void *events_seqnext (struct seq_file *m, void *p, loff_t *pos)

{

struct memmon_event *ev;

 

/* Dummy iterator – time to exit */

if (p == dummy_ptr)

return NULL;

 

++*pos;

ev = events + *pos;

 

/* Overflow */

if (ev – events > NEVENTS)

*pos = 0;

 

/*

* We reached end. Decrement file pos ('coz it will be incremented then back)

* and return dummy iterator (otherwise file pos won't be updated at all)

*/

if (*pos == (loff_t) ev_end)

{

–*pos;

return dummy_ptr;

}

 

return events + *pos;

}

 

/*

* Actually prints current iterator to read buffer

*/

static int events_seqprint (struct seq_file *m, void *p)

{

struct memmon_event *ev = p;

 

if (ev == dummy_ptr)

return 0;

 

seq_printf (m, «%d:», ev->pid);

 

switch (ev->type)

{

case MMAP2:

seq_printf (m, «mmap (%p,%u,», ev->mmap2.start, ev->mmap2.len);

if (ev->mmap2.prot & PROT_READ)

seq_puts (m, «r»);

else

seq_puts (m, «–»);

if (ev->mmap2.prot & PROT_WRITE)

seq_puts (m, «w»);

else

seq_puts (m, «–»);

if (ev->mmap2.prot & PROT_EXEC)

seq_puts (m, «x,»);

else

seq_puts (m, «-,»);

 

if (ev->mmap2.flags & MAP_SHARED)

seq_puts (m, «SHARED»);

else if (ev->mmap2.flags & MAP_PRIVATE)

seq_puts (m, «PRIVATE»);

 

if (ev->mmap2.flags & MAP_LOCKED)

seq_puts (m, «| LOCKED»);

if (ev->mmap2.flags & MAP_ANON)

seq_puts (m, «| ANON»);

if (ev->mmap2.flags & MAP_POPULATE)

seq_puts (m, «| READAHEAD»);

 

if (ev->mmap2.flags & MAP_ANON)

seq_puts (m,»)\n»);

else

seq_printf (m,», fd% ld, @%p)\n», (long) ev->mmap2.fd,

(void *) ev->mmap2.off);

break;

 

case MUNMAP:

seq_printf (m, «munmap (%p,%d)\n», ev->munmap.start, ev->munmap.len);

break;

 

case MREMAP:

seq_printf (m, «mremap (%p,%d ->%p,%d)\n», ev->mremap.start[0], ev->mremap.len[0],

ev->mremap.start[1], ev->mremap.len[1]);

break;

 

case MLOCK:

seq_printf (m, «mlock (%p,%d)\n», ev->mlock.start, ev->mlock.len);

break;

 

case MUNLOCK:

seq_printf (m, «munlock (%p,%d)\n», ev->munlock.start, ev->munlock.len);

break;

 

case MLOCKALL:

seq_puts (m, «mlockall(»);

if (ev->mlockall.flags & MCL_CURRENT)

{

seq_puts (m, «CURRENT»);

if (ev->mlockall.flags & MCL_FUTURE)

seq_puts (m, «| FUTURE»);

}

else if (ev->mlockall.flags & MCL_FUTURE)

seq_puts (m, «FUTURE»);

seq_puts (m,»)\n»);

 

break;

 

case MUNLOCKALL:

seq_puts (m, «munlockall()\n»);

break;

 

case BRK:

seq_printf (m, «brk(%p)\n», ev->brk.addr);

break;

 

case FSYNC:

seq_printf (m, «fsync(%d)\n», ev->fsync.fd);

break;

 

case ANON_PF:

seq_printf (m, «anon page @%p (%s)\n», ev->pagefault.addr,

ev->pagefault.write? «W»: «R»);

break;

 

case SWAP_PF:

seq_printf (m, «swapfile page @%p (%s)\n», ev->pagefault.addr,

ev->pagefault.write? «W»: «R»);

break;

 

case FILE_PF:

seq_printf (m, «shared file page @%p (%s)\n», ev->pagefault.addr,

ev->pagefault.write? «W»: «R»);

break;

 

case SYSCALLRET:

seq_printf (m, «%s ->%ld (%p)\n», ev->callret.callname, ev->callret.ret,

(void *) ev->callret.ret);

break;

 

default:

printk («memmon: Unexpected event% d\n», ev->type);

return 1;

}

 

return 0;

}

 

/*** Exported entries ***/

 

/*

* Initializes event ringbuffer & creates /proc entry

*/

int init_events(void)

{

struct proc_dir_entry *entry;

 

buflen = max (buflen, MIN_EVENTS_BUFLEN);

 

events = kzalloc (buflen, GFP_KERNEL);

if (! events)

{

printk («memmon: Event ringbuffer too big!\n»);

return 0;

}

 

ev_start = ev_end = 0;

 

entry = create_proc_entry (EVENTS_ENTRY, 0444, procdir);

if (entry)

entry->proc_fops = &events_fops;

else

{

kfree(events);

return 0;

}

 

return 1;

}

 

/*

* Destroys ringbuffer & removes /proc entry

*/

void fini_events(void)

{

remove_proc_entry (EVENTS_ENTRY, procdir);

kfree(events);

}

 

/*

* Adds events to ringbuffer tail

*/

void put_event (const struct memmon_event *ev)

{

spin_lock (&ev_lock);

 

events [ev_end] = *ev;

/* Overflow */

if (++ev_end > NEVENTS)

{

ev_start = ev_end = 0;

ev_ovf = 1;

}

 

/*

* If overflow happened at least once, ev_start must be next to ev_end.

* Otherwise, it remains zero.

*/

if (ev_ovf && ++ev_start > NEVENTS)

ev_start = 0;

 

spin_unlock (&ev_lock);

 

wake_up_interruptible_sync (&ev_waitq);

}

 

Watch-pids.h

/*

* Selection of PIDs to watch for.

*/

 

#ifndef MEMMON_WATCH_PIDS_H

#define MEMMON_WATCH_PIDS_H

 

/*

* Checks whether PID @pid is present in PID set

* Returns 1 if present

*/

int pid_present (pid_t pid);

 

/*

* Initializes PID set & creates /proc entry

*/

int init_watch_pids(void);

 

/*

* Destroys PID set & removes /proc entry

*/

void fini_watch_pids(void);

 

#endif // MEMMON_WATCH_PIDS_H

 

Watch-pids.c

/*

* Selection of PIDs to watch for.

*/

 

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

#include <linux/proc_fs.h>

#include <linux/bitmap.h>

#include <asm/uaccess.h>

#include <asm/bitops.h>

 

#include «common.h»

#include «watch-pids.h»

 

/*** Forward declarations ***/

 

static int watch_pids_open (struct inode *i, struct file *filp);

static int watch_pids_release (struct inode *i, struct file *filp);

static ssize_t watch_pids_read (struct file *filp, char __user *buf, size_t count, loff_t *off);

static ssize_t watch_pids_write (struct file *filp, const char __user *buf,

size_t count, loff_t *offp);

 

/*** Internal data ***/

 

/* Filename in procfs directory */

#define WATCHPID_ENTRY «watch-pids»

 

#define PID_COUNT PID_MAX_DEFAULT + 1

 

/* PIDs are stored in one single bitmap for 8192 entries

* This is VERY RARELY unacceptable */

static DECLARE_BITMAP (watched_pids, PID_COUNT);

 

/*** File operations ***/

 

static const struct file_operations watch_pids_fops =

{

owner = THIS_MODULE,

open = watch_pids_open,

read = watch_pids_read,

write = watch_pids_write,

release = watch_pids_release

};

 

/*** Entry points ***/

 

/*

* open() handler

*/

static int watch_pids_open (struct inode *i, struct file *filp)

{

try_module_get (THIS_MODULE);

 

/*

* If file opened for read, print PID set to internal buffer

*/

if (filp->f_mode & FMODE_READ)

{

const int FDATA_SIZ = 32*1024;

char *fdata;

int len;

 

/*

* Disallow mixed RW-access

*/

if (filp->f_mode & FMODE_WRITE)

return – EINVAL;

 

fdata = kzalloc (FDATA_SIZ, GFP_KERNEL);

len = bitmap_scnlistprintf (fdata, FDATA_SIZ – 1,

watched_pids, PID_COUNT);

/* Append \n */

if (len)

{

fdata [len++] = '\n';

fdata[len] = 0;

}

filp->private_data = fdata;

}

 

return 0;

}

 

/*

* close() handler

*/

static int watch_pids_release (struct inode *i, struct file *filp)

{

module_put (THIS_MODULE);

 

if (filp->private_data)

kfree (filp->private_data);

 

return 0;

}

 

/*

* read() handler – simply return chunk of data from

* previously allocated and formatted buffer

*/

static ssize_t watch_pids_read (struct file *filp, char __user *buf,

size_t count, loff_t *offp)

{

size_t len = strlen (filp->private_data);

char *fdata = filp->private_data;

 

if (*offp >= len)

return 0;

len = min (count, len – (size_t) (*offp));

if (copy_to_user (buf, fdata + (*offp), len))

return – EFAULT;

*offp += len;

 

return len;

}

 

/*

* write() handler

* Buffer must hold ASCII representation of single integer

* if positive, it's value is PID to add to set

* if negative, it's absolute value is PID to remove from set

* if zero, PID set is cleared

*/

static ssize_t watch_pids_write (struct file *filp, const char __user *buf,

size_t count, loff_t *offp)

{

const size_t maxlen = 4096;

size_t len;

pid_t new_pid;

char *data;

ssize_t res = – ENOMEM;

 

/* copy up to one page to our buffer */

len = min (maxlen, count);

data = kzalloc (len, GFP_KERNEL);

if (unlikely(! data))

return – ENOMEM;

 

if (copy_from_user (data, buf, len))

res = – EFAULT;

else if ((sscanf (data, «%d», &new_pid) == 1) &&

new_pid <= PID_COUNT && new_pid >= – PID_COUNT)

{

if (new_pid > 0)

set_bit (new_pid, watched_pids);

else if (new_pid < 0)

clear_bit (-new_pid, watched_pids);

else

bitmap_zero (watched_pids, PID_COUNT);

res = len;

}

else

/* buffer doesn't represent a number in PID range */

res = – EIO;

 

kfree(data);

return res;

}

 

/*** Exported entries ***/

 

/*

* Checks whether PID @pid is present in PID set

* Returns 1 if present

*/

int pid_present (pid_t pid)

{

if (pid > PID_COUNT || pid <= 0)

return 0;

return test_bit (pid, watched_pids)? 1: 0;

}

 

/*

* Initializes PID set & creates /proc entry

*/

int init_watch_pids(void)

{

struct proc_dir_entry *entry;

entry = create_proc_entry (WATCHPID_ENTRY, 0666, procdir);

if (entry)

entry->proc_fops = &watch_pids_fops;

else

return 0;

 

bitmap_zero (watched_pids, PID_COUNT);

 

return 1;

}

 

/*

* Destroys PID set & removes /proc entry

*/

void fini_watch_pids(void)

{

remove_proc_entry (WATCHPID_ENTRY, procdir);

}

 

Syscalls.h

/*

* Syscall capture facility.

*/

 

#ifndef MEMMON_SYSCALLS_H

#define MEMMON_SYSCALLS_H

 

/*

* Installs handlers.

*/

int capture_syscalls(void);

 

/*

* Uninstalls handlers

*/

void restore_syscalls(void);

 

#endif //MEMMON_SYSCALLS_H

 

Syscalls.c

/*

* Syscall capture facility.

*/

 

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

#include <linux/proc_fs.h>

 

#include «common.h»

#include «syscalls.h»

#include «events.h»

#include «watch-pids.h»

 

/*** Syscalls ***/

 

/*

* They just put an appropriate event into ringbuffer

*/

asmlinkage void sys2_mmap2 (void __user *start, size_t length,

unsigned long prot, unsigned long flags,

unsigned long fd, unsigned long pgoff)

{

struct memmon_event ev = {.type = MMAP2.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.mmap2.start = start;

ev.mmap2.len = length;

ev.mmap2.prot = prot >> 3;

ev.mmap2.flags = flags;

ev.mmap2.fd = fd;

ev.mmap2.off = pgoff;

put_event(&ev);

}

 

asmlinkage void sys2_mmap2_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «mmap»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_munmap (void __user *start, size_t length)

{

struct memmon_event ev = {.type = MUNMAP.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.munmap.start = start;

ev.munmap.len = length;

put_event(&ev);

}

 

asmlinkage void sys2_munmap_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «munmap»;

ev.callret.ret = ret;

put_event(&ev);

}

asmlinkage void sys2_mremap (void __user *addr1, size_t length1,

unsigned long length2, unsigned long flags,

void __user *addr2)

{

struct memmon_event ev = {.type = MREMAP.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.mremap.start[0] = addr1;

ev.mremap.start[1] = addr2;

ev.mremap.len[0] = length1;

ev.mremap.len[1] = length2;

ev.mremap.flags = flags;

put_event(&ev);

}

 

asmlinkage void sys2_mremap_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «mremap»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_mlock (void __user *start, size_t length)

{

struct memmon_event ev = {.type = MLOCK.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.mlock.start = start;

ev.mlock.len = length;

put_event(&ev);

}

 

asmlinkage void sys2_mlock_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «mlock»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_munlock (void __user *start, size_t length)

{

struct memmon_event ev = {.type = MUNLOCK.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.munlock.start = start;

ev.munlock.len = length;

put_event(&ev);

}

 

asmlinkage void sys2_munlock_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «munlock»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_mlockall (unsigned long flags)

{

struct memmon_event ev = {.type = MLOCKALL.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.mlockall.flags = flags;

put_event(&ev);

}

 

asmlinkage void sys2_mlockall_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «mlockall»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_munlockall()

{

struct memmon_event ev = {.type = MUNLOCKALL.pid = current->pid};

if (! pid_present (ev.pid)) return;

put_event(&ev);

}

 

asmlinkage void sys2_munlockall_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «munlockall»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_brk (void __user *start)

{

struct memmon_event ev = {.type = BRK.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.brk.addr = start;

put_event(&ev);

}

 

asmlinkage void sys2_brk_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «brk»;

ev.callret.ret = ret;

put_event(&ev);

}

 

asmlinkage void sys2_fsync (int fd)

{

struct memmon_event ev = {.type = FSYNC.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.fsync.fd = fd;

put_event(&ev);

}

 

asmlinkage void sys2_fsync_exit (long ret)

{

struct memmon_event ev = {.type = SYSCALLRET.pid = current->pid};

if (! pid_present (ev.pid)) return;

ev.callret.callname = «fsync»;

ev.callret.ret = ret;

put_event(&ev);

}

 

/*** Handler tables ***/

 

/* Kernel syscall table */

extern void *sys_call_table[];

 

/* Our table w/saved offsets */

void *old_sys_call [NR_syscalls];

 

/* Our pre-call handlers */

void *sys_call_trap [NR_syscalls];

 

/* Our post-call handlers */

void *sys_call_exit [NR_syscalls];

 

/*

* Struct describind our handler

*/

struct syscall_handler

{

/* Syscall nr */

int nr;

/* Pre-call & post-call handler */

void *hand1, *hand2;

};

#define SYSCALL_HANDLER(name) {__NR_##name, sys2_##name, sys2_##name##_exit}

#define SYSCALL_HANDLERS_END() {0, 0, 0}

 

/*

* Main handler table

* Each SYSCALL_HANDLER(name) entry installs handlers

* «sys2_name/sys2_name_exit for sys_name call.

*/

 

struct syscall_handler syscalls[] =

{

SYSCALL_HANDLER(mmap2),

SYSCALL_HANDLER(munmap),

SYSCALL_HANDLER(mremap),

SYSCALL_HANDLER(mlock),

SYSCALL_HANDLER(munlock),

SYSCALL_HANDLER(mlockall),

SYSCALL_HANDLER(munlockall),

SYSCALL_HANDLER(brk),

SYSCALL_HANDLER(fsync),

SYSCALL_HANDLERS_END()

};

 

/* Located in syscall-entry.S */

void syscalls_entry(void);

 

/*** Exported entries ***/

 

/*

* Installs handlers.

*/

int capture_syscalls(void)

{

int i;

for (i = 0; syscalls[i].hand1; ++i)

{

int nr = syscalls[i].nr;

sys_call_trap[nr] = syscalls[i].hand1;

sys_call_exit[nr] = syscalls[i].hand2;

old_sys_call[nr] = sys_call_table[nr];

sys_call_table[nr] = syscalls_entry;

}

 

return 1;

}

 

/*

* Uninstalls handlers

*/

void restore_syscalls(void)

{

int i;

for (i = 0; syscalls[i].hand1; ++i)

{

int nr = syscalls[i].nr;

sys_call_table[nr] = old_sys_call[nr];

}

}

 

Syscalls-entry.S

/*

* Syscall entry/exit capture

*/

 

#include «offsets.h»

 

/* Entry handler table */

extern    sys_call_trap

/* Exit handler table */

extern    sys_call_exit

 

/* Global entry for our syscalls */

syscalls_entry:

 

/* Save registers in order syscall handlers expect 'em */

pushl %eax

pushl %ebp

pushl %edi

pushl %esi

pushl %edx

pushl %ecx

pushl %ebx

 

/* Save eax */

movl %eax, TI_stk0 (%ebp)

/* Call our handler */

call *sys_call_trap (,%eax, 4)

/* Fake return address */

movl 28 (%esp),%eax

movl %eax, TI_stk0 + 4 (%ebp)

movl $sysreturn, 28 (%esp)

 

/* Restore context */

popl %ebx

popl %ecx

popl %edx

popl %esi

popl %edi

popl %ebp

popl %eax

 

/* Jump to default system handler */

jmpl *old_sys_call (,%eax, 4)        

 

sysreturn:

/* Save registers */

pushal

/* Pass new% eax to exit handler */

pushl %eax

/* Restore original% eax */

movl TI_stk0 (%ebp),%eax

/* Call our exit handler */

call *sys_call_exit (,%eax, 4)

/* Restore context */

popl %eax

popal

/* Jump back to syscall dispatcher entry */

jmpl *TI_stk0 + 4 (%ebp)

 

globl syscalls_entry

 

Gen-offsets.c

//

 

#define __KERNEL__

 

/* bugoga */

 

#include <linux/kernel.h>

#include <linux/autoconf.h>

#include <linux/thread_info.h>

#include <stdio.h>

 

int main()

{

printf («#define TI_stk0% d\n», offsetof (struct thread_info, supervisor_stack));

return 0;

}

 

Mm-fault.h

/*

* Pagefault interception.

*/

 

#ifndef MEMMON_MM_FAULT_H

#define MEMMON_MM_FAULT_H

 

/*

* Install pagefault handler

*/

void capture_mmfault(void);

 

/*

* Uninstall handler

*/

void release_mmfault(void);

 

#endif // MEMMON_MM_FAULT_H

 

Mm-fault.c

/*

* Pagefault interception.

*/

 

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

#include <linux/mm.h>

 

#include «common.h»

#include «mm-fault.h»

#include «events.h»

#include «watch-pids.h»

 

/*

* Dirty kernel hack: PF hook that is called every time

* some process PF's for some page that BELONGS to his VMA space.

*/

extern void (*mm_handle_fault_hook) (struct mm_struct *mm, struct vm_area_struct *vma,

void __user *address, pte_t *pte,

pmd_t *pmd, int write_access);

 

/*

* Pagefault handler

*/

void mm_handle_fault (struct mm_struct *mm, struct vm_area_struct *vma,

void __user *address, pte_t *pte,

pmd_t *pmd, int write_access)

{

struct memmon_event ev = {.pid = current->pid};

pte_t entry = *pte;

 

/*

* If PF happened due to R/W or U/S access violation, ignore it

*/

if (! pid_present (current->pid) || pte_present(entry))

return;

 

/*

* Faulted page is either backed by swapfile, some shared executable file

* or no file yet at all (anonymous page)

*/

if (pte_none(entry))

ev.type = ANON_PF;

else if (pte_file(entry))

ev.type = FILE_PF;

else

ev.type = SWAP_PF;

 

ev.pagefault.addr = address;

ev.pagefault.write = write_access;

 

put_event(&ev);

}

 

/*** Exported entries ***/

 

/*

* Install pagefault handler

*/

void capture_mmfault(void)

{

mm_handle_fault_hook = mm_handle_fault;

}

 

/*

* Uninstall handler

*/

void release_mmfault(void)

{

mm_handle_fault_hook = NULL;

}

 

Common.h

/*

* Common defines and global data

*/

#ifndef MEMMON_COMMON_H

#define MEMMON_COMMON_H

 

/* procfs directory name */

#define PROCDIR «memmon»

 

/*

* procfs directory entry

*/

extern struct proc_dir_entry *procdir;

 

#endif // MEMMON_COMMON_H

 

Makefile

#

 

ifneq ($(KERNELRELEASE),)

 

obj-m:= memmon.o

memmon-objs:= mmon.o events.o watch-pids.o syscalls.o syscalls-entry.o mm-fault.o

 

else

 

KERNELDIR?= /lib/modules/$(shell uname – r)/build

PWD:= $(shell pwd)

 

all: offsets.h modules

 

offsets.h: $(KERNELDIR)/include/asm/thread_info.h

$(MAKE) gen-offsets

gen-offsets > offsets.h

$(RM) gen-offsets

 

clean modules:

$(MAKE) – C $(KERNELDIR) M=$(PWD) $(MAKECMDGOALS)

 

PHONY: modules.DEFAULT all

 

endif

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...