Pengembangan OS seperti Unix - Shell. Kesimpulan (9)

Saatnya menulis program terpisah pertama untuk kernel kami - shell. Ini akan disimpan dalam file .elf terpisah dan diluncurkan oleh proses init ketika kernel dimulai.

Ini adalah artikel terakhir dalam siklus pengembangan sistem operasi kami.

Daftar isi


Membangun sistem (make, gcc, gas). Boot awal (multiboot). Luncurkan (qemu). Pustaka C (strcpy, memcpy, strext).

Pustaka C (sprintf, strcpy, strcmp, strtok, va_list ...). Membangun perpustakaan dalam mode kernel dan mode aplikasi pengguna.

Log sistem kernel. Memori video Output ke terminal (kprintf, kpanic, kassert).
Memori dinamis, tumpukan (kmalloc, kfree).

Organisasi memori dan penanganan interupsi (GDT, IDT, PIC, syscall). Pengecualian
Memori virtual (direktori halaman dan tabel halaman).

Proses Perencana Multitasking. Panggilan sistem (bunuh, keluar, ps).

Driver perangkat karakter. Panggilan sistem (ioctl, fopen, fread, fwrite). Pustaka C (fopen, fclose, fprintf, fscanf).

Sistem file kernel (initrd), elf, dan internalnya. Panggilan sistem (exec). Shell sebagai program lengkap untuk kernel.

Mode perlindungan pengguna (ring3). Segmen Status Tugas (tss).

Shell sebagai program kernel yang lengkap


Dalam artikel sebelumnya, kami melihat driver perangkat karakter dan menulis driver terminal.

Sekarang kami memiliki semua yang kami butuhkan untuk membuat aplikasi konsol pertama.

Kami akan menulis aplikasi konsol itu sendiri, yang akan kami kompilasi menjadi peri terpisah.

/* * Elf entry point */ void start() { u_int errno; stdio_init(); errno = main(); stdio_deinit(); exit(errno); } 

Kita perlu menginisialisasi perpustakaan standar dan mentransfer kontrol ke fungsi utama yang sudah dikenal.

 int main() { char cmd[255]; while (1) { printf(prompt); flush(); scanf(cmd); if (!execute_command(cmd)) { break; } } return 0; } 

Lebih lanjut dalam loop, kita cukup membaca baris dan menjalankan perintah.

Parsim memerintahkan melalui strtok_r jika ada argumen.

 static bool execute_command(char* cmd) { if (!strcmp(cmd, cmd_ps)) { /* show tasks list */ struct clist_definition_t *task_list; task_list = ps(); printf(" -- process list\n"); clist_for_each(task_list, print_task_info); } else if (!strcmp(cmd, cmd_clear)) { /* clear screen */ clear(); flush(); } else if (!strncmp(cmd, cmd_kill, strlen(cmd_kill))) { /* kill task */ char* save_ptr = null; strtok_r(cmd, " ", &save_ptr); char* str_tid = strtok_r(null, " ", &save_ptr); u_short tid = atou(str_tid); if (!kill(tid)) { printf(" There is no process with pid %u\n", tid); }; } else if (!strncmp(cmd, cmd_exit, strlen(cmd_exit))) { /* exit */ clear(); printf(prompt); flush(); return false; } else if (!strncmp(cmd, cmd_exec, strlen(cmd_exec))) { /* exec file on intrd */ char* save_ptr = null; strtok_r(cmd, " ", &save_ptr); char* str_file = strtok_r(null, " ", &save_ptr); exec(str_file); } else if (!strncmp(cmd, cmd_dev, strlen(cmd_dev))) { /* show device list */ struct clist_definition_t *dev_list; dev_list = devs(); printf(" -- device list\n"); clist_for_each(dev_list, print_dev_info); } else { printf(" There is no such command.\n Available command list:\n"); printf(" %s %s %s <pid> %s <file.elf> %s %s\n", cmd_ps, cmd_exit, cmd_kill, cmd_exec, cmd_clear, cmd_dev); } return true; } 

Faktanya, kita hanya menarik panggilan sistem.
Biarkan saya mengingatkan Anda tentang inisialisasi perpustakaan standar.

Dalam pelajaran terakhir, kami menulis fungsi berikut di perpustakaan:

 extern void stdio_init() { stdin = fopen(tty_dev_name, MOD_R); stdout = fopen(tty_dev_name, MOD_W); asm_syscall(SYSCALL_IOCTL, stdout, IOCTL_INIT); asm_syscall(SYSCALL_IOCTL, stdin, IOCTL_READ_MODE_LINE); asm_syscall(SYSCALL_IOCTL, stdin, IOCTL_READ_MODE_ECHO); } 

Ini hanya membuka file driver terminal khusus untuk membaca dan menulis, yang sesuai dengan input dan output keyboard ke layar.

Setelah kami mengumpulkan elf kami dengan sebuah shell, itu harus ditempatkan pada sistem file kernel asli (initrd).

Disk ram awal dimuat oleh kernel loader sebagai modul multiboot, jadi kami tahu alamat di memori initrd kami.

Masih mengatur sistem file untuk initrd, yang mudah dilakukan menurut sebuah artikel oleh James Molloy.

Oleh karena itu, formatnya adalah sebagai berikut:

 extern struct initrd_node_t { unsigned char magic; /* magic number */ char name[8]; /* file name */ unsigned int offset; /* file base */ unsigned int length; /* file length */ }; extern struct initrd_fs_t { int count; /* files count */ struct initrd_node_t node[INITRD_MAX_FILES]; /* files headers */ }; 

Selanjutnya, ingat format elf 32 bit.
 struct elf_header_t { struct elf_header_ident_t e_ident; u16 e_type; u16 e_machine; u32 e_version; u32 e_entry; /* virtual address of entry point */ u32 e_phoff; /* program headers table offset */ u32 e_shoff; /* program headers sections table offset */ u32 e_flags; u16 e_ehsize; /* file header size */ u16 e_phentsize; /* single header size */ u16 e_phnum; /* headers count */ u16 e_shentsize; /* section header size */ u16 e_shnum; /* sections headers count */ u16 e_shstrndx; }; 

Di sini kita tertarik pada titik masuk dan alamat tabel tajuk program.

Bagian kode dan data akan menjadi pos pertama, dan bagian tumpukan akan menjadi yang kedua (menurut hasil mempelajari elf melalui objdump).

 struct elf_program_header_t { u32 p_type; /* segment type */ u32 p_offset; /* segment offset from file begin */ u32 p_vaddr; /* target virtual address */ u32 p_paddr; /* target physical address */ u32 p_filesz; /* segment size in file */ u32 p_memsz; /* segment size in memory */ u32 p_flags; /* permissions and etc */ u32 p_align; /* alignment */ } attribute(packed); 

Informasi ini cukup untuk menulis loader file elf.
Kami sudah tahu cara memilih halaman untuk proses kustom.
Karena itu, kita hanya perlu mengalokasikan jumlah halaman yang cukup untuk pos dan menyalin konten ke dalamnya.
Kami akan menulis fungsi yang akan membuat proses berdasarkan pada file elf yang diuraikan.
Lihat cara mengurai elfik di tutorial video.
Kami hanya perlu mengunduh satu tajuk program dengan kode dan data, jadi kami tidak akan menggeneralisasi dan akan fokus pada kasus ini.

 /* * Api - execute elf as a task */ extern void elf_exec(struct elf_header_t* header) { assert(header->e_ident.ei_magic == EI_MAGIC); printf(MSG_KERNEL_ELF_LOADING, header->e_phnum); // elf_dump(header); size_t elf_base = (size_t)header; size_t entry_point = header->e_entry; struct task_mem_t task_mem; memset(&task_mem, 0, sizeof(struct task_mem_t)); // load sections in memory for (int i = 0; i < header->e_phnum; ++i) { struct elf_program_header_t* p_header = (void*)(header->e_phoff + elf_base + i * header->e_phentsize); task_mem.pages_count = (p_header->p_memsz / MM_PAGE_SIZE) + 1; if (p_header->p_memsz == 0) { continue; } // allocate pages assert(task_mem.pages_count > 0); assert(task_mem.pages == null); task_mem.pages = mm_phys_alloc_pages(task_mem.pages_count); void* section = (void*)(elf_base + p_header->p_offset); memcpy(task_mem.pages, section, p_header->p_memsz); // setup virtual memory task_mem.page_table = mmu_create_user_page_table(); task_mem.page_dir = mmu_create_user_page_directory(task_mem.page_table); for (int i = 0; i < task_mem.pages_count; ++i) { mmu_occupy_user_page(task_mem.page_table, (void*)((size_t)task_mem.pages + i * MM_PAGE_SIZE)); } } // create task u_short tid = next_tid++; assert(task_create(tid, (void*)entry_point, &task_mem)); // run task struct task_t* task; task = task_get_by_id(tid); task->status = TASK_RUNNING; strncpy(task->name, "elf", sizeof(task->name)); printf(MSG_KERNEL_ELF_LOADED); } 

Hal yang paling menarik di sini adalah pembuatan direktori halaman dan tabel halaman.
Perhatikan, pertama-tama kita memilih halaman fisik (mm_phys_alloc_pages), dan kemudian memetakannya ke halaman logis (mmu_occupy_user_page).
Di sini diasumsikan bahwa halaman-halaman dalam memori fisik dialokasikan secara terus menerus.
Itu saja. Sekarang Anda dapat mengimplementasikan shell Anda sendiri untuk kernel Anda! Tonton tutorial video dan pelajari detailnya.

Kesimpulan


Saya harap Anda menemukan serangkaian artikel ini bermanfaat.
Kami belum mempertimbangkan cincin perlindungan dengan Anda, tetapi karena relevansi topik yang rendah dan ulasan yang beragam, kami akan terus memecahkannya.
Saya pikir Anda sendiri sekarang siap untuk penelitian lebih lanjut, karena Anda dan saya telah memeriksa semua hal yang paling penting.

Karena itu, kencangkan ikat pinggang, dan ke medan pertempuran! Dengan menulis sistem operasi Anda sendiri!
Butuh waktu sekitar satu bulan (jika kami mempertimbangkan waktu penuh selama 6-8 jam sehari) untuk menerapkan semua yang Anda dan saya pelajari dari awal.

Oleh karena itu, dalam 2-3 bulan Anda akan dapat menulis OS lengkap dengan sistem file nyata, yang Anda dan saya tidak berhasil implementasikan.

Ketahuilah bahwa qemu tidak tahu cara bekerja dengan initrd format sewenang-wenang, dan memotongnya menjadi 4kb, jadi Anda harus membuatnya seperti di Linux, atau menggunakan borsch alih-alih qemu.
Jika Anda tahu cara mengatasi masalah ini, tulis dalam surat pribadi, saya akan sangat berterima kasih kepada Anda.

Itu saja! Sampai yang baru tidak lagi bertemu!

Referensi


Tonton tutorial video untuk informasi lebih lanjut.

Kode sumber di repositori git (Anda memerlukan cabang lesson9).

Referensi


1. James Molloy. Gulung mainan Anda sendiri UNIX-clone OS.
2. Gigi. Assembler untuk DOS, Windows, Unix
3. Kalashnikov. Assembler mudah!
4. Tanenbaum. Sistem operasi. Implementasi dan pengembangan.
5. Robert Love. Kernel Linux Deskripsi proses pengembangan.

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


All Articles