Bir bilgisayar programı çalıştırıldığında ne olur?


180

Genel teoriyi biliyorum ama ayrıntılara sığamıyorum.

Bir programın bilgisayarın ikincil belleğinde olduğunu biliyorum. Program yürütmeye başladıktan sonra tamamen RAM'e kopyalanır. Daha sonra işlemci bir kerede birkaç talimatı (otobüsün boyutuna bağlıdır) alır, kayıtlara koyar ve yürütür.

Ayrıca bir bilgisayar programının iki tür bellek kullandığını biliyorum: yığın ve yığın, ayrıca bilgisayarın birincil belleğinin bir parçasıdır. Yığın, dinamik olmayan bellek ve dinamik bellek (örneğin, newC ++ 'daki işleçle ilgili her şey ) için kullanılır

Anlayamadığım şey, bu iki şeyin nasıl birleştiği. Yığın talimatların yürütülmesi için hangi noktada kullanılır? Talimatlar RAM'den, yığına, kayıtlara mı gidiyor?


43
Temel bir soru sormak için +1!
mkelley33

21
hmm ... bilirsiniz, bunun hakkında kitaplar yazarlar. Gerçekten işletim sistemi mimarisinin bu bölümünü SO'nun yardımıyla incelemek istiyor musunuz?
Andrey

1
Sorunun hafızayla ilgili doğasına ve C ++ referansına dayanan birkaç etiket ekledim, ancak iyi bir yanıtın Java veya C # konusunda bilgili birinden de gelebileceğini düşünüyorum.)
mkelley33

14
Oy verilmiş ve favorilere eklenmiş. Her zaman sormaktan çok korktum ...
Maxpm

2
"Onları kayıtlara koyar" terimi tam olarak doğru değildir. Çoğu işlemcide, kaydediciler yürütülebilir kodu değil, ara değerleri tutmak için kullanılır.

Yanıtlar:


161

Gerçekten sisteme bağlıdır, ancak sanal belleğe sahip modern işletim sistemleri , işlem görüntülerini yükleme ve belleği böyle bir şey tahsis etme eğilimindedir:

+---------+
|  stack  |  function-local variables, return addresses, return values, etc.
|         |  often grows downward, commonly accessed via "push" and "pop" (but can be
|         |  accessed randomly, as well; disassemble a program to see)
+---------+
| shared  |  mapped shared libraries (C libraries, math libs, etc.)
|  libs   |
+---------+
|  hole   |  unused memory allocated between the heap and stack "chunks", spans the
|         |  difference between your max and min memory, minus the other totals
+---------+
|  heap   |  dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
|   bss   |  Uninitialized global variables; must be in read-write memory area
+---------+
|  data   |  data segment, for globals and static variables that are initialized
|         |  (can further be split up into read-only and read-write areas, with
|         |  read-only areas being stored elsewhere in ROM on some systems)
+---------+
|  text   |  program code, this is the actual executable code that is running.
+---------+

Bu, birçok yaygın sanal bellek sisteminde genel işlem adres alanıdır. "Delik", toplam belleğinizin boyutu, diğer tüm alanların kapladığı alan eksi; bu, yığının büyümesi için geniş bir alan sağlar. Bu aynı zamanda "sanal" dır, yani bir çeviri tablosu aracılığıyla gerçek belleğinizle eşleşir ve gerçek bellekte herhangi bir yerde saklanabilir. Bu şekilde, bir işlemin başka bir işlemin belleğine erişmesini önlemek ve her işlemin tam bir sistemde çalıştığını düşünmesini sağlamak için yapılır.

Yığın ve yığının konumlarının bazı sistemlerde farklı bir sırada olabileceğini unutmayın (bkz. Win32 hakkında daha fazla ayrıntı için aşağıdaki Billy O'Neal'ın cevabına bakın).

Diğer sistemler çok farklı olabilir. Örneğin, DOS gerçek modda koştu ve programları çalıştırırken bellek ayırması çok farklı görünüyordu:

+-----------+ top of memory
| extended  | above the high memory area, and up to your total memory; needed drivers to
|           | be able to access it.
+-----------+ 0x110000
|  high     | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
|  upper    | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
|           | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+ 
|    DOS    | DOS permanent area, kept as small as possible, provided routines for display,
|  kernel   | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained 
|  vector   | the addresses of routines called when interrupts occurred.  e.g.
|  table    | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that 
|           | location to service the interrupt.
+-----------+ 0x0

DOS'un işletim sistemi belleğine doğrudan erişime izin verdiğini, herhangi bir koruma olmadan görebildiğini görebilirsiniz;

Bununla birlikte, işlem adres alanında programlar benzer görünme eğilimindeydi, sadece kod segmenti, veri segmenti, yığın, yığın segmenti vb. Olarak tanımlandılar ve biraz farklı bir şekilde haritalandılar. Ancak genel alanların çoğu hala oradaydı.

Programı ve gerekli paylaşılan kütüphaneleri belleğe yükledikten ve programın parçalarını doğru alanlara dağıttıktan sonra, işletim sistemi ana yönteminin bulunduğu her yerde işleminizi yürütmeye başlar ve programınız oradan devralır ve gerektiğinde sistem çağrıları yapar onlara ihtiyacı var.

Farklı sistemler (gömülü, her neyse), istifsiz sistemler, Harvard mimari sistemleri (kod ve veriler ayrı fiziksel bellekte tutulur), aslında BSS'yi salt okunur bellekte tutan (başlangıçta Ancak bu genel özür.


Dedin:

Ayrıca bir bilgisayar programının iki tür bellek kullandığını biliyorum: yığın ve yığın, ayrıca bilgisayarın birincil belleğinin bir parçasıdır.

"Yığın" ve "yığın" fiziksel olarak farklı bellek türleri yerine zorunlu olarak soyut kavramlardır.

Bir yığın sadece bir son giren ilk çıkar veri yapısıdır. X86 mimarisinde, aslında bir ofset kullanılarak uçtan rastgele adreslenebilir, ancak en yaygın işlevler sırasıyla öğeleri eklemek ve çıkarmak için PUSH ve POP'dur. Genellikle işlev yerel değişkenleri ("otomatik depolama" olarak adlandırılır), işlev bağımsız değişkenleri, dönüş adresleri vb. İçin kullanılır (daha fazlası aşağıdadır)

Bir "yığın" sadece talep üzerine tahsis edilebilir belleğin bir yığın için bir takma ad olduğunu ve (Doğrudan herhangi bir konuma erişebilir, anlam) rastgele ele alınmaktadır. Genellikle çalışma zamanında ayırdığınız veri yapıları için kullanılır (C ++ 'da newve deletevemalloc C ve arkadaşlarınız vb.).

X86 mimarisindeki yığın ve yığın, fiziksel olarak sistem belleğinizde (RAM) bulunur ve sanal bellek ayırma yoluyla yukarıda açıklandığı gibi işlem adres alanına eşlenir.

Kayıt (hala x 86) İşlemci talimatlara bağlı olarak, fiziksel olarak işlemci (RAM aksine) içinde bulunan ve metin alanı, işlemci tarafından yüklenir (ve aynı zamanda bellek veya diğer yerlerde başka yüklenebilir bu aslında yürütülür). Aslında, çok farklı amaçlar için kullanılan çok küçük, çok hızlı çip üzeri bellek konumlarıdır.

Kayıt düzeni, mimariye oldukça bağımlıdır (aslında, kayıtlar, komut seti ve bellek düzeni / tasarımı, "mimari" ile kastedilen şeydir) ve bu yüzden üzerine genişlemeyeceğim, ancak bir montaj dil kursu daha iyi anlamak için.


Senin sorun:

Yığın talimatların yürütülmesi için hangi noktada kullanılır? Talimatlar RAM'den, yığına, kayıtlara mı gidiyor?

Yığın (bunları kullanan ve kullanan sistemlerde / dillerde) çoğunlukla şu şekilde kullanılır:

int mul( int x, int y ) {
    return x * y;       // this stores the result of MULtiplying the two variables 
                        // from the stack into the return value address previously 
                        // allocated, then issues a RET, which resets the stack frame
                        // based on the arg list, and returns to the address set by
                        // the CALLer.
}

int main() {
    int x = 2, y = 3;   // these variables are stored on the stack
    mul( x, y );        // this pushes y onto the stack, then x, then a return address,
                        // allocates space on the stack for a return value, 
                        // then issues an assembly CALL instruction.
}

Bunun gibi basit bir program yazın ve ardından derlemeye ( gcc -S foo.cGCC'ye erişiminiz varsa) derleyin ve bir göz atın. Montajı takip etmek oldukça kolaydır. Yığın, işlev yerel değişkenleri ve işlevleri çağırmak için argümanlarını ve dönüş değerlerini saklamak için kullanıldığını görebilirsiniz. Bu yüzden böyle bir şey yaptığınızda:

f( g( h( i ) ) ); 

Bunların hepsi sırayla çağrılır. Kelimenin tam anlamıyla bir işlev çağrıları ve argümanları yığını oluşturur, bunları yürütür ve sonra geri sarıldığında (veya yukarı;) patlar. Bununla birlikte, yukarıda belirtildiği gibi, yığın (x86'da) aslında işlem bellek alanınızda (sanal bellekte) bulunur ve bu nedenle doğrudan manipüle edilebilir; yürütme sırasında ayrı bir adım değildir (veya en azından sürece diktir).

FYI, yukarıdaki C ++ tarafından da kullanılan C çağırma kuralıdır . Diğer diller / sistemler, bağımsız değişkenleri yığına farklı bir sırayla aktarabilir ve bazı diller / platformlar yığınlar bile kullanmaz ve farklı şekillerde devam eder.

Ayrıca, bunların gerçek C kodu yürütme satırları olmadığını unutmayın. Derleyici bunları yürütülebilir dosyadaki makine dili yönergelerine dönüştürdü. Daha sonra (genellikle) METİN alanından CPU boru hattına, daha sonra CPU kayıtlarına kopyalanır ve oradan yürütülür. [Bu yanlıştı. Aşağıdaki Ben Voigt düzeltmesine bakın.]


4
üzgünüm, ama iyi bir kitap tavsiye daha iyi bir cevap olurdu, IMO
Andrey

13
Evet, "RTFM" her zaman daha iyidir.
Sdaz MacSkibbons

56
@Andrey: belki de bu yorumu "ayrıca, iyi-kitap- önerinizi okumak isteyebilirsiniz" olarak değiştirmeniz gerekir . .. "belki de gönderiyi moderatörlerin dikkatini çekmek için işaretlemeyi veya en azından sizin fikrinizin neden herkes için önemli olması gerektiğine dair bir açıklama sunmayı düşünmelisiniz.
mkelley33

2
Mükemmel cevap. Kesinlikle benim için bazı şeyleri temizledi!
Maxpm

2
@Mikael: Uygulamaya bağlı olarak, zorunlu önbelleklemeniz olabilir, bu durumda veriler bellekten her zaman okunur, tüm bir önbellek satırı okunur ve önbellek doldurulur. Veya önbellek yöneticisine verilere yalnızca bir kez ihtiyaç duyulacağına dair bir ipucu vermek mümkün olabilir, bu nedenle önbelleğe kopyalamak yardımcı olmaz. Bu okumak için. Yazma için, DMA denetleyicilerinin verileri ne zaman okuyabileceğini etkileyen geri yazma ve yazma önbellekleri vardır ve daha sonra, her biri kendi önbelleğine sahip birden çok işlemciyle uğraşmak için bir dizi önbellek tutarlılık protokolü vardır. Bu gerçekten kendi Q'unu hak ediyor
Ben Voigt

61

Sdaz çok kısa bir sürede dikkate değer sayıda yukarı oy aldı, ancak ne yazık ki talimatların CPU'da nasıl hareket ettiği hakkında yanlış bir anlayış sürdürüyor.

Soru sordu:

Talimatlar RAM'den, yığına, kayıtlara mı gidiyor?

Sdaz şunları söyledi:

Ayrıca, bunların gerçek C kodu yürütme satırları olmadığını unutmayın. Derleyici bunları yürütülebilir dosyadaki makine dili yönergelerine dönüştürdü. Daha sonra (genellikle) METİN alanından CPU boru hattına, daha sonra CPU kayıtlarına kopyalanır ve oradan yürütülür.

Ama bu yanlış. Kendini değiştiren özel kod durumu dışında, talimatlar asla veriyoluna girmez. Ve veri yolundan yürütülemezler, yürütülemezler.

X86 CPU kayıtları şunlardır:

  • Genel kayıtlar EAX EBX ECX EDX

  • Segment kayıtları CS DS ES FS GS SS

  • Endeks ve işaretçiler ESI EDI EBP EIP ESP

  • Gösterge EFLAGS

Bazı kayan nokta ve SIMD kayıtları da vardır, ancak bu tartışmanın amaçları için, bunları CPU değil, yardımcı işlemcinin bir parçası olarak sınıflandıracağız. CPU içindeki bellek yönetimi biriminin de kendine ait bazı kayıtları vardır, bunu ayrı bir işlem birimi olarak ele alacağız.

Bu kayıtların hiçbiri yürütülebilir kod için kullanılmaz. EIPtalimatın kendisini değil, yürütme talimatının adresini içerir.

Talimatlar, CPU'daki verilerden tamamen farklı bir yoldan geçer (Harvard mimarisi). Mevcut tüm makineler CPU içindeki Harvard mimarisidir. Bu günlerin çoğu aynı zamanda önbellekteki Harvard mimarisidir. x86 (ortak masaüstü makineniz) ana bellekte Von Neumann mimarisidir, yani RAM'de veri ve kod birbirine karışmıştır. CPU'nun içinde neler olduğu hakkında konuştuğumuz için, bu konunun yanında.

Bilgisayar mimarisinde öğretilen klasik dizi getirme-kod çözme-yürütmedir. Bellek denetleyicisi adreste depolanan talimatı arar EIP. Komutun bitleri, işlemcideki farklı çoklayıcılar için tüm kontrol sinyallerini oluşturmak için bazı kombinasyonel mantıktan geçer. Ve bazı döngülerden sonra, aritmetik mantık birimi hedefe saat yönünde gelen bir sonuca ulaşır. Sonra bir sonraki talimat getirilir.

Modern bir işlemcide işler biraz farklı çalışır. Gelen her talimat bir dizi mikrokod komutuna çevrilir. Bu, boru hattını etkinleştirir, çünkü ilk mikro-yapı tarafından kullanılan kaynaklara daha sonra gerek duyulmaz, böylece bir sonraki talimattan ilk mikro-yapı üzerinde çalışmaya başlayabilirler.

Üstelik, terminoloji biraz karışıktır çünkü kayıt D-flipflopların bir koleksiyonu için bir elektrik mühendisliği terimidir. Ve talimatlar (veya özellikle mikro talimatlar) böyle bir D-flipflop koleksiyonunda geçici olarak çok iyi saklanabilir. Ancak bu, bir bilgisayar bilimcisi veya yazılım mühendisi veya fabrika sahibi geliştirici kayıt terimini kullandığında kastedilen şey değildir . Yukarıda listelenen veri yolu kayıtları anlamına gelir ve bunlar kod taşımak için kullanılmaz.

ARM, MIPS, Alpha, PowerPC gibi diğer CPU mimarileri için adlar ve veriyolu kayıtlarının sayısı değişiklik gösterir, ancak hepsi ALU'dan geçmeden talimatları yürütür.


Açıklama için teşekkürler. Eklemekte tereddüt ettim, çünkü ona yakından aşina değilim, ama başka birinin isteği üzerine yaptım.
Sdaz MacSkibbons

s / ARM / RAM / in "anlamı veri ve kod ARM'de birbirine karışmıştır". Sağ?
Bjarke Freund-Hansen

@bjarkef: İlk seferinde evet, ikincisi değil. Ben düzeltirim.
Ben Voigt

17

Bir işlem yürütürken belleğin tam düzeni tamamen kullandığınız platforma bağlıdır. Aşağıdaki test programını göz önünde bulundurun:

#include <stdlib.h>
#include <stdio.h>

int main()
{
    int stackValue = 0;
    int *addressOnStack = &stackValue;
    int *addressOnHeap = malloc(sizeof(int));
    if (addressOnStack > addressOnHeap)
    {
        puts("The stack is above the heap.");
    }
    else
    {
        puts("The heap is above the stack.");
    }
}

Windows NT'de (ve çocukları), bu program genellikle aşağıdakileri üretecektir:

Yığın yığının üstünde

POSIX kutularında şunları söyleyecektir:

Yığın yığının üstünde

UNIX bellek modeli @Sdaz MacSkibbons tarafından oldukça iyi açıklanmıştır, bu yüzden burada tekrar etmeyeceğim. Ama bu tek hafıza modeli değil. POSIX'in bu modeli gerektirmesinin nedeni sbrk sistem çağrısıdır. Temel olarak, bir POSIX kutusunda, daha fazla bellek elde etmek için, bir işlem sadece Çekirdeğe "delik" ile "yığın" arasındaki ayırıcıyı "delik" bölgesine taşımasını söyler. Belleği işletim sistemine geri döndürmenin bir yolu yoktur ve işletim sisteminin kendisi yığınızı yönetmez. C çalışma zamanı kitaplığınız bunu sağlamalıdır (malloc aracılığıyla).

Bunun POSIX ikili dosyalarında kullanılan kod türü için de etkileri vardır. POSIX kutuları (neredeyse evrensel olarak) ELF dosya biçimini kullanır. Bu formatta, işletim sistemi farklı ELF dosyalarındaki kütüphaneler arasındaki iletişimden sorumludur. Bu nedenle, tüm kütüphaneler konumdan bağımsız kod kullanır (yani, kodun kendisi farklı bellek adreslerine yüklenebilir ve yine de çalışabilir) ve kütüphaneler arasındaki tüm çağrılar, denetimin çaprazlama için nereye atlaması gerektiğini bulmak için bir arama tablosundan geçirilir. kütüphane fonksiyon çağrıları. Bu, bazı ek yükler ekler ve kitaplıklardan biri arama tablosunu değiştirirse kullanılabilir.

Windows'un bellek modeli farklıdır çünkü kullandığı kod türü farklıdır. Windows, kodu konuma bağlı biçimde bırakan PE dosya biçimini kullanır. Yani, kod tam olarak sanal belleğe kodun yüklendiği yere bağlıdır. PE spesifikasyonunda, işletim sisteminize programınız çalıştığında kitaplığın veya yürütülebilir dosyanın tam olarak nerede bellekte eşleşmek istediğini bildiren bir bayrak vardır. Bir program veya kitaplık tercih edilen adreste yüklenemezse, Windows yükleyicisi Rebasekütüphane / çalıştırılabilir dosya - temel olarak, konuma bağlı kodu yeni konumlara işaret edecek şekilde taşır - bu, arama tabloları gerektirmez ve üzerine yazılacak arama tablosu olmadığından kullanılamaz. Ne yazık ki, bu Windows yükleyicide çok karmaşık bir uygulama gerektirir ve bir görüntünün yeniden temellenmesi gerekiyorsa önemli ölçüde başlangıç ​​zamanı yükü vardır. Büyük ticari yazılım paketleri, yeniden temellendirmeyi önlemek için genellikle farklı adreslerde başlamak üzere kitaplıklarını değiştirir; Windows kendisi bunu kendi kütüphaneleri ile yapar (örn. ntdll.dll, kernel32.dll, psapi.dll, vb. - varsayılan olarak farklı başlangıç ​​adresleri vardır)

Windows'da sanal bellek, VirtualAlloc'a çağrı yapılarak sistemden alınır ve VirtualFree aracılığıyla sisteme geri gönderilir (Tamam, teknik olarak VirtualAlloc NtAllocateVirtualMemory'ye çiftlikler gönderir, ancak bu bir uygulama detayıdır) (Belleğin yapamadığı POSIX ile karşılaştırınız) geri alınabilir). Bu işlem yavaştır (ve IIRC, fiziksel sayfa boyutunda parçalar halinde ayırmanızı gerektirir; genellikle 4kb veya daha fazla). Windows ayrıca, C çalışma zamanının (yani mallocve arkadaşlarının) genellikle uygulandığı , Windows'un kendisinin bir parçası olarak dahil edilen RtlHeap olarak bilinen bir kitaplığın parçası olarak kendi yığın işlevlerini (HeapAlloc, HeapFree, vb.) Sağlar .

Windows ayrıca eski 80386'larla uğraşmak zorunda olduğu günlerden birkaç eski bellek ayırma API'sine sahiptir ve bu işlevler artık RtlHeap'ın üzerine inşa edilmiştir. Windows'da bellek yönetimini denetleyen çeşitli API'ler hakkında daha fazla bilgi için şu MSDN makalesine bakın: http://msdn.microsoft.com/en-us/library/ms810627 .

Bunun, Windows'ta tek bir işlemin (ve genellikle) birden fazla yığını olduğu anlamına geldiğini unutmayın. (Genellikle, paylaşılan her kitaplık kendi yığınını oluşturur.)

(Bu bilgilerin çoğu Robert Seacord'un "C ve C ++ 'da Güvenli Kodlama" dan alınmıştır)


Harika bilgi, teşekkürler! Umut "user487117" sonunda aslında geri geliyor. :-)
Sdaz MacSkibbons

5

Yığın

X86 mimarisinde CPU, yazmaçlarla işlemleri yürütür. Yığın yalnızca kolaylık sağlamak amacıyla kullanılır. Bir alt programı veya bir sistem işlevini çağırmadan önce kayıtlarınızın içeriğini yığmak üzere kaydedebilir ve daha sonra işleminize kaldığınız yerden devam etmek için geri yükleyebilirsiniz. (Yığın olmadan manuel olarak yapabilirsiniz, ancak sık kullanılan bir işlevdir, bu nedenle CPU desteği vardır). Ancak PC'de yığın olmadan hemen hemen her şeyi yapabilirsiniz.

Örneğin bir tamsayı çarpımı:

MUL BX

AX kaydını BX kaydı ile çarpar. (Sonuç DX ve AX, daha yüksek bitleri içeren DX olacaktır).

Yığın tabanlı makineler (JAVA VM gibi), temel işlemleri için yığını kullanır. Yukarıdaki çarpma:

DMUL

Bu, yığının üstünden iki değer çıkarır ve tem ile çarpar, ardından sonucu yığına geri iter. Bu tür makineler için yığın esastır.

Bazı üst düzey programlama dilleri (C ve Pascal gibi) parametreleri daha sonra işlevlere aktarmak için bu daha sonraki yöntemi kullanır: parametreler yığına soldan sağa sırayla itilir ve işlev gövdesi tarafından açılır ve dönüş değerleri geri itilir. (Bu, derleyici üreticilerinin X86'nın yığını kullanma şeklini kötüye kullanma seçeneğidir).

Öbek

Yığın, sadece derleyicilerin alanında var olan başka bir kavramdır. Değişkenlerinizin arkasındaki hafızayı ele geçirmenin acısını alır, ancak CPU veya işletim sisteminin bir işlevi değildir, sadece işletim sistemi tarafından verilen bellek bloğunu temizlemenin bir seçimidir. İsterseniz bunu çok sayıda yapabilirsiniz.

Sistem kaynaklarına erişme

İşletim sistemi, işlevlerine nasıl erişebileceğiniz genel bir arayüze sahiptir. DOS parametreleri CPU kayıtlarında geçirilir. Windows yığını OS işlevleri (Windows API) parametrelerini iletmek için kullanır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.