Seluruh kebenaran tentang RTOS. Artikel # 10. Penjadwal: fitur canggih dan pelestarian konteks



Dalam artikel sebelumnya, kami melihat berbagai jenis perencanaan yang didukung oleh RTOS dan kemampuan terkait di Nucleus SE. Dalam artikel ini, kita akan melihat opsi perencanaan tambahan di Nucleus SE dan proses menyimpan dan memulihkan konteks.

Artikel sebelumnya dalam seri:
Artikel # 9. Penjadwal: implementasi
Artikel # 8. Nucleus SE: Desain dan Penyebaran Internal
Artikel # 7. Nucleus SE: Pendahuluan
Artikel # 6. Layanan RTOS lainnya
Artikel # 5. Interaksi tugas dan sinkronisasi
Artikel # 4. Tugas, pengalihan konteks, dan interupsi
Artikel # 3. Tugas dan Perencanaan
Artikel # 2. RTOS: Struktur dan mode waktu-nyata
Artikel # 1. RTOS: pengantar.

Fitur opsional


Selama pengembangan Nucleus SE, saya membuat jumlah maksimum fungsi opsional, yang menghemat memori dan / atau waktu.

Tangguhkan Tugas


Seperti yang disebutkan sebelumnya dalam Penjadwal: Artikel implementasi , Nucleus SE mendukung berbagai opsi untuk menjeda tugas, tetapi fitur ini opsional dan disertakan oleh simbol NUSE_SUSPEND_ENABLE di nuse_config.h . Jika diatur ke TRUE , maka struktur data didefinisikan sebagai NUSE_Task_Status [] . Jenis suspensi ini berlaku untuk semua tugas. Array adalah tipe U8 , di mana 2 camilan digunakan secara terpisah. 4 bit yang lebih rendah berisi status tugas:
NUSE_READY, NUSE_PURE_SUSPEND , NUSE_SLEEP_SUSPEND , NUSE_MAILBOX_SUSPEND , dll. Jika tugas ditangguhkan oleh panggilan API (misalnya, NUSE_MAILBOX_SUSPEND ), 4 bit tinggi berisi indeks objek di mana tugas ditangguhkan. Informasi ini digunakan ketika sumber daya tersedia dan untuk memanggil API Anda perlu mencari tahu dari tugas yang ditangguhkan yang harus dilanjutkan.

Untuk melakukan penangguhan tugas, sepasang fungsi penjadwal digunakan: NUSE_Suspend_Task () dan NUSE_Wake_Task () .

Kode NUSE_Suspend_Task () adalah sebagai berikut:



Fungsi menyimpan status tugas yang baru (semua 8 bit), yang diperoleh sebagai parameter suspend_code. Saat Anda mengaktifkan penguncian (lihat "Kunci Panggilan API" di bawah), kode pengembalian NUSE_SUCCESS disimpan . Selanjutnya, NUSE_Reschedule () dipanggil untuk mentransfer kontrol ke tugas berikutnya.

Kode NUSE_Wake_Task () cukup sederhana:



Status tugas diatur ke NUSE_READY . Jika penjadwal Prioritas tidak digunakan, tugas saat ini terus menempati prosesor hingga saatnya tiba untuk membebaskan sumber daya. Jika penjadwal Prioritas digunakan, NUSE_Reschedule () dipanggil dengan indeks tugas sebagai indikasi penyelesaian, karena tugas mungkin memiliki prioritas lebih tinggi dan harus segera dieksekusi.

Kunci panggilan API


Nucleus RTOS mendukung sejumlah panggilan API yang dengannya pengembang dapat menjeda (memblokir) tugas jika sumber daya tidak tersedia. Tugas akan dilanjutkan ketika sumber daya tersedia lagi. Mekanisme ini juga diimplementasikan dalam Nucleus SE dan berlaku untuk sejumlah objek kernel: tugas dapat diblokir di bagian memori, dalam grup acara, kotak surat, antrian, saluran, atau semafor. Tapi, seperti kebanyakan alat di Nucleus SE, itu opsional dan ditentukan oleh simbol NUSE_BLOCKING_ENABLE di nuse_config.h . Jika diatur ke TRUE , maka array NUSE_Task_Blocking_Return [] didefinisikan, yang berisi kode balik untuk setiap tugas; itu bisa berupa NUSE_SUCCESS atau kode NUSE_MAILBOX_WAS_RESET , yang menunjukkan bahwa objek diatur ulang saat tugas dikunci. Ketika penguncian diaktifkan, kode yang sesuai dimasukkan dalam fungsi API menggunakan kompilasi bersyarat.

Penghitung penjadwal


Nucleus RTOS menghitung berapa kali tugas telah dijadwalkan sejak tugas itu dibuat dan reset terakhir. Fitur ini juga diimplementasikan dalam Nucleus SE, tetapi bersifat opsional dan ditentukan oleh simbol NUSE_SCHEDULE_COUNT_SUPPORT di nuse_config.h . Jika diatur ke TRUE , array NUSE_Task_Schedule_Count [] dari tipe U16 dibuat , yang menyimpan konter setiap tugas dalam aplikasi.

Keadaan awal tugas


Ketika tugas dibuat di Nucleus RTOS, Anda dapat memilih statusnya: siap atau dijeda. Di Nucleus SE, secara default, semua tugas siap saat startup. Opsi yang dipilih dengan simbol NUSE_INITIAL_TASK_STATE_SUPPORT di nuse_config.h memungkinkan Anda untuk memilih kondisi awal. Array NUSE_Task_Initial_State [] didefinisikan dalam nuse_config.c dan membutuhkan inisialisasi NUSE_READY atau NUSE_PURE_SUSPEND untuk setiap tugas dalam aplikasi.

Menyimpan Konteks


Gagasan mempertahankan konteks tugas dengan semua jenis penjadwal, kecuali untuk RTC (Run to Completion), disajikan dalam artikel # 3 "Tugas dan penjadwalan". Seperti yang telah disebutkan, ada beberapa cara untuk mempertahankan konteks. Mengingat bahwa Nucleus SE tidak dirancang untuk prosesor 32-bit, saya memilih untuk menggunakan tabel, bukan tumpukan, untuk mempertahankan konteks.

Array dua dimensi dari tipe ADDR NUSE_Task_Context [] [] digunakan untuk menyimpan konteks untuk semua tugas dalam aplikasi. Barisnya adalah NUSE_TASK_NUMBER (jumlah tugas dalam aplikasi), kolomnya adalah NUSE_REGISTERS (jumlah register yang perlu disimpan; tergantung pada prosesor dan diatur ke nuse_types.h) .

Tentu saja, mempertahankan konteks dan mengembalikan kode tergantung pada prosesor. Dan ini adalah satu-satunya kode Nucleus SE yang diikat ke perangkat tertentu (dan lingkungan pengembangan). Saya akan memberikan contoh kode save / restore untuk prosesor ColdFire. Walaupun pilihan ini mungkin tampak aneh karena prosesor yang sudah ketinggalan zaman, assemblernya lebih mudah dibaca daripada assembler dari kebanyakan prosesor modern. Kode ini cukup sederhana untuk digunakan sebagai dasar untuk membuat saklar konteks untuk prosesor lain:



Ketika pengalihan konteks diperlukan, kode ini disebut dalam NUSE_Context_Swap. Dua variabel digunakan: NUSE_Task_Active , indeks tugas saat ini, konteks yang harus dilestarikan; NUSE_Task_Next , indeks tugas yang konteksnya ingin Anda muat (lihat bagian Data Global).

Proses pelestarian konteks berfungsi sebagai berikut:

  • Register A0 dan D0 disimpan sementara di stack;
  • A0 dikonfigurasikan untuk menunjuk ke array blok konteks NUSE_Task_Context [] [] ;
  • D0 dimuat menggunakan NUSE_Task_Active dan dikalikan dengan 72 (ColdFire memiliki 18 register, membutuhkan 72 byte untuk penyimpanan);
  • kemudian D0 ditambahkan ke A0 , yang sekarang menunjuk ke blok konteks untuk tugas saat ini;
  • kemudian register disimpan di blok konteks; pertama A0 dan D0 (dari stack), kemudian D1-D7 dan A1-A6 , kemudian SR dan PC (dari stack, kita akan melihat konteks switching yang dimulai dengan cepat), dan pada akhirnya penunjuk stack disimpan.

Proses pemuatan konteks adalah urutan tindakan yang sama dalam urutan terbalik:

  • A0 dikonfigurasikan untuk menunjuk ke array blok konteks NUSE_Task_Context [] [] ;
  • D0 dimuat menggunakan NUSE_Task_Active , bertambah dan dikalikan dengan 72;
  • kemudian D0 ditambahkan ke A0 , yang sekarang menunjuk ke blok konteks untuk tugas baru (karena pemuatan konteks harus dilakukan dalam proses kebalikan dari menyimpan urutan, penumpukan tumpukan diperlukan pertama kali);
  • kemudian register dipulihkan dari blok konteks; pertama, penunjuk tumpukan, kemudian PC dan SR didorong ke tumpukan, kemudian D1-D7 dan A1-A6 dimuat, dan pada akhir D0 dan A0 .

Kesulitan dalam menerapkan pengalihan konteks adalah bahwa akses ke register negara sulit bagi banyak prosesor (untuk ColdFire, ini SR ). Solusi umum adalah gangguan, yaitu gangguan program atau gangguan cabang bersyarat, yang menyebabkan SR memuat ke tumpukan bersama dengan PC . Beginilah cara Nucleus SE bekerja di ColdFire. Makro NUSE_CONTEXT_SWAP () diatur dalam nuse_types.h , yang meluas ke:
asm ("trap # 0");

Berikut ini adalah kode inisialisasi ( NUSE_Init_Task () di nuse_init.c ) untuk blok konteks:



Ini adalah bagaimana inisialisasi stack pointer, PC, dan SR terjadi. Dua yang pertama memiliki nilai yang ditetapkan oleh pengguna di nuse_config.c . Nilai SR didefinisikan sebagai karakter NUSE_STATUS_REGISTER di nuse_types.h . Untuk ColdFire, nilai ini adalah 0x40002000 .

Data global


Penjadwal Nucleus SE hanya membutuhkan sedikit memori untuk menyimpan data, tetapi, tentu saja, menggunakan struktur data yang terkait dengan tugas, yang akan dibahas secara rinci dalam artikel berikut.

Data RAM


Penjadwal tidak menggunakan data yang terletak di ROM, dan 2 hingga 5 variabel global ditempatkan di RAM (semua diatur dalam nuse_globals.c ), tergantung pada penjadwal mana yang digunakan:

  • NUSE_Task_Active - variabel tipe U8 yang berisi indeks tugas saat ini;
  • NUSE_Task_State - variabel tipe U8 yang mengandung nilai yang menunjukkan status kode yang saat ini dieksekusi, yang dapat berupa tugas, interrupt handler, atau kode startup; nilai yang mungkin adalah: NUSE_TASK_CONTEXT , NUSE_STARTUP_CONTEXT , NUSE_NISR_CONTEXT dan NUSE_MISR_CONTEXT ;
  • NUSE_Task_Saved_State - variabel tipe U8 yang digunakan untuk melindungi nilai NUSE_Task_State dalam interupsi terkelola;
  • NUSE_Task_Next - variabel tipe U8 yang berisi indeks tugas berikutnya, yang harus dijadwalkan untuk semua penjadwal kecuali RTC;
  • NUSE_Time_Slice_Ticks - variabel tipe U16 yang mengandung penghitung irisan waktu; hanya digunakan dengan penjadwal TS.

Footprint Data Penjadwal


Penjadwal Nucleus SE tidak menggunakan data ROM. Jumlah pasti data RAM bervariasi tergantung pada penjadwal yang digunakan:

  • untuk RTC - 2 byte ( NUSE_Task_Active dan NUSE_Task_State );
  • untuk RR dan Prioritas - 4 byte ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State dan NUSE_Task_Next );
  • untuk TS - 6 byte ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State , NUSE_Task_Next dan NUSE_Time_Slice_Ticks ).

Implementasi mekanisme perencanaan lainnya


Terlepas dari kenyataan bahwa Nucleus SE menawarkan pilihan 4 penjadwal, yang mencakup sebagian besar kasus, arsitektur terbuka memungkinkan Anda untuk menerapkan peluang untuk kasus-kasus lain.

Mengiris waktu dengan tugas latar belakang


Seperti yang sudah dijelaskan dalam artikel # 3, "Tugas dan Penjadwalan," Penjadwal Waktu Iris yang sederhana memiliki keterbatasan karena membatasi waktu maksimum yang dapat diambil oleh prosesor. Opsi yang lebih canggih adalah menambahkan dukungan untuk tugas latar belakang. Tugas semacam itu dapat dijadwalkan pada setiap slot yang dialokasikan untuk tugas yang dijeda, dan dijalankan ketika slot tersebut sebagian dibebaskan. Pendekatan ini memungkinkan Anda untuk menjadwalkan tugas secara berkala dan dengan perkiraan persentase waktu inti prosesor untuk diselesaikan.

Priority and Round Robin (RR)


Di sebagian besar inti waktu nyata, penjadwal prioritas mendukung beberapa tugas di setiap tingkat prioritas, tidak seperti Nucleus SE, di mana setiap tugas memiliki tingkat yang unik. Saya memberi preferensi pada opsi yang terakhir, karena sangat menyederhanakan struktur data dan, oleh karena itu, kode penjadwal. Untuk mendukung arsitektur yang lebih kompleks, diperlukan banyak tabel ROM dan RAM.

Tentang Pengarang: Colin Walls telah bekerja di industri elektronik selama lebih dari tiga puluh tahun, mencurahkan sebagian besar waktunya untuk firmware. Dia sekarang adalah seorang insinyur firmware di Mentor Embedded (sebuah divisi dari Mentor Graphics). Colin Walls sering berbicara di konferensi dan seminar, penulis berbagai artikel teknis dan dua buku tentang firmware. Tinggal di Inggris. Blog profesional Colin , email: colin_walls@mentor.com.

Tentang penerjemahan: seri artikel ini tampak menarik karena, meskipun ada pendekatan yang sudah ketinggalan zaman di beberapa tempat, penulis dalam bahasa yang sangat dimengerti memperkenalkan pembaca yang kurang terlatih ke fitur-fitur dari OS real-time. Saya sendiri termasuk dalam tim pembuat RTOS Rusia , yang ingin kami bebaskan , dan saya harap siklus ini akan bermanfaat bagi pengembang pemula.

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


All Articles