تطوير نظام تشغيل Unix-like - برامج تشغيل أجهزة الأحرف (8)

في المقالة السابقة ، قدمنا ​​تعدد المهام. اليوم حان الوقت للنظر في موضوع برامج تشغيل الأجهزة الشخصية.

على وجه التحديد ، سنقوم اليوم بكتابة برنامج تشغيل طرفي ، وهو آلية للمعالجة المؤجلة للمقاطعات ، وننظر في موضوع معالجات نصفي المقاطعات العليا والسفلى من المقاطعات.

نبدأ بإنشاء هيكل الجهاز ، ثم تقديم دعم I / O للملف الأساسي ، والنظر في هيكل ووظائف io_buf للعمل مع الملفات من stdio.h.

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


بناء نظام (جعل ، مجلس التعاون الخليجي ، الغاز). التمهيد الأولي (متعدد التمهيد). إطلاق (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).

شخصية السائقين


كل شيء يبدأ مع ظهور جهاز رمزي. كما تتذكر ، في Linux Device Drivers ، بدا تعريف الجهاز كما يلي:

struct cdev *my_cdev = cdev_alloc( ); my_cdev->ops = &my_fops; 

النقطة الأساسية هي تعيين الجهاز لتنفيذ وظائف إدخال / إخراج الملف.

سنتقدم بهيكل واحد ، لكن المعنى سيكون مماثل:

 extern struct dev_t { struct clist_head_t list_head; /* should be at first */ char name[8]; /* device name */ void* base_r; /* base read address */ void* base_w; /* base write address */ dev_read_cb_t read_cb; /* read handler */ dev_write_cb_t write_cb; /* write handler */ dev_ioctl_cb_t ioctl_cb; /* device specific command handler */ struct clist_definition_t ih_list; /* low half interrupt handlers */ }; 

يتوافق كل جهاز مع نصف قائمة المقاطعات التي يتم استدعاؤها عند إنشاء المقاطعات.

في نظام Linux ، تسمى هذه النصفات بالعلوية ، على العكس ، أقل (المستوى الأدنى).

شخصيا ، بدا لي أكثر منطقية وتذكرت بالصدفة المصطلحات في الاتجاه المعاكس. وصفنا كل عنصر من عناصر قائمة النصفين السفلى من الانقطاعات كما يلي:

 extern struct ih_low_t { struct clist_head_t list_head; /* should be at first */ int number; /* interrupt number */ ih_low_cb_t handler; /* interrupt handler */ }; 

عند التهيئة ، سيقوم برنامج التشغيل بتسجيل جهازه من خلال وظيفة dev_register ، بمعنى آخر إضافة جهاز جديد إلى قائمة الرنين:

 extern void dev_register(struct dev_t* dev) { struct clist_head_t* entry; struct dev_t* device; /* create list entry */ entry = clist_insert_entry_after(&dev_list, dev_list.head); device = (struct dev_t*)entry->data; /* fill data */ strncpy(device->name, dev->name, sizeof(dev->name)); device->base_r = dev->base_r; device->base_w = dev->base_w; device->read_cb = dev->read_cb; device->write_cb = dev->write_cb; device->ioctl_cb = dev->ioctl_cb; device->ih_list.head = dev->ih_list.head; device->ih_list.slot_size = dev->ih_list.slot_size; } 

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

أي سيكون فتح الملف مساوياً لإنشاء بنية FILE من stdio لملف برنامج التشغيل المقابل.

في هذه الحالة ، سوف تطابق أسماء الملفات اسم الجهاز. نحدد مفهوم واصف الملف في مكتبة C الخاصة بنا (stdio.h).

 struct io_buf_t { int fd; /* file descriptor */ char* base; /* buffer beginning */ char* ptr; /* position in buffer */ bool is_eof; /* whether end of file */ void* file; /* file definition */ }; #define FILE struct io_buf_t 

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

 extern struct file_t { struct clist_head_t list_head; /* should be at first */ struct io_buf_t io_buf; /* file handler */ char name[8]; /* file name */ int mod_rw; /* whether read or write */ struct dev_t* dev; /* whether device driver */ }; 

لكل ملف مفتوح ، سنقوم بتخزين رابط للجهاز. ننفذ قائمة حلقات من الملفات المفتوحة وننفذ مكالمات نظام القراءة / الكتابة / ioctl.

عند فتح أحد الملفات ، نحتاج فقط إلى تعيين المواضع الأولية لقواعد التخزين المؤقت للقراءة والكتابة من برنامج التشغيل إلى بنية io_buf_t ، وبالتالي ، ربط عمليات الملفات مع برنامج تشغيل الجهاز.

 extern struct io_buf_t* file_open(char* path, int mod_rw) { struct clist_head_t* entry; struct file_t* file; struct dev_t* dev; /* try to find already opened file */ entry = clist_find(&file_list, file_list_by_name_detector, path, mod_rw); file = (struct file_t*)entry->data; if (entry != null) { return &file->io_buf; } /* create list entry */ entry = clist_insert_entry_after(&file_list, file_list.head); file = (struct file_t*)entry->data; /* whether file is device */ dev = dev_find_by_name(path); if (dev != null) { /* device */ file->dev = dev; if (mod_rw == MOD_R) { file->io_buf.base = dev->base_r; } else if (mod_rw == MOD_W) { file->io_buf.base = dev->base_w; } } else { /* fs node */ file->dev = null; unreachable(); /* fs in not implemented yet */ } /* fill data */ file->mod_rw = mod_rw; file->io_buf.fd = next_fd++; file->io_buf.ptr = file->io_buf.base; file->io_buf.is_eof = false; file->io_buf.file = file; strncpy(file->name, path, sizeof(file->name)); return &file->io_buf; } 

يتم تعريف عمليات القراءة / الكتابة / ioctl في الملف بنمط واحد باستخدام استدعاء نظام القراءة كمثال.

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

 extern size_t file_read(struct io_buf_t* io_buf, char* buff, u_int size) { struct file_t* file; file = (struct file_t*)io_buf->file; /* whether file is device */ if (file->dev != null) { /* device */ return file->dev->read_cb(&file->io_buf, buff, size); } else { /* fs node */ unreachable(); /* fs in not implemented yet */ } return 0; } 

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

سائق المحطة


نحتاج إلى مخزن مؤقت لإخراج الشاشة ومخزن مؤقت لإدخال لوحة المفاتيح ، بالإضافة إلى عدة إشارات لأوضاع الإدخال والإخراج.

 static const char* tty_dev_name = TTY_DEV_NAME; /* teletype device name */ static char tty_output_buff[VIDEO_SCREEN_SIZE]; /* teletype output buffer */ static char tty_input_buff[VIDEO_SCREEN_WIDTH]; /* teletype input buffer */ char* tty_output_buff_ptr = tty_output_buff; char* tty_input_buff_ptr = tty_input_buff; bool read_line_mode = false; /* whether read only whole line */ bool is_echo = false; /* whether to put readed symbol to stdout */ 

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

 extern void tty_init() { struct clist_head_t* entry; struct dev_t dev; struct ih_low_t* ih_low; memset(tty_output_buff, 0, sizeof(VIDEO_SCREEN_SIZE)); memset(tty_input_buff, 0, sizeof(VIDEO_SCREEN_WIDTH)); /* register teletype device */ strcpy(dev.name, tty_dev_name); dev.base_r = tty_input_buff; dev.base_w = tty_output_buff; dev.read_cb = tty_read; dev.write_cb = tty_write; dev.ioctl_cb = tty_ioctl; dev.ih_list.head = null; /* add interrupt handlers */ dev.ih_list.slot_size = sizeof(struct ih_low_t); entry = clist_insert_entry_after(&dev.ih_list, dev.ih_list.head); ih_low = (struct ih_low_t*)entry->data; ih_low->number = INT_KEYBOARD; ih_low->handler = tty_keyboard_ih_low; dev_register(&dev); } 

يتم تعريف معالج المقاطعة السفلي للوحة المفاتيح كما يلي:

 /* * Key press low half handler */ static void tty_keyboard_ih_low(int number, struct ih_low_data_t* data) { /* write character to input buffer */ char* keycode = data->data; int index = *keycode; assert(index < 128); char ch = keyboard_map[index]; *tty_input_buff_ptr++ = ch; if (is_echo && ch != '\n') { /* echo character to screen */ *tty_output_buff_ptr++ = ch; } /* register deffered execution */ struct message_t msg; msg.type = IPC_MSG_TYPE_DQ_SCHED; msg.len = 4; *((size_t *)msg.data) = (size_t)tty_keyboard_ih_high; ksend(TID_DQ, &msg); } 

نحن هنا ببساطة وضع الحرف الذي تم إدخاله في المخزن المؤقت لوحة المفاتيح. في النهاية ، نسجل الدعوة المؤجلة لمعالج النصفين العلويين من مقاطعة لوحة المفاتيح. يتم ذلك عن طريق إرسال رسالة (IPC) إلى مؤشر ترابط kernel.

خيط النواة نفسه بسيط للغاية:

 /* * Deferred queue execution scheduler * This task running in kernel mode */ void dq_task() { struct message_t msg; for (;;) { kreceive(TID_DQ, &msg); switch (msg.type) { case IPC_MSG_TYPE_DQ_SCHED: /* do deffered callback execution */ assert(msg.len == 4); dq_handler_t handler = (dq_handler_t)*((size_t*)msg.data); assert((size_t)handler < KERNEL_CODE_END_ADDR); printf(MSG_DQ_SCHED, handler); handler(msg); break; } } exit(0); } 

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

 /* * Key press high half handler */ static void tty_keyboard_ih_high(struct message_t *msg) { video_flush(tty_output_buff); } 

الآن يبقى لكتابة وظائف I / O أنفسهم ، ودعا من عمليات الملفات.

 /* * Read line from tty to string */ static u_int tty_read(struct io_buf_t* io_buf, void* buffer, u_int size) { char* ptr = buffer; assert((size_t)io_buf->ptr <= (size_t)tty_input_buff_ptr); assert((size_t)tty_input_buff_ptr >= (size_t)tty_input_buff); assert(size > 0); io_buf->is_eof = (size_t)io_buf->ptr == (size_t)tty_input_buff_ptr; if (read_line_mode) { io_buf->is_eof = !strchr(io_buf->ptr, '\n'); } for (int i = 0; i < size - 1 && !io_buf->is_eof; ++i) { char ch = tty_read_ch(io_buf); *ptr++ = ch; if (read_line_mode && ch == '\n') { break; } } return (size_t)ptr - (size_t)buffer; } /* * Write to tty */ static void tty_write(struct io_buf_t* io_buf, void* data, u_int size) { char* ptr = data; for (int i = 0; i < size && !io_buf->is_eof; ++i) { tty_write_ch(io_buf, *ptr++); } } 

عمليات الحرف - الحرف ليست أكثر تعقيدًا ولا أعتقد أنها تحتاج إلى تعليق.

 /* * Write single character to tty */ static void tty_write_ch(struct io_buf_t* io_buf, char ch) { if ((size_t)tty_output_buff_ptr - (size_t)tty_output_buff + 1 < VIDEO_SCREEN_SIZE) { if (ch != '\n') { /* regular character */ *tty_output_buff_ptr++ = ch; } else { /* new line character */ int line_pos = ((size_t)tty_output_buff_ptr - (size_t)tty_output_buff) % VIDEO_SCREEN_WIDTH; for (int j = 0; j < VIDEO_SCREEN_WIDTH - line_pos; ++j) { *tty_output_buff_ptr++ = ' '; } } } else { tty_output_buff_ptr = video_scroll(tty_output_buff, tty_output_buff_ptr); tty_write_ch(io_buf, ch); } io_buf->ptr = tty_output_buff_ptr; } /* * Read single character from tty */ static char tty_read_ch(struct io_buf_t* io_buf) { if ((size_t)io_buf->ptr < (size_t)tty_input_buff_ptr) { return *io_buf->ptr++; } else { io_buf->is_eof = true; return '\0'; } } 

يبقى فقط للسيطرة على أوضاع المدخلات والمخرجات لتنفيذ ioctl.

 /* * Teletype specific command */ static void tty_ioctl(struct io_buf_t* io_buf, int command) { char* hello_msg = MSG_KERNEL_NAME; switch (command) { case IOCTL_INIT: /* prepare video device */ if (io_buf->base == tty_output_buff) { kmode(false); /* detach syslog from screen */ tty_output_buff_ptr = video_clear(io_buf->base); io_buf->ptr = tty_output_buff_ptr; tty_write(io_buf, hello_msg, strlen(hello_msg)); video_flush(io_buf->base); io_buf->ptr = tty_output_buff_ptr; } else if (io_buf->base == tty_input_buff) { unreachable(); } break; case IOCTL_CLEAR: if (io_buf->base == tty_output_buff) { /* fill output buffer with spaces */ tty_output_buff_ptr = video_clear(io_buf->base); video_flush(io_buf->base); io_buf->ptr = tty_output_buff_ptr; } else if (io_buf->base == tty_input_buff) { /* clear input buffer */ tty_input_buff_ptr = tty_input_buff; io_buf->ptr = io_buf->base; io_buf->is_eof = true; } break; case IOCTL_FLUSH: /* flush buffer to screen */ if (io_buf->base == tty_output_buff) { video_flush(io_buf->base); } else if (io_buf->base == tty_input_buff) { unreachable(); } break; case IOCTL_READ_MODE_LINE: /* read only whole line */ if (io_buf->base == tty_input_buff) { read_line_mode = true; } else if (io_buf->base == tty_output_buff) { unreachable(); } break; case IOCTL_READ_MODE_ECHO: /* put readed symbol to stdout */ if (io_buf->base == tty_input_buff) { is_echo = true; } else if (io_buf->base == tty_output_buff) { unreachable(); } break; default: unreachable(); } } 

ننفذ الآن إخراج إدخال الملف على مستوى مكتبتنا C.

 /* * Api - Open file */ extern FILE* fopen(const char* file, int mod_rw) { FILE* result = null; asm_syscall(SYSCALL_OPEN, file, mod_rw, &result); return result; } /* * Api - Close file */ extern void fclose(FILE* file) { asm_syscall(SYSCALL_CLOSE, file); } /* * Api - Read from file to buffer */ extern u_int fread(FILE* file, char* buff, u_int size) { return asm_syscall(SYSCALL_READ, file, buff, size); } /* * Api - Write data to file */ extern void fwrite(FILE* file, const char* data, u_int size) { asm_syscall(SYSCALL_WRITE, file, data, size); } 

حسنًا ، إليك بعض الوظائف الرفيعة المستوى:

 /* * Api - Print user message */ extern void uvnprintf(const char* format, u_int n, va_list list) { char buff[VIDEO_SCREEN_WIDTH]; vsnprintf(buff, n, format, list); uputs(buff); } /* * Api - Read from file to string */ extern void uscanf(char* buff, ...) { u_int readed = 0; do { readed = fread(stdin, buff, 255); } while (readed == 0); buff[readed - 1] = '\0'; /* erase new line character */ uprintf("\n"); uflush(); } 

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

هذا كل شيء. الآن يمكنك ربط السائقين بأمان إلى نواةكم!

مراجع


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

→ شفرة المصدر في مستودع بوابة (تحتاج فرع الدرس 8)

مراجع


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

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


All Articles