
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: implementasiArtikel # 8. Nucleus SE: Desain dan Penyebaran InternalArtikel # 7. Nucleus SE: PendahuluanArtikel # 6. Layanan RTOS lainnyaArtikel # 5. Interaksi tugas dan sinkronisasiArtikel # 4. Tugas, pengalihan konteks, dan interupsiArtikel # 3. Tugas dan PerencanaanArtikel # 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.