
Kami terus menjelajahi
Elbrus dengan memasukkan
Embox ke dalamnya.
Artikel ini adalah bagian kedua dari artikel teknis tentang arsitektur Elbrus. Bagian
pertama berurusan dengan tumpukan, register, dan sebagainya. Sebelum membaca bagian ini, kami sarankan Anda mempelajari yang pertama, karena membahas tentang hal-hal dasar dalam arsitektur Elbrus. Bagian ini akan fokus pada penghitung waktu, interupsi, dan pengecualian. Lagi-lagi ini bukan dokumentasi resmi. Untuk itu, Anda harus menghubungi pengembang Elbrus di
ICST .
Untuk mempelajari Elbrus, kami ingin memulai timer dengan cepat, karena, seperti yang Anda tahu, preemptive multitasking tidak berfungsi tanpa itu. Untuk melakukan ini, tampaknya cukup untuk mengimplementasikan pengontrol interupsi dan pengatur waktu itu sendiri, tetapi kami mengalami kesulitan yang
tidak terduga , di mana kami akan pergi tanpa mereka. Mereka mulai mencari kemampuan debugging dan menemukan bahwa para pengembang menangani ini dengan memperkenalkan beberapa perintah yang memungkinkan Anda untuk meningkatkan berbagai situasi luar biasa. Misalnya, Anda dapat membuat pengecualian jenis khusus melalui register PSR (Processor Status Register) dan UPSR (Register status prosesor pengguna). Untuk PSR, bit exc_last_wish adalah flag pengecualian exc_last_wish ketika kembali dari prosedur, dan untuk UPSR, exc_d_interrupt adalah flag interupsi tertunda yang dihasilkan oleh operasi VFDI (Periksa flag interrupt tertunda).
Kode tersebut adalah sebagai berikut:
#define UPSR_DI (1 << 3) rrs %upsr, %r1 ors %r1, UPSR_DI, %r1 rws %r1, %upsr vfdi
Diluncurkan. Tapi tidak ada yang terjadi, sistem tergantung di suatu tempat, tidak ada output ke konsol. Sebenarnya, kami melihat ini ketika kami mencoba memulai interupsi dari penghitung waktu, tetapi kemudian ada banyak komponen, dan di sini jelas bahwa ada sesuatu yang mengganggu kemajuan berurutan dari program kami, dan kontrol dipindahkan ke tabel pengecualian (dalam hal arsitektur Elbrus, lebih tepat untuk berbicara bukan tentang tabel) interupsi dan tentang tabel pengecualian). Kami berasumsi bahwa prosesor tetap melemparkan pengecualian, tetapi ada beberapa "sampah" di mana ia mentransfer kontrol. Ternyata, ia mentransfer kontrol ke tempat kami meletakkan gambar Embox, yang berarti ada titik masuk - fungsi entri.
Untuk verifikasi, kami melakukan hal berikut. Memulai penghitungan entri pada entri (). Awalnya, semua CPU mulai dengan interupsi dimatikan, masuk ke entri (), setelah itu kita hanya membiarkan satu inti aktif, semua sisanya masuk ke loop tanpa akhir. Setelah penghitung sama dengan jumlah CPU, kami menganggap bahwa semua klik berikutnya dalam entri adalah pengecualian. Saya mengingatkan Anda bahwa sebelum itu seperti yang dijelaskan dalam
artikel pertama kami tentang Elbrus cpuid = __e2k_atomic32_add(1, &last_cpuid); if (cpuid > 1) { while(1); } memcpy((void*)0, &_t_entry, 0x1800); kernel_start();
Melakukannya
if (entries_count >= CPU_COUNT) { e2k_trap_handler(regs); ... } e2k_wait_all(); entries_count = __e2k_atomic32_add(1, &entries_count); if (entries_count > 1) { cpu_idle(); } e2k_kernel_start(); }
Dan akhirnya kami melihat reaksi memasuki interupsi (hanya dengan bantuan printf kami mencetak garis).
Di sini perlu dijelaskan bahwa awalnya dalam versi pertama kami diharapkan untuk menyalin tabel pengecualian, tetapi pertama-tama, ternyata itu ada di alamat kami, dan kedua, kami tidak dapat membuat salinan yang benar. Saya harus menulis ulang skrip linker, titik masuk ke sistem, dan pengendali interupsi, yaitu, saya memerlukan bagian assembler, tentang hal itu sedikit kemudian.
Beginilah tampilan bagian yang diubah dari skrip tautan sekarang:
.text : { _start = .; _t_entry = .; *(.ttable_entry0) . = _t_entry + 0x800; *(.ttable_entry1) . = _t_entry + 0x1000; *(.ttable_entry2) . = _t_entry + 0x1800; _t_entry_end = .; *(.e2k_entry) *(.cpu_idle) }
yaitu, kami menghapus bagian entri untuk tabel pengecualian. Bagian cpu_idle juga terletak di sana untuk CPU yang tidak digunakan.
Seperti inilah fungsi input untuk kernel aktif kami, tempat Embox akan berjalan:
static void e2k_kernel_start(void) { extern void kernel_start(void); int psr; while (idled_cpus_count < CPU_COUNT - 1) ; ... e2k_upsr_write(e2k_upsr_read() & ~UPSR_FE); kernel_start(); }
Nah, pengecualian dilemparkan sesuai dengan instruksi VFDI. Sekarang Anda perlu mendapatkan nomornya untuk memastikan bahwa ini adalah pengecualian yang benar. Untuk ini, Elbrus memiliki register informasi interupsi TIR (register Trap Info). Mereka berisi informasi tentang beberapa perintah terakhir, yaitu bagian akhir dari penelusuran. Lacak kumpul selama eksekusi program dan "beku" saat memasukkan interupsi. TIR mencakup bagian rendah (64 bit) dan tinggi (64 bit). Kata rendah berisi bendera pengecualian, dan kata tinggi berisi pointer ke instruksi yang mengarah ke pengecualian dan nomor TIR saat ini. Dengan demikian, dalam kasus kami, exc_d_interrupt adalah bit ke-4.
Catatan Kami masih memiliki beberapa kesalahpahaman tentang kedalaman (jumlah) TIR. Dokumentasi menyediakan:
βKedalaman memori TIR, yaitu, jumlah register Info Perangkap, ditentukan
TIR_NUM makro sama dengan jumlah tahapan pipa prosesor yang diperlukan untuk
mengeluarkan semua situasi khusus yang mungkin terjadi. TIR_NUM = 19; "
Dalam praktiknya, kami melihat kedalaman = 1, dan oleh karena itu kami hanya menggunakan register TIR0.
Spesialis di MCST menjelaskan kepada kami bahwa semuanya benar, dan hanya akan ada TIR0 untuk gangguan "akurat", dan untuk situasi lain mungkin ada hal lain. Tetapi karena sementara kita hanya berbicara tentang penghentian waktu, ini tidak mengganggu kita.
Ok, sekarang mari kita lihat apa yang diperlukan untuk masuk / keluar dengan benar dari pengendali pengecualian. Bahkan, perlu untuk menyimpan pada input dan mengembalikan 5 register berikut pada output. Tiga register persiapan transfer kontrol adalah ctpr [1,2,3], dan dua register kontrol siklus adalah ILCR (Daftar nilai awal dari penghitung siklus) dan LSR (Daftar status siklus).
.type ttable_entry0,@function ttable_entry0: setwd wsz = 0x10, nfx = 1; rrd %ctpr1, %dr1 rrd %ctpr2, %dr2 rrd %ctpr3, %dr3 rrd %ilcr, %dr4 rrd %lsr, %dr5 getsp -(5 * 8), %dr0 std %dr1, [%dr0 + PT_CTRP1] std %dr2, [%dr0 + PT_CTRP2] std %dr3, [%dr0 + PT_CTRP3] std %dr4, [%dr0 + PT_ILCR] std %dr5, [%dr0 + PT_LSR] disp %ctpr1, e2k_entry ct %ctpr1
Sebenarnya, itu saja, setelah keluar dari penangan pengecualian, Anda harus mengembalikan 5 register ini.
Kami melakukan ini dengan makro:
#define RESTORE_COMMON_REGS(regs) \ ({ \ uint64_t ctpr1 = regs->ctpr1, ctpr2 = regs->ctpr2, \ ctpr3 = regs->ctpr3, lsr = regs->lsr, \ ilcr = regs->ilcr; \ \ E2K_SET_DSREG(ctpr1, ctpr1); \ E2K_SET_DSREG(ctpr2, ctpr2); \ E2K_SET_DSREG(ctpr3, ctpr3); \ E2K_SET_DSREG(lsr, lsr); \ E2K_SET_DSREG(ilcr, ilcr); \ })
Penting juga untuk tidak lupa setelah pemulihan register untuk memanggil operasi DONE (Return from the hardware interrupt handler). Operasi ini diperlukan, khususnya, agar dapat memproses operasi transfer kontrol yang terputus dengan benar. Kami melakukan ini dengan makro:
#define E2K_DONE \ do { \ asm volatile ("{nop 3} {done}" ::: "ctpr3"); \ } while (0)
Sebenarnya, kami melakukan pengembalian dari interupsi secara langsung dalam kode C menggunakan dua makro ini.
e2k_trap_handler(regs); RESTORE_COMMON_REGS(regs); E2K_DONE;
Interupsi eksternal
Mari kita mulai dengan cara mengaktifkan interupsi eksternal. Di Elbrus, APIC (atau lebih tepatnya analognya) digunakan sebagai pengontrol interupsi, Embox sudah memiliki driver ini. Karena itu, dimungkinkan untuk mengambil pengatur waktu sistem untuk itu. Ada dua timer, satu yang sangat mirip dengan
PIT ,
Timer LAPIC lainnya, juga cukup standar, sehingga tidak masuk akal untuk membicarakannya. Baik itu dan itu terlihat sederhana, dan itu dan itu sudah ada di Embox, tetapi driver timer LAPIC terlihat lebih perspektif, selain itu implementasi timer PIT bagi kami lebih non-standar. Karena itu, sepertinya lebih mudah untuk diselesaikan. Selain itu, dokumentasi resmi menggambarkan register APIC dan LAPIC, yang sedikit berbeda dari aslinya. Membawa mereka tidak masuk akal, seperti yang Anda lihat di aslinya.
Selain mengizinkan interupsi dalam APIC, Anda harus mengaktifkan penanganan interupsi melalui register PSR / UPSR. Kedua register memiliki tanda untuk mengaktifkan interupsi eksternal dan interupsi non-maskable.
TETAPI di sini sangat penting untuk dicatat bahwa register PSR bersifat
lokal untuk fungsi (ini telah dibahas pada bagian
teknis pertama ). Dan ini berarti bahwa jika Anda mengaturnya di dalam suatu fungsi, maka ketika Anda memanggil semua fungsi berikutnya, itu akan diwarisi, tetapi ketika Anda kembali dari fungsi, itu akan kembali ke keadaan semula. Karena itu pertanyaannya, tetapi bagaimana mengelola interupsi?
Kami menggunakan solusi berikut. Register PSR memungkinkan Anda untuk mengaktifkan manajemen melalui UPSR, yang sudah bersifat global (yang kami butuhkan). Oleh karena itu, kami mengaktifkan kontrol melalui UPSR secara langsung (penting!) Sebelum fungsi login inti Embox:
asm volatile ("rrs %%psr, %0" : "=r"(psr) :); psr |= (PSR_IE | PSR_NMIE | PSR_UIE); asm volatile ("rws %0, %%psr" : : "ri"(psr)); kernel_start();
Entah bagaimana secara kebetulan setelah refactoring saya mengambil dan meletakkan garis-garis ini ke fungsi yang terpisah ... Dan register adalah fungsi lokal. Jelas semuanya rusak :)
Jadi, semuanya tampaknya dihidupkan dalam prosesor, pergi ke pengontrol interupsi.
Seperti yang telah kita lihat di atas, informasi tentang nomor pengecualian ada di register TIR. Selanjutnya, bit ke-32 dalam register ini melaporkan bahwa gangguan eksternal telah terjadi.
Setelah menyalakan timer, beberapa hari siksaan terjadi, karena tidak ada gangguan yang dapat diperoleh. Alasannya cukup lucu. Ada pointer 64-bit di Elbrus, dan alamat register di APIC masuk ke uint32_t, itu sebabnya kami menggunakannya. Tetapi ternyata jika Anda perlu, misalnya, memberikan 0xF0000000 ke sebuah pointer, maka Anda tidak akan mendapatkan 0xF0000000, tetapi 0xFFFFFFFFF0000000. Artinya, kompiler akan memperluas tanda int Anda yang tidak ditandatangani.
Di sini, tentu saja, perlu menggunakan uintptr_t, karena, ternyata, dalam standar C99, jenis konversi ini didefinisikan oleh implementasi.
Setelah kami akhirnya melihat bit ke-32 di TIR, kami mulai mencari cara mendapatkan nomor interupsi. Ternyata cukup sederhana, meskipun tidak seperti pada x86, ini adalah salah satu perbedaan antara implementasi LAPIC. Untuk Elbrus, untuk mendapatkan nomor interupsi, Anda harus masuk ke register LAPIC khusus:
#define APIC_VECT (0xFEE00000 + 0xFF0)
di mana 0xFEE00000 adalah alamat dasar register LAPIC.
Itu saja, ternyata untuk mengambil timer sistem dan timer LAPIC.
Kesimpulan
Informasi yang diberikan dalam dua bagian teknis pertama artikel tentang arsitektur Elbrus cukup untuk mengimplementasikan interupsi perangkat keras dan multitasking preemptive di OS apa pun. Sebenarnya, screenshot yang diberikan bersaksi untuk ini.

Ini bukan bagian teknis terakhir tentang arsitektur Elbrus. Sekarang kami menguasai manajemen memori (MMU) di Elbrus, kami berharap segera membicarakannya. Kami membutuhkan ini tidak hanya untuk implementasi ruang alamat virtual, tetapi juga untuk pekerjaan normal dengan periferal, karena melalui mekanisme ini Anda dapat menonaktifkan atau mengaktifkan caching area spesifik ruang alamat.
Segala sesuatu yang ditulis dalam artikel dapat ditemukan di repositori
Embox . Anda juga dapat membangun dan menjalankan, jika tentu saja ada platform perangkat keras. Benar, kompiler diperlukan untuk ini, dan itu hanya dapat diperoleh di
MCST . Dokumentasi resmi dapat diminta di sana.