石家庄seo网站管理,网站页面设计优化方案,中牟建设局网站,python 网站开发 环境Ⅰ.锁
本质是互斥操作
原因#xff1a;针对公共资源访问时#xff0c;临界区若不加以互斥限制#xff0c;可能导致执行过程中突然的中断导致出现异常。
1.互斥过程
设定互斥量M为二值信号量#xff0c;0/1#xff0c;P-#xff0c;V#xff0c;现有两个进程A、B共同…Ⅰ.锁
本质是互斥操作
原因针对公共资源访问时临界区若不加以互斥限制可能导致执行过程中突然的中断导致出现异常。
1.互斥过程
设定互斥量M为二值信号量0/1P-V现有两个进程A、B共同访问公共资源R则有
1.线程A访问RP-M0
2.线程B访问RP-M0阻塞线程BP
3.线程A访问R结束V唤醒线程B
4.线程B开始执行
阻塞功能 阻塞是线程主动的行为 。己阻塞的线程是由别人来唤醒的通常是锁的持有者。不让线程在就绪队列中出现就行了这样线程便没有机会运行。
线程自主阻塞之后保存当前的寄存器栈信息当唤醒后并不会从头开始执行而是从线程阻塞的部分开始重新执行并且时间片依旧是阻塞前的数值。
1实现线程阻塞和唤醒
/* 阻塞过程
* 1.关中断
* 2.更改当前线程状态为阻塞态
* 3.调用schedule()从就绪队列中取出下一个就绪线程列执行
* 4.开中断
*/
void thread_blocked(enum task_status state){// 原子操作必须关中断enum intr_status old_status intr_distable();// 只有是传入下面三种状态才可以被阻塞ASSERT((state TASK_BLOCKED) || (state TASK_HANGING) || (state TASK_WAITING));struct task_struct * cur_thread running_thread();cur_thread-status state;schedule();// list_pop(thread_ready_list, cur_thread-general_tag);intr_set_status(old_status);
}
/* 唤醒线程
* 1.关中断
* 2.判断当前线程是否在就绪队列中
* 3.不在则添加在则PANIC
* 4.当前线程加入就绪队列
* 5.修改状态为TASK_READY
* 6.开中断
*/
// 被阻塞的进程由于没有执行因此不可以调度schedule()函数
void thread_unblocked(struct task_struct* pthread){enum intr_status old_status intr_disable();ASSERT((pthread-status TASK_BLOCKED) || (pthread-status TASK_HANGING) || (pthread-status TASK_WAITING));if(pthread-status ! TASK_READY){ASSERT(!elem_find(thread_ready_list, pthread-general_tag));if(elem_find(thread_ready_list, pthread-general_tag)){PANIC(thread_unblock:blocked thread in ready_list);}list_push(thread_ready_list, pthread-general_tag);pthread-status TASK_RUNNING;}intr_set_status(old_status);
}2实现PV操作
/* P操作存在线程阻塞
* 1.关中断
* 2.判断当前semophore0
* 3.为0循环等待并将当前线程加入信号量等待队列并阻塞当前线程
* 4.不为0semophore-1
* 5.开中断
*/
void sema_down(struct semaphore* psema) {emum intr_status old_status intr_disable();while(psema-value 0){ASSERT(!elem_find(psema-waiters, running_thread()-general_tag));if(elem_find(psema-waiters, running_thread()-general_tag)){PANIC(sema_down:P op failed); }list_append(psema-waiters, running_thread()-general_tag);thread_blocked(TASK_BLOCKED);}psema-value--;ASSERT(psema-value 0);intr_set_status(old_status);
}
/* V操作存在线程唤醒
* 1.关中断
* 2.判断当前信号量等待队列不为空根据elem2entry找到队头线程的PCB地址,并唤醒
* 3.为空semophore
* 4.开中断
*/
void sema_up(struct semaphore* psema) {emum intr_status old_status intr_disable();ASSERT(psema-value 0);if(!list_empty(psema-waiters)){struct task_struct* wait_thread elem2entry(struct task_struct, general_tag, list_pop(psema-waiters));thread_unblocked(wait_thread);}psema-value;ASSERT(psema-value 1);intr_set_status(old_status);
}3实现获得锁、释放锁操作
/* 获得锁
* 1.先判断自己是不是锁的持有者
* 2.不是P操作持有锁持有次数1
* 3.反之持有次数1
*/
void lock_acquire (struct lock* plock) {/* 排除曾经自己已经持有锁但还未将其释放的情况 */if(plock-holder ! running_thread()){sema_down(plock-semaphore);plock-holder running_thread();ASSERT(plock-holder_repeat_nr 0);plock-holder_repeat_nr 1;}else{plock-holder_repeat_nr;}
}
/* 释放锁
* 1.ASSERT()判断是否拥有锁
* 2.判断持有锁的次数防止进程需要多次访问资源。
* 3.清空锁
* 3.V操作
*/
void lock_release (struct lock* plock) {ASSERT(plock-holder running_thread());if(plock-holder_repeat_nr 1){plock-holder_repeat_nr--;return;}ASSERT(plock-holder_repeat_nr 1);plock-holder NULL;plock-holder_repeat_nr 0;sema_up(plock-semaphore);
}Ⅱ.从键盘获取输入
键的扫描码一个键的状态要么是按下要么是弹起因此一个键便有两个编码按键被按下时的编码叫通码也就是表示按键上的触点接通了内部电路使硬件产生了一个码故通码也称为 makecode。按键在被按住不松手时会持续产生相同的码直到按键被松开时才终止因此按键被松开弹起时产生的编码叫断码也就是电路被断开了不再持续产生码了故断码也称为 breakcode。一个键的扫描码是由通码和断码组成的。
键盘编码器键盘是个独立的设备在它内部有个叫作键盘编码器的芯片通常是 Intel 8048 或兼容芯片它的作用是每当键盘上发生按键操作它就向键盘控制器报告哪个键被按下以及键的扫描码。
键盘控制器在主机内部的主板上通常是 Intel 8042 或兼容芯片接收来自键盘编码器的按键信息将其解码后保存然后向中断代理发中断之后处理器执行相应的中断处理程序读入 8042 处理保存过的按键信息。
1.从键盘读取输入的过程
• 扫描码有 3 套现在一般键盘中的 8048 芯片支持的是第二套扫描码 。 因此每当有击键发生时 8048发给 8042 的都是第二套键盘扫描码。 • 8042 为了兼容性将接收到的第二套键盘扫描码转换成第一套扫描码。 8042 是按字节来处理的每处理一个字节的扫描码后将其存储到自己的输出缓冲区寄存器 。 • 然后向中断代理 8259A 发中断信号这样我们的键盘中断处理程序通过读取 8042 的输出缓冲区寄存器会获得第一套键盘扫描码。
2.键盘扫描码
根据键盘的更新迭代出现了3种常用的键盘扫描码为了兼容第一套键盘扫描码对应的中断处理程序不管键盘用的是何种键盘扫描码当键盘将扫描码发送到 8042 后都由 8042 转换成第一套扫描码。 8042介于8048与处理器中间担任中间人的职责完成处理器对8048的设置以及8048的扫描码输入给处理器。因此8042作为输入输出缓存区包括输入、输出缓冲寄存器以及状态、控制寄存器。 Ⅲ.键盘驱动程序
对于键盘驱动程序而言每次都需要读取输出寄存器取出键盘的扫描码并完成转为ASCII码的工作。
键盘驱动程序初始化注册键盘中断处理程序
#define KEY_BUF_PORT 0x60 // 键盘 buffer 寄存器端口号为 0x60
/* 键盘中断处理程序 */
static void intr_keyboard_handler(void) {
/* 必须要读取输出缓冲区寄存器,否则8042不再继续响应键盘中断 */uint8_t scancode inb(KBD_BUF_PORT);put_int(scancode);return;
}/* 键盘初始化注册键盘中断处理程序 */
void keyboard_init() {put_str(keyboard init start\n);register_handler(0x21, intr_keyboard_handler);put_str(keyboard init done\n);
}1.C语言中有3中转义字符
(1 一般转义字符‘\单个字母’ 的形式。 (2八进制转义字符‘\0三位八进制数字表示的 ASCII 码’ 的形式 。 (3十六进制转义字符‘\x两位十六进制数字表示的 ASCII 码’ 的形式 。
2.处理扫描码
键盘驱动程序需要完成 键盘扫描码-按键的ASCII码 的映射过程
1设计思路
如果是一些用于操作方面的控制键简称操作控制键如shift、ctrl、caps lock它通常是组合键需要与其他键一起考虑然后做出具体的行为展现在键盘驱动中完成处理。如果是一些用于字符方面的键无论是可见字符或是字符方面的控制键简称字符控制键如backspace统统交给字符处理程序完成。
对于第一阶段它与字符无直接的关系因此咱们就在键盘驱动中处理。 对于第二阶段咱们得知道用户按下的是什么宇符不能把操作控制键当成字符传给字符处理程序比如把 shift键的扫描码传给 put_char这不就乱了吗因此咱们得把按键的扫描码转换成对应的字符也就是将通码转换为字符的 ASCII 码这就是前面所说的源到目标的映射关系。
**简言之需要区分什么按键是需要显示的什么按键是负责控制的。**这就需要建立扫描码和ASCII码的映射表对键盘输入加以判定。
2建立键盘字符与通码映射关系
keymap[][0]表示未与shift组合的按键值keymap[][1]表示与shift组合的按键值
/* 以通码make_code为索引的二维数组 */
static char keymap[][2] {
/* 扫描码 未与shift组合 与shift组合*/
/* ---------------------------------- */
/* 0x00 */ {0, 0},
/* 0x01 */ {esc, esc},
/* 0x02 */ {1, !},
/* 0x03 */ {2, },
/* 0x04 */ {3, #},
/* 0x05 */ {4, $},
/* 0x06 */ {5, %},
/* 0x07 */ {6, ^},
/* 0x08 */ {7, },
/* 0x09 */ {8, *},
/* 0x0A */ {9, (},
/* 0x0B */ {0, )},
/* 0x0C */ {-, _},
/* 0x0D */ {, },
/* 0x0E */ {backspace, backspace},
/* 0x0F */ {tab, tab},
/* 0x10 */ {q, Q},
/* 0x11 */ {w, W},
/* 0x12 */ {e, E},
/* 0x13 */ {r, R},
/* 0x14 */ {t, T},
/* 0x15 */ {y, Y},
/* 0x16 */ {u, U},
/* 0x17 */ {i, I},
/* 0x18 */ {o, O},
/* 0x19 */ {p, P},
/* 0x1A */ {[, {},
/* 0x1B */ {], }},
/* 0x1C */ {enter, enter},
/* 0x1D */ {ctrl_l_char, ctrl_l_char},
/* 0x1E */ {a, A},
/* 0x1F */ {s, S},
/* 0x20 */ {d, D},
/* 0x21 */ {f, F},
/* 0x22 */ {g, G},
/* 0x23 */ {h, H},
/* 0x24 */ {j, J},
/* 0x25 */ {k, K},
/* 0x26 */ {l, L},
/* 0x27 */ {;, :},
/* 0x28 */ {\, },
/* 0x29 */ {, ~},
/* 0x2A */ {shift_l_char, shift_l_char},
/* 0x2B */ {\\, |},
/* 0x2C */ {z, Z},
/* 0x2D */ {x, X},
/* 0x2E */ {c, C},
/* 0x2F */ {v, V},
/* 0x30 */ {b, B},
/* 0x31 */ {n, N},
/* 0x32 */ {m, M},
/* 0x33 */ {,, },
/* 0x34 */ {., },
/* 0x35 */ {/, ?},
/* 0x36 */ {shift_r_char, shift_r_char},
/* 0x37 */ {*, *},
/* 0x38 */ {alt_l_char, alt_l_char},
/* 0x39 */ { , },
/* 0x3A */ {caps_lock_char, caps_lock_char}
/*其它按键暂不处理*/
};(3)更新键盘驱动程序
3.1读取扫描码
static void intr_keyboard_handler(void) {……bool break_code;uint16_t scancode inb(KBD_BUF_PORT);/* 若扫描码是e0开头的,表示此键的按下将产生多字节的扫描码,如shift、alt、ctrl、caps_lock等扫描码为2字节* 所以马上结束此次中断处理函数,等待下一个扫描码进来*/ if (scancode 0xe0) { ext_scancode true; // 打开e0标记return;}/* 如果上次是以0xe0开头,将扫描码合并 */if (ext_scancode) {scancode ((0xe000) | scancode);ext_scancode false; // 关闭e0标记} break_code ((scancode 0x0080) ! 0); // 获取break_code……
}3.2需要判断当前键盘是处于断码/通码
通码的扫描码scancode第8位为0断码为1。
若为断码需要将操作方面的控制键状态改为false如shift、alt。将他们的断码的第8位改为0作为通码访问keyb_map获取具体是哪个控制键然后更改状态即可。
/*---------------续上----------------*/if (break_code) { // 若是断码break_code(按键弹起时产生的扫描码)/* 由于ctrl_r 和alt_r的make_code和break_code都是两字节,所以可用下面的方法取make_code,多字节的扫描码暂不处理 */uint16_t make_code (scancode 0xff7f); // 得到其make_code(按键按下时产生的扫描码)/* 若是任意以下三个键弹起了,将状态置为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;} /* 由于caps_lock不是弹起后关闭,所以需要单独处理 */return; // 直接返回结束此次中断处理程序} 若为通码分为操作键和字符键两类先要读取操作键如shift、ctrl、alt、caps_lock然后修改操作键的状态根据操作键的状态确定映射的字符。
/*---------------续上----------------*//* 若为通码,只处理数组中定义的键以及alt_right和ctrl键,全是make_code */else if ((scancode 0x00 scancode 0x3b) ||(scancode alt_r_make) ||(scancode ctrl_r_make)){bool shift false; // 判断是否与shift组合,用来在一维数组中索引对应的字符if ((scancode 0x0e) || (scancode 0x29) ||(scancode 0x1a) || (scancode 0x1b) ||(scancode 0x2b) || (scancode 0x27) ||(scancode 0x28) || (scancode 0x33) ||(scancode 0x34) || (scancode 0x35)){/****** 代表两个字母的键 ********0x0e 数字0~9,字符-,字符0x29 字符0x1a 字符[0x1b 字符]0x2b 字符\\0x27 字符;0x28 字符\0x33 字符,0x34 字符.0x35 字符/*******************************/if (shift_down_last){ // 如果同时按下了shift键shift true;}}else{ // 默认为字母键if (shift_down_last caps_lock_last){ // 如果shift和capslock同时按下shift false;}else if (shift_down_last || caps_lock_last){ // 如果shift和capslock任意被按下shift true;}else{shift false;}}uint8_t index (scancode 0x00ff); // 将扫描码的高字节置0,主要是针对高字节是e0的扫描码.char cur_char keymap[index][shift]; // 在数组中找到对应的字符/* 只处理ascii码不为0的键 */if (cur_char){put_char(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键,当再次按下时则状态取反,* 即:已经开启时,再按下同样的键是关闭。关闭时按下表示开启。*/caps_lock_status !caps_lock_status;}}else{put_str(unknown key\n);}Ⅳ.环形输入缓冲区
构建缓冲区保存键盘扫描码转换的字符每次从缓冲区取出字符完成打印。
利用生产者消费者模式构建环形队列实现字符的取出和保存。主要完成缓冲区存储状态、添加1字节、删除1字节等操作。
1.环形队列结构体
/* 环形队列 */
struct ioqueue {struct lock lock;struct task_struct *producer;struct task_struct *consumer;// 缓冲区大小char buf[bufsize];// 队首数据往队首处写入int32_t head;// 队尾数据从队尾处读出int32_t tail;
};2.缓冲区满
/* 返回 pos 在缓冲区中的下一个位置值 */
static int32_t next_pos (int32_t pos) {return (pos1)%bufsize;
}
/* 采用头插法加入元素判断队列是否已满 */
bool ioq_full(struct ioqueue* ioq) {ASSERT(intr_get_status() INTR_OFF);return next_pos(ioq-head) ioq-tail;
}3.缓冲区为空
/* 采用头插法加入元素判断队列是否为空 */
bool ioq_empty(struct ioqueue* ioq) {ASSERT(intr_get_status() INTR_OFF);return ioq-head ioq-tail;
}4.缓冲区为空时等待
/* 当前生产者或消费者在此缓冲区上等待 */
static void ioq_wait(struct task_struct ** waiter) {ASSERT((waiter ! NULL) (waiter NULL));*waiter running_thread();thread_blocked(TASK_BLOCKED);
}5.缓冲区不为空唤醒waiterr
/* 唤醒 waiter */
static void wakeup(struct task_struct** waiter) {ASSERT(waiter ! NULL);thread_unblocked(*waiterr);*waiterr NULL;
}6.缓冲区添加一字节
/* 生产者往 ioq 队列中写入一个字符 byte */
void ioq_putchar(struct ioqueue* ioq, char byte) {ASSERT(intr_get_intr() INTR_OFF);while(ioq_full(ioq)){lock_acquire(ioq-lock);ioq_wait(ioq);lock_release(ioq-lock);}ioq-buf[ioq-head] byte;ioq-head next_pos(ioq-head);if(ioq-consumer ! NULL){wakeup(ioq-consumer);}
}判断缓冲区是否满了满了则请求锁并wait阻塞当前线程释放锁反之头插法在队列头插入新元素唤醒消费者
7.缓冲区取出一字节
/* 消费者从 ioq 队列中获取一个字符 */
char ioq_getchar(struct ioqueue* ioq) {ASSERT(intr_get_status() INTR_OFF);while(ioq_empty(ioq)){lock_acquire(ioq-lock);ioq_wait(ioq);lock_release(ioq-lock);}char byte ioq-buf[ioq-tail];ioq-tail next_pos(ioq-tail);if(ioq-producer ! NULL){wakeup(ioq-consumer);}return byte;
}判断缓冲区是否为空为空则请求锁并wait阻塞当前线程释放锁反之从队尾取出新元素唤醒生产者
over