Pengembangan OS seperti Unix - Driver perangkat karakter (8)

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

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; /* should be at first */ int number; /* interrupt number */ ih_low_cb_t handler; /* interrupt 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; /* 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; } 

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

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

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

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

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

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

Handler interupsi bawah untuk keyboard didefinisikan sebagai berikut:

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

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:

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

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.

 /* * Key press high half handler */ 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.

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

Operasi karakter demi karakter tidak jauh lebih rumit dan saya rasa mereka tidak perlu berkomentar.

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

Tetap hanya mengontrol mode input dan output untuk mengimplementasikan 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(); } } 

Sekarang kami menerapkan output input file di tingkat perpustakaan kami 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); } 

Nah, inilah beberapa fungsi tingkat tinggi:

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

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


  1. James Molloy. Gulung mainan Anda sendiri UNIX-clone OS.
  2. Zubkov. 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/id468509/


All Articles