88
Universitatea Politehnica Bucuresti Facultatea de Electronica, Telecomunicatii si Tehnologia Informatiei Studenti: Stefanescu Yasmin Laza Laura Stanculescu Laurentiu Catalin Guta Ovidiu-Gabriel Grupa 433A -2011- 1

stst.elia.pub.rostst.elia.pub.ro/news/SO/TEME_SO_2010_11/GEMLIN_Gestiune... · Web viewDe aceea, procesul caruia ii apartine pte nu va putea sa-l acceseze direct, cauzand o eroare

  • Upload
    others

  • View
    5

  • Download
    0

Embed Size (px)

Citation preview

Universitatea Politehnica BucurestiFacultatea de Electronica, Telecomunicatii si Tehnologia Informatiei

Studenti:Stefanescu YasminLaza LauraStanculescu Laurentiu CatalinGuta Ovidiu-GabrielGrupa 433A

-2011-

1

Cuprins

Capitolul 1: Initializarea-Stanculescu Catalin...................……………………31.1 Detectia memoriei...………………………………………………………….31.2 Aprovizionarea GDT…………………………………………………………..51.3 Activitatea paginarii…………………………………………………………..61.4 Tabela GDT finala………………………………………………………………7

Capitolul 2: Alocare Slab-Stanculescu Catalin.....………………………………..82.1 Cache…………………………………………………………………………………92.2 Slabs-Stefanescu Yasmin......................................................122.3 Cache-ul obiectelor per-CPU...............................................16

Capitolul 3: Alocarea noadiacenta a memoriei-Guta Ovidiu.................223.1 Structuri...............................................................................233.2 Alocare.................................................................................233.3 Dealocare.............................................................................28

Capitolul 4: Procesul de gestiune a memoriei virtuale-Laza Laura........324.1 Structuri................................................................................324.2 Crearea spatiului de adresa pentru un process....................344.3 Stergerea spatiului de adresa al unui process......................364.4 Alocarea unei regiuni de memorie.......................................37

Capitolul 5: Cererea de paginare- Laza Laura........................................39Capitolul 6: Swapping Stefanescu Yasmin..............................................54

6.1 Eliberarea paginilor din cache-uri.........................................546.2 Demaparea paginilor din procese.........................................606.3 Cache swap...........................................................................68

Bibliografie(+Documentatie+Dactilografierea)-Guta Ovidiu..................72

-2011-

2

Managementul memoriei in kernelul Linux

Capitolul 1: Initializarea

1.1 Detectia memoriei

Primul lucru pe care il face kernerul (relativ la managementul memoriei) este sa detecteze cantitatea totala de memorie instalata pe sistem. Acesta operatie e realizata in fisierul arch/i386/boot/setup_S intre liniile 281-382 prin trei rutine: E820H pentru a obtine harta memorie, E801H pentru a obtine dimensiunea si 88H care returneaza 0-64MB. Aceste rutine sunt executate una dupa alta si sunt redundante.

1.1.1 Metoda E820H

Metoda returneaza memoria clasificata in diferite tipuri. Foloseste intreruperea 0x15.

X = E820hEAX = 0000E820hEDX = 534D4150h ('SMAP')EBX = valoarea de continuare sau 00000000h

pentru a incepe de la baza memorieiECX = dimensiunea bufferului pentru rezultat,

in octeti ( >= 20 octeti)ES:DI -> buffer pentru rezultat

Returneaza:EAX = 534D4150h ('SMAP')

ES:DI buffer umplut EBX = urmatorul offset de la care sa copieze

sau 00000000h daca s-a terminat operatiaECX = dimensiunea actual in octeti, seteaza CF in cazul eroriiiAH = cod eroare (86h)

Formatul bufferului returnat este:

-2011-

3

Offset Dimensiune Descriere00h QWORD adresa baza08h QWORD dimensiune in octeti10h DWORD tipul adresei

Tipurile de adrese sunt: 01h memoria disponibila sistemului de operare 02h rezervat, indisponibil (exemplu ROM)03h ACPI

(folosita de SO dupa citirea tabelelor ACPI)04h Memorie ACPI

Metoda este folosita pentru a obtine o lista de adrese utilizabile. Exemplu:

Adresa de inceput Dimensiune Tip0K 639K RAM utilizabil639K 1K RAM utilizabil960K 64K ROM sistem4G-64k 64K ROM sistem1M 191M RAM utilizabil

1.1.2 Metoda E801H

Rutina returneaza dimensiunea memoriei in bucati de 1K pentru gama 1MB – 16MB si bucati de 64K pentru memoria mai mare de 16MB. Descrierea algoritmului de intrerupere este:

AX = E801hReturneaza: CF reseteaza daca se termina cu succesAX = memoie extinsa intre 1M si 16M, in K (maxim 3C00h = 15MB)BX = memorie extinsa peste 16M, in blocuri de 64KCX = memorie configurata de la 1M pana la 16M, in KDX = memorie configurata peste 16M, in blocuri de 64KCF setat in caz de eroare

Dimensiunea calculata este stocata la adresa 0x1e0h.

1.1.3 Metoda 88H

-2011-

4

Aceasta rutina este folosita, ca si celelalte doua pentru detectarea cantitatii de memorie folosibila pe sistem. Aceasta rutina va fi executata cu success (daca celelalte doua nu) intrucat este suportata de cele mai multe BIOS-uri. Returneaza o dimensiune maxima de 64MB sau 16MB in functie de versiunea BIOS. Descrierea algoritmului de intrerupere este:

AH = 88hReturneaza:CF reseteaza daca operatie se executa cu succes

AX = numar de KO incepand de la adresa absoluta 100000hCF setat in cazul unei eroriAH = status80h comanda incorecta (PC,PCjr)86h functie nesuportata (XT,PS30)

Rezultatul este stocat la adresa de memorie 0x2h.

1.2 Aprovizionarea GDT

Inainte de a intra in modul protejat, tabela globala de descriptori trebuie initializata. O tabela temporara este creata cu doua intrari, segmentul de cod si segmentul de date, fiecare acoperind intregul spatiu de adrese de 4GB. Codul ceinitializeaza GDT este:

/** /arch/i386/boot/setup.S **/

xorl %eax, %eax # Compute gdt_basemovw %ds, %ax # (Convert %ds:gdt to a linear ptr)shll $4, %eaxaddl $gdt, %eaxmovl %eax, (gdt_48+2)lgdt gdt_48 # load gdt with whatever is

# appropriate

unde gdt contine tabela, gdt_48 contine limita si adresa gdt.

-2011-

5

1.3 Activarea paginarii

1.3.1 Semnificatia PAGE_OFFSET

Valoarea variabilei PAGE_OFFSET este 0xc0000000 ceea ce reprezinta 3GB.

Spatiul de adrese liniare este impartit in doua parti: Adresele de la 0x00000000 pana la PAGE_OFFSET – 1 pot fi adresate de procese aflate

fie in modul kernel fie in modul utilizator. Adresele de la PAGE_OFFSET pana la 0xffffffff pot fi adresa doar de procesele aflate in

modul kernel. Acest spatiu de adrese este comun tuturor proceselor.

Spatiul de adrese dupa PAGE_OFFSET este rezervat kernerului, aici este mapata intreaga memorie fizica. Acest spatiu de adrese este deasemenea folosit pentru a mapa memoria discontinua fizica in memorie virtuala continua.

1.3.2 Tabelele pentru paginile kernerului

Rolul acestui director de pagini este de a mapa spatiul de adrese virtuale 0-8mb si PAGE_OFFSET-(PAGE_OFFSET + 8mb) la spatiul de adrese fizice 0-8mb. Aceasta mapare este realizata pentru ca spatiul de adrese din care este executat codul sa ramana valid.

1.3.3 Paginarea

Paginarea este activate prin setarea celui mai semnificativ big (PG) din registrul CR0. Codul folosit este urmatorul:

/** /arch/i386/kernel/head.S **//** Activarea paginarii */

3:movl $swapper_pg_dir-__PAGE_OFFSET,%eaxmovl %eax,%cr3 /* set the page table pointer.. */movl %cr0,%eaxorl $0x80000000,%eaxmovl %eax,%cr0 /* ..and set paging (PG) bit */jmp 1f /* flush the prefetch-queue */

1:Movl $1f,%eaxjmp *%eax /* make sure eip is relocated */

-2011-

6

1:

Dupa activarea managerului de memorie paginate, primul salt reseteaza coada de instructiuni. Operatia este necesara, intrucat instructiunile care au fost deja decodate vor folosi adresa veche. Urmatorul salt localizeaza pointerul de instructiuni la PAGE_OFFSET + ceva.

1.4 Tabela GDT finala

Dupa activarea paginarii, tabela GDT finala este incarcata. Tabela globala de descriptori contine acum segmente de cod si de date atat pentru utilizator cat si pentru kernel. In afara celor doua segmente, sunt definite segmente pentru APM si este rezervat spatiu pentru TSS-urile si LDT-urile proceselor.

Linux foloseste segmentele intr-un mod foarte limitat, utilizeaza modelul liniar, in care segmentele de cod si date adreseaza intregul spatiu de adrese.

/** /arch/i386/kernel/head.S **/

ENTRY(gdt_table).quad 0x0000000000000000 /*NULL descriptor */.quad 0x0000000000000000 /*not used */.quad 0x00cf9a000000ffff /*0x10 kernel 4GB code */.quad 0x00cf92000000ffff /*0x18 kernel 4GB data */.quad 0x00cffa000000ffff /*0x23 user 4GB code */.quad 0x00cff2000000ffff /*0x2b user 4GB data */.quad 0x0000000000000000 /*not used */.quad 0x0000000000000000 /*not used */

/** Segmentele APM au octet de granularitate, iar limitele sunt * setate la initializare */.quad 0x0040920000000000 /*0x40 APM set up for bad BIOS's.quad 0x00409a0000000000 /*0x48 APM CS code*/.quad 0x00009a0000000000 /*0x50 APM CS 16 code (16 bit)*/.quad 0x0040920000000000 /*0x58 APM DS data*/.fill NR_CPUS*4,8,0 /*space for TSS's and LDT's*/

-2011-

7

Capitolul 2: Alocarea Slab

Majoritatea cererilor de alocare a memoriei in kernel sunt pentru structure de date mici si frecvent folosite. Alocatorul de pagini lucreaza cu alocari de dimensiunea unei pagini. Alocatorul slab indeplineste trei roluri. Furnizeaza buffere mici de memorie organizate in pagini pentru a reduce fragmentarea interna. Furnizeaza obiecte folosite frecvent cum ar fi structuri mm pentru evitarea unor operatii suplimentare cum ar fi distrugerea de obiecte complexe. Incearca sa foloseasca cache-ul hardware cat mai eficient cu putinta.

Terminologie: Cache – este o zona pentru stocarea celor mai recente utilizate date de acelasi tip. In

alocatorul SLAB este cea mai mare unitate logica de stocare. Slab – reprezinta un recipient pentru obiecte si este format din mai una sau mai multe

pagini. Un cache are mai multe slab-uri. Obiect – este cea mai mica unitate. Se gaseste in slab si reprezinta cea mai mica

intrare.

Se doreste ca o pagina sa contina mai multe obiecte pentru economisirea memoriei si evitarea fragmentarii interne. Slab-urile sunt organizate in trei tipuri: slab-uri complete, partiale si goale. Cele partiale sunt folosite in cazul in care sunt disponibile pentru a evita fragmentarea. O comanda pentru afisarea informartiilor despre cache si slab-urile de pe sistem este: cat /proc/slabinfo . Campurile sunt:

- nume-cache : nume - num-active-objs : numar de obiecte in folosinta- total-objs: cate obiecte sunt disponibile in total- obj-size : dimensiunea fiecarui obiecte, de obicei sunt dimensiuni mici- num-active-slabs : numar de slab-uri care contin obiecte active- total-slabs: numar total slab-uri

Aceasta comanda ofera informatii per-CPU. Pentru a imbunatatii folosirea resurselor hardware si pentru a reduce numarul de lock-uri necesare unei alocari, este rezervata o zona pentru obiectele fiecarui procesor.

-2011-

8

2.1 Cache-urile

Cache-urile se gasesc intr-o structura numita kmem_cache_s. Principalele elemente ale structurii sunt:

Elemente legate de listaStruct list_head slabs_full Listeaza slab-urile completeStruct list_head slabs_partial Listeaza slab-urile partialeStruct list_head slabs_free Listeaza slab-urile goaleStruct list_head next Urmatorul cache in lista

Proprietati ale obiectelorUnsigned int objsize Dimensiune obiectUnsigned int flags Fanioane Unsigned int num Numar de obiecte pe slab

Crearea unui obiectVoid (*ctor)() Functia constructor pentru un obiect

-2011-

9

Void (*dtor)() Destructor pentru un obiect

2.1.1 Flagurile statice ale cache-urilor

Campul flags ofera informatii suplimentare despre slab. Exemple de fanioane sunt:

SLAB_HWCACHE_ALIGN – aliniaza obiectele cu cache-ul L1 al procesoruluiSLAB_CACHE_DMA – foloseste memoria din ZONE_DMA

In cazul in care CONFIG_SLAB_DEBUG este setat in momentul compilarii, urmatoarele fanioane sunt disponibile:

SLAB_DEBUG_FREE – ofera informatii extinse la dealocareSLAB_RED_ZONE – plaseaza un delimitator la ambele capete ale unui obiect

pentru verificarea depasirilor

2.1.2 Crearea unui cache

Urmatoarele actiuni au loc pentru crearea unui cache de catre functia kmem_cache_create:

se verifica daca CONFIG_SLAB_DEBUG e setat aloca kmem_cache_t drin slab-ul cache_cache aliniaza dimensiunea obiectului la dimensiunea unui word calculeaza cate obiecte pot fi adaugate intr-un slab aliniaza dimensiunea slab-ului la cache-ul hardware initializeaza campurile ramase in descriptorul cache-ului adauga noul cache lantului de cache-uri

-2011-

10

2.2.2.1 Functia kmem_cache_create()

Fisier: mm/slab.c

Prototip:

kmem_cache_t *kmem_cache_create (const char *name,

size_t size, size_t offset, unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long), void (*dtor)(void*, kmem_cache_t *, unsigned long))

Aceasta functie creaza cache-uri noi adaugandu-le in lantul de cache-urile.

2.1.3 Distrugerea unui cache

Managerul de liste are rolul de a sterge un cache. Rutina de stergere se apeleaza cand un modul se descarca sau este distrus. Acest mecanism are rolul de a evita aparitia cache-urilor duplicat in cazul in care un modul este descarcat si apoi reincarcat.

Pasii care se executa pentru distrugerea unui cache sunt:

se sterge cache-ul de la lista de cache-uri se ingusteaza cache-ul pentru stergerea tuturor stab-urilor stergerea cache-urilor procesoarelor (kfree) stergerea descriptorpului din cache_cache

-2011-

11

2.1.3.1 Functia kmem_cache_destroy()

Fisier: mm/slab.c

Prototip:

int kmem_cache_destroy (kmem_cache_t * cachep)

2.2 Slab

Un slab consta din una sau mai multe pagini asignate pentru a contine obiecte. Scopul acestei structuri este sa gestioneze obiectele din slab. Structura de descriere a unul slab este simpla:

typedef struct slab_s {

struct list_head list;

unsigned long colouroff;

-2011-

12

void *s_mem;

unsigned int inuse;

kmem_bufctl_t free;

} slab_t;

2.2.1 Stocarea descriptorului slab

Structura slab-ului trebuie stocata. Stocarea se poate face fie in afara slab-ului, caz in care memoria va fi alocata din una dintre cache-urile de dimensiune, fie chiar in slab. Cache-urile dimensiunilor sunt cache-uri ce stocheaza blocuri de moemnorie de dimensiuni ce sunt puteri ale lui doi.

Pentru un obiect din slab sau pentru managerul de slab nu pare a exista o metoda de determinare a cache-ului sau slab-ului caruia ii apartin.

Relatii pagina-cache si pagina-slab

SET_PAGE_CAHCE si SET_PAGE_SLAB utilizeaza ‘next’ si ‘prev’ pe pagina pentru a gasi carui cache si slab ii apartine un obiect. Pentru a obtine descriptorii din pagina, se utilizeaza GET_PAGE_CACHE si GET_PAGE_SLAB.

Cache/urile sunt legate impreuna prin campul next. Fiecare cache consta din una sau mai mutle slab-uri, ce sunt blocuri de memorie alcatuite din una sau mai multe pagini. Fiecare slab contine mai multe obiecte, posibil cu spatii itnre ele pentru a putea atinge diferite linii ale

-2011-

13

cache-ului. Daca, in timpul creari unui cache, se specifica fllag-ul SLAB_HWCACHE_ALIGN, se ajusteaza marimea obiectului pana la L1_CACHE_BYTES pentru ca obiectele sa fie aliniate in cache. Aceasta va creea spatieri itrne obiecte. Structurile slab t sau de management pot fi pastrate in slab sau in afara sa, daca se afla la inceput. Daca este in afara cache-ului, este stocat intr-o memorie cache de marime corespunzatoare.

Slab cu descriptor inclus in slab

-2011-

14

Slab cu descriptor in afara slab-ului

Figura de mai sus ilustreaza cum uttilizeaza un cache un cache de dimensiuni pentru a stoca descriptorul de slab.

Elementul list al structurii paginii este utilizat pentru a afla unde sunt stocate cache_t si slab_t. Pointerul list->next arata catre kmem_cache_t (cache-ul caruia ii apartine) si pointerul list->prev arata catre slab_t (slab-ul caruia ii apartine). Astfel, pentru un obiect dat, se poate afla usor cache-ul si slab-ul asociate cu ajutorul acestor pointeri.

Functia kmem_cache_slabmgmt()Prototip:

slab_t * kmem_cache_slabmgmt (kmem_cache_t *cachep,

void *objp,

int colour_off,-2011-

15

int local_flags)

Functia kmem_find_general_cachep()Prototip:

kmem_cache_t * kmem_find_general_cachep (size_t size,

int gfpflags)

{

cache_sizes_t *csizep = cache_sizes;

for ( ; csizep->cs_size; csizep++) {

if (size > csizep->cs_size)

continue;

break;

}

return (gfpflags & GFP_DMA) ? csizep->cs_dmacachep :

csizep->cs_cachep;

}

2.3 Cache-ul obiectelor Per-CPU

Una dinter sarcinile alocatorului de slab-uri este dedicata utilizarii imbunatatite a cache-ului hardware. Tinta computarii de performanta inalta in general este utilizarea datelor pe acelasi CPU cat de mult timp este posibil. Linux reuseste acest lucru prin incercarea de a tine obiecte in acelasi cache al CPU cu un cache obiect Per-CPU, numit un cpucache pentru fiecare CPU din sistem.

La alocarea sau dealocarea obiectelor, ele sunt plasate in cpucache. Cand nu mai exista niciun obiect liber, un set de obiecte este plasat in rezerva. Cand rezerva se mareste prea mult, jumatate din continut este inlaturat si plasat in cache-ul global. Astfel, cache-ul gardware va fi utilizat atat de mult timp cat este posibil in acelasi CPU.

-2011-

16

2.3.1 Adaugare/Stergere Obiecte din cacheul Per-CPU

Pentru a preveni fragmentarea, obiectele sunt intotdeauna adaugate sau inlaturate de la sfarsitul vectorului. Pentru a adauga un obiect in cache-ul CPU, se utilizeaza urmatorul bloc de cod:

cc_entry(cc)[cc->avail++] = obj;

Pentru a inlatura un obiect:

obj = cc_entry(cc)[--cc->avail];

cc_entry este definit astfel:

#define cc_entry(cpucache) \

((void **)(((cpucache_t*)(cpucache))+1))

2.3.2 Activare cache Per-CPU

La crearea unui cache, cache-ul CPU-ului sau trebuie sa fie activat si sa i se aloce memorie cu kmalloc. Functia de activare cpucache este responsabila cu deciderea marimii pe care s-o aiba cache-ul si cu apelarea functiei kmem_tune_cpucache pentru a-i aloca memorie.

In mod evident, cache-ul unui CPU nu poate exista decat dupa ce au fost activate diferitele cache-uri de dimensionare, astfel ca se utilizeaza o variabila globala g_cpucache_up pentru a preveni activarea cpcucache-ului inainte de a fi posibil acest lucru. Functia activeaza toate ciclurile cpucache din toate cache-urile din lantul de cache-uri si le activeaza cpucache-urile corespunzatoare.

Odata ce a fost setat cache-ul unui CPU, poate fi accesat fara blocare, deoarece un CPU nu va accesa niciodata cpucache/ul gresit, deci i se garanteaza accesul in siguranta.

Functia enable_all_cpucaches()Prototip:

static void enable_all_cpucaches (void){

struct list_head* p;

-2011-

17

down(&cache_chain_sem);

p = &cache_cache.next;

do {

kmem_cache_t* cachep = list_entry(p, kmem_cache_t, next);

enable_cpucache(cachep);

p = cachep->next.next;

} while (p != &cache_cache.next);

up(&cache_chain_sem);

}

Functia enable_cpucache()Prototip:

static void enable_cpucache (kmem_cache_t *cachep)

{

int err;

int limit;

if (cachep->objsize > PAGE_SIZE)

return;

if (cachep->objsize > 1024)

limit = 60;

else if (cachep->objsize > 256)

limit = 124;

else

limit = 252;

err = kmem_tune_cpucache(cachep, limit, limit/2);

-2011-

18

if (err)

printk(KERN_ERR "enable_cpucache failed for %s, error %d.\n", cachep->name, -err);

}

Functia kmem_tune_cpucache()Prototip:

static int kmem_tune_cpucache (kmem_cache_t* cachep, int limit, int batchcount){

ccupdate_struct_t new;

int i;

if (limit < 0)

return -EINVAL;

if (batchcount < 0)

return -EINVAL;

if (batchcount > limit)

return -EINVAL;

if (limit != 0 && !batchcount)

return -EINVAL;

memset(&new.new,0,sizeof(new.new));

if (limit) {

for (i = 0; i< smp_num_cpus; i++) {

cpucache_t* ccnew;

ccnew = kmalloc(sizeof(void*)*limit+

sizeof(cpucache_t), GFP_KERNEL);

if (!ccnew)

-2011-

19

goto oom;

ccnew->limit = limit;

ccnew->avail = 0;

new.new[cpu_logical_map(i)] = ccnew;

}

}

new.cachep = cachep;

spin_lock_irq(&cachep->spinlock);

cachep->batchcount = batchcount;

spin_unlock_irq(&cachep->spinlock);

smp_call_function_all_cpus(do_ccupdate_local, (void *)&new);

for (i = 0; i < smp_num_cpus; i++) {

cpucache_t* ccold = new.new[cpu_logical_map(i)];

if (!ccold)

continue;

local_irq_disable();

free_block(cachep, cc_entry(ccold), ccold->avail);

local_irq_enable();

kfree(ccold);

}

return 0;

oom: for (i--; i >= 0; i--)

kfree(new.new[cpu_logical_map(i)]);

return -ENOMEM;-2011-

20

}

2.3.3 Updatarea informatiei cache Per-CPU

Dupa ce au fost create sau schimbate cache-urile per-cpu, fiecare CPU trebuie sa fie anuntat de acest lucru. Nu este suficient sa se schimbe toate valorile din descriptorul de cache, deoarece aceasta ar duce la probleme de coerenta a cache-ului si ar trebui utilizate spinlock0uri pentru a proteha cpucache-urile. In schimb, o structura ccupdate_t este populata cu toata informatia de care are nevoie fiecare CPU si fiecare CPU schimba cu noile date vechea informatie din descriptorul de cache. Structura pentru stocarea noii informatii cpucache este definita astfel:

typedef struct ccupdate_struct_s{

kmem_cache_t *cachep;

cpucache_t *new[NR_CPUS];

} ccupdate_struct_t;

Cachep este cache-ul ce este updatat si vectorul new este al descriptorilor cpucache pentru fiecare CPU din sistem. Functia smp_function_all_cpus este utilizata pentru a face ca toate CPU-urile sa apeleze functia locala ccupdate do_ccupdate_local ce schimba informatia din ccupdate_struct_t cu informatia din descriptorul cache-ului. Odata ce informatia a fost schimbata, vechiile date pot fi sterse.

Functia smp_function_all_cpus()Prototip:

static void smp_call_function_all_cpus(void (*func) (void *arg), void *arg){

local_irq_disable();

func(arg);

local_irq_enable();

if (smp_call_function(func, arg, 1, 1))

BUG();

-2011-

21

}

Functia do_ccupdate_local()Prototip:

static void do_ccupdate_local(void *info){

ccupdate_struct_t *new = (ccupdate_struct_t *)info;

cpucache_t *old = cc_data(new->cachep);

cc_data(new->cachep) = new->new[smp_processor_id()];

new->new[smp_processor_id()] = old;

}

Capitolul 3: Alocarea nonadiacenta a memoriei

Interfata „vmalloc” pune la dispozitia utilizatorului functii care mapeaza cadre de pagina nonadiacente in pagini adiacente de memorie virtuala. Adresele libere de memorie virtuala din spatiu kernel-ului sunt folosite in acest scop. Kernelul utilizeaza primul gigabyte di spatiul de adrese pentru a mapa toata memoria fizica disponibila. Dupa mapare, de obicei ramane mult spatiu neocupat. Daca luam ca exemplu un sistem cu cu 192MB RAM, toata memoria este mapata de la PAGE_OFFSET pana la PAGE_OFFSET+192MB. Deci din totalul spatiului de adrese de 1GB, se folosesc doar 192MB. Cei 832MB ramasi in spatiul de adrese virtuale pot fi folositi de inretfata „vmalloc”. In cazul in care avem mai mult de 1GB de memorie fizica unele zone de memori sunt rezervate. Deci, zona normala de memorie este de 896MB datorita faptului ca 128MB sunt rezervati.

Aceste alocari incep de la VMALLOC_START care repretinta sfarsitul memorie fizice mapate direct plus un interval de garda de 8MB (VMALLOC_OFFSET). Pentru a descrie aceste yone de memorie, este folosita o structura de tipul celei prezentate in capitolul 4.1.

-2011-

22

3.1 Structurastruct vm_struct {

unsigned long flags;void * addr;unsigned long size;struct vm_struct * next;

}; Variabilele folosite au urmatoarea semnificatie: flags: Este folosit pentru a se specifica daca zona respectiva de memorie a fost alocata cu vmalloc() sau cu ioremap().addr : Adresa virtuala de start a acestei alocari.size: Dimensiunea spatiului de alocat plus 4k (umplutura intre 2 zone).next: Pointer folosit pentru a lega structurile intre ele.

Descriptorii zonei neadiacente de memorie sunt inlantuiti intr-o lista al carui inceput este specificat de vmlist. Interfata „vmalloc” este implementata in fisierul mm/vmalloc.c si pune la dispozitia utilizatorilor functi pentru alocarea, dealocare, citire si scriere a memoriei virtuale.

3.2 Alocare1. Functia vmalloc()Protoripuri: void *vmalloc (unsigned long size) – Doar primeste marimea ca parametru si este o fatada pentru straturile de nivel inferior.return _ _vmalloc(size, GFP_KERNEL | _ _GFP_HIGHMEM, PAGE_KERNEL); - Asigura faptul ca paginile sunt alocate kernelului si protejeaza paginile in cazul in care ar fi schimbate accidental prin setarea fanionului PAGE_KERNEL.

2. Functia _ _vmalloc()Prototipuri:void *_ _vmalloc(unsigned long size,

int gfp_mask, pgprot_t prot)

Aceasta functie realizeaza alocarea propriuzisa. Paginile alocate nu for fi adiacente in memoria fizica, ci doar in spatiul liniar de adrese. Functia nu se apeleaza in mod direct. Functia vmalloc o va apela cu fanioanele corect setate si cu elementele de protectie corespunzatoare.Implementare:void *add;struct vm_struct *areas

-2011-

23

size = PAGE_ALIGN(size) // variabila size este rotunjita la marimea unei pagini (4k)if(!size || (size>>PAGE_SHIFT) > num_physpages){

Bug();return NULL;

} //daca size=0 sau cererea depaseste numaru de cadre fizice se opreste alocarea.area = get_vm_area(size, VM_ALLOC);if(!area)

return NULL;//Functia get_vm_area)= aloca un bloc de adrese liniare de marimea cererii i returneaza o structura de tipul struct vm_struct.addr = area -> addr;if (vmalloc_area_pages(VMALLOC_VMADDR(ADDR),gfp_mask, prot)){

vfree(addr);return NULL;

} // Functia vmalloc_area_pages() incepe alocarea PDM-urile, PTM-urile si in final paginile fizice pentru alocarea memoriei.return addr; // Returneaza adresa virtuala.

3. Functia get_vm_area()Prototiuri:

struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)Aceasta este o functie care ajuta vmalloc sa gaseasca un bloc liniar de adrese destul de

mare pentru marimea memoriei alocate.Implementare:

unsigned long addr;struct vm_struct **pm, *tmp, *area;area = (struct vm_struct *)kmalloc(sizeof(*area),GFP_KERNEL);if(!area)

return NULL;//Mai intai repartitorul „slab” este apelat pentru a aloca o bucata de memorie folosita pentru a //pastra informatia despre zona neadiacenta.size += PAGE_SIZE;addr = VMALLOC_START;//size este incrementat cu PAGE_SIZE si addr este initializat cu VMALLOC_START in cazul in //care aceasta este prima zona alocata.write_lock(&vmlist_lock);

-2011-

24

for(p = &vmlist; (tmp = *p); p = &temp->next){if((size + addr) < addr)

goto out;if(size + addr <= (unsigned long) tmp->addr)

break;addr = tmp->size + (unsigned long) tmp->addr;if(addr > VMALLOC_END-size)

goto out;}//Mai intai lista este accesul la lista este restricsionat pentru protectie. Apoi este parcursa lista //si se fac urmatoarele verificari:// - S-a ajuns la sfarsitul spatiului de adrese?// -Daca alocarea ceruta incape ne oprim.// -Am ajuns la VMALLOC_END?//Daca una dintre conditii nu este respectata se ajunge la eticheta out.

area->flags = flags;area->addr = (void*)addr;area->size = size;area->next = *p;*p = area;write_unlock(&vmlist_lock);return area;//S-a gasit o zona care satisface toate conditiile. Putem sa inseram zona in lista si sa returnam --//adresa.

out:write_unclock(&vmlist_lock);kfree(area);return NULL;//Daca am ajuns aici, nu a fost posibila alocarea, deci vom opri cautarea, vom elibera zona //aleasa si vom returna esec.

4. Functia vmalloc_area_pages()Prototip:int vmalloc_area_pages (unsigned long address,unsigned long size,int gfp_mask, pgprot_t prot)

-2011-

25

//Aceasta functie incepe atribuirea spatiului liniar necesar alocarii. Va aloca un PGM fiecarei --//intrari in PGD care este necesara pentru a acoperii tot spatiul liniar pentru aceasta alocare.

Implementare:pgd_t * dir;unsigned long end = address + size;int ret;dir = pgd_offset_k(address);spin_lock(&init_mm.page_table_lock);//dir este initializat cu prima intrare in PGD pentru tabelele de pagini ale kernelului, iar apoi mm //este blocat.do {pmd_t *pmd;pmd = pmd_alloc(&init_mm, dir, address);ret = -ENOMEM;if (!pmd)break;//Aici se incearca alocarea unui bloc PMD pentru adresa asa cum este. Daca este nevoie de mai //mai mult de un PDM, el va fi alocat la urmatoarea iteratie a buclei while.ret = -ENOMEM;if (alloc_area_pmd(pmd, address, end - address,gfp_mask, prot))break;address = (address + PGDIR_SIZE) & PGDIR_MASK;dir++;ret = 0;} while (address && (address < end));//Aici se pregateste trecerea la urmatorul PGD atunci cand cantitatea de memorie ce va fi //alocata este mai mare de un PGD si se repeta alocarile de PMD si PTE-uri.spin_unlock(&init_mm.page_table_lock);flush_cache_all();return ret;//Se deblocheaza tabelul de pagini si se returneaza succes sau esec funcsiei _ _vmalloc.

5. Functia alloc_area_pdm

Prototip:

int alloc_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size, int gfp_mask, pgprot_t prot)

-2011-

26

//Aceasta functie este responsabila cu trecerea prin toate PMD-urile necesare acestei alocari si //cu apelul functiei alloc_area_pte pentru a asigna destule PTE-uri pentru fiecare PMD.

Implementare:unsigned long end;address &= ~PGDIR_MASK;end = address + size;if (end > PGDIR_SIZE)

end = PGDIR_SIZE;//Se asigura faptul ca adresa are butii cei mai putin semnificativi 0 si este valida pentru un PGD.do {pte_t * pte = pte_alloc(&init_mm, pmd, address);if (!pte)return -ENOMEM;if (alloc_area_pte(pte, address, end - address,gfp_mask, prot))return -ENOMEM;address = (address + PMD_SIZE) & PMD_MASK;pmd++;} while (address < end);return 0;//Aici se aloca un PTE pentru fiecare intrare in PMD necesara pentru aceasta alocare. Mai intai // intrarea in PTE propriuzisa si aloc_area_pte este responsabila cu gasirea cadrelor de pagini //pentru fiecare intrare. Odata alocare, adresa este incrementata, avand grija sa fie aliniata cu //intrarea PMD.

6. Funtia allo_area_pte()Prototip:int alloc_area_pte (pte_t * pte, unsigned long address,

unsigned long size, int gfp_mask,pgprot_t prot)

//Aceasta functie creeaza intrarile PTE propriuzise.

Implementare:

unsigned long end;address &= ~PMD_MASK;end = address + size;if (end > PMD_SIZE)

end = PMD_SIZE;-2011-

27

do {struct page * page;spin_unlock(&init_mm.page_table_lock);page = alloc_page(gfp_mask);spin_lock(&init_mm.page_table_lock);

//Se aloca un cadru de pagina pentru PTE-ul curent.if (!pte_none(*pte))

printk(KERN_ERR"alloc_area_pte: page already exists\n");

if (!page)return -ENOMEM;

//Daca algoritmul bubby returneaza o pagina care a fost schimbata sau nu este prezenta, avem //o problema majora.

set_pte(pte, mk_pte(page, prot));address += PAGE_SIZE;pte++;

} while (address < end);return 0;//Aci se realizeaza protectia paginii pentru a ne asigura ca ca nu va fi schimbata. Apoi urmatorul //PTE este mutat astfel incat sa fie alocat inaintea returnarii succesului.

3.3 De-alocarea

1.Functia vfree()

Prototipul:void vfree(void * addr)

//Aceasta functie primeste ca parametru adresa bazei. Aceata adresa trebuie sa fie aliniata cu //cea returnata de vmalloc mai sus. Se parcurg structurile vm si in final se dealoca toate PMD-//urile si PTE-urile alocate in prealabil.

Implementare:struct vm_struct **p, *tmp;if (!addr)

return;if ((PAGE_SIZE-1) & (unsigned long) addr) {

printk(KERN_ERR"Trying to vfree() bad address (%p)\n", addr);

return;-2011-

28

}write_lock(&vmlist_lock);

//Se face verificarea faptului ca adresa primita nu este NULL si ca este valida din punct de verere //al alinierii cu pagina. Apoi se blocheaza vm_list pentru a fi protejata.for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {

if (tmp->addr == addr) {*p = tmp->next;vmfree_area_pages(VMALLOC_VMADDR(tmp->addr)

,tmp->size);write_unlock(&vmlist_lock);kfree(tmp);return;

}}//Acest bloc de cod cauta in lista pana cand structura vm_struct corecta este gasita. Odata //gasita, este apelata functia vmfree_area_pages.write_unlock(&vmlist_lock);printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)",addr);//Daca zona nu este gasita, lista este deblocata si se printeaza un mesaj de eroare.

2.Functia vmfree_area_pages()

Prototip:void vmfree_area_pages(unsigned long address,

nsigned long size)

Implementare:

pgd_t * dir;unsigned long end = address + size;dir = pgd_offset_k(address);// Se seteaza directorul pentru a fi prima intrare in PGD pentru adresa.do {

free_area_pmd(dir, address, end - address);address = (address + PGDIR_SIZE) & PGDIR_MASK;dir++;

} while (address && (address < end));

-2011-

29

//Pentru fiecare intrare in PGD folosita de alocare, se apeleaza functia free_area_pmd() astfel //incat toate PTE-urile si cadrele de pagina alocate sa fie golite. Apoi, se incrementeaza adresa //avand grija sa ramana aliniata cu un PGD.

3. Funtia free_area_pmd()

Prototip:void free_area_pmd(pgd_t * dir,

unsigned long address,unsigned long size)

Implementare:

pmd_t * pmd;unsigned long end;if (pgd_none(*dir))

return;if (pgd_bad(*dir)) {

pgd_ERROR(*dir);pgd_clear(dir);return;

}//Daca aceasta functie este apelata cu un PGD care nu exista, acesta a fost deja eliberat. Acest //lucru se poate intampla daca vmalloc nu a alocat correct zona, si vfree a trebuit apelata //pentru toata zona liniara. pgd_bad are in vedere faptul ca PGD-care va fi eliberat este in //memoria principala, nu este read only si este marcat ca accesat sau dirty.

pmd = pmd_offset(dir, address);address &= ~PGDIR_MASK;end = address + size;if (end > PGDIR_SIZE)

end = PGDIR_SIZE;do {

free_area_pte(pmd, address, end - address);address = (address + PMD_SIZE) & PMD_MASK;pmd++;

} while (address < end);//Se parcurg toate PTE-urile referentiate de acest PMD si apeleaza free_area_pte //pentru el pentru ca pagina sa fie eliberata.

4. Functia free_area_pte()

Prototip:

-2011-

30

void free_area_pte(pmd_t * pmd,unsigned long address,unsigned long size)

//Aceasta functie asigura faptul ca nu este dealocata alta pagina decat cea intentionata.

Implementare:

pte_t * pte;unsigned long end;if (pmd_none(*pmd))

return;if (pmd_bad(*pmd)) {

pmd_ERROR(*pmd);pmd_clear(pmd);return;

}pte = pte_offset(pmd, address);address &= ~PMD_MASK;end = address + size;if (end > PMD_SIZE)

end = PMD_SIZE;// Se testeaza parametrii similari cu cei de la functia free_area_pmd.do {

pte_t page;page = ptep_get_and_clear(pte);address += PAGE_SIZE;pte++;if (pte_none(page))

continue;//Bucla while parcurge toate PTE-urile pentru acest PMD. ptep_get_and_clear returneaza //intrarea pte_t si apoi o sterge din tabelele de pagini.//Daca nu a fost alocata, se va continua execusia codului in mod normal.

if (pte_present(page)) {struct page *ptpage = pte_page(page);if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))

_ _free_page(ptpage);continue;

}//Daca pagina este prezenta, se preia structura acestui PTE si se trimite catre buddz allocator.

printk(KERN_CRIT"Whee.. Swapped out page in kernel page table\n");

} while (address < end);-2011-

31

//Daca pagina nu este prezenta, ea a fost schimbata ceea ce reprezinta o mare problem. In //situatiile normale, toate PTE-urile vor fi eliberate pentru acest PMD si functia returneaza cu //success.

Capitolul 4: Procesul de gestiune a memoriei virtual

4.1 Structuri

4.1.1 struct mm_structstruct mm_struct {struct vm_area_struct * mmap;rb_root_t mm_rb;struct vm_area_struct * mmap_cache;pgd_t * pgd;atomic_t mm_users;atomic_t mm_count;int map_count;struct rw_semaphore mmap_sem;spinlock_t page_table_lock;struct list_head mmlist;unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long rss, total_vm, locked_vm;unsigned long def_flags;unsigned long cpu_vm_mask;unsigned long swap_address;unsigned dumpable:1;mm_context_t context;};

mmap -> O lista legata de adresele memoriei virtuale apartinand acestui spatiu de adrese, ordonate dupa adresa.

mm_rb -> Cand numarul adreselor memoriei virtuale creste peste un anumit numar, un arbore rosu-negru (red black tree) este utilizat pentru accesarea lor. mm_rb arata spre radacina nodului.

mmap_cache -> Arata ultima adresa accesata a memoriei virtuale.

mm_users -> Numarul proceselor ce partajeaza aceasta structura.

map_count -> Numarul adreselor memoriei virtuale.

mmap_sem -> Semafor utilizat pentru inserierea accesului la aceasta structura.-2011-

32

page_table_lock -> Protejeaza tabelele paginii si campul rss (Really Simple Syndication) de accesul paralel.

mmlist -> Lista tuturor gestiunilor active ale memoriei.

start_code -> Arata adresa de inceput a sectiunii codului. end_code -> Arata adresa de sfarsit a sectiunii codului.

start_data -> Arata adresa de inceput a sectiunii de date.

end_data -> Arata adresa de sfarsit a sectiunii de date.

start_stack -> Arata adresa de inceput a stivei.

arg_start -> Arata adresa de inceput a argumentelor.

arg_end -> Arata adresa de sfarsit a argumentelor.

rss -> Numarul de pagini curente in memorie.

total_vm -> Numarul total de pagini utilizate in acest proces.

locked_vm -> Numarul de pagini blocate de acest proces.

def_flags -> Fanioanele implicite pentru acest spatiu al adresei.

4.1.2 struct vm_area_struct

Aceasta structura defineste o suprafata a gestiunii memoriei virtuale. O suprafata de memorie virtuala este orice parte a procesului zonei de memorie virtuala ce are o regula speciala pentru manipularile page-fault-urilor.

struct vm_area_struct {struct mm_struct * vm_mm;unsigned long vm_start;unsigned long vm_end;struct vm_area_struct *vm_next;pgprot_t vm_page_prot;unsigned long vm_flags;rb_node_t vm_rb;struct vm_area_struct *vm_next_share;struct vm_area_struct **vm_pprev_share;struct vm_operations_struct * vm_ops;unsigned long vm_pgoff;struct file * vm_file;

-2011-

33

unsigned long vm_raend;void * vm_private_data;};

vm_mm -> Spatiul de adrese.

vm_start -> Adresa de start din vm_mm.

vm_end -> Primul byte dupa sfarsitul adresei din vm_mm.

vm_next -> Arata urmatoarea adresa de memorie virtuala din lista.

vm_page_prot -> Permiterea accesului la adresa de memorie virtuala.

vm_flags -> Diverse fanioane ce descriu suprafata memoriei.

vm_pgoff -> Daca se mapeaza un fisier, acest camp da decalajul din fisier.

4.2 Crearea spatiului de adresa al unui proces

4.2.1 Functia copy_mm()

Prototip:int copy_mm(unsigned long clone_flags,struct task_struct * tsk)

Aceasta functie este apelata din do_fork() pentru a crea un nou spatiu de adresa al procesului. Parametrii acestei functii sunt:

clone_flags -> Fanioanele cu care a fost apelat fork().tsk -> Descriptorul noului task a caruit spatiu de adresa trebuie sa fie creat. In functie de diferite fanioane, spatiul adresei este fie partajat, fie duplicat.

struct mm_struct * mm, *oldmm;int retval;tsk->min_flt = tsk->maj_flt = 0;tsk->cmin_flt = tsk->cmaj_flt = 0;tsk->nswap = tsk->cnswap = 0;

Memoria legata de contoare in descriptorul task-ului este initializata. Aceste contoare sunt utilizate astfel:

min_flt -> Numara page-fault-urile minore.

-2011-

34

maj_flt -> Numara page-fault-urile majore.nswap, cnswap -> Nu sunt utilizate sau actualizate ; cod mort.

tsk->mm = NULL;tsk->active_mm = NULL;oldmm = current->mm;if (!oldmm)return 0;

Task-ul current este sursa task-ului creat. Asa ca exista un pointer spre descriptorul memoriei.if (clone_flags & CLONE_VM) {atomic_inc(&oldmm->mm_users);mm = oldmm;goto good_mm;}

Daca fanionul clone_vm este setat, atunci noul proces partajeaza acelasi descriptor al memoriei. Se incrementeaza numaratorul mm_users al structurii mm_struct si se duce la good_mm unde este atribut noului proces.

retval = -ENOMEM;mm = allocate_mm();if (!mm)goto fail_nomem;

In acest moment, trebuie creat o noua structura mm_struct, asa ca se apeleaza allocate_mm(), care returneaza un nou descriptor (mm_cachep).

memcpy(mm, oldmm, sizeof(*mm));if (!mm_init(mm))goto fail_nomem;

Mai departe, se copiaza mm_struct al sursei in noul descriptor creat, apoi se apeleaza functia mm_init().

if (init_new_context(tsk,mm))goto free_pt;

down_write(&oldmm->mmap_sem);retval = dup_mmap(mm);up_write(&oldmm->mmap_sem);if (retval)goto free_pt;

Apoi, se apeleaza dup_mmap() pentru initializarea campurilor si se copiaza descriptorii regiunii de memorie (vm_area_struct).

-2011-

35

copy_segments(tsk, mm);

good_mm:tsk->mm = mm;tsk->active_mm = mm;return 0;

free_pt:mmput(mm);

fail_nomem:return retval;

4.2.2 Functia dup_mmap()Prototip:int dup_mmap(struct mm_struct * mm)

Aceasta functie este apelata pentru initializarea unor campuri si pentru descriptorii regiunii de memorie a unei structuri mm_struct.

struct vm_area_struct * mpnt, *tmp, **pprev;int retval;flush_cache_mm(current->mm);

4.3 Stergerea spatiului de adresa al unui process

4.3.1 Functia exit_mm()Prototip:

void exit_mm(struct task_struct * tsk)void __exit_mm(struct task_struct * tsk)Aceasta functie este apelata din do_exit(), de fiecare data cand un proces ia sfarsit, pentru a sterge spatiul adresei sale.

struct mm_struct * mm = tsk->mm;mm_release();

Functia mm_release() este apelata doar pentru a informa sursa despre sfarsitul rezultatului sau, in caz ca acesta a fost creat prin vfork().

if (mm) {atomic_inc(&mm->mm_count);BUG_ON(mm != tsk->active_mm);

-2011-

36

Daca mm este inca valid, se incrementeaza mm_count.

task_lock(tsk);tsk->mm = NULL;task_unlock(tsk);enter_lazy_tlb(mm, current, smp_processor_id());mmput(mm);}

In final, mmput() este apelata pentru a distruge mm_struct.

4.3.2 Alte functii

Functia mmput()

Prototip:void mmput(struct mm_struct *mm)

Aceasta functie este utilizata pentru dealocarea diferitelor resurse detinute de mm_struct si apoi aruncate.

Functia exit_mmap()Prototip:

void exit_mmap(struct mm_struct * mm)

Aceasta functie elibereaza toate resursele din structura data mm_struct.

4.4 Alocarea unei regiuni de memorie

4.4.1 Functia do_mmap()Prototip:unsigned long do_mmap(struct file *file,unsigned long addr,unsigned long len,unsigned long prot,unsigned long flag,unsigned long offset)

Aceasta functie este utilizata pentru crearea unei noi regiuni de memorie pentru un process.

Functia get_unmapped_area()

-2011-

37

Prototip :unsigned long get_unmapped_area(struct file *file,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags)

Aceasta functie este utilizata in gasirea unui spatiu potrivit al adresei pentru mapare.

Functia find_vma_prepare()

struct vm_area_struct *find_vma_prepare(struct mm_struct * mm,unsigned long addr,struct vm_area_struct ** pprev,rb_node_t *** rb_link,rb_node_t ** rb_parent)struct vm_area_struct * vma;rb_node_t ** __rb_link, * __rb_parent, * rb_prev;__rb_link = &mm->mm_rb.rb_node;rb_prev = __rb_parent = NULL;vma = NULL;while (*__rb_link) {struct vm_area_struct *vma_tmp;__rb_parent = *__rb_link;vma_tmp = rb_entry(__rb_parent, struct vm_area_struct, vm_rb);if (vma_tmp->vm_end > addr) {vma = vma_tmp;if (vma_tmp->vm_start <= addr)return vma;__rb_link = &__rb_parent->rb_left;} else {rb_prev = __rb_parent;__rb_link = &__rb_parent->rb_right;}}*pprev = NULL;if (rb_prev)*pprev = rb_entry(rb_prev, struct vm_area_struct, vm_rb);*rb_link = __rb_link;*rb_parent = __rb_parent;return vma;

-2011-

38

CAPITOLUL 5: Cererea de paginare

Functie copy_cow_page()Prototip:void copy_cow_page(struct page * from,struct page * to,unsigned long address)if (from == ZERO_PAGE(address)) {clear_user_highpage(to, address);return;}copy_user_highpage(to, from, address);

Functia free_pte()Prototip:void __free_pte(pte_t pte)struct page *page = pte_page(pte);if ((!VALID_PAGE(page)) || PageReserved(page))return;if (pte_dirty(pte))set_page_dirty(page);free_page_and_swap_cache(page);

Functia free_one_pmd()Prototip:void free_one_pmd(pmd_t * dir)pte_t * pte;if (pmd_none(*dir))return;if (pmd_bad(*dir)) {pmd_ERROR(*dir);pmd_clear(dir);return;}pte = pte_offset(dir, 0);pmd_clear(dir);pte_free(pte);

-2011-

39

Functia free_one_pgd()Prototip:void free_one_pgd(pgd_t * dir)int j;pmd_t * pmd;if (pgd_none(*dir))return;if (pgd_bad(*dir)) {pgd_ERROR(*dir);pgd_clear(dir);return;}pmd = pmd_offset(dir, 0);pgd_clear(dir);for (j = 0; j < PTRS_PER_PMD ; j++) {prefetchw(pmd+j+(PREFETCH_STRIDE/16));free_one_pmd(pmd+j);}pmd_free(pmd);

Functia check_pgt_cache()int check_pgt_cache(void)Returns the number of pages freed.return do_check_pgt_cache(pgt_cache_water[0], pgt_cache_water[1]);

Functia clear_page_tables()Prototip:void clear_page_tables(struct mm_struct *mm,unsigned long first,int nr)pgd_t * page_dir = mm->pgd;spin_lock(&mm->page_table_lock);page_dir += first;do {free_one_pgd(page_dir);page_dir++;} while (--nr);spin_unlock(&mm->page_table_lock);

-2011-

40

check_pgt_cache();

Functia forget_pte()Prototip:void forget_pte(pte_t page)if (!pte_none(page)) {printk("forget_pte: old mapping existed!\n");BUG();}

Functia follow_page()Prototip:struct page * follow_page(struct mm_struct *mm,unsigned long address,int write)pgd_t *pgd;pmd_t *pmd;pte_t *ptep, pte;pgd = pgd_offset(mm, address);if (pgd_none(*pgd) || pgd_bad(*pgd))goto out;pmd = pmd_offset(pgd, address);if (pmd_none(*pmd) || pmd_bad(*pmd))goto out;ptep = pte_offset(pmd, address);if (!ptep)goto out;pte = *ptep;if (pte_present(pte)) {if (!write ||(pte_write(pte) && pte_dirty(pte)))return pte_page(pte);}out:return 0;

Functia get_page_map()Prototip:struct page * get_page_map(struct page *page)

-2011-

41

if (!VALID_PAGE(page))return 0;return page;

Functia zeromap_pte_range()Prototip:void zeromap_pte_range(pte_t * pte,unsigned long address,unsigned long size,pgprot_t prot)unsigned long end;address &= ~PMD_MASK;end = address + size;if (end > PMD_SIZE)end = PMD_SIZE;do {pte_t zero_pte = pte_wrprotect(mk_pte(ZERO_PAGE(address), prot));pte_t oldpage = ptep_get_and_clear(pte);set_pte(pte, zero_pte);forget_pte(oldpage);address += PAGE_SIZE;pte++;} while (address && (address < end));

Functia zeromap_pmd_range()Prototip:int zeromap_pmd_range(struct mm_struct *mm,pmd_t * pmd,unsigned long address,unsigned long size,pgprot_t prot)unsigned long end;address &= ~PGDIR_MASK;end = address + size;if (end > PGDIR_SIZE)end = PGDIR_SIZE;do {pte_t * pte = pte_alloc(mm, pmd, address);if (!pte)

-2011-

42

return -ENOMEM;zeromap_pte_range(pte, address, end - address, prot);address = (address + PMD_SIZE) & PMD_MASK;pmd++;} while (address && (address < end));return 0;

Functia zeromap_page_range()Prototip:int zeromap_page_range(unsigned long address,unsigned long size,pgprot_t prot)int error = 0;pgd_t * dir;unsigned long beg = address;unsigned long end = address + size;struct mm_struct *mm = current->mm;dir = pgd_offset(mm, address);flush_cache_range(mm, beg, end);if (address >= end)BUG();spin_lock(&mm->page_table_lock);do {pmd_t *pmd = pmd_alloc(mm, dir, address);error = -ENOMEM;if (!pmd)break;error = zeromap_pmd_range(mm, pmd, address, end - address, prot);if (error)break;address = (address + PGDIR_SIZE) & PGDIR_MASK;dir++;} while (address && (address < end));spin_unlock(&mm->page_table_lock);flush_tlb_range(mm, beg, end);return error;

Functia remap_pte_range()Prototip:

-2011-

43

void remap_pte_range(pte_t * pte,unsigned long address,unsigned long size,unsigned long phys_addr,pgprot_t prot)unsigned long end;address &= ~PMD_MASK;end = address + size;if (end > PMD_SIZE)end = PMD_SIZE;do {struct page *page;pte_t oldpage;oldpage = ptep_get_and_clear(pte);page = virt_to_page(__va(phys_addr));if ((!VALID_PAGE(page)) || PageReserved(page))set_pte(pte, mk_pte_phys(phys_addr, prot));forget_pte(oldpage);address += PAGE_SIZE;phys_addr += PAGE_SIZE;pte++;} while (address && (address < end));

Functia remap_pmd_range()Prototip:int remap_pmd_range(struct mm_struct *mm,pmd_t * pmd,249unsigned long address,unsigned long size,unsigned long phys_addr,pgprot_t prot)unsigned long end;address &= ~PGDIR_MASK;end = address + size;if (end > PGDIR_SIZE)end = PGDIR_SIZE;phys_addr -= address;do {pte_t * pte = pte_alloc(mm, pmd, address);

-2011-

44

if (!pte)return -ENOMEM;remap_pte_range(pte, address, end - address, address + phys_addr, prot);address = (address + PMD_SIZE) & PMD_MASK;pmd++;} while (address && (address < end));return 0;

Functia remap_page_range()Prototip:int remap_page_range(unsigned long from,unsigned long phys_addr,unsigned long size,pgprot_t prot)int error = 0;pgd_t * dir;unsigned long beg = from;unsigned long end = from + size;struct mm_struct *mm = current->mm;phys_addr -= from;dir = pgd_offset(mm, from);flush_cache_range(mm, beg, end);if (from >= end)BUG();spin_lock(&mm->page_table_lock);do {pmd_t *pmd = pmd_alloc(mm, dir, from);error = -ENOMEM;if (!pmd)break;error = remap_pmd_range(mm, pmd, from, end - from, phys_addr + from, prot);if (error)break;from = (from + PGDIR_SIZE) & PGDIR_MASK;dir++;} while (from && (from < end));spin_unlock(&mm->page_table_lock);flush_tlb_range(mm, beg, end);return error;

-2011-

45

Functia establish_pte()Prototip:void establish_pte(struct vm_area_struct * vma,unsigned long address,pte_t *page_table,pte_t entry)set_pte(page_table, entry);flush_tlb_page(vma, address);update_mmu_cache(vma, address, entry);

Functia vmtruncate_list()Prototip:void vmtruncate_list(struct vm_area_struct *mpnt,unsigned long pgoff)do {struct mm_struct *mm = mpnt->vm_mm;unsigned long start = mpnt->vm_start;unsigned long end = mpnt->vm_end;unsigned long len = end - start;unsigned long diff;if (mpnt->vm_pgoff >= pgoff) {zap_page_range(mm, start, len);continue;}len = len >> PAGE_SHIFT;diff = pgoff - mpnt->vm_pgoff;if (diff >= len)continue;start += diff << PAGE_SHIFT;len = (len - diff) << PAGE_SHIFT;zap_page_range(mm, start, len);} while ((mpnt = mpnt->vm_next_share) != NULL);

Functia vmtruncate()Prototip:int vmtruncate(struct inode * inode,loff_t offset)unsigned long pgoff;struct address_space *mapping = inode->i_mapping;

-2011-

46

unsigned long limit;if (inode->i_size < offset)goto do_expand;inode->i_size = offset;spin_lock(&mapping->i_shared_lock);if (!mapping->i_mmap && !mapping->i_mmap_shared)goto out_unlock;pgoff = (offset + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;if (mapping->i_mmap != NULL)vmtruncate_list(mapping->i_mmap, pgoff);if (mapping->i_mmap_shared != NULL)vmtruncate_list(mapping->i_mmap_shared, pgoff);out_unlock:spin_unlock(&mapping->i_shared_lock);truncate_inode_pages(mapping, offset);goto out_truncate;do_expand:limit = current->rlim[RLIMIT_FSIZE].rlim_cur;if (limit != RLIM_INFINITY && offset > limit)goto out_sig;if (offset > inode->i_sb->s_maxbytes)goto out;inode->i_size = offset;out_truncate:if (inode->i_op && inode->i_op->truncate) {lock_kernel();inode->i_op->truncate(inode);unlock_kernel();}return 0;out_sig:send_sig(SIGXFSZ, current, 0);out:return -EFBIG;

Functia swapin_readahead()Prototip:void swapin_readahead(swp_entry_t entry)int i, num;struct page *new_page;

-2011-

47

unsigned long offset;num = valid_swaphandles(entry, &offset);for (i = 0; i < num; offset++, i++) {new_page = read_swap_cache_async(SWP_ENTRY(SWP_TYPE(entry), offset));if (!new_page)break;page_cache_release(new_page);}return;

Functia do_swap_page()Prototip:int do_swap_page(struct mm_struct * mm,struct vm_area_struct * vma,unsigned long address,pte_t * page_table,pte_t orig_pte,int write_access)struct page *page;swp_entry_t entry = pte_to_swp_entry(orig_pte);pte_t pte;int ret = 1;spin_unlock(&mm->page_table_lock);page = lookup_swap_cache(entry);if (!page) {swapin_readahead(entry);page = read_swap_cache_async(entry);if (!page) {int retval;spin_lock(&mm->page_table_lock);retval = pte_same(*page_table, orig_pte) ? -1 : 1;spin_unlock(&mm->page_table_lock);return retval;}ret = 2;}mark_page_accessed(page);lock_page(page);spin_lock(&mm->page_table_lock);if (!pte_same(*page_table, orig_pte)) {

-2011-

48

spin_unlock(&mm->page_table_lock);unlock_page(page);page_cache_release(page);return 1;}swap_free(entry);if (vm_swap_full())remove_exclusive_swap_page(page);mm->rss++;pte = mk_pte(page, vma->vm_page_prot);if (write_access && can_share_swap_page(page))pte = pte_mkdirty(pte_mkwrite(pte));unlock_page(page);flush_page_to_ram(page);flush_icache_page(vma, page);set_pte(page_table, pte);update_mmu_cache(vma, address, pte);spin_unlock(&mm->page_table_lock);return ret;

Functia do_no_page()Prototip:int do_no_page(struct mm_struct * mm,struct vm_area_struct * vma,unsigned long address,int write_access,pte_t *page_table)struct page * new_page;pte_t entry;if (!vma->vm_ops || !vma->vm_ops->nopage)return do_anonymous_page(mm, vma, page_table, write_access, address);spin_unlock(&mm->page_table_lock);new_page = vma->vm_ops->nopage(vma, address & PAGE_MASK, 0);if (new_page == NULL) /* no page was available -- SIGBUS */return 0;if (new_page == NOPAGE_OOM)return -1;if (write_access && !(vma->vm_flags & VM_SHARED)) {struct page * page = alloc_page(GFP_HIGHUSER);if (!page) {

-2011-

49

page_cache_release(new_page);return -1;}copy_user_highpage(page, new_page, address);page_cache_release(new_page);lru_cache_add(page);new_page = page;}spin_lock(&mm->page_table_lock);if (pte_none(*page_table)) {++mm->rss;flush_page_to_ram(new_page);flush_icache_page(vma, new_page);entry = mk_pte(new_page, vma->vm_page_prot);if (write_access)entry = pte_mkwrite(pte_mkdirty(entry));set_pte(page_table, entry);} else {page_cache_release(new_page);spin_unlock(&mm->page_table_lock);return 1;}update_mmu_cache(vma, address, entry);spin_unlock(&mm->page_table_lock);return 2;

Functia handle_pte_fault()Prototip:int handle_pte_fault(struct mm_struct *mm,struct vm_area_struct * vma,unsigned long address,int write_access,pte_t * pte)pte_t entry;entry = *pte;if (!pte_present(entry)) {if (pte_none(entry))return do_no_page(mm, vma, address, write_access, pte);return do_swap_page(mm, vma, address, pte, entry, write_access);}

-2011-

50

if (write_access) {if (!pte_write(entry))return do_wp_page(mm, vma, address, pte, entry);entry = pte_mkdirty(entry);}entry = pte_mkyoung(entry);establish_pte(vma, address, pte, entry);spin_unlock(&mm->page_table_lock);return 1;

Functia handle_mm_fault()Prototip:int handle_mm_fault(struct mm_struct *mm,struct vm_area_struct * vma,unsigned long address,int write_access)pgd_t *pgd;pmd_t *pmd;current->state = TASK_RUNNING;pgd = pgd_offset(mm, address);spin_lock(&mm->page_table_lock);pmd = pmd_alloc(mm, pgd, address);if (pmd) {pte_t * pte = pte_alloc(mm, pmd, address);if (pte)return handle_pte_fault(mm, vma, address, write_access, pte);}spin_unlock(&mm->page_table_lock);return -1;

Functia pmd_alloc()Prototip:pmd_t *__pmd_alloc(struct mm_struct *mm,pgd_t *pgd,unsigned long address)pmd_t *new;new = pmd_alloc_one_fast(mm, address);if (!new) {spin_unlock(&mm->page_table_lock);

-2011-

51

new = pmd_alloc_one(mm, address);spin_lock(&mm->page_table_lock);if (!new)return NULL;if (!pgd_none(*pgd)) {pmd_free(new);goto out;}}pgd_populate(mm, pgd, new);out:return pmd_offset(pgd, address);

Functia pte_alloc()Prototip:pte_t *pte_alloc(struct mm_struct *mm,pmd_t *pmd,unsigned long address)if (pmd_none(*pmd)) {pte_t *new;new = pte_alloc_one_fast(mm, address);if (!new) {spin_unlock(&mm->page_table_lock);new = pte_alloc_one(mm, address);spin_lock(&mm->page_table_lock);if (!new)return NULL;if (!pmd_none(*pmd)) {pte_free(new);goto out;}}pmd_populate(mm, pmd, new);}out:return pte_offset(pmd, address);

Functia make_pages_present()Prototip:

-2011-

52

int make_pages_present(unsigned long addr,unsigned long end)int ret, len, write;struct vm_area_struct * vma;vma = find_vma(current->mm, addr);write = (vma->vm_flags & VM_WRITE) != 0;if (addr >= end)BUG();if (end > vma->vm_end)BUG();len = (end+PAGE_SIZE-1)/PAGE_SIZE-addr/PAGE_SIZE;ret = get_user_pages(current, current->mm, addr,len, write, 0, NULL, NULL);return ret == len ? 0 : -1;

Functia vmalloc_to_page()Prototip:struct page * vmalloc_to_page(void * vmalloc_addr)unsigned long addr = (unsigned long) vmalloc_addr;struct page *page = NULL;pmd_t *pmd;pte_t *pte;pgd_t *pgd;pgd = pgd_offset_k(addr);if (!pgd_none(*pgd)) {pmd = pmd_offset(pgd, addr);if (!pmd_none(*pmd)) {pte = pte_offset(pmd, addr);if (pte_present(*pte)) {page = pte_page(*pte);} } }return page;}

-2011-

53

Capitolul 6: Swapping

6.1 Eliberarea paginilor din cache-uriArhitectura Linux VM este compusa din doua liste LRU cunoscute ca liste active si

inactive. Imediat ce o pagina este adaugata la cache-ul de pagini (include cache-ul de swap), este adaugat la lista inactiva. Procesul de adaugare incearca sa detecteze, prin bitii de intrare in tabela de pagina si bitii de pagina ce pagini sunt cele mai accesate, mutandu-le la lista activa. In scenarile de presiune a memoriei, VM incearca mai itnai sa elibereze memoria prin eliminarea cache-urilor slab. Atunci cand acea procedura nu elibereaza suficienta memorie, se concentreaza pe eliberarea memoriei din cache-ul de pagini. Mai intai, verifica paginile din lista activa, mutandu-le pe cele mai putin accesate in lista inactiva, reumpland-o. Apoi, lista inactiva este scanata, sincronizand paginile cu bufferele si incercand sa elibereze paginile ce se pot elibera (fara utilizatori). Daca se gasesc pagini ce se pot elibera si sunt scrise, ele sunt sterse.

Liste LRU

-2011-

54

Totusi, paginile de pe listele activa si inactiva pot avea utilizatori, lucru care inseamna ca sunt mapate de procese. Cand se gasesc multe pagini de mapari pe lista inactiva, este invocat proceselul de demapare apeland functia swap_out(). Ca ultima resursa, daca nu este posibil sa se elibereze pagini din cache-ul de pagini, sistemul VM micsoreaza cache-urile fisierelor de sistem, cum ar fi cache-urile inode, dentry si quota. Daca nici asa nu se poate elibera memoria, sistemul VM ruleaza scenariul de ‘fara memorie’, in care alege un proces activ si incearca sa-l omoare pentru a elibera memoria.

Functia shrink_cache() Aceasta functie micsoreaza cache-ul de pagina si swap, verificand lista inactiva si incercand sa elimine pagini din ea. Poate fi nevoie sa se stearga pagini scrise prin rescrierea lor, ceea ce va fi facut daca este posibil (daca este permis de masca gfp). Valoarea de intoarcere este o valoare intreaga. Daca este zero, inseamna ca functia poate elibera numarul de pagini cerute anterior (parametrul ce desemneaza numarul de pagini nr_pages). Daca nu este zero, valoarea inseamna cate pagini lipseau pana la golire pentru a obtine numarul de pagini cerut.

Prototip:int shrink_cache(int nr_pages, zone_t * classzone, unsigned int gfp_mask, int priority)struct list_head * entry;int max_scan = nr_inactive_pages / priority;int max_mapped = min((nr_pages < < (10 - priority)), max_scan / 10);struct page * page;if (unlikely(current->need_resched)) {

spin_unlock(&pagemap_lru_lock);__set_current_state(TASK_RUNNING);schedule();spin_lock(&pagemap_lru_lock);continue;

}page = list_entry(entry, struct page, lru);BUG_ON(!PageLRU(page));BUG_ON(PageActive(page));list_del(entry);list_add(entry, &inactive_list);if (unlikely(!page_count(page)))

continue;if (!memclass(page_zone(page), classzone))

-2011-

55

continue;if (!page->buffers && (page_count(page) != 1 ||!page->mapping))

goto page_mapped;if (unlikely(TryLockPage(page))) {

if (PageLaunder(page) &&(gfp_mask & __GFP_FS)) {page_cache_get(page);spin_unlock(&pagemap_lru_lock);wait_on_page(page);page_cache_release(page);spin_lock(&pagemap_lru_lock);

}continue;

}if (PageDirty(page) && is_page_cache_freeable(page) &&page->mapping) {

int (*writepage)(struct page *);writepage = page->mapping->a_ops->writepage;if ((gfp_mask & __GFP_FS) && writepage) {

ClearPageDirty(page);SetPageLaunder(page);page_cache_get(page);spin_unlock(&pagemap_lru_lock);writepage(page);page_cache_release(page);spin_lock(&pagemap_lru_lock);continue;

}}if (page->buffers) {

spin_unlock(&pagemap_lru_lock);page_cache_get(page);if (try_to_release_page(page, gfp_mask)) {

if (!page->mapping) {spin_lock(&pagemap_lru_lock);UnlockPage(page);__lru_cache_del(page);page_cache_release(page);if (--nr_pages)

-2011-

56

continue;break;

} else {page_cache_release(page);spin_lock(&pagemap_lru_lock);

}} else {

UnlockPage(page);page_cache_release(page);spin_lock(&pagemap_lru_lock);continue;

}}spin_lock(&pagecache_lock);if (!page->mapping || !is_page_cache_freeable(page)) {

spin_unlock(&pagecache_lock);UnlockPage(page);page_mapped:if (--max_mapped >= 0)

continue;if (PageDirty(page)) {

spin_unlock(&pagecache_lock);UnlockPage(page);continue;

}if (likely(!PageSwapCache(page))) {

__remove_inode_page(page);spin_unlock(&pagecache_lock);

} else {swp_entry_t swap;swap.val = page->index;__delete_from_swap_cache(page);spin_unlock(&pagecache_lock);swap_free(swap);

}__lru_cache_del(page);UnlockPage(page);

-2011-

57

page_cache_release(page);if (--nr_pages)

continue;break;

}spin_unlock(&pagemap_lru_lock);return nr_pages;

Functia refill_inactive()

Aceasta functie incearca sa miste un numar cerut de pagini (nr_pages) din lista activa in lista inactiva. De asemenea, actualizeaza imbatranirea fiecarei pagini verificare. Imbatranirea este reprezentata de catre bitul Referenced.

Prototip:

void refill_inactive(int nr_pages)struct list_head * entry;spin_lock(&pagemap_lru_lock);entry = active_list.prev;while (nr_pages && entry != &active_list) {

struct page * page;page = list_entry(entry, struct page, lru);entry = entry->prev;if (PageTestandClearReferenced(page)) {

list_del(&page->lru);list_add(&page->lru, &active_list);continue;

}nr_pages--;del_page_from_active_list(page);add_page_to_inactive_list(page);SetPageReferenced(page);

}spin_unlock(&pagemap_lru_lock);

Functia shrink_caches()

-2011-

58

Avand un rol foarte important in procesul de eliberare a paginilor, aceasta functie defineste prioritatea pentru fiecare tip de memorie (cache-uri slab, cache-uri de pagini si swap, cache-uri dentry, inode si quota), incercand sa elibereze paginile in ordinea stabilita inainte. Pentru o zona data (parametru classzone), aceasta functie incearca sa elibereze numarul cerut de pagini (parametrul nr_pages), urmarind o masca GFP pentru permisiuni pe parcursul procesului de eliberare (masca gfp) si o prioritate ce este utilizata pentru a sti cat de greu trebuie sa fi incercat sa elibereze paginile dintr-un anumit fel de memorie. Valoarea returnata este una intreaga. O valoare zero inseamna ca numarul cerut de pagini a fost eliberat. O valoare diferita de zero este numarul de pagini ce lipsesc pentru a atinge numarul cerut de pagini.

Prototip:

int shrink_caches(zone_t * classzone, int priority, unsigned int gfp_mask, int nr_pages)int chunk_size = nr_pages;unsigned long ratio;nr_pages -= kmem_cache_reap(gfp_mask);if (nr_pages <= 0)

return 0;nr_pages = chunk_size;ratio = (unsigned long) nr_pages *nr_active_pages / ((nr_inactive_pages + 1) * 2);refill_inactive(ratio);nr_pages = shrink_cache(nr_pages, classzone, gfp_mask, priority);if (nr_pages <= 0)

return 0;shrink_dcache_memory(priority, gfp_mask);shrink_icache_memory(priority, gfp_mask);#ifdef CONFIG_QUOTAshrink_dqcache_memory(DEF_PRIORITY, gfp_mask);#endifreturn nr_pages;

Functia try_to_free_pages()

Este o functie simpla ce incearca sa goleasca paginile dintr-o anumita zona (parametrul classzone) prin apelarea shrink_caches(), crescand prioritatea daca este necesar. Functia shrink_caches() va urma masca GFP (parametrul masca gfp). In cazul in care nu a putut goli numarul definit de pagini (SWAP_CLUSTER_MAX), apeleaza functia memoru() ce poate opri anumite aplicatii.

-2011-

59

Ea returneaza o valoare intreaga. Valoarea 1 inseamna ca functia a eliberat cu succes numarul de pagini definit, iar 0 inseamna ca nu a reusit. Nu se utilizeaza parametrul de ordine.

Prototip:

int try_to_free_pages(zone_t *classzone, unsigned int gfp_mask, unsigned int order)int priority = DEF_PRIORITY;int nr_pages = SWAP_CLUSTER_MAX;gfp_mask = pf_gfp_mask(gfp_mask);do {

nr_pages = shrink_caches(classzone, priority, gfp_mask,nr_pages);if (nr_pages <= 0)

return 1;} while (--priority);out_of_memory();return 0;

6.2 Demaparea paginilor din procese

Sistemele ce au multe pagini mapate in lista inactiva trebuie sa inceapa sa demapeze pagini din procese. Acest lucru inseamna ca procesele vor incepe sa aiba propriile tabele de pagini scanate si toate intrarile in tabelele de pagini verificate. Intrarile ce nu au fost accesate recent vor fi golite (demapate) si, atunci cand a fost setat inainte pentru pagini anonime, se seteaza pe o noua adresa (remapare). Paginile anonime sunt paginile fara o stocare de intarire.

-2011-

60

Procesul de demapare

Functia try_to_swap_out()

Rolul functiei try_to_swap_out() este de a incerca demaparea paginilor din procesele ce le mapeaza. Aceasta este prima parte din tot procesul de swap out, din moment ce paginile pot fi eliberate doar daca toate procesele ce le mapeaza au fost demapate anterior in siguranta. Demaparea inseamna ca, pentru o intrare in tabela de pagini data (pte), fie este abia eliberata (paginile mapate in fisiere), fie sunt remapate la o adresa schimbata (pagini anonime). In ambele cazuri (eliberare sau remapare la adrese de schimb), bitul prezent in noul pte va fi dezactivat. De aceea, procesul caruia ii apartine pte nu va putea sa-l acceseze direct, cauzand o eroare a paginii pentru orice acces viitor.

-2011-

61

Aceasta functie returneaza o valoare intreaga. Acea valoare va fi zero daca nu a fost eliberata nicio pagina eliberabila (pagina ce nu mai este mapata de niciun proces). Acest lucru se va intampla chiar si in cazul in care o pagina a fost demapata de un proces, dar inca este mapata de alte procese. Acea valoare returnata va fi 1 daca o pagina a fost eliberata de utimul sau proces (niciun proces nu o mapeaza la momentul existentei acestei functii).

Prototip:

int try_to_swap_out(struct mm_struct * mm, struct vm_area_struct* vma, unsigned long address, pte_t * page_table, struct page *page, zone_t * classzone)pte_t pte;swp_entry_t entry;if ((vma->vm_flags & VM_LOCKED)|| ptep_test_and_clear_young(page_table)) {

mark_page_accessed(page);return 0;

}if (PageActive(page))

return 0;if (!memclass(page_zone(page), classzone))

return 0;if (TryLockPage(page))

return 0;flush_cache_page(vma, address);pte = ptep_get_and_clear(page_table);flush_tlb_page(vma, address);if (pte_dirty(pte))

set_page_dirty(page);if (PageSwapCache(page)) {

entry.val = page->index;swap_duplicate(entry);set_swap_pte:set_pte(page_table, swp_entry_to_pte(entry));drop_pte:mm->rss--;UnlockPage(page);{

int freeable = page_count(page) - !!page->buffers <= 2;

-2011-

62

page_cache_release(page);return freeable;

}}if (page->mapping)

goto drop_pte;if (!PageDirty(page))

goto drop_pte;if (page->buffers)

goto preserve;for (;;) {

entry = get_swap_page();if (!entry.val)break;if (add_to_swap_cache(page, entry) == 0) {

SetPageUptodate(page);set_page_dirty(page);goto set_swap_pte;

}swap_free(entry);

}preserve:set_pte(page_table, pte);UnlockPage(page);return 0;

Functia swap_out_pmd()

Aceasta functie scaneaza toate intrarile in tabela de pagini a unui director mijlociu al unei pagini (parametrul dir) pana la finalul directorului mijlociu al paginii sau pana la finalul zonei VM. Returneaza o valoare intreaga, ce va fi numarul de pagini ce lipsesc pana se atinge numarul de pagini cerut de pagini demapate (parameturl count).

Prototip:

int swap_out_pmd(struct mm_struct * mm, struct vm_area_struct * vma, pmd_t *dir, unsigned long address, unsigned long end, int count, zone_t * classzone)pte_t * pte;

-2011-

63

unsigned long pmd_end;if (pmd_none(*dir))

return count;if (pmd_bad(*dir)) {

pmd_ERROR(*dir);pmd_clear(dir);return count;

}pte = pte_offset(dir, address);pmd_end = (address + PMD_SIZE) & PMD_MASK;if (end > pmd_end)

end = pmd_end;do {

if (pte_present(*pte)) {struct page *page = pte_page(*pte);if (VALID_PAGE(page) && !PageReserved(page)) {

count -= try_to_swap_out(mm, vma, address, pte, page,classzone);if (!count) {

address += PAGE_SIZE;break;

}}

}address += PAGE_SIZE;pte++;

} while (address && (address < end));mm->swap_address = address;return count;

Functia swap_out_pgd()

Aceasta functie scaneaza toate directoarele mijlocii ale paginii unui offset de director global de pagini (parametrul dir), pana la finalul zonei VM (parameturl VMA). Returneaza o valoare intreaga, ce reprezinta cate pagini lipsesc pana la numarul cerut de pagini complet demapate (parameturl count).

Prototip:

-2011-

64

int swap_out_pgd(struct mm_struct * mm, struct vm_area_struct * vma, pgd_t *dir, unsigned long address, unsigned long end, int count, zone_t * classzone)pmd_t * pmd;unsigned long pgd_end;if (pgd_none(*dir))

return count;if (pgd_bad(*dir)) {

pgd_ERROR(*dir);pgd_clear(dir);return count;

}pmd = pmd_offset(dir, address);pgd_end = (address + PGDIR_SIZE) & PGDIR_MASK;if (pgd_end && (end > pgd_end))

end = pgd_end;do {

count = swap_out_pmd(mm, vma, pmd, address, end, count,classzone);if (!count)

break;address = (address + PMD_SIZE) & PMD_MASK;pmd++;} while (address && (address < end));

return count;

Functia swap_out_vma() Aceasta functie scaneaza o zona VM (parametrul vma), returnand numarul de pagini ce lipsesc pentru a atinge numarul cerut de pagini complet nemapate (parametrul count).

Prototip:

int swap_out_vma(struct mm_struct * mm, struct vm_area_struct * vma, unsigned long address, int count, zone_t * classzone)pgd_t *pgdir;unsigned long end;

-2011-

65

if (vma->vm_flags & VM_RESERVED)return count;

pgdir = pgd_offset(mm, address);end = vma->vm_end;BUG_ON(address >= end);do {

count = swap_out_pgd(mm, vma, pgdir, address, end,count, classzone);if (!count)

break;address = (address + PGDIR_SIZE) & PGDIR_MASK;pgdir++;} while (address && (address < end));

return count;

Functia swap_out_mm()

Aceasta functie scaneaza toate zonele VM ale unui proces (parametrul mm). Returneaza numarul de pagini ce lipsesc pentru a atinge numarul cerut de pagini complet nemapate (parametrul count). Daca valoarea returnata este zero, inseamna ca paginile cerute au fost complet demapate.

Prototip:

int swap_out_mm(struct mm_struct * mm, int count, int * mmcounter, zone_t *classzone)unsigned long address;struct vm_area_struct* vma;spin_lock(&mm->page_table_lock);address = mm->swap_address;if (address == TASK_SIZE || swap_mm != mm) {

++*mmcounter;goto out_unlock;

}vma = find_vma(mm, address);if (vma) {

if (address < vma->vm_start)address = vma->vm_start;

for (;;) {-2011-

66

count = swap_out_vma(mm, vma, address, count, classzone);vma = vma->vm_next;if (!vma)

break;if (!count)

goto out_unlock;address = vma->vm_start;

}}mm->swap_address = TASK_SIZE;out_unlock:spin_unlock(&mm->page_table_lock);return count;

Functia swap_out()

Aceasta functie alege un task ce va avea tabelul de pagini scanat. Mai precis, o structura mm-ce este una per task- este aleasa. Valoarea returnata este una intreaga, deci numarul de pagini cerut a fost atins (se retuneaza 1) sau nu ( se returneaza 0).

Prototip:

int swap_out(unsigned int priority, unsigned int gfp_mask, zone_t * classzone)int counter, nr_pages = SWAP_CLUSTER_MAX;struct mm_struct *mm;counter = mmlist_nr;do {

if (unlikely(current->need_resched)) {__set_current_state(TASK_RUNNING);schedule();

}spin_lock(&mmlist_lock);mm = swap_mm;while (mm->swap_address == TASK_SIZE ||mm == &init_mm) {

mm->swap_address = 0;mm = list_entry(mm->mmlist.next, struct mm_struct, mmlist);if (mm == swap_mm)

goto empty;-2011-

67

swap_mm = mm;}atomic_inc(&mm->mm_users);spin_unlock(&mmlist_lock);nr_pages = swap_out_mm(mm, nr_pages, &counter, classzone);mmput(mm);if (!nr_pages)

return 1;} while (--counter >= 0);

return 0;empty:spin_unlock(&mmlist_lock);return 0;

6.3 Cache Swap

Functia swap_writepage()

Aceasta functie este utilizata pentru a scrie o pagina swap (parameturl page). Este apelata din shrink_cache() si returneaza intotdeauna zero.

Prototip:

static int swap_writepage(struct page *page)if (remove_exclusive_swap_page(page)) {

UnlockPage(page);return 0;

}rw_swap_page(WRITE, page);return 0;

Functia add_to_swap_cache()

-2011-

68

Functia adauga o pagina (parameturl page) la cache-ul swap, setand pagina la intrarea swap considerata parametru (entry). Ea returneaza o valoare intreaga ce corespunde erorii, deci valoarea zero inseamna ca pagina a fost adaugata cu succes.

Prototip:

int add_to_swap_cache(struct page *page, swp_entry_t entry)if (page->mapping)

BUG();if (!swap_duplicate(entry)) {

INC_CACHE_INFO(noent_race);return -ENOENT;

}if (add_to_page_cache_unique(page, &swapper_space, entry.val, page_hash(&swapper_space, entry.val)) != 0) {

swap_free(entry);INC_CACHE_INFO(exist_race);return -EEXIST;

}if (!PageLocked(page))

BUG();if (!PageSwapCache(page))

BUG();INC_CACHE_INFO(add_total);return 0;

Functia _delete_from_swap_cache()

Aceasta functie inlatura pagina din cache-ul swap, dar nu renunta la referinta la intrarea swap, nici la referinta paginii cache pe care o are pe aceasta pagina.

Prototip:

void __delete_from_swap_cache(struct page *page)if (!PageLocked(page))

BUG();if (!PageSwapCache(page))

BUG();-2011-

69

ClearPageDirty(page);__remove_inode_page(page);INC_CACHE_INFO(del_total);

Functia delete_from_swap_cache()

Aceasta functie elibereaza pagina cache de swap si o sterge din cache-ul swap, eliminand referinta la intrarea swap si cache-ul de referinta al paginii pe care o are la aceasta pagina.

Prototip:

void delete_from_swap_cache(struct page *page)swp_entry_t entry;if (!PageLocked(page))

BUG();block_flushpage(page, 0);entry.val = page->index;spin_lock(&pagecache_lock);__delete_from_swap_cache(page);spin_unlock(&pagecache_lock);swap_free(entry);page_cache_release(page);

Functia free_page_and_swap_cache()

Rolul principal al aceste functii este de a lansa o referinta la o pagina. De asemenea, daca este o pagina de cache swap si este capabila sa blocheze pagina pe loc, poate verifica daca este o pagina exclusiva de swap, sa o inlature din cache-ul swap si sa-si lase referinta la intrarea swap.

Prototip:

void free_page_and_swap_cache(struct page *page)if (PageSwapCache(page) && !TryLockPage(page)) {

remove_exclusive_swap_page(page);UnlockPage(page);

}page_cache_release(page);

-2011-

70

Functia lookup_swap_cache()

Functia cauta o anumita intrare swap (parameturl entry) in cache-ul swap, obtinand o referinta a paginii daca este gasita. Ea returneaza un pointer la pagina gasita daca exista, sau zero in caz contrar.

Prototip:

struct page * lookup_swap_cache(swp_entry_t entry)struct page *found;found = find_get_page(&swapper_space, entry.val);INC_CACHE_INFO(find_total);if (found)

INC_CACHE_INFO(find_success);return found;

Functia read_swap_page_async()

Aceasta functie incearca sa gaseasca o intrare in cache-ul swap. Daca nu este gasita, ea aloca o pagina, o adauga unui cache swap si citeste data din ea de pe disc. Un pointer la aceasta pagina (gasit sau adaugat cache-ului swap) este returnat catre sistem.

Prototip:

struct page *found_page, *new_page = NULL;int err;do {

struct page * read_swap_cache_async(swp_entry_t entry)found_page = find_get_page(&swapper_space, entry.val);if (found_page)

break;if (!new_page) {

new_page = alloc_page(GFP_HIGHUSER);if (!new_page)

break;}err = add_to_swap_cache(new_page, entry);if (!err) {

-2011-

71

rw_swap_page(READ, new_page);return new_page;

}} while (err != -ENOENT);if (new_page)

page_cache_release(new_page);return found_page;

Bibliografie:

[1] Daniel P. Bovet & Marco Cesati. Understanding the Linux Kernel.O’Reilly, 2001, ISBN 81-7366-233-9.

[2] Joe Knapka. Outline of the Linux Memory Management System.http://home.earthlink.net/~jknapka/linux-mm/vmoutline.html.

[3] Martin Devera. Functional Callgraph of the Linux VM.http://luxik.cdi.cz/~devik/mm.htm.

[4] Linux MM. Website and mailing list for linux-mm.http://www.linux-mm.org. Contine multe linkuri catre documentatie linux pentru managementul memoriei.

[5] Mel Gorman. Documentation Patches for the linux kernel.http://www.csn.ul.ie/~mel/projects/vm/. Contine documentatie pentru Memoria Virtuala sub linux, si mult cod comentat.

[6] Jeff Bonwick. The Slab Allocator: An Object Caching Kernel Memory Allocator. http://www.usenix.org/publications/library/proceedings/bos94/bonwick.html. Ofera o privire de ansamblu cuprinzatoare a alocatorului de memorie SunOS 5.4.

[7] Ralf Brown. Interrupt List. http://www.ctyme.com/rbrown.htm. Aceasta lista contine toate apelurile la intreruperi documentate.

-2011-

72