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().
В рамках данной работы были исследованы вопросы, связанные с разработкой драйверов под 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,