Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
La pila d’execu...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
Quan s'inicia l'execució d'un programa, el si...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
 
 
Un aspecte rellevant, el qual mostra la i...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
s'executa en segon pla i que va buscant blocs...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
 
 
La il∙lustració anterior representa de la...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
posició que dóna una adreça relativa dels par...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
s’emmagatzemarà informació de caràcter tempor...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
el qual és usat també en les operacions aritm...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
 
 
Notem que a la cinquena vinyeta s’ha recu...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
 
Intentem anar una mica més enllà. Pensem un...
 
Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014 
 
Llicència del document
 
Per a fer ús d’aques...
Próxima SlideShare
Cargando en…5
×

Pila d'execució / pila de llamadas / call stack [Català]

119 visualizaciones

Publicado el

Es tracta d'uns apunts personals que tenen per objectiu explicar el funcionament de la pila d'execució d'una aplicació.

Publicado en: Software
0 comentarios
0 recomendaciones
Estadísticas
Notas
  • Sé el primero en comentar

  • Sé el primero en recomendar esto

Sin descargas
Visualizaciones
Visualizaciones totales
119
En SlideShare
0
De insertados
0
Número de insertados
9
Acciones
Compartido
0
Descargas
1
Comentarios
0
Recomendaciones
0
Insertados 0
No insertados

No hay notas en la diapositiva.

Pila d'execució / pila de llamadas / call stack [Català]

  1. 1.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014                                  La pila d’execució                                              Ferran Verdés Castelló  ferranverdes@gmail.com  www.ferranverdes.cat  Primera versió    Ferran Verdés Castelló   Pàgina 1 de 11   
  2. 2.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014    Quan s'inicia l'execució d'un programa, el sistema operatiu carrega el codi executable en una                            zona de memòria no ocupada i reserva dos espais més de memòria que seran utilitzats en la                                  seva execució, en concret, com a pila d'execució i com a monticle (en anglès, heap). A grans                                  trets, podem dir que la pila és l'encarregada de contenir els paràmetres de les funcions, les                                seves variables locals i els seus valors de retorn, mentre que el heap emmagatzema aquelles                              dades que s'han sol∙licitat mitjançant una instrucció de reserva de memòria, com per                          exemple, utilitzant malloc en C o new en C++. Bé, doncs una vegada el codi estigui copiat a                                    la memòria, només caldrà que el sistema operatiu li indiqui a la CPU que ha d’apuntar amb el                                    registre del punter d'instruccions (conegut com EIP, de l'anglès extended instruction pointer)                        a la primera instrucció d’aquest.    En aquells llenguatges que permeten la creació de variables globals o de variables                          estàtiques, com són C i C++, es reserva també una tercera zona que s'anomena zona de                                dades estàtiques. Aquesta zona es subdivideix en dos apartats, els quals es diferencien en                            funció de si les dades que conten es troben inicialitzades o no abans de l’inici de l’execució                                  de la funció principal main. Ambdues zones ocuparan una mida fixa de memòria durant tota                              l’execució del programa, de la mateixa manera que ho fa la zona del codi, degut a que l’espai                                    que es requereix és possible determinar­lo en temps de compilació i no variarà en temps                              d’execució. Aquest fet succeeix perquè el compilador, quan acaba el procés de compilació,                          coneix totes les variables globals i estàtiques i el seu tipus, i per tant, sap perfectament la                                  quantitat de memòria que cal reservar. Així doncs, escriu en l'executable les instruccions                          necessàries per reservar aquesta memòria, que serà exclusivament la justa i la necessària, i                            també aquelles instruccions per donar­los­hi els valors inicials si és el cas.     Un dels motius pels quals es defineix una àrea de dades estàtiques és perquè les variables                                globals poden ser usades en qualsevol moment i per tant, han de ser accessibles des del                                principi fins al final de l'execució, a diferència de les variables que es defineixen dins de les                                  funcions. Amb el mateix criteri, alguns compiladors de llenguatges orientats a objectes                        emmagatzement també en aquesta zona les variables estàtiques de classe, que són úniques                          per a totes les instàncies d'una classe.    Per tal de comprendre correctament el text anterior, anem a veure una imatge representativa                            del que s’ha descrit fins ara:            ( espai en blanc intencionat )    Ferran Verdés Castelló   Pàgina 2 de 11   
  3. 3.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014        Un aspecte rellevant, el qual mostra la imatge, és que la pila creix cap a les posicions més                                    baixes de la memòria. Més endavant ho comentarem més profundament.    Com hem dit al començament, el heap és l’àrea de memòria que s’utilitza per concedir                              assignacions de blocs de bits. Quan en una funció o mètode trobem una instrucció que                              realitza aquesta petició, el sistema operatiu reserva la memòria que li hem sol∙licitat en una                              zona lliure del heap, la qual marcarà com assignada i ens retornarà la direcció de memòria de                                  la primera posició dels bytes reservats mitjançant un punter (o una referència, en funció del                              llenguatge de programació).     En el moment en que no necessitem la memòria que hem reservat dinàmicament, es                            convenient informar­ho al sistema operatiu per a que pugui ser assignada en una nova petició                              de memòria. Aquest procediment es realitza amb instruccions com free en C o delete en                              C++, en que s’utilitza el mateix punter que el sistema operatiu ens ha retornat inicialment en                                la petició de reserva.     En els llenguatges més moderns, com ara Java, C# o C++11, existeix un mecanisme                            anomenat recol∙lector de deixalles (en anglès, garbage collector) que consta d'un procés que                            Ferran Verdés Castelló   Pàgina 3 de 11   
  4. 4.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014    s'executa en segon pla i que va buscant blocs de memòria que no es troben referenciats en                                  un instant donat, és a dir, que no existeix en el programa un punter o referència que apunti a                                      la direcció del bloc. Si aquest és el cas, el bloc és marcat com a lliure. Cal comentar però,                                      que l’ús d’aquest mecanisme és molt qüestionat pel que fa als aspectes de rendiment.    Una altra dada molt important, és que en alguns casos el sistema operatiu no prepara una                                àrea de heap per a cada aplicació, sinó que ell mateix gestiona un heap global que és usat                                    per a totes les aplicacions que es troben en execució.    Per altra banda, tenim la pila d’execució. Com s’ha comentat anteriorment, la pila d’execució                            conté els paràmetres de les funcions, les seves variables locals i els seus valors de retorn, i                                  també alguns valors de registres que seran detallats a continuació. D’alguna manera, el                          contingut relacionat per a cada funció es troba clarament diferenciat gràcies a una espècie de                              context que es coneix amb el nom de stack frame. En altres paraules, per una funció                                determinada el seu stack frame, marc o context, és tot allò que es troba emmagatzemat a la                                  pila degut a la seva execució. Considerem el següent codi:     int main() { func1(10); func2(); return 0; }  void func1(int i) { func3(); }    Ara, anem a representar gràficament l’evolució dels diferents stack frames que trobaríem a la                            pila d’execució per a cada funció:                      ( espai en blanc intencionat )    Ferran Verdés Castelló   Pàgina 4 de 11   
  5. 5.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014        La il∙lustració anterior representa de la manera més esquemàtica i intuïtiva el mecanisme que                            s’utilitza dins la pila per a tenir el control de les diferents funcions que es van executant.                                  Intuïtiva, en el sentit que la pila de la imatge creix en el sentit esperat, de la manera en què                                        estem més acostumats a veure­ho, que és cap amunt. En les il∙lustracions posteriors, la pila                              s’intentarà representar de la manera més calcada a la realitat, i això significa que creixerà                              descendentment, tal i com ho mostra la primera imatge del document.    Pel que fa al contingut de la imatge, notem que el stack frame corresponent a la funció                                  func1() és lleugerament més gran que la resta degut a que ha de contenir l’espai per un valor                                    enter que rep per paràmetre i que no reben les altres funcions. Notem també que a totes les                                    vinyetes hi ha un registre anomenat ESP (de l’anglès, extended stack pointer) que apunta                            sempre a la cima de la pila (a la següent posició disponible per escriure). A la majoria de                                    registres s’hi afegeix la característica extended perquè en les arquitectures actuals tenen una                          mida de 32 bits enlloc dels 16 bits que tenien anteriorment. I aprofitant que estem parlant de                                  registres, anem a anomenar­ne un altre de molt important, el registre EBP (de l’anglès,                            extended base pointer). Aquest, és un registre que, atenció, apunta a una posició de la pila                                que conté l’adreça de memòria a la que apuntava ell mateix al stack frame anterior, és a dir,                                    al stack frame de la funció que ha cridat a la vigent. El seu ús radica en que apunta a una                                            Ferran Verdés Castelló   Pàgina 5 de 11   
  6. 6.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014    posició que dóna una adreça relativa dels paràmetres i les variables locals, en altres                            paraules, a la part superior d’on apunta el registre EBP s’hi troben les variables locals de la                                  funció actual i a la part inferior s’hi troben la direcció de retorn d’aquesta i els valors dels                                    paràmetres que ha rebut, i en segon pla, la resta de stack frames de les funcions “anteriors”.     El motiu pel qual s’utilitza el registre EBP és perquè el registre ESP pot incrementar o                                decrementar en l’execució de la funció, i en canvi, el registre EBP sempre romandrà fix.                              Aquest fet és convenient perquè així sempre podrem referir­nos al contingut de la pila de la                                mateixa manera, és a dir, amb exactament el mateix desplaçament del registre en qualsevol                            instant de l’execució.     També cal destacar que a la pila s’hi poden emmagatzemar valors temporals d’operacions,                          és a dir, que es possible utilitzar la pila per a que contingui valors intermedis d’operacions                                complexes. Aquests valors temporals, de la mateixa manera que les variable locals, poden                          ser referenciats per un desplaçament “negatiu” del registre EBP.    Bé, doncs ara que ja tenim el concepte de stack frame, context o marc de les funcions assolit,                                    anem a veure exactament de que es compon. Per a fer­ho, ho exemplificarem utilitzant el                              següent codi:    int add(int x, int y) {  int sum;  sum = x + y;  return sum;  }    func1() { add(3, 4); }    En aquest cas, tenim una funció add() que rep dos enters per paràmetre, defineix una                              variable local i retorna un valor. Si anéssim a mirar el codi ensamblador generat per aquest                                exemple, dins de la funció func1() veuríem que el primer pas per executar la funció add() és                                  col∙locar els paràmetres a la pila ordenats de manera inversa a la llista de paràmetres                              establerta i seguidament, executar la instrucció CALL. La instrucció CALL realitza dues                        coses: primerament emmagatzema a la pila el valor actual del registre EIP (recordem que                            significa extended instruction pointer i és el registre que apunta a la següent instrucció a                              executar) i en segon lloc, el modifica per a que apunti a la primera instrucció del codi de la                                      funció. El motiu pel qual s’emmagatzema el valor actual del registre EIP és perquè una                              vegada s’hagi finalitzat l’execució de la funció add() és necessari saber on ens havíem                            quedat per a poder reemprendre­ho des del mateix punt. Una vegada executada la instrucció                            CALL, ja estem llestos per emmagatzemar el valor del registre EBP a la següent posició                              disponible de la pila (apuntada pel registre ESP) i modificar el seu valor per a que apunti a                                    aquesta. A partir d’aquí, es col∙locaran a la pila les diferents variables locals de la funció a                                  mesura que l’execució del seu codi va progressant, i també, com hem dit anterioment,                              Ferran Verdés Castelló   Pàgina 6 de 11   
  7. 7.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014    s’emmagatzemarà informació de caràcter temporal, ja sigui per exemple valors intermedis                      d’operacions molt complexes. En l’exemple que ens correspon però, només s’hi                      emmagatzemarà una variable local que és sum. Tot aquest procediment es troba                        gràficament representat a la següent imatge:        Una vegada s’ha finalitzat l’execució de la funció queden dos procediments més a efectuar:                            retornar al mateix punt d’on s’havia fet la seva crida i retornar també, si és el cas, el valor de                                        retorn de la funció, que pel codi de l’exemple és un valor enter que conté la variable sum.    Anem per parts. Primerament, quin és el mecanisme que s’utilitza per retornar un valor d’una                              funció? Com veurem, aquest mecanisme no és sempre el mateix. Quan una funció retorna un                              valor d’un tipus que ocupa 4 bytes o menys (com a l’exemple en que es retorna un int),                                    aquest valor és emmagatzemat al registre EAX (de l’anglès, extended accumulator register,                          Ferran Verdés Castelló   Pàgina 7 de 11   
  8. 8.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014    el qual és usat també en les operacions aritmètiques). En canvi si el valor que s’ha de                                  retornar és d’un tipus que ocupa més de 4 bytes (i per tant, supera la mida del registre EAX),                                      aleshores es modifica el codi de la crida de la funció afegint un paràmetre addicional: un                                punter del tipus que es desitja retornar, el qual contindrà la direcció de memòria en la que el                                    valor de retorn hi ha de ser emmagatzemat. És a dir, per una funció de C++ com aquesta:    obj x = func1(3, 4);    L’estructura de la crida que en realitzat s’efectuarà, suposant que el tipus obj ocupa més de 4                                  bytes, és la següent:    func1(&x, 3, 4);    En aquest cas el valor de retorn ja serà salvat a la zona de memòria que li pertoca, però si el                                          valor de retorn s’ha emmagatzemat al registre EAX, el compilador és l’encarregat d’afegir el                            codi necessari per a que la funció a la qual es retorna (és a dir, la funció que ha fet la crida)                                            assigni aquest valor a la variable que li pertoca. En qualsevol cas, una vegada obtingut el                                valor de retorn, s’ha de reemprendre l’execució amb la següent instrucció que es troba a                              continuació de la crida a la subrutina. Però com? Que s’ha de fer per retornar al mateix punt?                                    Parem­ho a pensar, si ho recordem, tots aquells registres que hem volgut modificar els hem                              anat salvant a la pila abans d’alterar­los, com per exemple el registre EIP o el registre EBP.                                  Aleshores, el procediment que hem de seguir sembla lògic: recuperar tots aquests valors                          salvats i col∙locar­los de nou als registres. Aquest procediment es realitza gràcies al registre                            ESP, amb el qual anirem recorrent el stack frame de la funció actual des de la seva cima fins                                      a la seva base per tal de localitzar cadascún dels registres emmagatzemats. Anem a veureu                              gràficament:                    ( espai en blanc intencionat )      Ferran Verdés Castelló   Pàgina 8 de 11   
  9. 9.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014        Notem que a la cinquena vinyeta s’ha recuperat el registre EIP i això significa que ja s’ha                                  cedit el control d’execució a la funció que ha cridat a la subrutina, és a dir, que la següent                                      instrucció que s’executarà és aquella que es troba a continuació de la crida a la subrutina. No                                  obstant, com podem veure a la vinyeta, encara hi ha present a la pila una part del stack frame                                      de la funció add(), la qual correspon als arguments que ha rebut per paràmetre. El més                                probable és que aquests arguments no s’hagin d’utilitzar més. Aleshores, la funció que ha fet                              la crida s’ha d’encarrergar d’alliberar aquest espai ocupat pels paràmetres i ho farà                          senzillament sumant al registre ESP el número de bytes que ocupen, és a dir, que per a                                  l’exemple en qüestió seria executar la instrucció ESP + 8, degut a que els dos paràmetres                                ocupen 4 bytes cadascun. D’aquesta manera ja s’assoliria la representació de la vinyeta                          número 6.    Ferran Verdés Castelló   Pàgina 9 de 11   
  10. 10.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014      Intentem anar una mica més enllà. Pensem un moment amb la següent qüestió: és possible                              que la funció que havia realitzat la crida mantenia uns valors a diferents registres els quals                                poden haver estat modificats en execució de la subrutina? Hem refereixo a registres com el                              EAX (ja comentat), el ECX (de l’anglès, extended counter register) o bé el EDX (de l’anglès,                                extended data register), entre altres. La resposta, com podem imaginar, és que si. Així doncs,                              quin és el mecanisme que s’empra per evitar aquesta problemàtica? Una vegada més la pila                              és omnipotent. El procediment que duu a terme, en el cas que sigui necessari, és que el                                  compilador escriu les instruccions adequades per emmagatzemar el contingut dels diferents                      registres a la pila just abans de que s’hi col∙loquin els paràmetres de la funció. Aquest                                procediment és completament opcional i només es realitza per a que la subrutina pugui                            efectuar lliurement canvis als registres i que posteriorment es puguin restaurar amb els valors                            que mantenien just abans de realitzar la crida.    Ja hem vist que un dels trets més característics de la pila és que creix cap a les posicions                                      més baixes de la memòria i això, en altres paraules, significa que a mesura que ella va                                  creixent els valors dels diferents registres (en concret, aquells que apunten a alguna posició                            de la memòria que té assignada) van disminuint, com per exemple els registres ESP i EBP. I                                  si això sembla una mica rebuscat ara val més que ens preparem. Resulta que les diferents                                zones que s’hi defineixen per a guardar­hi informació (com la zona de paràmetres, variables                            locals, valors temporals, etcètera) es van omplin ocupant progressivament l’espai disponible                      en direcció a les posicions més altes de la memòria, és a dir, en la direcció oposada al                                    creixement de la pila. I encara més! Normalment s’utilitza el format little­endian per                          emmagatzemar aquestes dades, la qual cosa significa que el byte menys significatiu                        s’escriurà primer. Pròximament, intentaré escriure sobre aquest fet.    Finalment, per acabar, només hem queda comentar el que acostuma a passar amb els                            documents tècnics informàtics: tot allò que s’ha explicat en aquest document no pot ser                            interpretat fil per randa. La idea principal és aproximar­se a la realitat dels mecanismes que                              s’utilitzen però la gran varietat d’arquitectures existents i el ràpid progrés de la tecnologia fa                              que s’introdueixin canvis i millores constantment. Un clar exemple són els registres del                          processador, on hem comentat que actualment s’hi afegeix una ‘E’ de extended a l’inici del                              seu nom per indicar que tenen una mida de 32 bits enlloc dels 16 que tenien anteriorment.                                  Però això també ja és aigua passada! Aquesta ‘E’ que s’utilitza és per referir­se a registres                                d’arquitectures de 32 bits, ja que a les arquitectures de 64 bits ja tenen un nom diferent, el                                    qual comença per una ‘R’, com per exemple RSP pel registre Stack Pointer. Un altre                              exemple, és que el compilador reserva espais addicionals a la pila per portar a terme                              correctament les operacions encomanades, i a més, cada compilador i cada versió ho farà a                              la seva manera. De fet, algunes vegades es veu un espai entre les variables locals i els                                  registres EBP i EIP, però normalment no és res més que una qüestió d’alineació de                              direccions de memòria que el compilador gestiona internament. En qualsevol cas, resulta                        impossible representar tots aquests detalls sense incrementar la complexitat de comprensió                      dels conceptes i per aquest motiu s’opta per a realitzar representacions més esquemàtiques.    Ferran Verdés Castelló   Pàgina 10 de 11   
  11. 11.   Pila d’execució / Pila de llamadas / Call stack  13 de Desembre de 2014    Llicència del document   Per a fer ús d’aquest document, heu de respectar la següent llicència:      Llicència de Creative Commons Reconeixement­NoComercial­CompartirIgual 4.0  Internacional    Ferran Verdés Castelló   Pàgina 11 de 11   

×