按下键盘后为何屏幕上就会有输出

你的位置:开元ky888棋牌平台手机版网址 > 产品服务 > 按下键盘后为何屏幕上就会有输出
按下键盘后为何屏幕上就会有输出
发布日期:2022-08-07 01:13    点击次数:179

书接上回,上回书咱们说到,继内存打点组织 mem_map 和中缀形貌符表 idt 直立好今后,咱们又在内存中捣腾出一个新的数据组织 request。

并且把它们都放在了一个数组中。

这是块动作举措驱动顺序与内存缓冲区的桥梁,经由过程它可以或许完备地默示一个块动作举措读写操作要做的事。

咱们延续往下看,tty_init。

void main(void) {     ...     mem_init(main_memory_start,memory_end);     trap_init();     blk_dev_init();     chr_dev_init();     tty_init();     time_init();     sched_init();     buffer_init(buffer_memory_end);     hd_init();     floppy_init();          sti();     move_to_user_mode();     if (!fork()) {init();}     for(;;) pause(); } 

这个编制执行实现今后,咱们将会具备键盘输入到发挥阐发器输出字符这个最经常使用的功用。

关上这个函数后我有点慌。

void tty_init(void) {     rs_init();     con_init(); } 

看来这个编制已经多到需求拆成两个子编制了。

关上第一个编制,还好。

void rs_init(void) {     set_intr_gate(0x24,rs1_interrupt);     set_intr_gate(0x23,rs2_interrupt);     init(tty_table[1].read_q.data);     init(tty_table[2].read_q.data);     outb(inb_p(0x21)&0xE7,0x21); } 

这个编制是串口中缀的开启,以及配置对应的中缀处理惩罚顺序,串口在咱们今朝的 PC 机上已经很少用到了,所以这个间接轻忽,要讲我也不懂。

看第二个编制,这是重点。代码极度长,有点吓人,我先把概略框架写出。

void con_init(void) {     ...     if (ORIG_VIDEO_MODE == 7) {         ...         if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...}         else {...}     } else {         ...         if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...}         else {...}     }     ... } 

可以或许看出,极度多的 if else。

这是为了应对差别的发挥阐发情势,来分派差别的变量值,那假设咱们仅仅找出一个发挥阐发情势,这些分支就能只看一个了。 啥是发挥阐发情势呢?那咱们得俭朴说说发挥阐发,一个字符是怎么表今朝屏幕上的呢?换句话说,假设你可以或许随意操作内存和 CPU 等动作举措,你怎么操作材干使得你的发挥阐发器上,发挥阐发一个字符‘a’呢?

咱们先看一张图。

内存中有这样一部份地区,是和显存晖映的。啥意义,就是你往上图的这些内存地区中写数据,相当于写在了显存中。而往显存中写数据,就相当于在屏幕上输出文本了。

没错,就是这么俭朴。 假设咱们写这一行汇编语句。

mov [0xB8000],'h' 

后面那个 h 相当于汇编编辑器帮咱们转换成 ASCII 码的二进制数值,固然咱们也可以间接写。

mov [0xB8000],0x68 

着实就是往内存中 0xB8000 这个职位地方写了一个值,只需一写,屏幕上就会是这样。

俭朴吧,具体说来,这片内存是每两个字节默示一个表今朝屏幕上的字符,第一个是字符的编码,第二个是字符的颜色,那咱们先不论颜色,假设多写几个字符就像这样。

mov [0xB8000],'h' mov [0xB8002],'e' mov [0xB8004],'l' mov [0xB8006],'l' mov [0xB8008],'o' 

此时屏幕上就会是这样。

是否是贼俭朴?那咱们回偏激看适才的代码,咱们就假设发挥阐发情势是咱们今朝的这类文本情势,那条件分支就能去掉很多若干。 代码可以或许简化成这个样子。

#define ORIG_X          (*(unsigned char *)0x90000) #define ORIG_Y          (*(unsigned char *)0x90001) void con_init(void) {     register unsigned char a;     // 第一部份 获取发挥阐发情势相干信息     video_num_columns = (((*(unsigned short *)0x90006) & 0xff00) >> 8);     video_size_row = video_num_columns * 2;     video_num_lines = 25;     video_page = (*(unsigned short *)0x90004);     video_erase_char = 0x0720;     // 第二部份 显存晖映的内存地区      video_mem_start = 0xb8000;     video_port_reg  = 0x3d4;     video_port_val  = 0x3d5;     video_mem_end = 0xba000;     // 第三部份 动弹屏幕操作时的信息     origin  = video_mem_start;     scr_end = video_mem_start + video_num_lines * video_size_row;     top = 0;     bottom  = video_num_lines;     // 第四部份 定位光标并开启键盘中缀     gotoxy(ORIG_X, ORIG_Y);     set_trap_gate(0x21,&keyboard_interrupt);     outb_p(inb_p(0x21)&0xfd,0x21);     a=inb_p(0x61);     outb_p(a|0x80,0x61);     outb(a,0x61); } 

别看这么多,一点都不难。

首先还记不记得从前汇编言语的岁月做的事变,存了很多若干今后要用的数据在内存中。

内存地点 长度(字节) 名称 0x90000 2 光标职位地方 0x90002 2 扩张内存数 0x90004 2 发挥阐发页面 0x90006 1 发挥阐发情势 0x90007 1 字符列数 0x90008 2 未知 0x9000A 1 发挥阐发内存 0x9000B 1 发挥阐发形态 0x9000C 2 显卡特点参数 0x9000E 1 屏幕行数 0x9000F 1 屏幕列数 0x90080 16 硬盘1参数表 0x90090 16 硬盘2参数表 0x901FC 2 根动作举措号

所以,第一部份获取 0x90006 地点处的数据,就是获取发挥阐发情势等相干信息。

第二部份就是显存晖映的内存地点领域,咱们今朝假设是 CGA 范例的文本情势,所以晖映的内存是从 0xB8000 到 0xBA000。

第三部份是配置一些动弹屏幕时需求的参数,定义顶行和底行是何处,这里顶行就是第一行,底行就是最后一行,很公正。

第四部份是把光标定位到从前生活生涯的光标职位地方处(取内存地点 0x90000 处的数据),尔后配置并开启键盘中缀。

开启键盘中缀后,键盘上敲击一个按键后就会触发中缀,中缀顺序就会读键盘码转换成 ASCII 码,尔后写到光标处的内存地点,也就相当于往显存写,是以这个键盘敲击的字符就表今朝了屏幕上。

这通通具体是怎么做到的呢?咱们先看看咱们干了什么。

1. 咱们今朝痛处已有信息已经可以或许实现往屏幕上的肆意职位地方写字符了,并且还能指定颜色。

2. 并且,咱们也能担任键盘中缀,痛处键盘码中缀处理惩罚顺序就能得悉哪一个键按下了。

有了这俩功用,那咱们想干吗还不是随心所欲?

好,接上去咱们看看代码是怎么处理惩罚的,很俭朴。通通的起点,就是第四步的 gotoxy 函数,定位今后光标。

#define ORIG_X          (*(unsigned char *)0x90000) #define ORIG_Y          (*(unsigned char *)0x90001) void con_init(void) {     ...     // 第四部份 定位光标并开启键盘中缀     gotoxy(ORIG_X, ORIG_Y);     ... } 

这内里干吗了呢?

static inline void gotoxy(unsigned int new_x,unsigned int new_y) {    ...    x = new_x;    y = new_y;    pos = origin + y*video_size_row + (x<<1); } 

就是给 x y pos 这三个参数附上了值。

个中 x 默示光标在哪一列,y 默示光标在哪一行,pos 默示痛处列号和行号计算进去的内存指针,也就是往这个 pos 指向的地点处写数据,就相当于往掌握台的 x 列 y 行处写入字符了,俭朴吧?

尔后,当你按下键盘后,触发键盘中缀,今后的顺序调用链是这样的。

_keyboard_interrupt:     ...     call _do_tty_interrupt     ...      void do_tty_interrupt(int tty) {    copy_to_cooked(tty_table+tty); }  void copy_to_cooked(struct tty_struct * tty) {     ...     tty->write(tty);     ... }  // 掌握台时 tty 的 write 为 con_write 函数 void con_write(struct tty_struct * tty) {     ...     __asm__("movb _attr,%%ah\n\t"       "movw %%ax,%1\n\t"       ::"a" (c),"m" (*(short *)pos)       :"ax");      pos += 2;      x++;     ... } 

后面的进程不消管,咱们看最后一个函数 con_write 中的关键代码。

__asm__ 内联汇编,就是把键盘输入的字符 c 写入pos 指针指向的内存,相当于往屏幕输出了。

今后两行 pos+=2 和 x++,就是调整所谓的光标。

你看,写入一个字符,最底层,着实就是往内存的某处写个数据,尔后顺便调整一下光标。

由此咱们也可以看出,光标的本质,着实就是这里的 x y pos 这仨变量而已。

咱们还可以或许做换行结果,当缔造光标职位地方处于某一行的扫尾时(这个该当很好算吧,咱们都晓得屏幕上一共有几行几列了),就把光标计算出一个新值,让其处于下一行的扫尾。

就一个小计算公式即可搞定,仍然在 con_write 源码处有发挥阐发,就是鉴定列号 x 是否大于了总列数。

void con_write(struct tty_struct * tty) {     ...     if (x>=video_num_columns) {         x -= video_num_columns;         pos -= video_size_row;         lf();   }   ... }  static void lf(void) {    if (y+1<bottom) {       y++;       pos += video_size_row;       return;    }  ... } 

类似的,咱们还可以或许实现滚屏的结果,无非就是当检测到光标已经出当初最后一行最后一列了,那就把每一行的字符,都复制到它上一行,着实就是算好哪些内存地点上的值,拷贝到哪些内存地点,就行了。

这里巨匠自身看源码寻找。 所以,有了这个初始化事变,咱们就能行使这些信息,弄几个小算法,实现种种咱们罕见掌握台的操作。

或许换句话说,咱们见惯不怪的掌握台,回车、换行、删除、滚屏、清屏等操作,着实底层都要实现响应的代码的。 所以 console.c 中的别的编制就是做这个事的,咱们就不开展每一个功用的编制体了,俭朴看看有哪些编制。

// 定位光标的 static inline void gotoxy(unsigned int new_x, unsigned int new_y){} // 滚屏,即内容向上动弹一行 static void scrup(void){} // 光标同列职位地方下移一行 static void lf(int currcons){} // 光标回到第一列 static void cr(void){} ... // 删除一行 static void delete_line(void){} 

内容单一,但没什么难度,只需懂患有根抵道理即可了。

OK,全副 console.c 就讲完了,要晓得这个文件但是全副内核中代码量最大的文件,但是功用特殊单一,也都很俭朴,主若是处理惩罚键盘种种差别的按键,需求写很多若干 switch case 等语句,异常麻烦,咱们这里就齐全没须要去开展了,就是个夫役活。 到这里,咱们就正式讲完了 tty_init 的浸染。

在此今后,内核代码就能用它来方便地在掌握台输出字符啦!这在今后内核想要在启动进程中陈诉用户一些信息,以及后面内核齐全直立起来今后,由用户用 shell 举行操作时手动输入敕令,都是可以或许用到这里的代码的! 让咱们延续向前进发,看下一个被初始化的倒楣鬼是什么东东。 欲知后事怎么,且听下回合成。

本文转载自微信群众号「低并发编程」,可以或许经由过程下列二维码关注。转载本文请联络低并发编程群众号。本网站已获取低并发编程的授权。