Apa yang terjadi ketika program komputer berjalan?

180

Saya tahu teori umum tetapi saya tidak bisa memasukkan rinciannya.

Saya tahu bahwa sebuah program berada di memori sekunder komputer. Setelah program mulai dieksekusi sepenuhnya disalin ke RAM. Kemudian prosesor mengeluarkan beberapa instruksi (tergantung pada ukuran bus) sekaligus, menempatkannya dalam register dan menjalankannya.

Saya juga tahu bahwa program komputer menggunakan dua jenis memori: tumpukan dan tumpukan, yang juga merupakan bagian dari memori utama komputer. Tumpukan digunakan untuk memori non-dinamis, dan tumpukan untuk memori dinamis (misalnya, semua yang terkait dengan newoperator di C ++)

Yang tidak saya mengerti adalah bagaimana kedua hal itu terhubung. Pada titik apa tumpukan digunakan untuk pelaksanaan instruksi? Instruksi pergi dari RAM, ke tumpukan, ke register?

gaijinco
sumber
43
+1 untuk mengajukan pertanyaan mendasar!
mkelley33
21
hmm ... Anda tahu, mereka menulis buku tentang itu. Apakah Anda benar-benar ingin mempelajari bagian arsitektur OS ini dengan bantuan SO?
Andrey
1
Saya menambahkan beberapa tag berdasarkan sifat pertanyaan yang terkait dengan memori, dan referensi ke C ++, meskipun saya pikir jawaban yang baik juga bisa datang dari seseorang yang berpengetahuan di Jawa atau C #!)
mkelley33
14
Terpilih dan difavoritkan. Saya selalu terlalu takut untuk bertanya ...
Maks.
2
Istilah "menempatkan mereka dalam register" tidak tepat. Pada kebanyakan prosesor, register digunakan untuk menyimpan nilai menengah, bukan kode yang dapat dieksekusi.

Jawaban:

161

Itu benar-benar tergantung pada sistem, tetapi OS modern dengan memori virtual cenderung memuat gambar proses mereka dan mengalokasikan memori seperti ini:

+---------+
|  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.
+---------+

Ini adalah ruang alamat proses umum pada banyak sistem memori virtual umum. "Lubang" adalah ukuran total memori Anda, dikurangi ruang yang digunakan oleh semua area lainnya; ini memberi banyak ruang bagi tumpukan untuk tumbuh. Ini juga "virtual", artinya memetakan ke memori Anda yang sebenarnya melalui tabel terjemahan, dan dapat disimpan di lokasi mana pun di memori yang sebenarnya. Hal ini dilakukan dengan cara ini untuk melindungi satu proses dari mengakses memori proses lain, dan membuat setiap proses berpikir itu berjalan pada sistem yang lengkap.

Perhatikan bahwa posisi, misalnya, tumpukan dan tumpukan mungkin berada dalam urutan berbeda pada beberapa sistem (lihat jawaban Billy O'Neal di bawah ini untuk detail lebih lanjut tentang Win32).

Sistem lain bisa sangat berbeda. DOS, misalnya, berjalan dalam mode nyata , dan alokasi memorinya ketika menjalankan program tampak jauh berbeda:

+-----------+ 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

Anda dapat melihat bahwa DOS memungkinkan akses langsung ke memori sistem operasi, tanpa perlindungan, yang berarti bahwa program ruang pengguna secara umum dapat langsung mengakses atau menimpa apa pun yang mereka sukai.

Namun, dalam ruang alamat proses, program cenderung terlihat serupa, hanya saja mereka digambarkan sebagai segmen kode, segmen data, heap, segmen stack, dll., Dan dipetakan sedikit berbeda. Tetapi sebagian besar area umum masih ada.

Setelah memuat program dan lib bersama yang diperlukan ke dalam memori, dan mendistribusikan bagian-bagian program ke area yang tepat, OS mulai menjalankan proses Anda di mana pun metode utamanya berada, dan program Anda mengambil alih dari sana, membuat panggilan sistem seperlunya saat itu membutuhkan mereka.

Sistem yang berbeda (tertanam, apa pun) mungkin memiliki arsitektur yang sangat berbeda, seperti sistem tanpa tumpukan, sistem arsitektur Harvard (dengan kode dan data disimpan dalam memori fisik terpisah), sistem yang benar-benar menyimpan BSS dalam memori hanya baca (awalnya ditetapkan oleh programmer), dll. Tapi inilah intinya.


Kamu berkata:

Saya juga tahu bahwa program komputer menggunakan dua jenis memori: tumpukan dan tumpukan, yang juga merupakan bagian dari memori utama komputer.

"Stack" dan "heap" hanyalah konsep abstrak, daripada "jenis" memori yang berbeda secara fisik.

Sebuah tumpukan hanyalah struktur data pertama-out terakhir di,. Dalam arsitektur x86, itu sebenarnya dapat diatasi secara acak dengan menggunakan offset dari ujung, tetapi fungsi yang paling umum adalah PUSH dan POP untuk menambah dan menghapus item dari masing-masing. Ini biasanya digunakan untuk variabel fungsi-lokal (disebut "penyimpanan otomatis"), argumen fungsi, alamat pengirim, dll. (Selengkapnya di bawah)

Sebuah "tumpukan" hanya nama panggilan untuk sepotong memori yang dapat dialokasikan pada permintaan, dan ditujukan secara acak (yang berarti, Anda dapat mengakses setiap lokasi di langsung). Ini biasanya digunakan untuk struktur data yang Anda alokasikan saat runtime (dalam C ++, menggunakan newdan delete, dan mallocdan teman-teman di C, dll).

Tumpukan dan tumpukan, pada arsitektur x86, keduanya secara fisik berada di memori sistem Anda (RAM), dan dipetakan melalui alokasi memori virtual ke dalam ruang alamat proses seperti yang dijelaskan di atas.

The register (masih pada x86), secara fisik berada di dalam prosesor (sebagai lawan RAM), dan dimuat oleh prosesor, dari daerah TEXT (dan juga dapat diambil dari tempat lain dalam memori atau tempat lain tergantung pada instruksi CPU yang sebenarnya dieksekusi). Mereka pada dasarnya hanya sangat kecil, lokasi memori on-chip yang sangat cepat yang digunakan untuk sejumlah tujuan berbeda.

Tata letak register sangat tergantung pada arsitektur (pada kenyataannya, register, set instruksi, dan tata letak / desain memori, persis apa yang dimaksud dengan "arsitektur"), jadi saya tidak akan memperluasnya, tetapi menyarankan Anda untuk mengambil kursus bahasa assembly untuk memahami mereka dengan lebih baik.


Pertanyaanmu:

Pada titik apa tumpukan digunakan untuk pelaksanaan instruksi? Instruksi pergi dari RAM, ke tumpukan, ke register?

Tumpukan (dalam sistem / bahasa yang memiliki dan menggunakannya) paling sering digunakan seperti ini:

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.
}

Tulis program sederhana seperti ini, dan kemudian kompilasi untuk perakitan ( gcc -S foo.cjika Anda memiliki akses ke GCC), dan lihatlah. Perakitan ini cukup mudah diikuti. Anda dapat melihat bahwa stack digunakan untuk variabel fungsi lokal, dan untuk memanggil fungsi, menyimpan argumen mereka dan mengembalikan nilai. Ini juga mengapa ketika Anda melakukan sesuatu seperti:

f( g( h( i ) ) ); 

Semua ini dipanggil pada gilirannya. Ini benar-benar membangun setumpuk pemanggilan fungsi dan argumen mereka, mengeksekusi mereka, dan kemudian memunculkannya saat angin turun kembali (atau naik;). Namun, seperti yang disebutkan di atas, tumpukan (pada x86) sebenarnya berada di ruang memori proses Anda (dalam memori virtual), dan sehingga dapat dimanipulasi secara langsung; itu bukan langkah terpisah selama eksekusi (atau setidaknya orthogonal ke proses).

FYI, di atas adalah konvensi pemanggilan C , juga digunakan oleh C ++. Bahasa / sistem lain dapat mendorong argumen ke tumpukan dalam urutan yang berbeda, dan beberapa bahasa / platform bahkan tidak menggunakan tumpukan, dan melakukannya dengan cara yang berbeda.

Perhatikan juga, ini bukan baris aktual dari eksekusi kode C. Kompiler telah mengubahnya menjadi instruksi bahasa mesin di executable Anda. Mereka kemudian (umumnya) disalin dari area TEXT ke dalam pipeline CPU, kemudian ke register CPU, dan dieksekusi dari sana. [Ini tidak benar. Lihat koreksi Ben Voigt di bawah ini.]

Sdaz MacSkibbons
sumber
4
maaf, tetapi rekomendasi buku yang bagus akan menjadi jawaban yang lebih baik, IMO
Andrey
13
Ya, "RTFM" selalu lebih baik.
Sdaz MacSkibbons
56
@ Andrew: mungkin Anda harus mengubah komentar itu menjadi "juga, Anda mungkin ingin membaca rekomendasi buku bagus Anda " Saya mengerti bahwa pertanyaan semacam ini perlu penyelidikan lebih lanjut, tetapi setiap kali Anda harus memulai komentar dengan "maaf tapi. .. "mungkin Anda harus benar-benar mempertimbangkan menandai pos untuk perhatian moderator atau setidaknya menawarkan penjelasan mengapa pendapat Anda penting bagi siapa pun.
mkelley33
2
Jawaban yang sangat bagus. Itu jelas membuat beberapa hal bagi saya!
Maks.
2
@Mikael: Bergantung pada implementasinya, Anda mungkin memiliki caching wajib, dalam hal ini setiap saat data dibaca dari memori, seluruh baris cache dibaca dan cache diisi. Atau mungkin memberikan petunjuk kepada pengelola cache bahwa data hanya akan diperlukan sekali, jadi menyalinnya ke cache tidak akan membantu. Itu untuk dibaca. Untuk write ada cache write-back dan write-through, yang mempengaruhi ketika pengontrol DMA dapat membaca data, dan kemudian ada sejumlah protokol koherensi cache untuk menangani beberapa prosesor yang masing-masing memiliki cache sendiri. Ini benar-benar layak dengan Q.
Ben Voigt
61

Sdaz mendapatkan banyak sekali upvotes dalam waktu yang sangat singkat, tetapi sayangnya terus melanggengkan kesalahpahaman tentang bagaimana instruksi bergerak melalui CPU.

Pertanyaan yang diajukan:

Instruksi pergi dari RAM, ke tumpukan, ke register?

Sdaz berkata:

Perhatikan juga, ini bukan baris aktual dari eksekusi kode C. Kompiler telah mengubahnya menjadi instruksi bahasa mesin di executable Anda. Mereka kemudian (umumnya) disalin dari area TEXT ke dalam pipeline CPU, kemudian ke register CPU, dan dieksekusi dari sana.

Tapi ini salah. Kecuali untuk kasus khusus kode modifikasi diri, instruksi tidak pernah memasukkan datapath. Dan mereka tidak, tidak bisa, dieksekusi dari datapath.

The register x86 CPU adalah:

  • Daftar umum EAX EBX ECX EDX

  • Register segmen CS DS ES FS GS SS

  • Indeks dan petunjuk ESI EDI EBP EIP ESP

  • Indikator EFLAGS

Ada juga beberapa floating-point dan register SIMD, tetapi untuk keperluan diskusi ini kita akan mengklasifikasikan mereka sebagai bagian dari coprocessor dan bukan CPU. Unit manajemen memori di dalam CPU juga memiliki beberapa register sendiri, kami akan memperlakukannya sebagai unit pemrosesan yang terpisah.

Tidak satu pun dari register ini digunakan untuk kode yang dapat dieksekusi. EIPberisi alamat instruksi pelaksanaan, bukan instruksi itu sendiri.

Instruksi melalui jalur yang sama sekali berbeda dalam CPU dari data (arsitektur Harvard). Semua mesin saat ini adalah arsitektur Harvard di dalam CPU. Sebagian besar hari ini juga arsitektur Harvard di cache. x86 (mesin desktop umum Anda) adalah arsitektur Von Neumann di memori utama, yang berarti data dan kode saling terkait dalam RAM. Itu intinya, karena kita sedang berbicara tentang apa yang terjadi di dalam CPU.

Urutan klasik yang diajarkan dalam arsitektur komputer adalah fetch-decode-execute. Pengontrol memori mencari instruksi yang tersimpan di alamat EIP. Bit-bit instruksi melewati beberapa logika kombinasional untuk membuat semua sinyal kontrol untuk multiplexer berbeda dalam prosesor. Dan setelah beberapa siklus, unit logika aritmatika tiba pada hasil, yang di-clock ke tujuan. Kemudian instruksi selanjutnya diambil.

Pada prosesor modern, semuanya bekerja sedikit berbeda. Setiap instruksi yang masuk diterjemahkan ke dalam serangkaian instruksi mikrokode. Ini memungkinkan pipelining, karena sumber daya yang digunakan oleh microinstruction pertama tidak diperlukan nanti, sehingga mereka dapat mulai bekerja pada microinstruction pertama dari instruksi selanjutnya.

Di atas itu, terminologi sedikit bingung karena register adalah istilah teknik elektrik untuk koleksi D-flipflops. Dan instruksi (atau terutama microinstructions) dapat disimpan sementara dalam koleksi D-flipflops. Tapi ini bukan apa yang dimaksud ketika seorang ilmuwan komputer atau insinyur perangkat lunak atau pengembang run-of-the-mill menggunakan istilah register . Mereka berarti register datapath seperti yang tercantum di atas, dan ini tidak digunakan untuk mengangkut kode.

Nama dan jumlah register data bervariasi untuk arsitektur CPU lainnya, seperti ARM, MIPS, Alpha, PowerPC, tetapi semuanya menjalankan instruksi tanpa melewati mereka melalui ALU.

Ben Voigt
sumber
Terimakasih atas klarifikasinya. Saya ragu untuk menambahkan itu karena saya tidak akrab dengannya, tetapi melakukannya atas permintaan orang lain.
Sdaz MacSkibbons
s / ARM / RAM / in "yang berarti data dan kode disatukan dalam ARM". Baik?
Bjarke Freund-Hansen
@ bbjef: Pertama kali ya, tapi bukan yang kedua. Saya akan memperbaikinya.
Ben Voigt
17

Tata letak memori yang tepat saat proses dieksekusi sepenuhnya tergantung pada platform yang Anda gunakan. Pertimbangkan program uji berikut:

#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.");
    }
}

Pada Windows NT (dan ini anak-anak), program ini biasanya akan menghasilkan:

Tumpukan berada di atas tumpukan

Pada kotak POSIX, ia akan mengatakan:

Tumpukan berada di atas tumpukan

Model memori UNIX dijelaskan dengan sangat baik di sini oleh @Sdaz MacSkibbons, jadi saya tidak akan mengulanginya di sini. Tapi itu bukan satu-satunya model memori. Alasan POSIX memerlukan model ini adalah panggilan sistem sbrk . Pada dasarnya, pada kotak POSIX, untuk mendapatkan lebih banyak memori, sebuah proses hanya memberitahu Kernel untuk memindahkan pembagi antara "lubang" dan "tumpukan" lebih jauh ke wilayah "lubang". Tidak ada cara untuk mengembalikan memori ke sistem operasi, dan sistem operasi itu sendiri tidak mengelola tumpukan Anda. Pustaka runtime C Anda harus menyediakannya (via malloc).

Ini juga memiliki implikasi untuk jenis kode yang sebenarnya digunakan dalam binari POSIX. Kotak POSIX (hampir secara universal) menggunakan format file ELF. Dalam format ini, sistem operasi bertanggung jawab untuk komunikasi antar perpustakaan dalam file ELF yang berbeda. Oleh karena itu, semua perpustakaan menggunakan kode posisi-independen (Yaitu, kode itu sendiri dapat dimuat ke alamat memori yang berbeda dan masih beroperasi), dan semua panggilan antara perpustakaan dilewatkan melalui tabel pencarian untuk mencari tahu di mana kontrol perlu melompat untuk menyeberang panggilan fungsi perpustakaan. Ini menambahkan beberapa overhead dan dapat dieksploitasi jika salah satu pustaka mengubah tabel pencarian.

Model memori Windows berbeda karena jenis kode yang digunakan berbeda. Windows menggunakan format file PE, yang meninggalkan kode dalam format tergantung posisi. Artinya, kode tergantung di mana tepatnya dalam memori virtual kode tersebut dimuat. Ada bendera dalam spesifikasi PE yang memberitahu OS di mana tepatnya di memori perpustakaan atau executable ingin dipetakan ketika program Anda berjalan. Jika suatu program atau pustaka tidak dapat dimuat pada alamat yang diinginkan, pemuat Windows harus melakukan rebaselibrary / executable - pada dasarnya, ini memindahkan kode yang tergantung pada posisi untuk menunjuk pada posisi baru - yang tidak memerlukan tabel pencarian dan tidak dapat dieksploitasi karena tidak ada tabel pencarian untuk ditimpa. Sayangnya, ini membutuhkan implementasi yang sangat rumit di Windows loader, dan memang memiliki overhead waktu startup yang cukup jika sebuah gambar perlu direbase. Paket perangkat lunak komersial besar sering memodifikasi perpustakaan mereka untuk memulai dengan sengaja di alamat yang berbeda untuk menghindari rebasing; windows sendiri melakukan ini dengan pustaka sendiri (mis. ntdll.dll, kernel32.dll, psapi.dll, dll. - semuanya memiliki alamat awal yang berbeda secara default)

Pada Windows, memori virtual diperoleh dari sistem melalui panggilan ke VirtualAlloc , dan itu dikembalikan ke sistem melalui VirtualFree (Oke, secara teknis VirtualAlloc membuka ke NtAllocateVirtualMemory, tapi itu detail implementasi) (Bandingkan ini dengan POSIX, di mana memori tidak bisa direklamasi). Proses ini lambat (dan IIRC, mengharuskan Anda mengalokasikan dalam potongan berukuran halaman fisik; biasanya 4kb atau lebih). Windows juga menyediakan fungsi tumpukan itu sendiri (HeapAlloc, HeapFree, dll.) Sebagai bagian dari perpustakaan yang dikenal sebagai RtlHeap, yang dimasukkan sebagai bagian dari Windows itu sendiri, di mana runtime C (yaitu, mallocdan teman-teman) biasanya diimplementasikan.

Windows juga memiliki beberapa API alokasi memori lama dari masa ketika harus berurusan dengan 80386s lama, dan fungsi-fungsi ini sekarang dibangun di atas RtlHeap. Untuk informasi lebih lanjut tentang berbagai API yang mengontrol manajemen memori di Windows, lihat artikel MSDN ini: http://msdn.microsoft.com/en-us/library/ms810627 .

Perhatikan juga bahwa ini berarti pada Windows sebuah proses tunggal dan biasanya memiliki lebih dari satu tumpukan. (Biasanya, setiap perpustakaan bersama menciptakan tumpukan itu sendiri.)

(Sebagian besar informasi ini berasal dari "Secure Coding in C and C ++" oleh Robert Seacord)

Billy ONeal
sumber
Info bagus, terima kasih! Harapan "user487117" akhirnya benar-benar kembali. :-)
Sdaz MacSkibbons
5

Tumpukan

Dalam arsitektur X86, CPU menjalankan operasi dengan register. Tumpukan hanya digunakan untuk alasan kenyamanan. Anda dapat menyimpan konten register Anda untuk ditumpuk sebelum memanggil subrutin atau fungsi sistem dan kemudian memuatnya kembali untuk melanjutkan operasi di mana Anda pergi. (Anda bisa melakukannya secara manual tanpa tumpukan, tetapi ini adalah fungsi yang sering digunakan sehingga memiliki dukungan CPU). Tetapi Anda dapat melakukan hampir semua hal tanpa tumpukan di PC.

Misalnya perkalian bilangan bulat:

MUL BX

Mengalikan register AX dengan register BX. (Hasilnya akan di DX dan AX, DX berisi bit yang lebih tinggi).

Mesin berbasis stack (seperti JAVA VM) menggunakan stack untuk operasi dasar mereka. Perkalian di atas:

DMUL

Ini memunculkan dua nilai dari atas tumpukan dan mengalikan tem, kemudian mendorong hasilnya kembali ke tumpukan. Stack sangat penting untuk jenis mesin ini.

Beberapa bahasa pemrograman tingkat yang lebih tinggi (seperti C dan Pascal) menggunakan metode ini nanti untuk meneruskan parameter ke fungsi: parameter didorong ke tumpukan di urutan kiri ke kanan dan muncul oleh badan fungsi dan nilai kembali didorong kembali. (Ini adalah pilihan yang dibuat oleh pembuat kompiler dan jenis penyalahgunaan cara X86 menggunakan stack).

Tumpukan itu

Heap adalah konsep lain yang hanya ada di ranah kompiler. Dibutuhkan rasa sakit menangani memori di belakang variabel Anda, tetapi itu bukan fungsi dari CPU atau OS, itu hanya pilihan housekeeping blok memori yang diberikan oleh OS. Anda bisa melakukan ini banyak-banyak jika mau.

Mengakses sumber daya sistem

Sistem operasi memiliki antarmuka publik bagaimana Anda dapat mengakses fungsinya. Dalam parameter DOS dilewatkan dalam register CPU. Windows menggunakan tumpukan untuk melewati parameter untuk fungsi OS (API Windows).

vbence
sumber