Pada artikel sebelumnya, kami memperkenalkan multitasking. Hari ini saatnya untuk mempertimbangkan topik driver perangkat karakter.
Secara khusus, hari ini kita akan menulis driver terminal, mekanisme untuk pemrosesan interupsi yang ditunda, dan mempertimbangkan topik penangan untuk bagian interupsi atas dan bawah.
Kami mulai dengan membuat struktur perangkat, kemudian memperkenalkan dukungan I / O file dasar, pertimbangkan struktur io_buf dan fungsi untuk bekerja dengan file dari stdio.h.
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).
Driver Karakter
Semuanya dimulai dengan penampilan perangkat simbolis. Seingat Anda, di Linux Device Drivers, definisi perangkat tampak seperti ini:
struct cdev *my_cdev = cdev_alloc( ); my_cdev->ops = &my_fops;
Poin utama adalah untuk menetapkan perangkat implementasi fungsi I / O file.
Kami akan bertahan dengan satu struktur, tetapi artinya akan serupa:
extern struct dev_t { struct clist_head_t list_head; char name[8]; void* base_r; void* base_w; dev_read_cb_t read_cb; dev_write_cb_t write_cb; dev_ioctl_cb_t ioctl_cb; struct clist_definition_t ih_list; };
Setiap perangkat sesuai dengan setengah daftar interupsi yang dipanggil ketika interupsi dihasilkan.
Di Linux, belahan seperti itu disebut atas, sebaliknya, lebih rendah (level bawah).
Secara pribadi, itu tampak lebih masuk akal bagi saya dan saya tidak sengaja mengingat istilah-istilah itu sebaliknya. Kami menjelaskan masing-masing elemen dari daftar setengah bagian interupsi sebagai berikut:
extern struct ih_low_t { struct clist_head_t list_head; int number; ih_low_cb_t handler; };
Setelah inisialisasi, driver akan mendaftarkan perangkatnya melalui fungsi dev_register, dengan kata lain menambahkan perangkat baru ke daftar dering:
extern void dev_register(struct dev_t* dev) { struct clist_head_t* entry; struct dev_t* device; entry = clist_insert_entry_after(&dev_list, dev_list.head); device = (struct dev_t*)entry->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; }
Agar semua ini bisa berfungsi, kita membutuhkan dasar dari sistem file. Pada awalnya, kami hanya akan memiliki file untuk perangkat karakter.
Yaitu membuka file akan sama dengan membuat struktur FILE dari stdio untuk file driver yang sesuai.
Dalam hal ini, nama file akan cocok dengan nama perangkat. Kami mendefinisikan konsep deskriptor file di perpustakaan C kami (stdio.h).
struct io_buf_t { int fd; char* base; char* ptr; bool is_eof; void* file; }; #define FILE struct io_buf_t
Untuk mempermudah, biarkan semua file yang terbuka disimpan dalam daftar dering untuk saat ini. Kami menggambarkan item daftar sebagai
extern struct file_t { struct clist_head_t list_head; struct io_buf_t io_buf; char name[8]; int mod_rw; struct dev_t* dev; };
Untuk setiap file yang terbuka, kami akan menyimpan tautan ke perangkat. Kami menerapkan daftar dering file yang terbuka dan menerapkan panggilan sistem baca / tulis / ioctl.
Saat membuka file, kita hanya perlu menetapkan posisi awal buffer baca dan tulis dari driver ke struktur io_buf_t, dan, karenanya, kaitkan operasi file dengan driver perangkat.
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; 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; } entry = clist_insert_entry_after(&file_list, file_list.head); file = (struct file_t*)entry->data; dev = dev_find_by_name(path); if (dev != null) { 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 { file->dev = null; unreachable(); } 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; }
Operasi file baca / tulis / ioctl didefinisikan oleh satu pola menggunakan panggilan sistem baca sebagai contoh.
Panggilan sistem yang kita pelajari untuk menulis dalam pelajaran terakhir hanya akan memanggil fungsi-fungsi ini.
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; if (file->dev != null) { return file->dev->read_cb(&file->io_buf, buff, size); } else { unreachable(); } return 0; }
Singkatnya, mereka hanya akan menarik panggilan balik dari definisi perangkat. Sekarang kita akan menulis driver terminal.
Sopir terminal
Kita membutuhkan buffer output layar dan input keyboard, serta beberapa flag untuk mode input dan output.
static const char* tty_dev_name = TTY_DEV_NAME; static char tty_output_buff[VIDEO_SCREEN_SIZE]; static char tty_input_buff[VIDEO_SCREEN_WIDTH]; char* tty_output_buff_ptr = tty_output_buff; char* tty_input_buff_ptr = tty_input_buff; bool read_line_mode = false; bool is_echo = false;
Kami menulis fungsi membuat perangkat. Ini hanya membuat panggilan balik operasi file dan penangan dari bagian yang lebih rendah dari gangguan, setelah itu mendaftarkan perangkat dalam daftar dering.
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)); 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; 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); }
Handler interupsi bawah untuk keyboard didefinisikan sebagai berikut:
static void tty_keyboard_ih_low(int number, struct ih_low_data_t* data) { 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') { *tty_output_buff_ptr++ = ch; } 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); }
Di sini kita cukup memasukkan karakter yang dimasukkan ke buffer keyboard. Pada akhirnya, kami mendaftarkan panggilan yang ditangguhkan dari prosesor bagian atas dari gangguan keyboard. Ini dilakukan dengan mengirim pesan (IPC) ke utas kernel.
Utas kernel itu sendiri cukup sederhana:
void dq_task() { struct message_t msg; for (;;) { kreceive(TID_DQ, &msg); switch (msg.type) { case IPC_MSG_TYPE_DQ_SCHED: 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); }
Dengan menggunakannya, pawang dari bagian atas dari interupsi keyboard akan dipanggil. Tujuannya adalah untuk menduplikasi karakter di layar dengan menyalin buffer output ke memori video.
static void tty_keyboard_ih_high(struct message_t *msg) { video_flush(tty_output_buff); }
Sekarang tinggal menulis fungsi I / O sendiri, dipanggil dari operasi file.
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; } 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++); } }
Operasi karakter demi karakter tidak jauh lebih rumit dan saya rasa mereka tidak perlu berkomentar.
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') { *tty_output_buff_ptr++ = ch; } else { 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; } 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'; } }
Tetap hanya mengontrol mode input dan output untuk mengimplementasikan ioctl.
static void tty_ioctl(struct io_buf_t* io_buf, int command) { char* hello_msg = MSG_KERNEL_NAME; switch (command) { case IOCTL_INIT: if (io_buf->base == tty_output_buff) { kmode(false); 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) { 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) { tty_input_buff_ptr = tty_input_buff; io_buf->ptr = io_buf->base; io_buf->is_eof = true; } break; case IOCTL_FLUSH: 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: 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: if (io_buf->base == tty_input_buff) { is_echo = true; } else if (io_buf->base == tty_output_buff) { unreachable(); } break; default: unreachable(); } }
Sekarang kami menerapkan output input file di tingkat perpustakaan kami C.
extern FILE* fopen(const char* file, int mod_rw) { FILE* result = null; asm_syscall(SYSCALL_OPEN, file, mod_rw, &result); return result; } extern void fclose(FILE* file) { asm_syscall(SYSCALL_CLOSE, file); } extern u_int fread(FILE* file, char* buff, u_int size) { return asm_syscall(SYSCALL_READ, file, buff, size); } extern void fwrite(FILE* file, const char* data, u_int size) { asm_syscall(SYSCALL_WRITE, file, data, size); }
Nah, inilah beberapa fungsi tingkat tinggi:
extern void uvnprintf(const char* format, u_int n, va_list list) { char buff[VIDEO_SCREEN_WIDTH]; vsnprintf(buff, n, format, list); uputs(buff); } extern void uscanf(char* buff, ...) { u_int readed = 0; do { readed = fread(stdin, buff, 255); } while (readed == 0); buff[readed - 1] = '\0'; uprintf("\n"); uflush(); }
Agar tidak terkecoh dengan pembacaan format sejauh ini, kami akan selalu membaca ke dalam baris seolah-olah bendera% s diberikan. Saya terlalu malas untuk memperkenalkan status tugas baru untuk menunggu deskriptor file, jadi kami hanya mencoba membaca sesuatu dalam loop tanpa batas hingga kami berhasil.
Itu saja. Sekarang Anda dapat memasang driver dengan aman ke kernel Anda!
Referensi
Tonton
tutorial video untuk informasi lebih lanjut.
→ Kode sumber
di repositori git (Anda membutuhkan cabang lesson8)
Referensi
- James Molloy. Gulung mainan Anda sendiri UNIX-clone OS.
- Zubkov. Assembler untuk DOS, Windows, Unix
- Kalashnikov. Assembler mudah!
- Tanenbaum. Sistem operasi. Implementasi dan pengembangan.
- Robert Love. Kernel Linux Deskripsi proses pengembangan.