📄 console.c
字号:
lf();// 如果字符c 是回车符CR(13),则将光标移动到头列(0 列)。else if (c==13)cr();// 如果字符c 是DEL(127),则将光标右边一字符擦除(用空格字符替代),并将光标移到被擦除位置。else if (c==ERASE_CHAR(tty))del();// 如果字符c 是BS(backspace,8),则将光标右移1 格,并相应调整光标对应内存位置指针pos。else if (c==8) {if (x) {x--;pos -= 2;}// 如果字符c 是水平制表符TAB(9),则将光标移到8 的倍数列上。若此时光标列数超出屏幕最大列数,// 则将光标移到下一行上。} else if (c==9) {c=8-(x&7);x += c;pos += c<<1;if (x>video_num_columns) {x -= video_num_columns;pos -= video_size_row;lf();}c=9;// 如果字符c 是响铃符BEL(7),则调用蜂鸣函数,是扬声器发声。} else if (c==7)sysbeep();break;// 如果原状态是0,并且字符是转义字符ESC(0x1b = 033 = 27),则转到状态1 处理。case 1:state=0;// 如果字符c 是'[',则将状态state 转到2。if (c== '[')state=2;// 如果字符c 是'E',则光标移到下一行开始处(0 列)。else if (c== 'E')gotoxy(0,y+1);// 如果字符c 是'M',则光标上移一行。else if (c== 'M')ri();// 如果字符c 是'D',则光标下移一行。else if (c== 'D')lf();// 如果字符c 是'Z',则发送终端应答字符序列。else if (c== 'Z')respond(tty);// 如果字符c 是'7',则保存当前光标位置。注意这里代码写错!应该是(c=='7')。else if (x== '7')save_cur();// 如果字符c 是'8',则恢复到原保存的光标位置。注意这里代码写错!应该是(c=='8')。else if (x== '8')restore_cur();break;// 如果原状态是1,并且上一字符是'[',则转到状态2 来处理。case 2:// 首先对ESC 转义字符序列参数使用的处理数组par[]清零,索引变量npar 指向首项,并且设置状态// 为3。若此时字符不是'?',则直接转到状态3 去处理,否则去读一字符,再到状态3 处理代码处。for(npar=0;npar<NPAR;npar++)par[npar]=0;npar=0;state=3;if (ques=(c== '?'))break;// 如果原来是状态2;或者原来就是状态3,但原字符是';'或数字,则在下面处理。case 3:// 如果字符c 是分号';',并且数组par 未满,则索引值加1。if (c== ';' && npar<NPAR-1) {npar++;break;// 如果字符c 是数字字符'0'-'9',则将该字符转换成数值并与npar 所索引的项组成10 进制数。} else if (c>= '' && c<= '9') {par[npar]=10*par[npar]+c- '';break;// 否则转到状态4。} else state=4;// 如果原状态是状态3,并且字符不是';'或数字,则转到状态4 处理。首先复位状态state=0。case 4:state=0;switch(c) {// 如果字符c 是'G'或'`',则par[]中第一个参数代表列号。若列号不为零,则将光标右移一格。case 'G': case '`':if (par[0]) par[0]--;gotoxy(par[0],y);break;// 如果字符c 是'A',则第一个参数代表光标上移的行数。若参数为0 则上移一行。case 'A':if (!par[0]) par[0]++;gotoxy(x,y-par[0]);break;// 如果字符c 是'B'或'e',则第一个参数代表光标下移的行数。若参数为0 则下移一行。case 'B': case 'e':if (!par[0]) par[0]++;gotoxy(x,y+par[0]);break;// 如果字符c 是'C'或'a',则第一个参数代表光标右移的格数。若参数为0 则右移一格。case 'C': case 'a':if (!par[0]) par[0]++;gotoxy(x+par[0],y);break;// 如果字符c 是'D',则第一个参数代表光标左移的格数。若参数为0 则左移一格。case 'D':if (!par[0]) par[0]++;gotoxy(x-par[0],y);break;// 如果字符c 是'E',则第一个参数代表光标向下移动的行数,并回到0 列。若参数为0 则下移一行。case 'E':if (!par[0]) par[0]++;gotoxy(0,y+par[0]);break;// 如果字符c 是'F',则第一个参数代表光标向上移动的行数,并回到0 列。若参数为0 则上移一行。case 'F':if (!par[0]) par[0]++;gotoxy(0,y-par[0]);break;// 如果字符c 是'd',则第一个参数代表光标所需在的行号(从0 计数)。case 'd':if (par[0]) par[0]--;gotoxy(x,par[0]);break;// 如果字符c 是'H'或'f',则第一个参数代表光标移到的行号,第二个参数代表光标移到的列号。case 'H': case 'f':if (par[0]) par[0]--;if (par[1]) par[1]--;gotoxy(par[1],par[0]);break;// 如果字符c 是'J',则第一个参数代表以光标所处位置清屏的方式:// ANSI 转义序列:'ESC [sJ'(s = 0 删除光标到屏幕底端;1 删除屏幕开始到光标处;2 整屏删除)。case 'J':csi_J(par[0]);break;// 如果字符c 是'K',则第一个参数代表以光标所在位置对行中字符进行删除处理的方式。// ANSI 转义字符序列:'ESC [sK'(s = 0 删除到行尾;1 从开始删除;2 整行都删除)。case 'K':csi_K(par[0]);break;// 如果字符c 是'L',表示在光标位置处插入n 行(ANSI 转义字符序列'ESC [nL')。case 'L':csi_L(par[0]);break;// 如果字符c 是'M',表示在光标位置处删除n 行(ANSI 转义字符序列'ESC [nM')。case 'M':csi_M(par[0]);break;// 如果字符c 是'P',表示在光标位置处删除n 个字符(ANSI 转义字符序列'ESC [nP')。case 'P':csi_P(par[0]);break;// 如果字符c 是'@',表示在光标位置处插入n 个字符(ANSI 转义字符序列'ESC [n@')。case '@':csi_at(par[0]);break;// 如果字符c 是'm',表示改变光标处字符的显示属性,比如加粗、加下划线、闪烁、反显等。// ANSI 转义字符序列:'ESC [nm'。n = 0 正常显示;1 加粗;4 加下划线;7 反显;27 正常显示。case 'm':csi_m();break;// 如果字符c 是'r',则表示用两个参数设置滚屏的起始行号和终止行号。case 'r':if (par[0]) par[0]--;if (!par[1]) par[1] = video_num_lines;if (par[0] < par[1] &&par[1] <= video_num_lines) {top=par[0];bottom=par[1];}break;// 如果字符c 是's',则表示保存当前光标所在位置。case 's':save_cur();break;// 如果字符c 是'u',则表示恢复光标到原保存的位置处。case 'u':restore_cur();break;}}}// 最后根据上面设置的光标位置,向显示控制器发送光标显示位置。set_cursor();}/** void con_init(void);** This routine initalizes console interrupts, and does nothing* else. If you want the screen to clear, call tty_write with* the appropriate escape-sequece.** Reads the information preserved by setup.s to determine the current display* type and sets everything accordingly.*//** void con_init(void);* 这个子程序初始化控制台中断,其它什么都不做。如果你想让屏幕干净的话,就使用* 适当的转义字符序列调用tty_write()函数。** 读取setup.s 程序保存的信息,用以确定当前显示器类型,并且设置所有相关参数。*/void con_init(void){register unsigned char a;char *display_desc = "????";char *display_ptr;video_num_columns = ORIG_VIDEO_COLS; // 显示器显示字符列数。video_size_row = video_num_columns * 2; // 每行需使用字节数。video_num_lines = ORIG_VIDEO_LINES; // 显示器显示字符行数。video_page = ORIG_VIDEO_PAGE; // 当前显示页面。video_erase_char = 0x0720; // 擦除字符(0x20 显示字符, 0x07 是属性)。// 如果原始显示模式等于7,则表示是单色显示器。if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */{video_mem_start = 0xb0000; // 设置单显映象内存起始地址。video_port_reg = 0x3b4; // 设置单显索引寄存器端口。video_port_val = 0x3b5; // 设置单显数据寄存器端口。// 根据BIOS 中断int 0x10 功能0x12 获得的显示模式信息,判断显示卡单色显示卡还是彩色显示卡。// 如果使用上述中断功能所得到的BX 寄存器返回值不等于0x10,则说明是EGA 卡。因此初始// 显示类型为EGA 单色;所使用映象内存末端地址为0xb8000;并置显示器描述字符串为'EGAm'。// 在系统初始化期间显示器描述字符串将显示在屏幕的右上角。if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10){video_type = VIDEO_TYPE_EGAM; // 设置显示类型(EGA 单色)。video_mem_end = 0xb8000; // 设置显示内存末端地址。display_desc = "EGAm"; // 设置显示描述字符串。}// 如果BX 寄存器的值等于0x10,则说明是单色显示卡MDA。则设置相应参数。else{video_type = VIDEO_TYPE_MDA; // 设置显示类型(MDA 单色)。video_mem_end = 0xb2000; // 设置显示内存末端地址。display_desc = "*MDA"; // 设置显示描述字符串。}}// 如果显示模式不为7,则为彩色模式。此时所用的显示内存起始地址为0xb800;显示控制索引寄存// 器端口地址为0x3d4;数据寄存器端口地址为0x3d5。else /* If not, it is color. */{video_mem_start = 0xb8000; // 显示内存起始地址。video_port_reg = 0x3d4; // 设置彩色显示索引寄存器端口。video_port_val = 0x3d5; // 设置彩色显示数据寄存器端口。// 再判断显示卡类别。如果BX 不等于0x10,则说明是EGA 显示卡。if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10){video_type = VIDEO_TYPE_EGAC; // 设置显示类型(EGA 彩色)。video_mem_end = 0xbc000; // 设置显示内存末端地址。display_desc = "EGAc"; // 设置显示描述字符串。}// 如果BX 寄存器的值等于0x10,则说明是CGA 显示卡。则设置相应参数。else{video_type = VIDEO_TYPE_CGA; // 设置显示类型(CGA)。video_mem_end = 0xba000; // 设置显示内存末端地址。display_desc = "*CGA"; // 设置显示描述字符串。}}/* Let the user known what kind of display driver we are using *//* 让用户知道我们正在使用哪一类显示驱动程序 */// 在屏幕的右上角显示显示描述字符串。采用的方法是直接将字符串写到显示内存的相应位置处。// 首先将显示指针display_ptr 指到屏幕第一行右端差4 个字符处(每个字符需2 个字节,因此减8)。display_ptr = ((char *)video_mem_start) + video_size_row - 8;// 然后循环复制字符串中的字符,并且每复制一个字符都空开一个属性字节。while (*display_desc){*display_ptr++ = *display_desc++; // 复制字符。display_ptr++; // 空开属性字节位置。}/* Initialize the variables used for scrolling (mostly EGA/VGA) *//* 初始化用于滚屏的变量(主要用于EGA/VGA) */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); // 初始化光标位置x,y 和对应的内存位置pos。set_trap_gate(0x21,&keyboard_interrupt); // 设置键盘中断陷阱门。outb_p(inb_p(0x21)&0xfd,0x21); // 取消8259A 中对键盘中断的屏蔽,允许IRQ1。a=inb_p(0x61); // 延迟读取键盘端口0x61(8255A 端口PB)。outb_p(a|0x80,0x61); // 设置禁止键盘工作(位7 置位),outb(a,0x61); // 再允许键盘工作,用以复位键盘操作。}/* from bsd-net-2: *///// 停止蜂鸣。// 复位8255A PB 端口的位1 和位0。void sysbeepstop(void){/* disable counter 2 */ /* 禁止定时器2 */outb(inb_p(0x61)&0xFC, 0x61);}int beepcount = 0;// 开通蜂鸣。// 8255A 芯片PB 端口的位1 用作扬声器的开门信号;位0 用作8253 定时器2 的门信号,该定时器的// 输出脉冲送往扬声器,作为扬声器发声的频率。因此要使扬声器蜂鸣,需要两步:首先开启PB 端口// 位1 和位0(置位),然后设置定时器发送一定的定时频率即可。static void sysbeep(void){/* enable counter 2 */ /* 开启定时器2 */outb_p(inb_p(0x61)|3, 0x61);/* set command for counter 2, 2 byte write */ /* 送设置定时器2 命令 */outb_p(0xB6, 0x43);/* send 0x637 for 750 HZ */ /* 设置频率为750HZ,因此送定时值0x637 */outb_p(0x37, 0x42);outb(0x06, 0x42);/* 1/8 second */ /* 蜂鸣时间为1/8 秒 */beepcount = HZ/8;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -