Linux процесите взаимодействат с виртуалната памет, а не с физическата памет. Всеки процес има представа, че е единственият процес, работещ в системата и следователно има неограничен достъп до паметта, присъстваща в системата.
Различни процеси може да имат едно и също адресно пространство на виртуална памет, но то не се сблъсква, защото ядрото се грижи за съпоставянето на виртуалната към физическата памет. Пример, когато даден процес може да се наложи да споделя виртуалната си памет, е когато създава нишки или нишки за изпълнение.
Процесът няма разрешение за достъп до определени части от адресното пространство, което е запазено от ядрото. Процесът може да получи достъп до адрес на паметта само ако е в валидната област. Адресите на паметта могат да имат свързани разрешения, които процесът трябва да спазва. Ако това не се спазва от процеса, тогава ядрото хвърля съобщение за Грешка на сегментиранетои убива процеса.
Областите на паметта могат да имат следното съдържание:
- Кодът на изпълнимия файл, който е известен като текстова секция
- Инициализираните глобални променливи на изпълнимия файл, които са известни като секция с данни
- Неинициализирани променливи, наречени секция bss (блок, започнат от символ)
- Натрупване
- Група
Дескриптор на паметта:
В кода на ядрото на Linux адресното пространство на процесите може да бъде дефинирано в следната структура от данни.
struct mm_struct { struct vm_area_struct *mmap; /* list of memory areas */ struct rb_root mm_rb; /* red-black tree of VMAs */ struct vm_area_struct *mmap_cache; /* last used memory area */ unsigned long free_area_cache; /* 1st address space hole */ pgd_t *pgd; /* page global directory */ atomic_t mm_users; /* address space users */ atomic_t mm_count; /* primary usage counter */ int map_count; /* number of memory areas */ struct rw_semaphore mmap_sem; /* memory area semaphore */ spinlock_t page_table_lock; /* page table lock */ struct list_head mmlist; /* list of all mm_structs */ unsigned long start_code; /* start address of code */ unsigned long end_code; /* final address of code */ unsigned long start_data; /* start address of data */ unsigned long end_data; /* final address of data */ unsigned long start_brk; /* start address of heap */ unsigned long brk; /* final address of heap */ unsigned long start_stack; /* start address of stack */ unsigned long arg_start; /* start of arguments */ unsigned long arg_end; /* end of arguments */ unsigned long env_start; /* start of environment */ unsigned long env_end; /* end of environment */ unsigned long rss; /* pages allocated */ unsigned long total_vm; /* total number of pages */ unsigned long locked_vm; /* number of locked pages */ unsigned long def_flags; /* default access flags */ unsigned long cpu_vm_mask; /* lazy TLB switch mask */ unsigned long swap_address; /* last scanned address */ unsigned dumpable:1; /* can this mm core dump? */ int used_hugetlb; /* used hugetlb pages? */ mm_context_t context; /* arch-specific data */ int core_waiters; /* thread core dump waiters */ struct completion *core_startup_done; /* core start completion */ struct completion core_done; /* core end completion */ rwlock_t ioctx_list_lock; /* AIO I/O list lock */ struct kioctx *ioctx_list; /* AIO I/O list */ struct kioctx default_kioctx; /* AIO default I/O context */ };
Броят процеси/нишки, използващи едно и също адресно пространство, може да се провери чрез променливата mm_users. mmapи mm_rb сочат към адресите на паметта в адресното пространство. И двете променливи сочат към една и съща информация, но в различни представяния. mmapе свързан списък, докато mm_rbе червено черно дърво. Това се прави, за да може mmapда се използва за просто обхождане, а mm_rbда може да се използва за целите на търсене.
Ядрото представлява адресното пространство на процеса чрез дескриптора на паметта. Дескрипторът на паметта на процеса се посочва чрез полето mmв структурата task_struct.
struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ long counter; long priority; unsigned long signal; unsigned long blocked; /* bitmap of masked signals */ unsigned long flags; /* per process flags, defined below */ int errno; long debugreg[8]; /* Hardware debugging registers */ struct exec_domain *exec_domain; struct linux_binfmt *binfmt; struct task_struct *next_task, *prev_task; struct task_struct *next_run, *prev_run; unsigned long saved_kernel_stack; unsigned long kernel_stack_page; int exit_code, exit_signal; unsigned long personality; int dumpable:1; int did_exec:1; int pid; int pgrp; int tty_old_pgrp; int session; /* boolean value for session group leader */ int leader; int groups[NGROUPS]; struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct wait_queue *wait_chldexit; unsigned short uid,euid,suid,fsuid; unsigned short gid,egid,sgid,fsgid; unsigned long timeout, policy, rt_priority; unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; long utime, stime, cutime, cstime, start_time; unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; unsigned long swap_address; unsigned long old_maj_flt; /* old value of maj_flt */ unsigned long dec_flt; /* page fault count of the last time */ unsigned long swap_cnt; /* number of pages to swap on next pass */ struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; char comm[16]; int link_count; struct tty_struct *tty; struct sem_undo *semundo; struct sem_queue *semsleeping; struct desc_struct *ldt; struct thread_struct tss; struct fs_struct *fs; struct files_struct *files; struct mm_struct *mm; struct signal_struct *sig; #ifdef __SMP__ int processor; int last_processor; int lock_depth; #endif };
current-›mm сочи към дескриптора на паметта на процеса. copy_mm() се използва за копиране на дескриптора на паметта на родителя в дъщерния по време на fork(). Всеки процес получава уникален mm_struct, следователно уникално адресно пространство. В някои случаи, когато адресното пространство се споделя от множество процеси, те са известни като нишки и се извършват чрез извикване на clone()с зададен флаг CLONE_VM. Ето защо нишките са просто друг процес според ядрото на Linux, който случайно споделя адресното пространство, т.е. някои от своите ресурси с друг процес.
Когато процесът излезе, той извиква функцията exit_mm(), която на свой ред извиква free_mm(), ако референтният брой на процеса е 0, и извършва известно почистване и актуализиране на статистиката.
Области на виртуална памет
Областите на паметта са представени в кода на ядрото чрез vm_area_struct, които също се наричат области на виртуална памет.
struct vm_area_struct { struct mm_struct *vm_mm; /* associated mm_struct */ unsigned long vm_start; /* VMA start, inclusive */ unsigned long vm_end; /* VMA end , exclusive */ struct vm_area_struct *vm_next; /* list of VMA's */ pgprot_t vm_page_prot; /* access permissions */ unsigned long vm_flags; /* flags */ struct rb_node vm_rb; /* VMA's node in the tree */ union { /* links to address_space->i_mmap or i_mmap_nonlinear */ struct { struct list_head list; void *parent; struct vm_area_struct *head; } vm_set; struct prio_tree_node prio_tree_node; } shared; struct list_head anon_vma_node; /* anon_vma entry */ struct anon_vma *anon_vma; /* anonymous VMA object */ struct vm_operations_struct *vm_ops; /* associated ops */ unsigned long vm_pgoff; /* offset within file */ struct file *vm_file; /* mapped file, if any */ void *vm_private_data; /* private data */ };
Той описва една област на паметта в непрекъснат интервал. Всяка област на паметта има определени асоциирани разрешения и флагове, които помагат да се обозначи типът област на паметта - например зони с карта на паметта или стека на потребителското пространство на процесите.
Структурата vm_mmсочи към съответната mm_struct, към която принадлежи, което потвърждава уникалността на адресното пространство на даден процес.
Въпреки че приложенията работят с адресното пространство на виртуалната памет, процесорите работят с физическата памет. Следователно, когато дадено приложение има достъп до адрес на виртуална памет, то първо се преобразува във физическата памет, т.е. там, където данните действително се намират. Това търсене се извършва чрез таблици на страници. Виртуалната памет се разделя на части и индексът се съхранява. Индексът може да сочи към друга таблица или към физическата страница.
Linux по подразбиране поддържа 3 нива на таблици на страници, за да оптимизира допълнително търсенето на страници. Дори на системи, които нямат хардуерна поддръжка, той все още оптимизира таблицата на страниците на 3 нива, тъй като е необходимо да има индексирани таблици на страници за по-бързо търсене.
Таблицата на горната страница е известна като Глобална директория на страницата (PGD), която съдържа масив от неподписани дълги записи. Вписването в PGD сочи към PMD.
Втората таблица на страниците е известна като Средна директория на страницата (PMD), която допълнително сочи към PTE.
Записите в таблицата на страниците (PTE)сочат към действителните физически страници.
Всеки процес има свои собствени таблици на страници и е насочен към PGD чрез структурата от данни pgd в дескриптора на паметта.
Дори след поддържане на 3 нива на таблици на страници, търсенето може да бъде толкова бързо, колкото е обширна област за търсене. За да подобрят допълнително това, повечето процесори прилагат Translation Lookaside Buffer (TLB), който действа като хардуерен кеш между виртуални към физически съпоставяния. Следователно, ако кешът бъде ударен, той се връща директно от TLB или допълнително обработва съпоставянето на виртуалната към физическата памет.
Повечето от данните в статията са вдъхновени от книгата за разработка на ядрото на Linux от Робърт Лав. Това трябва да се прочете за всеки, който иска действително да разбере работата отдолу на ядрото на linux.