【自制操作系统14】实现键盘输入

一、到目前为止的程序流程图

  为了让大家清楚目前的程序进度,画了到目前为止的程序流程图,如下。(红色部分就是我们今天要实现的)

二、简单打通键盘中断

  既然要打通键盘中断,那必然需要你回顾一下 【自制操作系统08】中断 所讲述的外部中断的流程,下面我把图贴上。

如图所示,将上图中的某外部设备,换成下图中的具体的键盘,就是键盘中断流程啦。简单说就是:

  • 因此每当有击键发生时,键盘中的设备 8048 会把键盘扫描码发给主板上的设备 8042
  • 8042 是按字节来处理的,每处理一个字节的扫描码后,将其存储到自己的 输出缓冲区 寄存器。
  • 然后向中断代理 8059A 发中断信号,这样我们的键盘 中断处理程序 通过读取 8042 的输出缓冲区寄存器,会获得键盘扫描码。

那我们 CPU 收到的中断号是多少呢?我们看下面两段代码

 1 static void pic_init(void) {
 2
 3     /*初始化主片 */
 4     outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4
 5     outb (PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为0x20, 也就是IR[0-7] 为 0x20 ~ 0x27
 6     outb (PIC_M_DATA, 0x04); // ICW3: IR2 接从片
 7     outb (PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常EOI
 8
 9     /*初始化从片 */
10     outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4
11     outb (PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为0x28, 也就是IR[8-15]为0x28 ~ 0x2F
12     outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2 引脚
13     outb (PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常EOI
14
15     /*打开主片上IR0,也就是目前只接受时钟产生的中断 */
16     // 测试键盘中断 0xfd
17     outb (PIC_M_DATA, 0xfd);
18     outb (PIC_S_DATA, 0xff);
19     ...
20 }
 1 VECTOR 0x20,ZERO ;时钟中断对应的入口
 2 VECTOR 0x21,ZERO ;键盘中断对应的入口
 3 VECTOR 0x22,ZERO ;级联用的
 4 VECTOR 0x23,ZERO ;串口2 对应的入口
 5 VECTOR 0x24,ZERO ;串口1 对应的入口
 6 VECTOR 0x25,ZERO ;并口2 对应的入口
 7 VECTOR 0x26,ZERO ;软盘对应的入口
 8 VECTOR 0x27,ZERO ;并口1 对应的入口
 9 VECTOR 0x28,ZERO ;实时时钟对应的入口
10 VECTOR 0x29,ZERO ;重定向
11 VECTOR 0x2a,ZERO ;保留
12 VECTOR 0x2b,ZERO ;保留
13 VECTOR 0x2c,ZERO ;ps/2 鼠标
14 VECTOR 0x2d,ZERO ;fpu 浮点单元异常
15 VECTOR 0x2e,ZERO ;硬盘
16 VECTOR 0x2f,ZERO ;保留

我们将 8059A 这个设备的 IR0 端口设置了起始中断号为 0x20,这是我们自己定义的,也就是说可以改的,再看下硬件定死的东西。

 可以看出,键盘被固定连接在了 IR1 口上。也就是说,通过硬件的固定连接,以及我们软件将 IR0 设定为了初始中断号 0x20,所以导致了我们按下键盘后的中断向量号为 20。这块说出来真的很简单很直观,但我刚学的时候,硬是没想明白这个道理。

 OK,大功告成,接下来我们用之前已有的代码就好了,就是将一段中断程序,对应给 0x21 这个中断向量号。

keyboard.c

 1 #include "keyboard.h"
 2 #include "print.h"
 3 #include "interrupt.h"
 4 #include "io.h"
 5 #include "global.h"
 6
 7 #define KBD_BUF_PORT 0x60 // 键盘 buffer 寄存器端口号为 0x60
 8
 9 // 键盘中断处理程序
10 static void intr_keyboard_handler(void) {
11     put_char(‘k‘);
12     inb(KBD_BUF_PORT);
13     return;
14 }
15
16 // 键盘初始化
17 void keyboard_init() {
18     put_str("keyboard init start\n");
19     register_handler(0x21, intr_keyboard_handler);
20     put_str("keyboard init done\n");
21 }

init.c

1 ...
2 mem_init(); // 初始化内存管理
3 thread_init(); // 初始化线程相关结构
4 console_init(); // 控制台初始化
5 keyboard_init(); // 键盘初始化
6 ...

运行后可以看到,每次我们按下键盘,就在屏幕上输出 ‘k‘,由于我们没做其他处理,不论按什么键,都会只在屏幕上输出 ’k‘,有个细节就是按下一个键会输出两个‘k’,是因为键盘的按下和弹起是会传输两个键盘码,也会发生两次中断。

虽然只是简简单单输出一个‘k’,但还是很兴奋,我觉得往往最难的地方就是打通和硬件的交互,把控制权交给软件,剩下的事就掌控在我们手中啦,我们继续往下看。

三、实现输入字符缓冲区

  这块我懒了,不想看代码了,直接把书中的代码全部 copy 过来了。一是因为这块是为后续的用户交互进程,也就是 shell 做准备的;二是因为这块十分繁琐,又很好理解,简单说就是,把输入进来的键盘码转换成 ASCII 码,并输出到一个缓冲区(我们用队列结构实现)里,另外意思一下跑两个线程从缓冲区里拿数据,直接输出到屏幕上。

  那不难想象后续的用户进程,无非就是 shell 进程读取缓冲区数据,输出到屏幕,遇到回车后把整个字符串理解一下,交给指定程序去处理,我猜的啊。

  所以这块直接把代码放上来。

 1 #include "ioqueue.h"
 2 #include "interrupt.h"
 3 #include "global.h"
 4 #include "debug.h"
 5
 6 /* 初始化io队列ioq */
 7 void ioqueue_init(struct ioqueue* ioq) {
 8    lock_init(&ioq->lock);     // 初始化io队列的锁
 9    ioq->producer = ioq->consumer = NULL;  // 生产者和消费者置空
10    ioq->head = ioq->tail = 0; // 队列的首尾指针指向缓冲区数组第0个位置
11 }
12
13 /* 返回pos在缓冲区中的下一个位置值 */
14 static int32_t next_pos(int32_t pos) {
15    return (pos + 1) % bufsize;
16 }
17
18 /* 判断队列是否已满 */
19 bool ioq_full(struct ioqueue* ioq) {
20    ASSERT(intr_get_status() == INTR_OFF);
21    return next_pos(ioq->head) == ioq->tail;
22 }
23
24 /* 判断队列是否已空 */
25 bool ioq_empty(struct ioqueue* ioq) {
26    ASSERT(intr_get_status() == INTR_OFF);
27    return ioq->head == ioq->tail;
28 }
29
30 /* 使当前生产者或消费者在此缓冲区上等待 */
31 static void ioq_wait(struct task_struct** waiter) {
32    ASSERT(*waiter == NULL && waiter != NULL);
33    *waiter = running_thread();
34    thread_block(TASK_BLOCKED);
35 }
36
37 /* 唤醒waiter */
38 static void wakeup(struct task_struct** waiter) {
39    ASSERT(*waiter != NULL);
40    thread_unblock(*waiter);
41    *waiter = NULL;
42 }
43
44 /* 消费者从ioq队列中获取一个字符 */
45 char ioq_getchar(struct ioqueue* ioq) {
46    ASSERT(intr_get_status() == INTR_OFF);
47
48 /* 若缓冲区(队列)为空,把消费者ioq->consumer记为当前线程自己,
49  * 目的是将来生产者往缓冲区里装商品后,生产者知道唤醒哪个消费者,
50  * 也就是唤醒当前线程自己*/
51    while (ioq_empty(ioq)) {
52       lock_acquire(&ioq->lock);
53       ioq_wait(&ioq->consumer);
54       lock_release(&ioq->lock);
55    }
56
57    char byte = ioq->buf[ioq->tail];      // 从缓冲区中取出
58    ioq->tail = next_pos(ioq->tail);      // 把读游标移到下一位置
59
60    if (ioq->producer != NULL) {
61       wakeup(&ioq->producer);          // 唤醒生产者
62    }
63
64    return byte;
65 }
66
67 /* 生产者往ioq队列中写入一个字符byte */
68 void ioq_putchar(struct ioqueue* ioq, char byte) {
69    ASSERT(intr_get_status() == INTR_OFF);
70
71 /* 若缓冲区(队列)已经满了,把生产者ioq->producer记为自己,
72  * 为的是当缓冲区里的东西被消费者取完后让消费者知道唤醒哪个生产者,
73  * 也就是唤醒当前线程自己*/
74    while (ioq_full(ioq)) {
75       lock_acquire(&ioq->lock);
76       ioq_wait(&ioq->producer);
77       lock_release(&ioq->lock);
78    }
79    ioq->buf[ioq->head] = byte;      // 把字节放入缓冲区中
80    ioq->head = next_pos(ioq->head); // 把写游标移到下一位置
81
82    if (ioq->consumer != NULL) {
83       wakeup(&ioq->consumer);          // 唤醒消费者
84    }
85 }
86
87 /* 返回环形缓冲区中的数据长度 */
88 uint32_t ioq_length(struct ioqueue* ioq) {
89    uint32_t len = 0;
90    if (ioq->head >= ioq->tail) {
91       len = ioq->head - ioq->tail;
92    } else {
93       len = bufsize - (ioq->tail - ioq->head);
94    }
95    return len;
96 }

ioqueue.c

  1 #include "interrupt.h"
  2 #include "io.h"
  3 #include "global.h"
  4 #include "ioqueue.h"
  5
  6 #define KBD_BUF_PORT 0x60 // 键盘 buffer 寄存器端口号为 0x60
  7 #define KBD_BUF_PORT 0x60     // 键盘buffer寄存器端口号为0x60
  8
  9 // 键盘中断处理程序
 10 /* 用转义字符定义部分控制字符 */
 11 #define esc        ‘\033‘     // 八进制表示字符,也可以用十六进制‘\x1b‘
 12 #define backspace    ‘\b‘
 13 #define tab        ‘\t‘
 14 #define enter        ‘\r‘
 15 #define delete        ‘\177‘     // 八进制表示字符,十六进制为‘\x7f‘
 16
 17 /* 以上不可见字符一律定义为0 */
 18 #define char_invisible    0
 19 #define ctrl_l_char    char_invisible
 20 #define ctrl_r_char    char_invisible
 21 #define shift_l_char    char_invisible
 22 #define shift_r_char    char_invisible
 23 #define alt_l_char    char_invisible
 24 #define alt_r_char    char_invisible
 25 #define caps_lock_char    char_invisible
 26
 27 /* 定义控制字符的通码和断码 */
 28 #define shift_l_make    0x2a
 29 #define shift_r_make     0x36
 30 #define alt_l_make       0x38
 31 #define alt_r_make       0xe038
 32 #define alt_r_break       0xe0b8
 33 #define ctrl_l_make      0x1d
 34 #define ctrl_r_make      0xe01d
 35 #define ctrl_r_break     0xe09d
 36 #define caps_lock_make     0x3a
 37
 38 struct ioqueue kbd_buf;       // 定义键盘缓冲区
 39
 40 /* 定义以下变量记录相应键是否按下的状态,
 41  * ext_scancode用于记录makecode是否以0xe0开头 */
 42 static bool ctrl_status, shift_status, alt_status, caps_lock_status, ext_scancode;
 43
 44 /* 以通码make_code为索引的二维数组 */
 45 static char keymap[][2] = {
 46 /* 扫描码   未与shift组合  与shift组合*/
 47 /* ---------------------------------- */
 48 /* 0x00 */    {0,    0},
 49 /* 0x01 */    {esc,    esc},
 50 /* 0x02 */    {‘1‘,    ‘!‘},
 51 /* 0x03 */    {‘2‘,    ‘@‘},
 52 /* 0x04 */    {‘3‘,    ‘#‘},
 53 /* 0x05 */    {‘4‘,    ‘$‘},
 54 /* 0x06 */    {‘5‘,    ‘%‘},
 55 /* 0x07 */    {‘6‘,    ‘^‘},
 56 /* 0x08 */    {‘7‘,    ‘&‘},
 57 /* 0x09 */    {‘8‘,    ‘*‘},
 58 /* 0x0A */    {‘9‘,    ‘(‘},
 59 /* 0x0B */    {‘0‘,    ‘)‘},
 60 /* 0x0C */    {‘-‘,    ‘_‘},
 61 /* 0x0D */    {‘=‘,    ‘+‘},
 62 /* 0x0E */    {backspace, backspace},
 63 /* 0x0F */    {tab,    tab},
 64 /* 0x10 */    {‘q‘,    ‘Q‘},
 65 /* 0x11 */    {‘w‘,    ‘W‘},
 66 /* 0x12 */    {‘e‘,    ‘E‘},
 67 /* 0x13 */    {‘r‘,    ‘R‘},
 68 /* 0x14 */    {‘t‘,    ‘T‘},
 69 /* 0x15 */    {‘y‘,    ‘Y‘},
 70 /* 0x16 */    {‘u‘,    ‘U‘},
 71 /* 0x17 */    {‘i‘,    ‘I‘},
 72 /* 0x18 */    {‘o‘,    ‘O‘},
 73 /* 0x19 */    {‘p‘,    ‘P‘},
 74 /* 0x1A */    {‘[‘,    ‘{‘},
 75 /* 0x1B */    {‘]‘,    ‘}‘},
 76 /* 0x1C */    {enter,  enter},
 77 /* 0x1D */    {ctrl_l_char, ctrl_l_char},
 78 /* 0x1E */    {‘a‘,    ‘A‘},
 79 /* 0x1F */    {‘s‘,    ‘S‘},
 80 /* 0x20 */    {‘d‘,    ‘D‘},
 81 /* 0x21 */    {‘f‘,    ‘F‘},
 82 /* 0x22 */    {‘g‘,    ‘G‘},
 83 /* 0x23 */    {‘h‘,    ‘H‘},
 84 /* 0x24 */    {‘j‘,    ‘J‘},
 85 /* 0x25 */    {‘k‘,    ‘K‘},
 86 /* 0x26 */    {‘l‘,    ‘L‘},
 87 /* 0x27 */    {‘;‘,    ‘:‘},
 88 /* 0x28 */    {‘\‘‘,    ‘"‘},
 89 /* 0x29 */    {‘`‘,    ‘~‘},
 90 /* 0x2A */    {shift_l_char, shift_l_char},
 91 /* 0x2B */    {‘\\‘,    ‘|‘},
 92 /* 0x2C */    {‘z‘,    ‘Z‘},
 93 /* 0x2D */    {‘x‘,    ‘X‘},
 94 /* 0x2E */    {‘c‘,    ‘C‘},
 95 /* 0x2F */    {‘v‘,    ‘V‘},
 96 /* 0x30 */    {‘b‘,    ‘B‘},
 97 /* 0x31 */    {‘n‘,    ‘N‘},
 98 /* 0x32 */    {‘m‘,    ‘M‘},
 99 /* 0x33 */    {‘,‘,    ‘<‘},
100 /* 0x34 */    {‘.‘,    ‘>‘},
101 /* 0x35 */    {‘/‘,    ‘?‘},
102 /* 0x36    */    {shift_r_char, shift_r_char},
103 /* 0x37 */    {‘*‘,    ‘*‘},
104 /* 0x38 */    {alt_l_char, alt_l_char},
105 /* 0x39 */    {‘ ‘,    ‘ ‘},
106 /* 0x3A */    {caps_lock_char, caps_lock_char}
107 /*其它按键暂不处理*/
108 };
109
110 /* 键盘中断处理程序 */
111 static void intr_keyboard_handler(void) {
112     put_char(‘k‘);
113     inb(KBD_BUF_PORT);
114     return;
115
116 /* 这次中断发生前的上一次中断,以下任意三个键是否有按下 */
117    bool ctrl_down_last = ctrl_status;
118    bool shift_down_last = shift_status;
119    bool caps_lock_last = caps_lock_status;
120
121    bool break_code;
122    uint16_t scancode = inb(KBD_BUF_PORT);
123
124 /* 若扫描码是e0开头的,表示此键的按下将产生多个扫描码,
125  * 所以马上结束此次中断处理函数,等待下一个扫描码进来*/
126    if (scancode == 0xe0) {
127       ext_scancode = true;    // 打开e0标记
128       return;
129    }
130
131 /* 如果上次是以0xe0开头,将扫描码合并 */
132    if (ext_scancode) {
133       scancode = ((0xe000) | scancode);
134       ext_scancode = false;   // 关闭e0标记
135    }
136
137    break_code = ((scancode & 0x0080) != 0);   // 获取break_code
138
139    if (break_code) {   // 若是断码break_code(按键弹起时产生的扫描码)
140
141    /* 由于ctrl_r 和alt_r的make_code和break_code都是两字节,
142    所以可用下面的方法取make_code,多字节的扫描码暂不处理 */
143       uint16_t make_code = (scancode &= 0xff7f);   // 得到其make_code(按键按下时产生的扫描码)
144
145    /* 若是任意以下三个键弹起了,将状态置为false */
146       if (make_code == ctrl_l_make || make_code == ctrl_r_make) {
147      ctrl_status = false;
148       } else if (make_code == shift_l_make || make_code == shift_r_make) {
149      shift_status = false;
150       } else if (make_code == alt_l_make || make_code == alt_r_make) {
151      alt_status = false;
152       } /* 由于caps_lock不是弹起后关闭,所以需要单独处理 */
153
154       return;   // 直接返回结束此次中断处理程序
155
156    }
157    /* 若为通码,只处理数组中定义的键以及alt_right和ctrl键,全是make_code */
158    else if ((scancode > 0x00 && scancode < 0x3b) || 159            (scancode == alt_r_make) || 160            (scancode == ctrl_r_make)) {
161       bool shift = false;  // 判断是否与shift组合,用来在一维数组中索引对应的字符
162       if ((scancode < 0x0e) || (scancode == 0x29) || 163      (scancode == 0x1a) || (scancode == 0x1b) || 164      (scancode == 0x2b) || (scancode == 0x27) || 165      (scancode == 0x28) || (scancode == 0x33) || 166      (scancode == 0x34) || (scancode == 0x35)) {
167         /****** 代表两个字母的键 ********
168              0x0e 数字‘0‘~‘9‘,字符‘-‘,字符‘=‘
169              0x29 字符‘`‘
170              0x1a 字符‘[‘
171              0x1b 字符‘]‘
172              0x2b 字符‘\\‘
173              0x27 字符‘;‘
174              0x28 字符‘\‘‘
175              0x33 字符‘,‘
176              0x34 字符‘.‘
177              0x35 字符‘/‘
178         *******************************/
179      if (shift_down_last) {  // 如果同时按下了shift键
180         shift = true;
181      }
182       } else {      // 默认为字母键
183      if (shift_down_last && caps_lock_last) {  // 如果shift和capslock同时按下
184         shift = false;
185      } else if (shift_down_last || caps_lock_last) { // 如果shift和capslock任意被按下
186         shift = true;
187      } else {
188         shift = false;
189      }
190       }
191
192       uint8_t index = (scancode &= 0x00ff);  // 将扫描码的高字节置0,主要是针对高字节是e0的扫描码.
193       char cur_char = keymap[index][shift];  // 在数组中找到对应的字符
194
195    /* 如果cur_char不为0,也就是ascii码为除‘\0‘外的字符就加入键盘缓冲区中 */
196       if (cur_char) {
197
198      /*****************  快捷键ctrl+l和ctrl+u的处理 *********************
199       * 下面是把ctrl+l和ctrl+u这两种组合键产生的字符置为:
200       * cur_char的asc码-字符a的asc码, 此差值比较小,
201       * 属于asc码表中不可见的字符部分.故不会产生可见字符.
202       * 我们在shell中将ascii值为l-a和u-a的分别处理为清屏和删除输入的快捷键*/
203      if ((ctrl_down_last && cur_char == ‘l‘) || (ctrl_down_last && cur_char == ‘u‘)) {
204         cur_char -= ‘a‘;
205      }
206       /****************************************************************/
207
208    /* 若kbd_buf中未满并且待加入的cur_char不为0,
209     * 则将其加入到缓冲区kbd_buf中 */
210      if (!ioq_full(&kbd_buf)) {
211         ioq_putchar(&kbd_buf, cur_char);
212      }
213      return;
214       }
215
216       /* 记录本次是否按下了下面几类控制键之一,供下次键入时判断组合键 */
217       if (scancode == ctrl_l_make || scancode == ctrl_r_make) {
218      ctrl_status = true;
219       } else if (scancode == shift_l_make || scancode == shift_r_make) {
220      shift_status = true;
221       } else if (scancode == alt_l_make || scancode == alt_r_make) {
222      alt_status = true;
223       } else if (scancode == caps_lock_make) {
224       /* 不管之前是否有按下caps_lock键,当再次按下时则状态取反,
225        * 即:已经开启时,再按下同样的键是关闭。关闭时按下表示开启。*/
226      caps_lock_status = !caps_lock_status;
227       }
228    } else {
229       put_str("unknown key\n");
230    }
231 }
232
233 // 键盘初始化
234 /* 键盘初始化 */
235 void keyboard_init() {
236     put_str("keyboard init start\n");
237     register_handler(0x21, intr_keyboard_handler);
238     put_str("keyboard init done\n");
239    put_str("keyboard init start\n");
240    ioqueue_init(&kbd_buf);
241    register_handler(0x21, intr_keyboard_handler);
242    put_str("keyboard init done\n");
243 }

keyboard.c

main.c

 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 #include "interrupt.h"
 5
 6 #include "ioqueue.h"
 7 #include "keyboard.h"
 8
 9 void k_thread_a(void*);
10 void k_thread_b(void*);
11
12 int main(void){
13     put_str("I am kernel\n");
14     init_all();
15
16     thread_start("consumer_a", 31, k_thread_a, "AOUT_");
17     thread_start("consumer_b", 31, k_thread_b, "BOUT_");
18
19     intr_enable();
20
21     while(1) {
22         //console_put_str("Main ");
23     }
24     return 0;
25 }
26
27 void k_thread_a(void* arg) {
28     while(1) {
29         enum intr_status old_status = intr_disable();
30         if (!ioq_empty(&kbd_buf)) {
31             console_put_str(arg);
32             char byte = ioq_getchar(&kbd_buf);
33             console_put_char(byte);
34             console_put_str("\n");
35         }
36         intr_set_status(old_status);
37     }
38 }
39
40 void k_thread_b(void* arg) {
41     while(1) {
42         enum intr_status old_status = intr_disable();
43         if (!ioq_empty(&kbd_buf)) {
44             console_put_str(arg);
45             char byte = ioq_getchar(&kbd_buf);
46             console_put_char(byte);
47             console_put_str("\n");
48         }
49         intr_set_status(old_status);
50     }
51 }

  第一个 ioqueue.c 就是个队列的实现类,准确说是个线程安全队列。第二个 keyboard.c 从我们原来无论按什么键都输出 ‘k’,变成了把键盘码转换成 ASCII,还包括对 controll 键等处理,反正就是一堆杂事,转换成我们平时认知中按键应该对应的字符,把这个字符的 ASCII 码放入队列,等着 main.c 的两个线程取出来,打印在屏幕上,就这么点事,但每个字符都要细心处理,十分繁琐。

  所以不再赘述,不影响我们理解操作系统主流程,运行后结果如下

写在最后:开源项目和课程规划

如果你对自制一个操作系统感兴趣,不妨跟随这个系列课程看下去,甚至加入我们(下方有公众号和小助手微信),一起来开发。

参考书籍

《操作系统真相还原》这本书真的赞!强烈推荐

项目开源

项目开源地址:https://gitee.com/sunym1993/flashos

当你看到该文章时,代码可能已经比文章中的又多写了一些部分了。你可以通过提交记录历史来查看历史的代码,我会慢慢梳理提交历史以及项目说明文档,争取给每一课都准备一个可执行的代码。当然文章中的代码也是全的,采用复制粘贴的方式也是完全可以的。

如果你有兴趣加入这个自制操作系统的大军,也可以在留言区留下您的联系方式,或者在 gitee 私信我您的联系方式。

课程规划

本课程打算出系列课程,我写到哪觉得可以写成一篇文章了就写出来分享给大家,最终会完成一个功能全面的操作系统,我觉得这是最好的学习操作系统的方式了。所以中间遇到的各种坎也会写进去,如果你能持续跟进,跟着我一块写,必然会有很好的收货。即使没有,交个朋友也是好的哈哈。

目前的系列包括

微信公众号

  我要去阿里(woyaoquali)

 小助手微信号

  Angel(angel19980323)

原文地址:https://www.cnblogs.com/flashsun/p/12490765.html

时间: 2024-10-10 12:56:38

【自制操作系统14】实现键盘输入的相关文章

[自制简单操作系统] 5、杂七杂八(高分辨率和键盘输入)

前言: >_<" 这几天正在研究一个好玩的,准备写<软硬结合第三篇——科班的还是可以修电脑的>,可是当前遇到个技术难点——WHDI,所以操作系统这里就慢了好大一节啦!但是以操作系统多任务的思路,感觉还是把这个优先级并不是太低的进程拿出来做一下吧!毕竟技术难点有时候需要灵感的哈~(不过有大神知道VGA接口的通信原理吗?求给个好一点的链接看看!).回到正题上来,这次学的东西有点杂,主要就是高分辨率和键盘输入相关~对于前者和汇编及硬件联系较多(具体可以查VBE Video El

(一)Python入门-2编程基本概念:14字符串-转义字符-字符串拼接-字符串复制-input()获得键盘输入

一:转义字符 我们可以使用“\+特殊字符”,实现某些难以用字符表示的效果.比如:换行等.常见的 转义字符有这些: 转义字符 描述 \(在行尾时) 续行符 \\ 反斜杠符号 \' 单引号 \" 双引号 \b 退格(Backspace) \n 换行 \t 横向制表符 \r 回车 [操作]测试转义字符的使用 1 >>> a = 'i\nlove\nu' 2 >>> a 3 'i\nlove\nu' 4 >>> print(a) 5 i 6 love

《30天自制操作系统》读书笔记(2)hello, world

让系统跑起来 要写一个操作系统,我们首先要有一个储存系统的介质,原版书似乎是06年出版的,可惜那时候没有电脑,没想到作者用的还是软盘,现在的电脑谁有软驱?不得已我使用一张128M的SD卡来代替,而事实上你用的是U盘还是软盘对我们的操作系统没有影响,缺点是你的U盘刷入系统后容量只能是1440 MB,即当年流行的3.5英寸软盘的大小,当然不用担心,再格式化一次(用DiskGeniu),就可以恢复. 我做事情的话,总是怕自己的努力的结果白费了,害怕辛辛苦苦看完这本书但是发现做出来的东西现在根本没法用,

《30天自制操作系统》笔记(12)——多任务入门

<30天自制操作系统>笔记(12)——多任务入门 进度回顾 上一篇介绍了设置显示器高分辨率的方法.本篇讲一下操作系统实现多任务的方法. 什么是多任务 对程序员来说,也许这是废话,不过还是说清楚比较好. 多任务就是让电脑同时运行多个程序(如一边写代码一边听音乐一边下载电影). 电脑的CPU只有固定有限的那么一个或几个,不可能真的同时运行多个程序.所以就用近似的方式,让多个程序轮换着运行.当轮换速度够快(0.01秒),给人的感觉就是"同时"运行了. 多任务之不实用版 我们首先从

&lt;&lt;30天自制操作系统&gt;&gt;(1)初体验汇编程序

我们这次使用的汇编语言编译器是原书作者自己开发的,名为“nask”,很多语法和著名的汇编语言编译器nasm很像.由于原书作者没有给出有哪些不同,这里就无法给出不同了! 现在仅仅使用汇编语言中的DB指令来写个“操作系统”吧. DB指令是"define byte"的缩写,往文件里写入1个字节 超长的源代码 1 DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f 2 DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01,

《30天自制操作系统》笔记(08)——叠加窗口刷新

<30天自制操作系统>笔记(08)--叠加窗口刷新 进度回顾 上一篇中介绍了内存管理的思路和算法,我们已经可以动态申请和释放内存了.这不就是堆(Heap)么.在此基础上,本篇要做一段程序,一并解决窗口和鼠标的叠加处理问题. 问题 在之前的<<30天自制操作系统>笔记(05)--启用鼠标键盘>篇,已经能够移动鼠标了.但是遗留了如下图所示的一个小问题. 我们希望的情形是这样的: 实际上,当前版本的OS还没有窗口图层的东西.本篇要做一段程序,一并解决窗口和鼠标的叠加处理问题.

《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

<30天自制操作系统>笔记(01)--hello bitzhuwei's OS! 最初的OS代码 1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序的装载地址 5 6 ; 以下这段是标准FAT32格式软盘专用的代码 7 8 JMP entry 9 DB 0x90 10 DB "HELLOIPL" ; freeparam 启动区的名称可以是任意的字符串(8字节) 11 DW 512 ; 每个扇区(sector)的大小(必须为512字节)

30天自制操作系统之第13天 定时器(2)

这一节我们同样只讲优化工作,关于缓冲区的优化. 我们为键盘.鼠标和定时器各维护了一个FIFO缓冲区,如果定时器有100个,我们要创建100个FIFO缓冲区.这是不优雅的. FIFO缓冲区的作用:拿定时器1来说,我们怎样知道定时器1超时了.假设它的超时时间是10s,那么10s后,定时器1被告知超时,同时往它的FIFO缓冲区写入数据,这样在其他地方,我们只需查看它的FIFO缓冲区是否有数据,就知道它是否发生了超时,如果超时了进行相应的提示工作.其实简单的说,FIFO缓冲区起到了通讯员的作用. 回到我

《30天自制操作系统》笔记(02)——导入C语言

<30天自制操作系统>笔记(02)--导入C语言 进度回顾 在上一篇,记录了计算机开机时加载IPL程序(initial program loader,一个nas汇编程序)的情况,包括IPL代码(helloos.nas).编译生成helloos.img文件.用虚拟机QEMU加载helloos.img.制作U盘启动盘和用物理机加载helloos.img. 计算机启动时会自动加载和执行IPL程序,但IPL程序只能占用512字节.若直接用IPL写OS,空间不够用.所以IPL程序一般用于将真正的OS程序