os-xv6分析-设备管理-console.c 代码分析
Console.c 描述了控制台的基本功能
//Console.c 描述了控制台的基本功能
// Console input and output.
// Input is from the keyboard or serial port.
// Output is written to the screen and serial port.
//控制台输入和输出。
//输入来自键盘或串行端口。
//输出被写入屏幕和串行端口。
// 导包
#include "types.h"
#include "defs.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
// 声明显示输出函数
static void consputc(int);
// 静态变量 panicked
static int panicked = 0;
// 定义cons结构体,描述控制台
static struct {
struct spinlock lock;
int locking;
} cons;
// 定义函数printint,打印base进制字符 base<= 16
static void
printint(int xx, int base, int sign)
{
static char digits[] = "0123456789abcdef";// 定义十六进制字符
char buf[16];
int i;
uint x;
if(sign && (sign = xx < 0)) // 根据sign和xx值判断x为多少
x = -xx;
else
x = xx;
i = 0;
do{
buf[i++] = digits[x % base]; // x%base余数
}while((x /= base) != 0); // 进制转换
if(sign) // 判断是否是负数
buf[i++] = '-';
while(--i >= 0) // 倒序调用显示函数,输出xx的base进制表示
consputc(buf[i]);
}
//PAGEBREAK: 50
// Print to the console. only understands %d, %x, %p, %s.
// 打印到控制台。仅理解%d、%x、%p、%s。
// 定义打印函数,理解printf中的%d,%x,%p和%s,并输出对应值
void
cprintf(char *fmt, ...)
{
int i, c, locking;
uint *argp;
char *s;
locking = cons.locking; // 获取cons的锁标记
if(locking) //判断是否被锁,若被锁申请锁
acquire(&cons.lock);
if (fmt == 0)
panic("null fmt"); //无须打印
argp = (uint*)(void*)(&fmt + 1); //定义函数
for(i = 0; (c = fmt[i] & 0xff) != 0; i++){
if(c != '%'){
consputc(c); //调用显示函数,显示字符
continue;
}
c = fmt[++i] & 0xff;//取下一个字符
if(c == 0)
break;
switch(c){ //按照条件打印c
case 'd': //数字
printint(*argp++, 10, 1); //调用printint打印十进制数字
break;
case 'x': //十六进制
case 'p': // 输出内存地址
printint(*argp++, 16, 0);
break;
case 's': //输出字符穿
if((s = (char*)*argp++) == 0)
s = "(null)"; //s赋值为null
for(; *s; s++) //循环调用输出s
consputc(*s);
break;
case '%':
consputc('%'); //打印%
break;
default:
// Print unknown % sequence to draw attention.
consputc('%'); //打印%
consputc(c); //打印c
break;
}
}
if(locking)// 若被锁
release(&cons.lock); //释放锁
}
void
panic(char *s) //
{
int i;
uint pcs[10];
cli(); //调用命令行界面函数
cons.locking = 0; //控制台locking置0
// use lapiccpunum so that we can call panic from mycpu()
//使用lapiccpunum,以便我们可以从mycpu()调用panic
cprintf("lapicid %d: panic: ", lapicid());// 调用cprintf,输出lapicid //lapicid(): panic:
cprintf(s); // 打印字符串s
cprintf("\n"); //换行
getcallerpcs(&s, pcs); // 获取调用信息
for(i=0; i<10; i++) //输出前10个
cprintf(" %p", pcs[i]);
panicked = 1; // freeze other CPU //冻结其他CPU
for(;;) // 死循环
;
}
//PAGEBREAK: 50
// 声明变量
#define BACKSPACE 0x100 //退格键
#define CRTPORT 0x3d4
static ushort *crt = (ushort*)P2V(0xb8000); // CGA memory //CGA存储器
// 对应CGA设备使用的0xb8000地址物理区,通过P2V转换成虚地址
static void
cgaputc(int c) //将显示字符送往CRT显示器
{
int pos; //光标变量函数
// Cursor position: col + 80*row. //光标位置:列+80*行。
outb(CRTPORT, 14); //指出十四号寄存器要被操作
pos = inb(CRTPORT+1) << 8; //读14号寄存器,并左移8位
outb(CRTPORT, 15); //指出十五号寄存器要被操作
pos |= inb(CRTPORT+1); //读15号寄存器,并添加到pos后八位
if(c == '\n') //若字符为换行符
pos += 80 - pos%80; //光标位置跳到下一行行首
else if(c == BACKSPACE){ //若为退格键
if(pos > 0) --pos; //如果光标不在首位,则位置回退1
} else
crt[pos++] = (c&0xff) | 0x0700; // black on white //显示字符
if(pos < 0 || pos > 25*80) //如果光标位置出错,则输出pos under/overflow
panic("pos under/overflow");
if((pos/80) >= 24){ // Scroll up. //换行显示
memmove(crt, crt+80, sizeof(crt[0])*23*80);
pos -= 80;
memset(crt+pos, 0, sizeof(crt[0])*(24*80 - pos));
}
outb(CRTPORT, 14); //指出要操作的字节
outb(CRTPORT+1, pos>>8);
outb(CRTPORT, 15);
outb(CRTPORT+1, pos);
crt[pos] = ' ' | 0x0700; //显示空
}
void
consputc(int c) //显示输出函数
{
if(panicked){ //是否出错,若出错,则停止运行,进入死循环
cli();
for(;;)
;
}
if(c == BACKSPACE){ //判断是否是回退键
uartputc('\b'); uartputc(' '); uartputc('\b'); //是通过uarputc函数将空送往串口
} else
uartputc(c); //否通过uarputc函数将字符送往串口显示
cgaputc(c); //CRT显示器显示
}
//定义输入缓冲区
#define INPUT_BUF 128
struct {
char buf[INPUT_BUF];
uint r; // Read index //读指针
uint w; // Write index //写指针
uint e; // Edit index //编辑指针
} input;
#define C(x) ((x)-'@') // Control-x //ctrl 键与其他按键的组合
void
consoleintr(int (*getc)(void)) //控制器中断 串口中断传入uartgec函数指针
// 键盘中断传入kbdgetc函数指针
{
int c, doprocdump = 0;
acquire(&cons.lock); //获得锁
while((c = getc()) >= 0){ //读入输入字符
switch(c){ //分情况判断
case C('P'): // Process listing. //ctrl+P 组合键
// procdump() locks cons.lock indirectly; invoke later
// procdump()间接锁定cons.lock;稍后调用
doprocdump = 1;
break;
case C('U'): // Kill line. //ctrl+U 组合键
// 循环回退撤销指令执行
while(input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] != '\n'){
input.e--;
consputc(BACKSPACE); //显示回退键
}
break;
case C('H'): case '\x7f': // Backspace //ctrl+H组合键
//撤回操作
if(input.e != input.w){
input.e--;
consputc(BACKSPACE); //显示回退键
}
break;
default: //写缓存
if(c != 0 && input.e-input.r < INPUT_BUF){
c = (c == '\r') ? '\n' : c;
input.buf[input.e++ % INPUT_BUF] = c; //字符存入循坏缓存区
consputc(c); //显示字符
// 换行||C(‘D’)|| 编辑指针位置==输入指针+缓冲区大小
if(c == '\n' || c == C('D') || input.e == input.r+INPUT_BUF){
input.w = input.e; // 写指针赋值为编辑指针值
wakeup(&input.r); //唤醒
}
}
break;
}
}
release(&cons.lock); //释放锁
if(doprocdump) {
procdump(); // now call procdump() wo. cons.lock held现在调用//procdump()wo。消费者锁定
}
}
int
consoleread(struct inode *ip, char *dst, int n)
//从输入缓冲区input.buf[]读入数据,输入缓冲区的唯一“消费者”,通过//cons.lock 自旋锁与此缓冲区的“生产者”(键盘中断,串口代码中断)实现//互斥
{
uint target;
int c;
iunlock(ip); //释放指定inode锁
target = n;
acquire(&cons.lock); //获得锁
while(n > 0){
while(input.r == input.w){ //互斥判断
if(myproc()->killed){
release(&cons.lock); //释放锁
ilock(ip);
return -1;
}
sleep(&input.r, &cons.lock); //睡眠
}
c = input.buf[input.r++ % INPUT_BUF]; //读指定大小的数据
if(c == C('D')){ // EOF //结束
if(n < target){
// Save ^D for next time, to make sure
// caller gets a 0-byte result.
//保存以备下次使用,以确保调用者得到0字节的结果。
input.r--;
}
break;
}
*dst++ = c; //写入磁盘
--n;
if(c == '\n') //换行
break;
}
release(&cons.lock); //释放锁
ilock(ip); //获取指定inode锁
return target - n; //返回读入数据数量
}
int
consolewrite(struct inode *ip, char *buf, int n)
//向控制台设备(文件类型3)文件进行写操作,可以实现输出显示。此文件操//作实际指向consloewrite()函数,通过uartputc函数和cgaputc将输出缓//冲区内容逐个字节输出到CGA显卡和串口终端上。
{
int i;
iunlock(ip); // 释放指定inode锁
acquire(&cons.lock); //获得锁
for(i = 0; i < n; i++) // 循坏在控制台显示
consputc(buf[i] & 0xff);
release(&cons.lock); //释放锁
ilock(ip); //获得指定inode锁
return n;
}
void
consoleinit(void)
{
initlock(&cons.lock, "console"); // 初始化自旋锁
// 建立文件系统于控制台设备的联系
devsw[CONSOLE].write = consolewrite; //写
devsw[CONSOLE].read = consoleread; // 读
cons.locking = 1; //锁置1
ioapicenable(IRQ_KBD, 0); //使能键盘中断
}