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.