
Sistem operasi apa pun memiliki mekanisme startup tertentu. Prinsip operasi mekanisme ini untuk setiap sistem berbeda. Biasanya mereka mengatakan bahwa sistem melakukan boot (Eng. Boot), ini adalah singkatan untuk "bootstrap", yang mengacu pada ungkapan "tarik diri melalui pagar dengan tali sepatu seseorang" (untuk melewati pagar, menarik diri dengan tali pada sepatu), yang secara kasar menggambarkan bagaimana sistem bergerak secara independen dari keadaan di mana memori penuh kekosongan (
kira-kira penerjemah: jika benar-benar akurat, maka sampah ) ke eksekusi program yang stabil. Secara tradisional, sebagian kecil dari program dimuat ke dalam memori, dapat disimpan dalam ROM. Di masa lalu, itu bisa dimasukkan menggunakan sakelar di bagian depan komputer. Bootloader ini membaca program boot yang lebih kompleks yang telah memuat sistem operasi. Saat ini, komputer desktop melakukan booting sebagai berikut: kode BIOS mencari perangkat (hard drive, CD-ROM, stik USB) untuk boot, dan kemudian sistem operasi melakukan booting.
OS untuk sistem tertanam juga dapat diinisialisasi dengan cara yang sama. Dan pada kenyataannya, sistem operasi tertanam yang dikembangkan berdasarkan sistem operasi desktop dimuat. Tetapi dalam kebanyakan RTOS "klasik", metode yang lebih sederhana (dan karena itu lebih cepat) digunakan.
Sistem operasi adalah bagian dari perangkat lunak. Jika perangkat lunak ini sudah ada dalam memori (misalnya, dalam satu atau beberapa bentuk ROM), maka Anda hanya perlu memastikan bahwa urutan perintah CPU setelah reset berakhir dengan eksekusi kode inisialisasi OS. Begitulah cara kerja kebanyakan RTOS, termasuk Nucleus SE (
catatan penerjemah: ini juga berlaku untuk RTOS MAX kami ).
Sebagian besar alat pengembangan perangkat lunak tertanam mencakup kode startup yang diperlukan untuk menangani reset CPU dan mentransfer kontrol ke fungsi Entry Point di fungsi
utama () . Nucleus SE redistributable code tidak berurusan dengan proses ini, karena harus se portable mungkin. Sebaliknya, ini berisi fungsi
utama () , yang mengendalikan CPU dan menginisialisasi dan memulai OS. Fitur ini akan dibahas secara rinci di bawah ini.
Artikel sebelumnya dalam seri: Inisialisasi memori
Deklarasi semua variabel statis dalam kode SE Nucleus dimulai dengan awalan
ROM atau
RAM untuk menunjukkan di mana mereka seharusnya berada. Dua arahan
#define ini didefinisikan dalam file
nuse_types.h dan harus dikonfigurasikan dengan mempertimbangkan spesifikasi dari set alat pengembangan yang digunakan (kompiler dan tautan). Biasanya,
ROM harus bertipe
const (
catatan penerjemah: dari pengalaman saya, const tidak selalu cukup, statis lebih baik ), dan
RAM adalah nilai kosong.
Semua variabel
ROM diinisialisasi secara statis, yang logis. Variabel
RAM tidak diinisialisasi secara statis (karena ini hanya bekerja dengan kotak peralatan tertentu yang dikonfigurasi untuk secara otomatis menyalin dari ROM ke RAM); kode inisialisasi eksplisit termasuk dalam aplikasi dan akan dijelaskan secara rinci di bawah ini.
Nucleus SE tidak menyimpan data "konstan" dalam RAM, yang biasanya kekurangan pasokan dalam sistem kecil. Alih-alih menggunakan struktur data yang kompleks untuk menggambarkan objek kernel, set tabel (array) digunakan, yang mudah ditempatkan dalam ROM atau RAM, tergantung pada kebutuhan.
Fungsi utama ()
Berikut ini adalah kode lengkap untuk fungsi
utama () dari Nucleus SE:
void main(void) { NUSE_Init(); /* initialize kernel data */ /* user initialization code here */ NUSE_Scheduler(); /* start tasks */ }
Urutan operasi ini cukup sederhana:
- Pertama, fungsi NUSE_Init () dipanggil . Ini menginisialisasi semua struktur data Nucleus SE dan akan dijelaskan secara rinci di bawah ini.
- Kemudian pengguna dapat memasukkan kode inisialisasi aplikasi apa pun yang akan dieksekusi sebelum penjadwal tugas dimulai. Untuk informasi lebih lanjut tentang apa yang dapat dicapai dengan kode ini, lihat nanti dalam artikel ini.
- Akhirnya, penjadwal Nucleus SE ( NUSE_Scheduler () ) dimulai. Ini juga akan dibahas secara rinci nanti dalam artikel ini.
Fungsi NUSE_Init ()
Fungsi ini menginisialisasi semua variabel kernel Nucleus SE dan struktur data.
Di bawah ini adalah kode fungsi lengkap: void NUSE_Init(void) { U8 index; /* global data */ NUSE_Task_Active = 0; NUSE_Task_State = NUSE_STARTUP_CONTEXT; #if NUSE_SYSTEM_TIME_SUPPORT NUSE_Tick_Clock = 0; #endif #if NUSE_SCHEDULER_TYPE == NUSE_TIME_SLICE_SCHEDULER NUSE_Time_Slice_Ticks = NUSE_TIME_SLICE_TICKS; #endif /* tasks */ #if ((NUSE_SCHEDULER_TYPE != NUSE_RUN_TO_COMPLETION_SCHEDULER) || NUSE_SIGNAL_SUPPORT || NUSE_TASK_SLEEP || NUSE_SUSPEND_ENABLE || NUSE_SCHEDULE_COUNT_SUPPORT) for (index=0; index<NUSE_TASK_NUMBER; index++) { NUSE_Init_Task(index); } #endif /* partition pools */ #if NUSE_PARTITION_POOL_NUMBER != 0 for (index=0; index<NUSE_PARTITION_POOL_NUMBER; index++) { NUSE_Init_Partition_Pool(index); } #endif /* mailboxes */ #if NUSE_MAILBOX_NUMBER != 0 for (index=0; index<NUSE_MAILBOX_NUMBER; index++) { NUSE_Init_Mailbox(index); } #endif /* queues */ #if NUSE_QUEUE_NUMBER != 0 for (index=0; index<NUSE_QUEUE_NUMBER; index++) { NUSE_Init_Queue(index); } #endif /* pipes */ #if NUSE_PIPE_NUMBER != 0 for (index=0; index<NUSE_PIPE_NUMBER; index++) { NUSE_Init_Pipe(index); } #endif /* semaphores */ #if NUSE_SEMAPHORE_NUMBER != 0 for (index=0; index<NUSE_SEMAPHORE_NUMBER; index++) { NUSE_Init_Semaphore(index); } #endif /* event groups */ #if NUSE_EVENT_GROUP_NUMBER != 0 for (index=0; index<NUSE_EVENT_GROUP_NUMBER; index++) { NUSE_Init_Event_Group(index); } #endif /* timers */ #if NUSE_TIMER_NUMBER != 0 for (index=0; index<NUSE_TIMER_NUMBER; index++) { NUSE_Init_Timer(index); } #endif }
Pertama, variabel global diinisialisasi:
- NUSE_Task_Active - indeks tugas aktif, diinisialisasi ke nol; nanti ini dapat mengubah penjadwal.
- NUSE_Task_State - diinisialisasi dengan nilai NUSE_STARTUP_CONTEXT , yang membatasi fungsionalitas API untuk kode inisialisasi aplikasi berikutnya.
- Jika dukungan waktu sistem diaktifkan, NUSE_Tick_Clock diatur ke nol.
- Jika penjadwal Time Slice diaktifkan, NUSE_Time_Slice_Ticks diberi nilai yang dikonfigurasi NUSE_TIME_SLICE_TICKS .
Kemudian fungsi dipanggil untuk menginisialisasi objek kernel:
- NUSE_Init_Task () dipanggil untuk menginisialisasi struktur data setiap tugas. Panggilan ini dilewati hanya jika penjadwal Run to Completion digunakan, dan sinyal, penghentian sementara tugas, dan penghitung penjadwalan tidak dikonfigurasi (karena kombinasi fungsi ini akan mengakibatkan tidak adanya struktur tugas ini dalam RAM, oleh karena itu, inisialisasi tidak akan dilakukan).
- NUSE_Init_Partition_Pool () dipanggil untuk menginisialisasi setiap objek kumpulan partisi. Panggilan ini dilewati jika tidak ada kumpulan partisi yang dikonfigurasi.
- NUSE_Init_Mailbox () dipanggil untuk menginisialisasi setiap objek kotak surat. Panggilan ini dilewati jika tidak ada kotak surat yang dikonfigurasi.
- NUSE_Init_Queue () dipanggil untuk menginisialisasi setiap objek antrian. Panggilan ini dilewati jika tidak ada antrian yang dikonfigurasi.
- NUSE_Init_Pipe () dipanggil untuk menginisialisasi setiap objek saluran. Panggilan ini dilewati jika tidak ada saluran yang dikonfigurasi.
- NUSE_Init_Semaphore () dipanggil untuk menginisialisasi setiap objek semaphore. Panggilan ini dilewati jika tidak ada semafor yang dikonfigurasi.
- NUSE_Init_Event_Group () dipanggil untuk menginisialisasi setiap objek grup acara. Panggilan ini dilewati jika tidak ada grup acara yang dikonfigurasi.
- NUSE_Init_Timer () dipanggil untuk menginisialisasi setiap objek pengatur waktu. Panggilan-panggilan ini dilewati jika tidak ada timer yang dikonfigurasi.
Inisialisasi tugas
Berikut ini adalah kode lengkap untuk fungsi NUSE_Init_Task (): void NUSE_Init_Task(NUSE_TASK task) { #if NUSE_SCHEDULER_TYPE != NUSE_RUN_TO_COMPLETION_SCHEDULER NUSE_Task_Context[task][15] = /* SR */ NUSE_STATUS_REGISTER; NUSE_Task_Context[task][16] = /* PC */ NUSE_Task_Start_Address[task]; NUSE_Task_Context[task][17] = /* SP */ (U32 *)NUSE_Task_Stack_Base[task] + NUSE_Task_Stack_Size[task]; #endif #if NUSE_SIGNAL_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Signal_Flags[task] = 0; #endif #if NUSE_TASK_SLEEP || NUSE_INCLUDE_EVERYTHING NUSE_Task_Timeout_Counter[task] = 0; #endif #if NUSE_SUSPEND_ENABLE || NUSE_INCLUDE_EVERYTHING #if NUSE_INITIAL_TASK_STATE_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Status[task] = NUSE_Task_Initial_State[task]; #else NUSE_Task_Status[task] = NUSE_READY; #endif #endif #if NUSE_SCHEDULE_COUNT_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Schedule_Count[task] = 0; #endif }
Jika penjadwal Run to Completion belum dikonfigurasi, blok konteks untuk
tugas NUSE_Task_Context [task] [] diinisialisasi. Sebagian besar elemen bukan nilai yang ditetapkan, karena mereka mewakili register mesin yang umum, yang seharusnya memiliki nilai menengah ketika memulai tugas. Dalam contoh (Freescale ColdFire) dari implementasi Nucleus SE (tetapi untuk prosesor lainnya mekanismenya akan serupa) tiga entri terakhir diatur secara eksplisit:
- NUSE_Task_Context [task] [15] berisi register status ( SR , register status) dan memiliki nilai arahan #define NUSE_STATUS_REGISTER .
- NUSE_Task_Context [task] [16] berisi penghitung program ( PC , penghitung program) dan memiliki nilai alamat dari titik input dari kode tugas: NUSE_Task_Start_Address [task] .
- NUSE_Task_Context [task] [17] berisi penunjuk tumpukan ( SP , penunjuk tumpukan) dan diinisialisasi dengan nilai yang dihitung sebagai jumlah alamat dari tumpukan tugas ( NUSE_Task_Stack_Base [tugas] ) dan ukuran tumpukan tugas ( NUSE_Task_Stack_Size [tugas] ).
Jika dukungan sinyal diaktifkan, tanda sinyal tugas (
NUSE_Task_Signal_Flags [tugas] ) diatur ke nol.
Jika penangguhan tugas diaktifkan (yaitu, panggilan layanan API
NUSE_Task_Sleep () ), penghitung waktu tunggu tugas (
NUSE_Task_Timeout_Counter [tugas] ) diatur ke nol.
Jika status penundaan tugas diaktifkan, status tugas (
NUSE_Task_Status [tugas] ) diinisialisasi. Nilai awal ini ditetapkan oleh pengguna (dalam
NUSE_Task_Initial_State [tugas] ) jika dukungan untuk keadaan awal tugas diaktifkan. Jika tidak, status akan ditetapkan ke
NUSE_READY .
Jika penghitung perencanaan diaktifkan, penghitung tugas (
NUSE_Task_Schedule_Count [tugas] ) disetel ke nol.
Menginisialisasi Pools Partisi
Berikut ini adalah kode lengkap untuk fungsi
NUSE_Init_Partition_Pool () :
void NUSE_Init_Partition_Pool(NUSE_PARTITION_POOL pool) { NUSE_Partition_Pool_Partition_Used[pool] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Partition_Pool_Blocking_Count[pool] = 0; #endif }
Penghitung kumpulan partisi "bekas" (
NUSE_Partition_Pool__Partition_Used [pool] ) disetel ke nol.
Jika kunci tugas diaktifkan, penghitung tugas yang diblokir dari kumpulan partisi (
NUSE_Partition_Pool_Blocking_Count [pool]) disetel ke nol.
Inisialisasi Kotak Surat
Di bawah ini adalah kode
NUSE_Init_Mailbox () lengkap :
void NUSE_Init_Mailbox(NUSE_MAILBOX mailbox) { NUSE_Mailbox_Data[mailbox] = 0; NUSE_Mailbox_Status[mailbox] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Mailbox_Blocking_Count[mailbox] = 0; #endif }
Penyimpanan data kotak surat (
NUSE_Mailbox_Data [mailbox] ) diatur ke nol, dan status (
NUSE_Mailbox_Status [mailbox] ) menjadi "tidak digunakan" (yaitu, nol).
Jika penguncian tugas diaktifkan, penghitung tugas kotak surat yang diblokir (
NUSE_Mailbox_Blocking_Count [kotak surat] ) diatur ke nol.
Inisialisasi antrian
Berikut ini adalah kode lengkap untuk fungsi
NUSE_Init_Queue () :
void NUSE_Init_Queue(NUSE_QUEUE queue) { NUSE_Queue_Head[queue] = 0; NUSE_Queue_Tail[queue] = 0; NUSE_Queue_Items[queue] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Queue_Blocking_Count[queue] = 0; #endif }
Pointer ke awal dan akhir antrian (pada kenyataannya, ini adalah indeks
NUSE_Queue_Head [queue ] dan
NUSE_Queue_Tail [queue] ) diberi nilai yang menunjukkan awal dari area data antrian (yaitu, mereka mengambil nilai nol). Penghitung dalam antrian (
NUSE_Queue_Items [antrian] ) juga disetel ke nol.
Jika penguncian tugas diaktifkan, penghitung tugas antrian yang diblokir (
NUSE_Queue_Blocking_Count [antrian] ) diatur ke nol.
Inisialisasi saluran
Berikut ini adalah kode lengkap untuk fungsi
NUSE_Init_Pipe () :
void NUSE_Init_Pipe(NUSE_PIPE pipe) { NUSE_Pipe_Head[pipe] = 0; NUSE_Pipe_Tail[pipe] = 0; NUSE_Pipe_Items[pipe] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Pipe_Blocking_Count[pipe] = 0; #endif }
Pointer ke awal dan akhir saluran (pada kenyataannya, ini adalah indeks -
NUSE_Pipe_Head [pipe] dan
NUSE_Pipe_Tail [pipe] ) diberi nilai yang menunjukkan awal dari area data saluran (yaitu, mereka mengambil nilai nol).
Penghitung saluran (
NUSE_Pipe_Items [pipe] ) juga disetel ke nol.
Jika penguncian tugas diaktifkan, penghitung tugas saluran yang diblokir (
NUSE_Pipe_Blocking_Count [pipa] ) disetel ke nol.
Inisialisasi semaphore
Berikut ini adalah kode lengkap untuk fungsi
NUSE_Init_Semaphore () :
void NUSE_Init_Semaphore(NUSE_SEMAPHORE semaphore) { NUSE_Semaphore_Counter[semaphore] = NUSE_Semaphore_Initial_Value[semaphore]; #if NUSE_BLOCKING_ENABLE NUSE_Semaphore_Blocking_Count[semaphore] = 0; #endif }
Penghitung semaphore (
NUSE_Semaphore_Counter [semaphore] ) diinisialisasi dengan nilai yang ditetapkan oleh pengguna (
NUSE_Semaphore_Initial_Value [semaphore] ).
Jika penguncian tugas diaktifkan, penghitung tugas semafor yang dikunci (
NUSE_Semaphore_Blocking_Count [semaphore] ) disetel ke nol.
Menginisialisasi Grup Acara
Berikut ini adalah kode lengkap untuk fungsi
NUSE_Init_Event_Group () :
void NUSE_Init_Event_Group(NUSE_EVENT_GROUP group) { NUSE_Event_Group_Data[group] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Event_Group_Blocking_Count[group] = 0; #endif }
Bendera grup acara diatur ulang, mis.
NUSE_Event_Group_Data [grup] diberi nilai nol.
Jika penguncian tugas diaktifkan, penghitung tugas yang diblokir dari grup bendera acara (
NUSE_Event_Group_Blocking_Count [grup] ) disetel ke nol.
Timer Inisialisasi
Di bawah ini adalah kode lengkap
NUSE_Init_Timer () ;
void NUSE_Init_Timer(NUSE_TIMER timer) { NUSE_Timer_Status[timer] = FALSE; NUSE_Timer_Value[timer] = NUSE_Timer_Initial_Time[timer]; NUSE_Timer_Expirations_Counter[timer] = 0; }
Keadaan timer (
NUSE_Timer_Status [timer] ) diatur ke "tidak digunakan", mis.
SALAHNilai hitung mundur (
NUSE_Timer_Value [timer ]) diinisialisasi oleh nilai yang ditetapkan oleh pengguna (
NUSE_Timer_Initial_Time [timer] ).
Penghitung penyelesaian (
NUSE_Timer_Expirations_Counter [timer] ) disetel ke nol.
Menginisialisasi Kode Aplikasi
Setelah struktur data Inti N telah diinisialisasi, menjadi mungkin untuk mengeksekusi kode yang bertanggung jawab untuk menginisialisasi aplikasi sebelum memulai tugas. Fitur ini mungkin berguna untuk tugas-tugas berikut:
- Inisialisasi struktur data aplikasi. Pengisian eksplisit struktur data lebih mudah dipahami dan didebug dibandingkan dengan inisialisasi otomatis variabel statis.
- Penugasan objek kernel. Mengingat bahwa semua objek kernel dibuat secara statis pada tahap build dan diidentifikasi menggunakan nilai indeks, mungkin berguna untuk menetapkan "pemilik" atau menentukan penggunaan objek-objek ini. Ini dapat dilakukan dengan menggunakan arahan #define, namun, jika ada beberapa contoh tugas, lebih baik untuk menetapkan indeks objek melalui array global (diindeks oleh ID tugas).
- Inisialisasi perangkat. Ini dapat berguna untuk pemasangan awal perangkat.
Jelas, banyak dari tujuan ini dapat dicapai sebelum inisialisasi Nucleus SE, tetapi keuntungan di lokasi kode aplikasi di sini adalah bahwa sekarang Anda dapat menggunakan layanan kernel (panggilan API). Misalnya, antrian atau kotak surat mungkin sudah diisi sebelumnya dengan data yang perlu diproses saat tugas dimulai.
Panggilan API memiliki batasan: semua tindakan yang biasanya mengarah pada aktivasi penjadwal dilarang (misalnya, menjeda / memblokir tugas). Variabel global
NUSE_Task_State telah disetel ke
NUSE_STARTUP_CONTEXT untuk
menunjukkan batasan ini.
Luncurkan penjadwal
Setelah inisialisasi selesai, tetap hanya menjalankan penjadwal untuk mulai mengeksekusi kode aplikasi - tugas. Konfigurasi penjadwal dan pekerjaan berbagai jenis penjadwal dijelaskan secara rinci di salah satu artikel sebelumnya (
# 9 ), jadi hanya ringkasan singkat yang diperlukan di sini.
Urutan langkah-langkah utama adalah sebagai berikut:
- Mengatur variabel global NUSE_Task_State ke NUSE_TASK_CONTEXT .
- Pilih indeks tugas pertama yang akan dijalankan. Jika dukungan untuk tugas awal diaktifkan, pencarian untuk tugas jadi pertama dilakukan, jika tidak, nilai nol digunakan.
- Penjadwal disebut - NUSE_Scheduler () .
Apa yang sebenarnya terjadi pada langkah terakhir tergantung pada penjadwal yang dipilih. Saat menggunakan penjadwal Run to Completion, siklus perencanaan dimulai dan tugas dipanggil secara berurutan. Saat menggunakan penjadwal lain, konteks tugas pertama dimuat dan kontrol ditransfer ke tugas.
Artikel berikut akan membahas diagnostik dan pemeriksaan kesalahan.
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.