تطوير نظام التشغيل مثل Unix - Shell. الاستنتاج (9)

حان الوقت لكتابة أول برنامج منفصل لنواة لدينا - قذيفة. سيتم تخزينه في ملف .elf منفصل وسيتم تشغيله بواسطة عملية init عند بدء تشغيل kernel.

هذا هو المقال الأخير في دورة تطوير نظام التشغيل لدينا.

جدول المحتويات


بناء نظام (جعل ، مجلس التعاون الخليجي ، الغاز). التمهيد الأولي (متعدد التمهيد). إطلاق (qemu). مكتبة C (strcpy ، memcpy ، strext).

مكتبة C (sprintf ، strcpy ، strcmp ، strtok ، va_list ...). بناء المكتبة في وضع kernel ووضع تطبيق المستخدم.

سجل نظام النواة. ذاكرة الفيديو الإخراج إلى المحطة (kprintf ، kpanic ، kassert).
الذاكرة الديناميكية ، الكومة (kmalloc ، kfree).

تنظيم الذاكرة والتعامل مع المقاطعة (GDT ، IDT ، PIC ، syscall). الاستثناءات.
الذاكرة الظاهرية (دليل الصفحة وجدول الصفحة).

العملية. المجدول. تعدد المهام. مكالمات النظام (القتل ، الخروج ، ملاحظة).

برامج تشغيل الأجهزة الشخصية. مكالمات النظام (ioctl ، fopen ، fread ، fwrite). مكتبة C (fopen ، fclose ، fprintf ، fscanf).

نظام ملفات kernel (initrd) ، قزم ، وداخله. مكالمات النظام (exec). شل كبرنامج كامل للنواة.

وضع حماية المستخدم (ring3). قسم حالة المهمة (tss).

شل كبرنامج نواة كاملة


في مقال سابق ، نظرنا في برامج تشغيل الأجهزة الشخصية وكتبنا برنامج تشغيل طرفي.

الآن لدينا كل ما نحتاج إليه لإنشاء تطبيق وحدة التحكم الأول.

سنكتب تطبيق وحدة التحكم نفسها ، والتي سنقوم بتجميعها في قزم منفصل.

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

سنحتاج إلى تهيئة المكتبة القياسية ونقل التحكم إلى الوظيفة الرئيسية المألوفة.

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

علاوة على ذلك في الحلقة ، نقرأ ببساطة السطر وننفذ الأمر.

أوامر Parsim خلال strtok_r إذا كان هناك وسيطات.

 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; } 

في الواقع ، نحن فقط سحب المكالمات النظام.
اسمحوا لي أن أذكركم بتهيئة المكتبة القياسية.

في الدرس الأخير ، كتبنا الوظيفة التالية في المكتبة:

 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); } 

إنه ببساطة يفتح ملفات برنامج التشغيل الطرفي الخاص للقراءة والكتابة ، والتي تتوافق مع إدخال لوحة المفاتيح وإخراجها إلى الشاشة.

بعد قيامنا بتجميع قزمنا بقذيفة ، يجب وضعه على نظام ملفات kernel الأصلي (initrd).

يتم تحميل قرص ذاكرة الوصول العشوائي الأولي من قِبل لوادر kernel كوحدة متعددة التمهيد ، لذلك نحن نعرف العنوان في ذكرى initrd لدينا.

يبقى تنظيم نظام الملفات لـ initrd ، والذي من السهل القيام به وفقًا لمقالة كتبها جيمس مولوي.

لذلك ، سيكون التنسيق كما يلي:

 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 */ }; 

بعد ذلك ، تذكر تنسيق قزم 32 بت.
 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; }; 

نحن هنا مهتمون بنقطة الدخول وعنوان جدول رؤوس البرامج.

سيكون قسم الكود والبيانات هو العنوان الأول ، وسيكون قسم المكدس هو الثاني (وفقًا لنتائج دراسة قزم من خلال 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); 

هذه المعلومات كافية لكتابة محمل ملف قزم.
نحن نعرف بالفعل كيفية اختيار صفحات للعمليات المخصصة.
لذلك ، نحتاج فقط إلى تخصيص عدد كاف من الصفحات للعناوين ونسخ المحتويات فيها.
سنكتب وظيفة ستنشئ عملية بناءً على ملف العفريت المحلل.
تعرف على كيفية تحليل elfik في الفيديو التعليمي.
نحتاج إلى تنزيل رأس برنامج واحد فقط مع رمز وبيانات ، لذلك لن نعمم وسنركز على هذه الحالة.

 /* * 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); } 

الشيء الأكثر إثارة للاهتمام هنا هو إنشاء دليل الصفحة وجدول الصفحة.
انتبه ، نختار أولاً الصفحات الفعلية (mm_phys_alloc_pages) ، ثم نعيّنها في الصفحات المنطقية (mmu_occupy_user_page).
هنا من المفترض أن يتم تخصيص الصفحات في الذاكرة الفعلية بشكل مستمر.
هذا كل شيء. الآن يمكنك تنفيذ shell الخاص بك للنواة الخاصة بك! شاهد فيديو تعليمي وافحص التفاصيل.

استنتاج


آمل أن تكونوا قد وجدت هذه السلسلة من المقالات مفيدة.
لم نفكر بعد في حلقات الحماية معك ، ولكن نظرًا لانخفاض درجة ارتباط الموضوع والمراجعات المختلطة ، سنستمر في كسرها.
أعتقد أنك نفسك جاهز الآن لإجراء مزيد من البحث ، ولقد درست كل الأشياء المهمة لك.

لذلك ، تشديد الحزام ، وإلى المعركة! عن طريق كتابة نظام التشغيل الخاص بك!
استغرق الأمر مني شهرًا تقريبًا (إذا أخذنا في الاعتبار الوقت الكامل لمدة 6-8 ساعات يوميًا) لتنفيذ كل ما تعلمته أنت وأنا من الصفر.

لذلك ، في غضون 2-3 أشهر ، ستكون قادرًا على كتابة نظام تشغيل كامل مع نظام ملفات حقيقي ، لم تنفذه أنت وأنا.

فقط اعلم أن qemu لا يعرف كيفية التعامل مع initrd من التنسيق التعسفي ، ويقتصر على 4 كيلو بايت ، لذلك ستحتاج إلى جعله مثل نظام Linux ، أو استخدم borsch بدلاً من qemu.
إذا كنت تعرف كيفية التغلب على هذه المشكلة ، فاكتب في خطاب شخصي ، سأكون ممتنًا جدًا لك.

هذا كل شئ! حتى الجديد لم يعد يجتمع!

مراجع


شاهد الفيديو التعليمي لمزيد من المعلومات.

شفرة المصدر في مستودع بوابة (تحتاج إلى فرع الدرس 9).

مراجع


1. جيمس مولوي. لفة نظام التشغيل الخاص بك UNIX استنساخ.
2. الأسنان. مجمع ل DOS ، ويندوز ، يونيكس
3. كلاشينكوف. المجمع سهل!
4. Tanenbaum. أنظمة التشغيل. التنفيذ والتطوير.
5. روبرت لوف. نواة لينكس وصف عملية التطوير.

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


All Articles