Реферати

Курсова робота: Моніторинг віртуальної пам'яті в ОС Linux

Обмеження людського розуму. Моральний зміст обмеження людського розуму і розширення границь віри у філософії И. Канта. Вплив науки на людину. Віра і моральність.

Технологія оброблення кукурудзи. Уведення Кукурудза - культура різнобічного застосування. В усіх країнах світу на продовольчі цілі використовується 20-25% валового збору її зерна. В даний час з нього виготовляють понад 500 різні основні і побічні продукти, у тому числі борошно, крупу, кукурудзяні хлопья. Кукурудзяне борошно застосовують у хлібопеченні й у кондитерському виробництві.

Аналіз фінансового стану підприємства 58. Уведення. Однією з найважливіших умов успішного керування фінансами підприємства є аналіз його фінансового стану. Фінансовий стан підприємства

Організація керування державними фінансами. План: Фінансові відносини ...... Ст. 2. Державні органи фінансового керування ...... Ст. 3. Правові повноваження і задачі державних органів фінансового керування ...... Ст. 5.

Статистика ринку житла і житлових умов населення. К У Р С О В А Я за курсом "Соціальна статистика" на тему: "Статистика ринку житла і житлових умов населення" ОДЕРЖАНИЕ Введення Характеристика ринку житла і житлових умов населення

РОЗРАХУНКОВО-ПОЯСНЮВАЛЬНА ЗАПИСКА

до курсової роботи на тему:

"Моніторинг віртуальної пам'яті в ОС Linux"

Введення

Часто буває необхідно відстежити за роботою того або інакшого процесу з пам'яттю, наприклад, щоб виявити витоки пам'яті, взнати, в які моменти і скільки пам'яті процес виділяє. Для рішення даної задачі існує ряд коштів, а саме:

· Файлова система /proc- дозволяє прочитати різну інформацію про всю систему загалом і про кожного з процесів, в тому числі інформацію про використання процесом пам'яті і про відображення пам'яті даного процесу. (приклад:

# cat /proc/'pgrep test'/status

Name: test1.

..

VmPeak: 1556 kB

VmSize: 1544 kB

VmLck: 0 kB

VmHWM: 308 kB

VmRSS: 308 kB

VmData: 148 kB

VmStk: 88 kB

VmExe: 4 kB

VmLib: 1276 kB

VmPTE: 12 kB

# cat /proc/'pgrep test'/maps

08048000-08049000 r-xp 00000000 08:01 17432879 /home/twee/work/mstu/coding/memmon/test/test1

08049000-0804a000 rw-p 00000000 08:01 17432879 /home/twee/work/mstu/coding/memmon/test/test1

0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]

b7e4b000-b7e4c000 rw-p b7e4b000 00:00 0

b7e4c000-b7f75000 r-xp 00000000 03:05 1604119 /.lib/tls/libc-2.3.6 so

b7f75000-b7f76000 r-p 00128000 03:05 1604119 /.lib/tls/libc-2.3.6 so

b7f76000-b7f79000 rw-p 00129000 03:05 1604119 /.lib/tls/libc-2.3.6 so

b7f79000-b7f7c000 rw-p b7f79000 00:00 0

b7f9d000-b7fb3000 r-xp 00000000 03:05 752968 /.lib/ld-2.3.6 so

b7fb3000-b7fb5000 rw-p 00015000 03:05 752968 /.lib/ld-2.3.6 so

bfc2a000-bfc40000 rw-p bfc2a000 00:00 0 [stack]

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

). На приведеному роздруку видно, скільки пам'яті використовує процес test і під які саме потреби, а так само його карту пам'яті. Однак таким чином не можна відстежити динаміку роботи процесу з пам'яттю.

· strace- утиліта, що дозволяє трасувати всі системні виклики, що виконуються даним процесом (зокрема, виділення пам'яті визовамиbrk/mmap). Вона використовує стандартний відлагоджувальний механізм ядра під названиемptrace - підключається до досліджуваного процесу як відладчик (визововptrace(), вказуючи при цьому флагPTRACE_SYSCALL, що примушує систему повідомляти трасуючий процес про всі системні виклики що трасується). Приклад його роботи:

execve (»./test3», [«test3»], [/* 61 vars */]) = 0.

..

fsync(0) = -1 EINVAL (Invalid argument)

mmap2 (NULL, 2101248, PROT_READ¦PROT_WRITE, MAP_PRIVATE¦MAP_ANONYMOUS, -1, 0) = 0xb7bf4000

fsync(1) = -1 EINVAL (Invalid argument)

fsync(2) = -1 EINVAL (Invalid argument)

munmap (0xb7bf4000, 2101248) = 0

exit_group(0)

На приведеному трасуванні видно, як процес виділяє і звільняє 2101248 байт пам'яті. На жаль, цей засіб не дозволяє стежити за всіма процесами в системі загалом, а так само за виділеннями фізичних сторінок процесу.

Таким чином, виникає необхідність в засобі, що дозволяє відстежувати не тільки виділення віртуальної пам'яті процесу, але і виділення окремих сторінок фізичної пам'яті внаслідок сторінкових збоїв.

1. Аналітичний розділ

1.1 Постановка задачі

Розробити драйвер під Linux, що відстежує виділення і звільнення процесами віртуальної пам'яті і виділення фізичних сторінок при сторінкових відмовах.

Драйвер повинен підтримувати динамічне завантаження і вивантаження без перезапуску системи і завдання списку процесів, за якими необхідно виконувати моніторинг.

Взаємодія з призначеною для користувача програмою здійснюється за допомогою файлів, що створюються в файловій системі/proc.

Ядро відповідає за створення і знищення процесів і за їх зв'язок із зовнішнім світом (введення і висновок). Взаємодія процесів один з одним (за допомогою сигналів, програмних каналів або інших коштів межпроцессного взаємодії) є основою загальної системної функціональності, і також здійснюється за допомогою ядра. Планувальник, що розподіляє час центрального процесора між процесами, є частиною підсистеми управління процесами. У загальних словах, підсистема управління процесами реалізовує абстракцію безлічі процесів, працюючих одночасно на одному або декількох процесорах.

1.1.1 Управління пам'яттю

Пам'ять комп'ютера - один з головних ресурсів, і продуктивність системи критично залежить від політики розподілу пам'яті. Ядро створює віртуальний адресний простір для кожного процесу, використовуючи при цьому обмежену кількість фізичної пам'яті і, при необхідності, повторну пам'ять, таку, як жорсткий диск. По мірі необхідності сторінки можуть бути вивантажені в файл подкачки, або файл, з якого вони були відображені в пам'ять (у випадку, якщо вони не були модифіковані з моменту завантаження з файла, вони просто видаляються з пам'яті). За умовчанням ядро не дозволяє виділити одному процесу більше пам'яті, ніж сумарний об'єм доступної оперативної і swap-пам'яті. Однак є така можливість, як overcommit («перевиделение»), яка дозволяє виділити набагато більше пам'яті, при умові, що реально використовуватися буде лише невелика її частина (допустимо, при роботі з розрідженим масивом). Overcommit включається командою

# echo 1 > /proc/sys/vm/overcommit_memory

а відключається

# echo 0 > /proc/sys/vm/overcommit_memory

Цифра 1 означає вибраний режим управління перевиделением (0 означає його відсутність, 1 - допустиме перевиделение необмежених об'ємів пам'яті, 2 - деякий евристичний алгоритм визначення максимально допустимого об'єму перевиделения).

1.1.2 Файлова система

Система Linux заснована на концепції файлової системи. Практично будь-який об'єкт системи може бути розглянутий як файл. Ядро будує єдину ієрархічну файлову систему (єдине дерево директорія) на основі не володіючого ієрархічною структурою обладнання. У результаті абстракція файла активно використовується всією системою.

1.1.3 Підсистема введення-висновку

Практично будь-яка операція в системі є, по суті, операція з пристроєм. За винятком процесора, пам'яті і дуже невеликого числа інших елементів, порядок виконання роботи з пристроєм, а отже і код, що виконується, що виконує цю роботу, залежить головним чином від конкретного пристрою. Такий код називається драйвером пристрою. Ядро повинно включати в себе драйвери для всіх периферійних пристроїв, вхідних в систему, від жорсткого диска до клавіатури.

1.1.4 Мережева підсистема

Ядро повинно управляти роботою з мережею, оскільки більшість мережевих операцій не залежить від процесу. Приходячі мережеві пакети є асинхронними подіями, т. е. під час роботи одного процесу може прийти пакет, адресований іншому процесу. Пакети повинні бути прийняті, розпізнані і розподілені до того, як про них взнає процес. Система відповідає за передачу даних через програмні і мережеві інтерфейси, а так само управляти виконанням програм відповідно до роботи мережі. До того ж, всі задачі маршрутизації і виділення мережевих адрес виконує ядро.

1.1.5 Системні виклики

Системні виклики, такі какopen(), fork(), read(), etc є зв'язуючим інтерфейсом між ядром і призначеними для користувача додатками. У Linux 2.6 існує біля 330 різних викликів (багато хто з них надлишковий або збережений по причинами сумісності). Їх виклик відбувається через переривання 0x80 або інструкцію sysenter (на сучасних процесорах). При цьому в регистрEAXпомещается номер системного виклику, а в інші 6 регістрів (кромеESP) - аргументи (т. е. будь-який системний виклик може приймати до шести 32-битних аргументів) в порядкеEBX, ECX, EDX, ESI, EDI, EBP. Точка входу всіх системних викликів розташована в файлеarch/i386/kernel/entry.S, який викликає обробник конкретного виклику по таблиці визововsys_call_table, передаючи їй регістри через стек.

1.1.6 модулі,

що Завантажуються Одна з важливих особливостей ядра Linux - це здатність розширювати власну функціональність безпосередньо в період виконання.

Кожний фрагмент коду, що виконується, який може бути доданий в ядро під час його роботи, називається модулем ядра. Кожний модуль створюється з об'єктного коду, не пов'язаного в повноцінний файл, що виконується. Модуль може бути завантажений в ядро за допомогою програми insmod (зухвалої функцииcreate_module() /init_module()), і вивантажений з допомогою rmmod (визивающегоdelete_module()). У даній роботі реалізовує саме такий модуль, що динамічно завантажується.

1.1.7 Типи пристроїв

В Linux розрізнюють три основних типи пристроїв. Кожний драйвер звичайно відповідає одному з цих типів. Виділяють:

· Символьні драйвери

· Блокові драйвери

· Мережеві драйвери

Символьний пристрій може розглядатися як потік байт (так само як і файл); символьний драйвер повинен реалізовувати таку поведінку. Такий драйвер має, як правило, функції відкриття, закриття, читання і записів. Текстова консоль і послідовний порт - приклади символьних пристроїв. Вони можуть легко бути представлені абстракцією потоків. Робота з символьними пристроями здійснюється через спеціальні файли пристроїв, що знаходяться в директорія /dev. Єдина значуща відмінність звичайного файла від такого пристрою - це довільний доступ, тоді як до більшості символьних пристроїв можна звертатися лише послідовно.

Як і до символьних, доступ до блокових пристроїв можна отримати через файли в директорія /dev. Блоковий пристрій - це пристрій (наприклад, диск), здатне містити в собі файлову систему. У системі Unix блоковий пристрій може лише передавати один або більш цілих блоків даних, звичайно по 512 байт. Інтерфейс взаємодії блокових драйверів з ядром значно відрізняється від інтерфейса символьних драйверів.

Будь-яка мережева транзакція виконується через інтерфейс сокетов. Мережевий інтерфейс відповідає за передачу і прийом пакетів під управлінням мережевої підсистеми ядра незалежно від того, до яких саме транзакцій вони відносяться. Багато які мережеві з'єднання (що особливо використовують протокол TCP) орієнтовані на потоки даних. Але мережеві пристрої звичайно працюють з пакетами, а не з потоками. Таким чином, мережеві пристрої містять в собі риси як символьних, так і блокових.

2. Конструкторський розділ

2.1 Модульна структура драйвера

Драйвер memmon складається з наступних модулів:

mmon.c- основний модуль, що відповідає за ініціалізацію і вивантаження драйвера

mm-fault.c- обробник сторінкових помилок

syscalls.c- високоуровневая частина перехоплення системних викликів

syscalls-entry.S- низкоуровневая частина перехоплення системних викликів

watch-pids.c- список процесів, за якими здійснюється моніторинг, додавання і видалення з нього

events.c- кільцевий буфер подій

2.2 Ініціалізація і вивантаження драйвера

Ініціалізацію драйвера виконує функцияint _init init(void). Вона викликається при завантаженні драйвера в контексті процесу, визвашегоinit_module()(системний виклик завантаження драйвера) і виконує наступні дії:

1. Ініціалізує бітову карту процесів, що відстежуються і кільцевий буфер подій

2. Встановлює обробники системних викликів і сторінкової помилки

3. Створює директорія/proc/memmonи файли в ній

Створення файлів відбувається в останню чергу для того, щоб призначені для користувача додатки не могли звернутися до драйвера до завершення ініціалізації.

Вивантаження виконує функцияvoid _exit exit(void), що викликається в контексті процесу, сделавшегоdelete_module(). Вона виконує дії, зворотні кinit():

1. Видаляє директорія/proc/memmon

2. Знімає перехоплення системних викликів і сторінкової відмови

3. Звільняє пам'ять

2.3 Взаємодію з призначеними для користувача додатками

Для взаємодії з призначеними для користувача додатками драйвер використовує файлову системуprocfs - псевдофайлову систему, що надає різну інформацію про систему. Будь-який драйвер може додавати в її ієрархію свої файли і папки для передачі призначеним для користувача програмам різної інформації. Ядро надає ряд функцій для роботи сprocfs, з який даний драйвер використовує наступні:

proc_mkdir() - створює папку в/proc

create_proc_entry() - створює файл у вказаній папці/procили самої/proc

remove_proc_entry() - видаляє папку або файл в/proc

На рівні ядра Linux будь-який відкритий файл представлений структуройfile, що зберігає указательf_fopsна структуруfile_operations, вмісну адреси обробників різних запитів до файла. Допустимо, коли додаток відкриває файл (роблячи системний визовopen()), він викликає узагальнений рівень файлових систем VFS (функциюvfs_read()), яка в свою чергу викликає обработчикf_ops- > open. Для звичайних файлових систем обробники запитів до файла знаходяться в драйвері файлової системи, якою належить даний файл. Однак/procне представляє з себе який-небудь реальної ФС на реальному сховищі даних, і кожний драйвер, що додає туди файли, повинен для них надавати свої обробники (точки входу), які і будуть викликатися при роботі з цими файлами.

Файли, що створюються в /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.

Необхідність в асемблерний коді зумовлена механізмом обробки системних викликів в 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означает, що пам'ять виділяється для ядра (т. е. не може бути пізніше вивантажена з диска), але не в атомарному контексті (т. е. поточний процес може бути відкладений до звільнення необхідної пам'яті).

Кожний запис в буфері представляє з себе наступну структуру:

enum memmon_event_type - типсобития

{

NOTUSED = 0

MMAP2,

MUNMAP,

MREMAP,

MLOCK,

MUNLOCK,

MLOCKALL,

MUNLOCKALL,

BRK,

FSYNC,

ANON_PF, - сторінкова помилка на анонімній сторінці

SWAP_PF, - на сторінці з файла подкачки

FILE_PF, - з файла,

що розділяється SYSCALLRET - повернення з системного виклику

};

struct memmon_event

{

enum memmon_event_type type; - типсобития

pid_t pid; - PID процесу,

що викликав 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;

...

};

}

З буфером подій пов'язані наступні змінні:

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 обработчиками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.

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

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

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

3.1 Мова і кошти програмування

Ядро Linux написано на мові З з невеликою кількістю коду на асемблері, і його система зборки, взагалі говорячи, підтримує тільки дані мови для використання в модулях. Вибір з них був зроблений на користь З внаслідок значно більшої структурированности написаного на йому коду. Однак, невеликий об'єм коду драйвера довелося написати на асемблері, внаслідок чого він не є платформенно-независмим і прив'язаний до архітектури x86.

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

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

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

# cd /usr/src/linux

# patch- p0 <on.patch

# make

# make install INSTALL_PATH=/boot modules_install

# lilo

# reboot

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

3.3 Робота з драйвером

Для завантаження драйвера необхідно виконати команду

# insmodprocmon.ko

Можна указати розмір буфера подій:

# insmodprocmon.kobuflen=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

Вивантаження драйвера робиться командою

# rmmodprocmon.ko

(можливо, тільки якщо файли драйвера ніким не відкриті)

4. Експериментальний розділ

З метою вивчення особливостей виділення пам'яті в 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().

5. 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 *р);

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

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

/* 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 а 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 *р)

{

spin_unlock (&ev_lock);

}

/*

* Iterate to next event

*/

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

{

struct memmon_event *ev;

/* Dummy iterator - time to exit */

if (р == 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 *р)

{

struct memmon_event *ev = р;

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, «х,»);

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 (, «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 (, «anon page @%p (%s)\n»,)( ev- > pagefault.addr,

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

break;

case SWAP_PF:

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

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

break;

case FILE_PF:

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

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

break;

case SYSCALLRET:

seq_printf (, «%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 (truct 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 (data, 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 (truct 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 (truct 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 а 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 (id _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 (id _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) (ruct 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 (ruct 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) - З $(KERNELDIR) M=$(PWD) $(MAKECMDGOALS)

PHONY: modules.DEFAULT all

endif

Іспрвленієдлядляядра (2.6.20.1)

diff - arNC 3 linux-2.6.20.1-j/kernel/kallsyms.c linux-2.6.20.1-a/kernel/kallsyms.c

*** linux-2.6.20.1-j/kernel/kallsyms.c 2007-02-20 09:34:32.000000000 +0300

- linux-2.6.20.1-a/kernel/kallsyms.c 2007-05-26 22:27:23.000000000 +0400

***************

*** 452,454 ****

- 452,460 -

_initcall (kallsyms_init);

EXPORT_SYMBOL (_print_symbol);

+

+ /* HACK */

+

+ extern void *sys_call_table[];

+

+ EXPORT_SYMBOL_GPL (sys_call_table);

diff - arNC 3 linux-2.6.20.1-j/mm/memory.c linux-2.6.20.1-a/mm/memory.c

*** linux-2.6.20.1-j/mm/memory.c 2007-02-20 09:34:32.000000000 +0300

- linux-2.6.20.1-a/mm/memory.c 2007-05-28 22:08:41.000000000 +0400

***************

*** 2369,2374 ****

- 2378,2390 -

return VM_FAULT_MAJOR;

}

+ /* DIRTY HACK */

+ void (*mm_handle_fault_hook) (ruct mm_struct *mm,

+ struct vm_area_struct *vma, unsigned long address,

+ pte_t *pte, pmd_t *pmd, int write_access) = NULL;

+

+ EXPORT_SYMBOL_GPL (mm_handle_fault_hook);

+

/*

* These routines also need to handle stuff like marking pages dirty

* and/or accessed for architectures that don't do it in hardware (

***************

*** 2390,2395 ****

- 2406,2414 -

pte_t old_entry;

spinlock_t *ptl;

+ if (mm_handle_fault_hook)

+ mm_handle_fault_hook (mm, vma, address, pte, pmd, write_access);

+

old_entry = entry = *pte;

if (! pte_present(entry)) {

if (pte_none(entry)) {