《操作系统真象还原》第十三篇:实现io缓冲区和键盘驱动

第十三篇:实现io缓冲区和键盘驱动

环形缓冲区

计划将键盘的输入存入一个io缓冲区中,以备消费者获取。

因为缓冲区大小固定,所以采用环形队列的数据结构。

环形队列结构如下:

在这里插入图片描述

#define bufsize 64

//环形队列
struct ioqueue {
	//生产者消费者问题
	struct lock lock;
	//此项用于记录哪个生产者在此缓冲区上睡眠
	struct task_struct* producer;
	//此项用于记录哪个消费者在此缓冲区上睡眠
	struct task_struct* consumer;
	//缓冲区
	uint8_t buf[bufsize];
	//队首
	int32_t head;
	//队尾
	int32_t tail;
};

相关函数如下:

//初始化io队列
void ioqueue_init(struct ioqueue* ioq) {
	lock_init(&ioq->lock);
	ioq->producer = NULL;
	ioq->consumer = NULL;
	ioq->head = 0;
	ioq->tail = 0;
}

//返回pos在缓冲区的下一个位置
int32_t next_pos(uint32_t pos) {
	return (pos + 1) % bufsize;
}

//判断队列是否已满
bool ioq_full(struct ioqueue* ioq) {
	ASSERT(get_intr_status() == INTR_OFF);
	return next_pos(ioq->head) == ioq->tail; 
}

//判断队列是否已空
bool ioq_empty(struct ioqueue* ioq) {
	ASSERT(get_intr_status() == INTR_OFF);
	return ioq->head == ioq->tail;
}

//使当前生产者或消费者在缓冲区上等待
void ioq_wait(struct task_struct** waiter) {
	ASSERT(*waiter == NULL && waiter != NULL);
	*waiter = running_thread();
	thread_block(TASK_BLOCKED);
}

//唤醒waiter
void wakeup(struct task_struct** waiter) {
	ASSERT(*waiter != NULL);
	thread_unblock(*waiter);
	*waiter = NULL;
}

//消费者从ioq队列中获取一个字符
uint8_t ioq_getchar(struct ioqueue* ioq) {
	//确保在关中断情况下运行
	ASSERT(get_intr_status() == INTR_OFF);
	
	//如果队列为空,消费者就阻塞在缓冲区上
	//目的是让生产者知道要通知哪个消费者
	while (ioq_empty(ioq)) {
		lock_acquire(&ioq->lock);
		ioq_wait(&ioq->consumer);
		lock_release(&ioq->lock);
	}
	
	//获取一字节的数据
	uint8_t byte = ioq->buf[ioq->tail];
	ioq->tail = next_pos(ioq->tail);
	
	//唤醒生产者
	if (ioq->producer != NULL) {
		wakeup(&ioq->producer);
	}
	
	return byte;
}

//生产者往ioq队列中写入一个字符
void ioq_putchar(struct ioqueue* ioq, uint8_t byte) {
	//确保在关中断情况下运行
	ASSERT(get_intr_status() == INTR_OFF);
	
	//如果队列满,生产者就阻塞在缓冲区上
	//目的是让消费者知道要唤醒哪个生产者
	while (ioq_full(ioq)) {
		lock_acquire(&ioq->lock);
		ioq_wait(&ioq->producer);
		lock_release(&ioq->lock);
	}
	
	//往缓冲区写入数据
	ioq->buf[ioq->head] = byte;
	ioq->head = next_pos(ioq->head);
	
	//唤醒消费者
	if (ioq->consumer != NULL) {
		wakeup(&ioq->consumer);
	}
}

键盘驱动

键盘驱动用于将8042缓冲寄存器中的扫描码转换为ASCII码,以备上层模块使用。

  • 键盘扫描码共有三套,当有键盘敲击时,8048会将扫描码发给8042
  • 为了兼容性,8042同意将扫描码转为第一套扫描码,并将其存储在自身的缓冲寄存器中
  • 随后8042向8259a发送中断信号,随后键盘中断处理程序会读取扫描码并进行处理

三个中断代理的关系如下:

在这里插入图片描述

扫描码相关定义如下:

#define KBD_BUF_PORT 0x60	//键盘buffer寄存器端口号为0x60

//用转移字符定义部分控制字符(8进制)
#define esc 		'\033'
#define backspace	'\b'
#define tab			'\t'
#define enter		'\r'
#define delete		'\0177'

//以下控制字符不可见,一律定义为0
#define char_invisible	0
#define ctrl_l_char		char_invisible
#define ctrl_r_char		char_invisible
#define shift_l_char	char_invisible
#define shift_r_char	char_invisible
#define alt_l_char		char_invisible
#define alt_r_char		char_invisible
#define caps_lock_char	char_invisible

//定义控制字符的通码和段码
#define shift_l_make 0x2a 
#define shift_r_make 0x36
#define alt_l_make   0x38
#define alt_r_make   0xe038
#define alt_r_break  0xe0b8
#define ctrl_l_make  0x1d
#define ctrl_r_make  0xe01d
#define ctrl_r_break 0xe09d
#define caps_lock_make 0x3a

//定义键盘缓冲区
struct ioqueue kbd_buf;

//定义以下变量记录相应健是否按下,ext_scancode用于记录makecode是否以0xe0开头
bool ctrl_status, shift_status, alt_status, caps_lock_status, ext_scancode;

//以通码make_code为索引的二维数组
static char keymap[][2] = {
	{0, 0},
	{esc, esc},
	{'1', '!'},
	{'2', '@'},
	{'3', '#'},
	{'4', '$'},
	{'5', '%'},
	{'6', '^'},
	{'7', '&'},
	{'8', '*'},
	{'9', '('},	
	{'0', ')'},
	{'-', '_'},
	{'=', '+'},
	{backspace, backspace},
	{tab, tab},
	{'q', 'Q'},
	{'w', 'W'},
	{'e', 'E'},
	{'r', 'R'},
	{'t', 'T'},
	{'y', 'Y'},
	{'u', 'U'},
	{'i', 'I'},
	{'o', 'O'},
	{'p', 'P'},
	{'[', '{'},
	{']', '}'},
	{enter, enter},
	{ctrl_l_char, ctrl_l_char},
	{'a', 'A'},
	{'s', 'S'},
	{'d', 'D'},
	{'f', 'F'},
	{'g', 'G'},
	{'h', 'H'},
	{'j', 'J'},
	{'k', 'K'},
	{'l', 'L'},
	{';', ':'},
	{'\'', '"'},
	{'`', '~'},
	{shift_l_char, shift_l_char},
	{'\\', '|'},
	{'z', 'Z'},
	{'x', 'X'},
	{'c', 'C'},
	{'v', 'V'},
	{'b', 'B'},
	{'n', 'N'},
	{'m', 'M'},
	{',', '<'},
	{'.', '>'},
	{'/', '?'},
	{shift_r_char, shift_r_char},
	{'*', '*'},
	{alt_l_char, alt_l_char},
	{' ', ' '},
	{caps_lock_char, caps_lock_char}
	//其他键暂不处理
};

键盘中断处理函数如下:

//键盘中断处理程序
void intr_keyboard_handler(void) {
	//这次中断前的中断,以下三个键是否有按下
	bool ctrl_down_last = ctrl_status;
	bool shift_down_last = shift_status;
	bool caps_lock_last = caps_lock_status;
	
	bool break_code;
	uint16_t scancode = inb(KBD_BUF_PORT);
	
	//如果此次扫描码为0xeo,则表示扩展,直接返回
	if (scancode == 0xe0) {
		ext_scancode = true;
		return;
	}
	
	//如果上次扫描码为0xe0,则合并
	if (ext_scancode == true) {
		scancode = (0xe000 | scancode);
		ext_scancode = false;
	}
	
	//判断是否为断码
	break_code = ((scancode & 0x0080) != 0);
	
	//如果是断码
	if (break_code) {
		//通过断码得到其扫描码
		uint16_t make_code = (scancode &= 0xff7f);
		
		//如果以下三个键松开了,则设置对应状态为false
		if (make_code == ctrl_l_make || make_code == ctrl_r_make) { 
			ctrl_status = false;
		} else if (make_code == shift_l_make || make_code == shift_r_make) {
			shift_status = false;
		} else if (make_code == alt_l_make || make_code == alt_r_make) {
			alt_status = false;
		} 
		return;
	}
	//若为通码
	if ((scancode > 0x00 && scancode < 0x3b) || \
		(scancode == alt_r_make) || \
		(scancode == ctrl_r_make)) {
		//用来判断是否与shift组合,将来用于在一维数组中索引对应的字符
		bool shift = false;
		
		//如果是表示两个字符的键,例如'0'~'9'
		if ((scancode < 0x0e) || (scancode == 0x29) || \
			(scancode == 0x1a) || (scancode == 0x1b) || \
			(scancode == 0x2b) || (scancode == 0x27) || \
			(scancode == 0x28) || (scancode == 0x33) || \
			(scancode == 0x34) || (scancode == 0x35)) {
				//表示两个字符的键的具体表示只与shift的状态有关				
				if (shift_down_last) shift = true;
		} else {
			//如果是字母键,只有shift和caps按下其中一个才表示大写
			shift = shift_down_last ^ caps_lock_last;
		}
		
		//一律当作非扩展处理
		uint8_t index = (scancode &= 0x00ff);
		uint8_t cur_char = keymap[index][shift];
		
		//只打印ascii码不为0的键
		if (cur_char) {
			if (!ioq_full(&kbd_buf)) {
				ioq_putchar(&kbd_buf, cur_char);
			}
			return;
		}
		
		//判断此次按下的是否为以下控制键
		if (scancode == ctrl_l_make || scancode == ctrl_r_make) {
			ctrl_status = true;
		} else if (scancode == shift_l_make || scancode == shift_r_make) {
			shift_status = true;
		} else if (scancode == alt_l_make || scancode == alt_r_make) {
			alt_status = true;
		} else if (scancode == caps_lock_make) {
			caps_lock_status ^= 1;
		}
	} else {
		put_str("unknown key\n");
	}
}

在开启键盘中断前,还需要初始化io队列,并注册键盘中断处理函数

初始化函数如下:

//键盘初始化
void keyboard_init(void) {
	put_str("keyboard init start\n");
	ioqueue_init(&kbd_buf);
	register_handler(0x21, intr_keyboard_handler);
	put_str("keyboard init done\n");
}
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值