Core CPU atau apa itu SMP dan apa yang dimakannya

Pendahuluan


Selamat siang, hari ini saya ingin menyentuh pada topik yang cukup sederhana yang hampir tidak diketahui oleh programmer biasa, tetapi Anda masing-masing kemungkinan besar menggunakannya.
Ini akan menjadi tentang multiprocessing simetris (populer - SMP) - arsitektur yang ditemukan di semua sistem operasi multitasking, dan tentu saja, merupakan bagian integral dari mereka. Semua orang tahu bahwa semakin banyak core yang dimiliki prosesor, semakin kuat pula prosesornya, ya, tetapi bagaimana sebuah OS dapat menggunakan beberapa core secara bersamaan? Beberapa programmer tidak turun ke tingkat abstraksi ini - mereka hanya tidak membutuhkannya, tapi saya pikir semua orang akan tertarik pada bagaimana SMP bekerja.

Multitasking dan implementasinya


Mereka yang pernah mempelajari arsitektur komputer tahu bahwa prosesor itu sendiri tidak dapat melakukan beberapa tugas sekaligus, multitasking hanya memberi kita OS, yang mengalihkan tugas-tugas ini. Ada beberapa jenis multitasking, tetapi yang paling tepat, nyaman dan banyak digunakan adalah crowding out multitasking (Anda dapat membaca aspek utamanya di Wikipedia). Hal ini didasarkan pada kenyataan bahwa setiap proses (tugas) memiliki prioritas sendiri, yang mempengaruhi berapa banyak waktu prosesor akan dialokasikan untuk itu. Setiap tugas diberikan satu irisan waktu selama proses melakukan sesuatu, setelah irisan waktu berakhir, OS mentransfer kontrol ke tugas lain. Muncul pertanyaan - bagaimana mendistribusikan sumber daya komputer, seperti memori, perangkat, dll. antar proses? Semuanya sangat sederhana: Windows melakukannya sendiri, Linux menggunakan sistem semaphore. Tetapi satu inti tidak serius, kami melanjutkan.

Interupsi dan PIC


Mungkin bagi sebagian orang ini akan menjadi berita, sebagian tidak, tetapi arsitektur i386 (saya akan berbicara tentang arsitektur x86, ARM tidak masuk hitungan, karena saya tidak mempelajari arsitektur ini, dan saya tidak pernah menemukan itu. (bahkan pada tingkat penulisan beberapa layanan atau program penduduk)) menggunakan interupsi (kami hanya akan berbicara tentang gangguan perangkat keras, IRQ) untuk memberi tahu OS atau program tentang suatu peristiwa. Misalnya, ada interupsi 0x8 (untuk mode terproteksi dan panjang, misalnya, 0x20, tergantung pada cara mengkonfigurasi PIC, lebih lanjut tentang itu nanti), yang disebut dengan PIT, yang, misalnya, dapat menghasilkan interupsi dengan frekuensi yang diperlukan. Kemudian pekerjaan OS untuk distribusi kuanta waktu dikurangi menjadi 0, ketika interupsi dipanggil, program berhenti dan kontrol diberikan, misalnya, ke kernel, yang pada gilirannya menyimpan data program saat ini (register, flag, dll.) Dan memberikan kontrol untuk proses selanjutnya .

Seperti yang mungkin Anda pahami, interupsi adalah fungsi (atau prosedur) yang dipanggil pada suatu saat oleh peralatan, atau oleh program itu sendiri. Secara total, prosesor ini mendukung 16 interupsi pada dua PIC. Prosesor memiliki flag, dan salah satunya adalah flag "I" - Interrupt Control. Dengan mengatur tanda ini ke 0, prosesor tidak akan menyebabkan gangguan perangkat keras. Tetapi saya juga ingin mencatat bahwa ada yang disebut NMI - Non-Maskable Interrupts - data interupsi masih akan dipanggil, bahkan jika bit saya diatur ke 0. Menggunakan pemrograman PIC, Anda dapat menonaktifkan data interupsi, tetapi setelah kembali dari interupsi apa pun dengan IRET - mereka lagi tidak akan dilarang. Saya perhatikan bahwa dari program biasa Anda tidak dapat melacak panggilan interupsi - program Anda berhenti dan hanya dilanjutkan setelah beberapa saat, program Anda bahkan tidak menyadarinya (ya, Anda dapat memeriksa apakah interupsi dipanggil - tetapi mengapa?

PIC - Pengendali Interupsi yang Dapat Diprogram

Dari Wiki:
Sebagai aturan, ini adalah perangkat elektronik, kadang-kadang dibuat sebagai bagian dari prosesor itu sendiri atau chip kompleks dari bingkainya, input yang terhubung secara elektrik ke output yang sesuai dari berbagai perangkat. Nomor input pengontrol interupsi dilambangkan "IRQ." Nomor ini harus dibedakan dari prioritas interupsi, serta dari nomor entri dalam tabel vektor interupsi (INT). Jadi, misalnya, dalam PC IBM dalam mode operasi nyata (MS-DOS bekerja dalam mode ini) dari prosesor, gangguan dari keyboard standar menggunakan IRQ 1 dan INT 9.

Platform IBM PC asli menggunakan skema interupsi yang sangat sederhana. Pengendali interupsi adalah penghitung sederhana yang berurutan berulang pada sinyal dari perangkat yang berbeda, atau diatur ulang ke awal ketika ditemukan interupsi baru. Dalam kasus pertama, perangkat memiliki prioritas yang sama, di kedua, perangkat dengan nomor seri lebih rendah (atau lebih tinggi dalam penghitungan) memiliki prioritas yang lebih tinggi.

Seperti yang Anda pahami, ini adalah sirkuit elektronik yang memungkinkan perangkat mengirim permintaan interupsi, biasanya hanya ada 2 di antaranya.

Sekarang, mari kita beralih ke topik artikel.

SMP


Untuk menerapkan standar ini, skema baru mulai ditempatkan pada motherboard: APIC dan ACPI. Mari kita bicara tentang yang pertama.

APIC - Advanced Interrupt Programmable Controller, versi PIC yang ditingkatkan. Ini digunakan dalam sistem multiprosesor dan merupakan bagian integral dari semua prosesor Intel terbaru (dan kompatibel). APIC digunakan untuk penerusan interupsi yang kompleks dan untuk mengirim interupsi antar prosesor. Hal-hal ini tidak dimungkinkan menggunakan spesifikasi PIC yang lebih lama.

APIC Lokal dan IO APIC


Dalam sistem berbasis APIC, setiap prosesor terdiri dari "inti" dan "APIC lokal". APIC lokal bertanggung jawab untuk menangani konfigurasi interupsi khusus prosesor. Antara lain, ini berisi tabel vektor lokal (LVT), yang menerjemahkan peristiwa, seperti "jam internal" dan sumber interupsi "lokal" lainnya, menjadi vektor interupsi (misalnya, kontak LocalINT1 dapat meningkatkan pengecualian NMI sambil mempertahankan " 2 "ke input LVT yang sesuai).

Informasi lebih lanjut tentang APIC lokal dapat ditemukan di "Panduan Pemrograman Sistem" dari prosesor Intel modern.

Selain itu, ada APIC IO (misalnya, intel 82093AA), yang merupakan bagian dari chipset dan menyediakan kontrol interupsi multi-prosesor, termasuk distribusi interupsi simetris statis dan dinamis untuk semua prosesor. Pada sistem dengan banyak subsistem I / O, setiap subsistem dapat memiliki set interupsi sendiri.

Setiap pin interupsi diprogram secara individual sebagai pemicu tepi atau level. Vektor interupsi dan informasi kontrol interupsi dapat ditentukan untuk setiap interupsi. Skema akses register tidak langsung mengoptimalkan ruang memori yang diperlukan untuk mengakses register I / O APIC internal. Untuk meningkatkan fleksibilitas sistem ketika mengalokasikan ruang memori, kedua register I / O APIC dapat dipindahkan, tetapi standarnya adalah 0xFEC00000.

Menginisialisasi APIC "lokal"


APIC lokal diaktifkan saat boot dan dapat dinonaktifkan dengan mengatur ulang bit 11 IA32_APIC_BASE (MSR) (ini hanya bekerja dengan prosesor dengan keluarga> 5, karena Pentium tidak memiliki MSR seperti itu), maka prosesor menerima interupsi langsung dari 8259 PIC yang kompatibel. . Namun, panduan pengembangan perangkat lunak Intel menyatakan bahwa setelah menonaktifkan APIC lokal melalui IA32_APIC_BASE, Anda tidak akan dapat menyalakannya sampai sepenuhnya direset. APO IO juga dapat dikonfigurasi untuk beroperasi dalam mode lama sehingga mengemulasi perangkat 8259.

APIC lokal dipetakan ke halaman fisik FEE00xxx (lihat Tabel 8-1 Intel P4 SPG). Alamat ini sama untuk setiap APIC lokal yang ada dalam konfigurasi, yang berarti Anda dapat langsung mengakses register kernel APIC lokal tempat kode Anda saat ini berjalan. Perhatikan bahwa ada MSR yang mendefinisikan basis APIC yang sebenarnya (hanya tersedia untuk prosesor dengan keluarga> 5). MADT berisi basis APIC lokal, dan pada sistem 64-bit, ia juga dapat berisi bidang yang menentukan redefinisi 64-bit dari alamat basis, yang harus Anda gunakan sebagai gantinya. Anda dapat meninggalkan basis APIC lokal hanya di tempat Anda menemukannya, atau memindahkannya ke mana pun Anda inginkan. Catatan: Saya tidak berpikir bahwa Anda dapat memindahkannya lebih jauh dari RAM 4 GB.

Untuk mengaktifkan APIC lokal untuk menerima interupsi, Anda harus mengonfigurasi Daftar Vektor Interrupt Spurious. Nilai yang benar untuk bidang ini adalah nomor IRQ yang ingin Anda petakan ke gangguan palsu dengan 8 bit yang lebih rendah, dan bit ke-8 yang diatur ke 1 untuk benar-benar mengaktifkan APIC (lihat spesifikasi untuk detail). Anda harus memilih nomor interupsi yang memiliki 4 bit lebih rendah yang ditetapkan; Cara termudah adalah menggunakan 0xFF. Ini penting untuk beberapa prosesor lama, karena untuk nilai-nilai ini, 4 bit yang lebih rendah harus ditetapkan ke 1.

Nonaktifkan 8259 PIC dengan benar. Ini hampir sama pentingnya dengan mengkonfigurasi APIC. Anda melakukan ini dalam dua langkah: menutupi semua interupsi dan menugaskan IRQ. Menyamarkan semua interupsi menonaktifkannya di PIC. Memotong ulang interupsi adalah apa yang mungkin sudah Anda lakukan ketika Anda menggunakan PIC: Anda ingin permintaan interupsi dimulai pada 32 alih-alih 0 untuk menghindari konflik dengan pengecualian (dalam mode prosesor lama dan terlindungi, karena 32 interupsi pertama adalah pengecualian). Maka Anda harus menghindari penggunaan vektor interupsi ini untuk tujuan lain. Hal ini diperlukan karena, meskipun Anda menyembunyikan semua interupsi PIC, ia masih dapat membuang interupsi palsu, yang kemudian akan diproses secara tidak benar sebagai pengecualian di kernel Anda.
Mari kita beralih ke SMP.

Symmetric Multitasking: Inisialisasi


Urutan startup berbeda untuk CPU yang berbeda. Panduan Programmer Intel (Bagian 7.5.4) berisi protokol inisialisasi untuk prosesor Intel Xeon dan tidak mencakup prosesor yang lebih lama. Untuk algoritma "semua jenis prosesor" umum, lihat Spesifikasi Intel Multiprosesor.

Untuk 80486 (dengan APIC eksternal 8249DX), Anda harus menggunakan IPIT INIT diikuti oleh IPI "INIT level de-assert" tanpa SIPI. Ini berarti Anda tidak dapat memberi tahu mereka di mana harus mulai mengeksekusi kode Anda (bagian vektor SIPI), dan mereka selalu mulai mengeksekusi kode BIOS. Dalam hal ini, Anda mengatur nilai reset BIOS CMOS ke "mulai hangat dengan lompatan jauh" (yaitu, atur posisi CMOS 0x0F ke 10) sehingga BIOS melakukan jmp jauh ~ [0: 0x0469], lalu atur segmen dan offset Titik masuk AP pada 0x0469.

"INIT level de-assert" "IPI tidak didukung pada prosesor baru (Pentium 4 dan Intel Xeon), dan AFAIK sepenuhnya diabaikan pada prosesor ini.

Untuk prosesor yang lebih baru (P6, Pentium 4) satu SIPI sudah cukup, tetapi saya tidak yakin bahwa prosesor Intel yang lebih lama (Pentium) atau prosesor dari produsen lain memerlukan SIPI kedua. Mungkin juga bahwa SIPI kedua ada jika terjadi kegagalan pengiriman untuk SIPI pertama (kebisingan bus, dll.).

Biasanya saya mengirim SIPI pertama, dan kemudian menunggu untuk melihat apakah AP meningkatkan jumlah prosesor yang berjalan. Jika tidak meningkatkan penghitung ini dalam beberapa milidetik, saya akan mengirim SIPI kedua. Ini berbeda dari algoritma Intel umum (yang memiliki penundaan 200 mikrodetik antara SIPI), tetapi mencoba menemukan sumber waktu yang dapat secara akurat mengukur keterlambatan 200 mikrodetik selama boot awal tidak begitu sederhana. Saya juga menemukan bahwa pada perangkat keras nyata, jika penundaan antara SIPI terlalu lama (dan Anda tidak menggunakan metode saya), AP utama dapat menjalankan kode startup AP awal untuk OS dua kali (yang dalam kasus saya akan menyebabkan OS berpikir bahwa kami memiliki prosesor dua kali lebih banyak dari yang sebenarnya).

Anda dapat menyiarkan sinyal-sinyal ini di bus untuk memulai setiap perangkat yang ada. Namun, Anda juga dapat menyalakan prosesor yang dinonaktifkan khusus (karena mereka "rusak").

Mencari informasi menggunakan tabel MT


Beberapa informasi (yang mungkin tidak tersedia pada mesin yang lebih baru) dimaksudkan untuk multi-pemrosesan. Pertama, Anda perlu menemukan struktur MP floating pointer. Itu selaras pada batas 16-byte dan berisi tanda tangan pada awal "_MP_" atau 0x5F504D5F. OS harus melihat dalam EBDA, ruang ROM BIOS, dan dalam kilobyte terakhir "memori dasar"; ukuran memori dasar ditentukan dalam nilai 2-byte 0x413 dalam kilobyte, minus 1 KB. Seperti inilah strukturnya:

struct mp_floating_pointer_structure { char signature[4]; uint32_t configuration_table; uint8_t length; // In 16 bytes (eg 1 = 16 bytes, 2 = 32 bytes) uint8_t mp_specification_revision; uint8_t checksum; // This value should make all bytes in the table equal 0 when added together uint8_t default_configuration; // If this is not zero then configuration_table should be // ignored and a default configuration should be loaded instead uint32_t features; // If bit 7 is then the IMCR is present and PIC mode is being used, otherwise // virtual wire mode is; all other bits are reserved } 

Inilah tabel konfigurasi yang ditunjukkan oleh struktur apung pointer:

 struct mp_configuration_table { char signature[4]; // "PCMP" uint16_t length; uint8_t mp_specification_revision; uint8_t checksum; // Again, the byte should be all bytes in the table add up to 0 char oem_id[8]; char product_id[12]; uint32_t oem_table; uint16_t oem_table_size; uint16_t entry_count; // This value represents how many entries are following this table uint32_t lapic_address; // This is the memory mapped address of the local APICs uint16_t extended_table_length; uint8_t extended_table_checksum; uint8_t reserved; } 

Setelah tabel konfigurasi adalah entri entry_count, yang berisi lebih banyak informasi tentang sistem, diikuti oleh tabel yang diperluas. Entri adalah 20 byte untuk mewakili prosesor, atau 8 byte untuk sesuatu yang lain. Berikut ini contoh prosesor APIC dan I / O.

 struct entry_processor { uint8_t type; // Always 0 uint8_t local_apic_id; uint8_t local_apic_version; uint8_t flags; // If bit 0 is clear then the processor must be ignored // If bit 1 is set then the processor is the bootstrap processor uint32_t signature; uint32_t feature_flags; uint64_t reserved; } 

Inilah entri IO APIC.

 struct entry_io_apic { uint8_t type; // Always 2 uint8_t id; uint8_t version; uint8_t flags; // If bit 0 is set then the entry should be ignored uint32_t address; // The memory mapped address of the IO APIC is memory } 

Mencari informasi dengan APIC


Anda dapat menemukan tabel MADT (APIC) di ACPI. Tabel ini mencantumkan APIC lokal, yang jumlahnya harus sesuai dengan jumlah inti pada prosesor Anda. Detail tabel ini tidak ada di sini, tetapi Anda dapat menemukannya di Internet.

Luncurkan AP


Setelah Anda mengumpulkan informasi, Anda perlu menonaktifkan PIC dan mempersiapkan untuk APIC I / O. Anda juga perlu mengkonfigurasi BSP dari APIC lokal. Kemudian mulai AP menggunakan SIPI.

Kode untuk meluncurkan kernel:

Saya perhatikan bahwa vektor yang Anda tentukan saat startup menunjukkan alamat awal: vektor 0x8 - alamat 0x8000, vektor 0x9 - alamat 0x9000, dll.

 // ------------------------------------------------------------------------------------------------ static u32 LocalApicIn(uint reg) { return MmioRead32(*g_localApicAddr + reg); } // ------------------------------------------------------------------------------------------------ static void LocalApicOut(uint reg, u32 data) { MmioWrite32(*g_localApicAddr + reg, data); } // ------------------------------------------------------------------------------------------------ void LocalApicInit() { // Clear task priority to enable all interrupts LocalApicOut(LAPIC_TPR, 0); // Logical Destination Mode LocalApicOut(LAPIC_DFR, 0xffffffff); // Flat mode LocalApicOut(LAPIC_LDR, 0x01000000); // All cpus use logical id 1 // Configure Spurious Interrupt Vector Register LocalApicOut(LAPIC_SVR, 0x100 | 0xff); } // ------------------------------------------------------------------------------------------------ uint LocalApicGetId() { return LocalApicIn(LAPIC_ID) >> 24; } // ------------------------------------------------------------------------------------------------ void LocalApicSendInit(uint apic_id) { LocalApicOut(LAPIC_ICRHI, apic_id << ICR_DESTINATION_SHIFT); LocalApicOut(LAPIC_ICRLO, ICR_INIT | ICR_PHYSICAL | ICR_ASSERT | ICR_EDGE | ICR_NO_SHORTHAND); while (LocalApicIn(LAPIC_ICRLO) & ICR_SEND_PENDING) ; } // ------------------------------------------------------------------------------------------------ void LocalApicSendStartup(uint apic_id, uint vector) { LocalApicOut(LAPIC_ICRHI, apic_id << ICR_DESTINATION_SHIFT); LocalApicOut(LAPIC_ICRLO, vector | ICR_STARTUP | ICR_PHYSICAL | ICR_ASSERT | ICR_EDGE | ICR_NO_SHORTHAND); while (LocalApicIn(LAPIC_ICRLO) & ICR_SEND_PENDING) ; } void SmpInit() { kprintf("Waking up all CPUs\n"); *g_activeCpuCount = 1; uint localId = LocalApicGetId(); // Send Init to all cpus except self for (uint i = 0; i < g_acpiCpuCount; ++i) { uint apicId = g_acpiCpuIds[i]; if (apicId != localId) { LocalApicSendInit(apicId); } } // wait PitWait(200); // Send Startup to all cpus except self for (uint i = 0; i < g_acpiCpuCount; ++i) { uint apicId = g_acpiCpuIds[i]; if (apicId != localId) LocalApicSendStartup(apicId, 0x8); } // Wait for all cpus to be active PitWait(10); while (*g_activeCpuCount != g_acpiCpuCount) { kprintf("Waiting... %d\n", *g_activeCpuCount); PitWait(10); } kprintf("All CPUs activated\n"); } 

 [org 0x8000] AP: jmp short bsp ;     -   BSP xor ax,ax mov ss,ax mov sp, 0x7c00 xor ax,ax mov ds,ax ; Mark CPU as active lock inc byte [ds:g_activeCpuCount] ;   ,   jmp zop bsp: xor ax,ax mov ds,ax mov dword[ds:g_activeCpuCount],0 mov dword[ds:g_activeCpuCount],0 mov word [ds:0x8000], 0x9090 ;  JMP   2 NOP' ;   ,   

Sekarang, seperti yang Anda pahami, agar OS menggunakan banyak core, Anda perlu mengkonfigurasi stack untuk setiap core, setiap core, interrupts, dll, tetapi yang paling penting adalah bahwa ketika menggunakan multiprocessing simetris, semua sumber daya core adalah sama: satu memori, satu PCI, dll., dan OS hanya dapat memparalelkan tugas di antara inti.

Saya harap artikelnya tidak cukup membosankan, dan cukup informatif. Lain kali, saya pikir, kita dapat berbicara tentang bagaimana mereka menggambar di layar (dan sekarang mereka menggambar), tanpa menggunakan shader dan kartu video keren.

Semoga beruntung

Source: https://habr.com/ru/post/id426497/


All Articles