Climbing Elbrus - Pengintaian dalam pertempuran. Bagian Teknis 2. Mengganggu, pengecualian, pengatur waktu sistem

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) /*   .h  */ rrs %upsr, %r1 ors %r1, UPSR_DI, %r1 /* upsr |= UPSR_DI; */ 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) { /* XXX currently we support only single core */ while(1); } /* copy of trap table */ memcpy((void*)0, &_t_entry, 0x1800); kernel_start(); 

Melakukannya

  /* Since we enable exceptions only when all CPUs except the main one * reached the idle state (cpu_idle), we can rely that order and can * guarantee exceptions happen strictly after all CPUS entries. */ if (entries_count >= CPU_COUNT) { /* Entering here because of expection or interrupt */ e2k_trap_handler(regs); ... } /* It wasn't exception, so we decide this usual program execution, * that is, Embox started on CPU0 or CPU1 */ e2k_wait_all(); entries_count = __e2k_atomic32_add(1, &entries_count); if (entries_count > 1) { /* XXX currently we support only single core */ 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 = .; /* Interrupt handler */ *(.ttable_entry0) . = _t_entry + 0x800; /* Syscall handler */ *(.ttable_entry1) . = _t_entry + 0x1000; /* longjmp handler */ *(.ttable_entry2) . = _t_entry + 0x1800; _t_entry_end = .; *(.e2k_entry) *(.cpu_idle) /* text */ } 

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; /*    CPU β€œβ€ */ while (idled_cpus_count < CPU_COUNT - 1) ; ... /*     ,     */ e2k_upsr_write(e2k_upsr_read() & ~UPSR_FE); kernel_start(); /*   Embox */ } 

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 /* sizeof pt_regs */ getsp -(5 * 8), %dr0 std %dr1, [%dr0 + PT_CTRP1] /* regs->ctpr1 = ctpr1 */ std %dr2, [%dr0 + PT_CTRP2] /* regs->ctpr2 = ctpr2 */ std %dr3, [%dr0 + PT_CTRP3] /* regs->ctpr3 = ctpr3 */ std %dr4, [%dr0 + PT_ILCR] /* regs->ilcr = ilcr */ std %dr5, [%dr0 + PT_LSR] /* regs->lsr = 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; \ /* ctpr2 is restored first because of tight time constraints \ * on restoring ctpr2 and aaldv. */ \ 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.
  /* Entering here because of expection or interrupt */ 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:

  /* PSR is local register and makes sense only within a function, * so we set it here before kernel start. */ 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.

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


All Articles