锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

linux0.11字符设备驱动及访问请求管理程序阅读注释笔记

时间:2023-10-01 16:07:02 sr6b4048继电器

[ 1] linux0.11指导程序阅读注释。
[ 2] linux0.11实际模式进入保护模式程序阅读注释 。
[ 3] linux0.初始化程序阅读注释11护理模式。
[ 4] linux0.阅读注释11主存管理程序。
[ 5] linux0.相关程序阅读注释初始设置为中断/异常机制。
[ 6] linux0.阅读缓冲区管理程序注释。
[ 7] linux0.阅读注释文件系统管理程序。
[ 8] linux0.阅读注释11个设备驱动和访问请求管理程序。

通过浏览器的搜索功能,篇幅较长(Ctrl f)了解相应函数的实现机制,如搜索函数名 tty_read。

[9] linux0.阅读注释

回顾[5]中粗略制定的阅读计划——他确实有一些不合理之处。然而,本文坚持克服这些不合理之处。这篇文章将再坚持一段时间。

/* 此外,学习字符设备管理程序后,可以粗略了解计算机(键盘)数据输入流向。  *   |------------|  *   |  terminal  |  *   |------------|  *   |UART|network|  *   |------------|  *         ^  *         |将其发送到串口/网口控制器  *         v  *     |------|  *     |  CPU | to echo |----------|   |-------|  *     |======| ------> |video card|-->|monitor|  *     |QUEUES|         |----------|   |-------|  *     |======|  *        ^  *        |I/O指令和中断机制  *        V  * |--------------------------|  * |keyboard && its controller|  * |--------------------------| */ 
main.c
/* head.s保护模式初始化完成后,  * 便跳转执行C程序入口处指令。*/ void main(void) { /* ... */  /* 字符设备(串口&&键盘&&显卡等相关)初始化。*/  chr_dev_init();     tty_init();      /* ... */ } 
tty_io.c
/*  *  linux/kernel/tty_io.c  *  *  (C) 1991  Linus Torvalds  */  /*  * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles  * or rs-channels. It also implements echoing, cooked mode etc.  *  * Kill-line thanks to John T Kohl.  */ #include  #include  #include   #define ALRMMASK (1<<(SIGALRM-1)) #define KILLMASK (1<<(SIGKILL-1)) #define INTMASK (1<<(SIGINT-1)) #define QUITMASK (1<<(SIGQUIT-1)) #define TSTPMASK (1<<(SIGTSTP-1))  #include  #include  #include  #include   /* 判断字符设备本地模式,输入模式,  * 标志是否设置在输出模式中?f。  *  * 表达式值为1时,表示已设置标志f。*/ #define _L_FLAG(tty,f)  ((tty)->termios.c_lflag & f) #define _I_FLAG(tty,f)  ((tty)->termios.c_iflag & f) #define _O_FLAG(tty,f)  ((tty)->termios.c_oflag & f)  /* 判断字符设备本地模式是否设置。  * 标准化标志,生成过程信号标志,返回标志,  * 标准模式下的擦除标志,标准模式下的删除行标志,  * 回显控制字符标志,回显删除行标志。  *  * 表达值为1,表示已设置。*/ #define L_CANON(tty)    _L_FLAG((tty),ICANON) #define L_ISIG(tty)     _L_FLAG((tty),ISIG) #define L_ECHO(tty)     _L_FLAG((tty),ECHO) #define L_ECHOE(tty)    _L_FLAG((tty),ECHOE) #define L_ECHOK(tty)    _L_FLAG((tty),ECHOK) #define L_ECHOCTL(tty)  _L_FLAG((tty),ECHOCTL) #define L_ECHOKE(tty)   _L_FLAG((tty),ECHOKE)  /* 判断是否设置了字符设备的输入模式  * 输入字符转大写字符标志,输入换行符转回车辆标志,  * 输入回车转换行符标志,忽略回车标志。  *  * 表达值为1,表示已设置。*/ #define I_UCLC(tty) _I_FLAG((tty),IUCLC) #define I_NLCR(tty) _I_FLAG((tty),INLCR) #define I_CRNL(tty) _I_FLAG((tty),ICRNL) #define I_NOCR(tty) _I_FLAG((tty),IGNCR)  /* 判断是否设置了字符设备的输出模式  * 输出处理标志、换回车标志、转回车标志、,  * 遇换行回车处理标志,小写字符转大写字符标志。  *  * 表达时返回1时表示已设置。*/ #define O_POST(tty)  _O_FLAG((tty),OPOST) #define O_NLCR(tty)  _O_FLAG((tty),ONLCR) #define O_CRNL(tty)  _O_FLAG((tty),OCRNL) #define O_NLRET(tty) _O_FLAG((tty),ONLRET) #define O_LCUC(tty)  _O_FLAG((tty),OLCUC)  /* tty_table,  * 接收和发送管理字符设备数据的全局数组。  * tty_table[0] - 管理控制台终端(console)接收和发送数据;  * tty_table[1] - 管理串口1接收和发送数据;  * tty_table[2] - 接收和发送管理串口2的数据。*/ struct tty_struct tty_table[] = {     {         {ICRNL,      /* change incoming CR to NL */         OPOST|ONLCR, /* change outgoing NL to CRNL */         0,           /* 初始化为0的控制模式 */         /* 生成过程信号;规范模式;回显;控制字符回显;删除行时回显; */         ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,         0,          /* console termio */         INIT_C_CC}, /* 控制字符序列 */         0,          /* initial pgrp */         0,          /* initial stopped */         con_write,  /* 写入数据后的终端写队列回调函数 */         {0,0,0,0,""}, /* console read-queue */         {0,0,0,0,""}, /* console write-queue */         {0,0,0,0,""}  /* console secondary queue */     },{         {0, /* no translation */         0,  /* no translation */         B2400 | CS8, /* 波特率=2400,每字符8位 */         0,           /* 本地模式初始化为0 */         0,           /* 线速初始化为0 */         INIT_C_CC},  /* 控制字符序列 */         0,           /* 进程组号初始化为0 */         0,           /* 停止标志初始化为0 */         rs_write,    /* 串口1写下队列被写入数据后的回调函数 */         {0x3f8,0,0,0,""}, /* 0x3f8是串口1端口的起始地址,串口1读取队列的初始化 */         {0x3f8,0,0,0,""}, /* 串口1写队列初始化 */         {0,0,0,0,""}      /* 串口1辅助队列初始化 */     },{         {0, /* no translation */         0,  /* no translation */         B2400 | CS8, /* 波特率=2400,每字符8位 */         0,           /* 本地模式始化为0 */
        0,           /* 线路速率初始化为0 */
        INIT_C_CC},  /* 控制字符序列 */
        0,           /* 进程组号初始化为0 */
        0,           /* 停止标志初始化为0 */
        rs_write,         /* 串口2写队列被写入数据后的回调函数 */
        {0x2f8,0,0,0,""}, /* 0x2f8为串口2端口起始地址,串口2读队列初始化 */
        {0x2f8,0,0,0,""}, /* 串口2写队列初始化 */
        {0,0,0,0,""}      /* 串口2辅助队列初始化 */
    }
};

/*
 * these are the tables used by the machine code handlers.
 * you can implement pseudo-tty's or something by changing
 * them. Currently not done.
 */
/* table_list 
 * -------------------------------------------
 * |      |      |      |      |      |      |
 * -------------------------------------------
 * +0     +4     +8     +12    +16    +20 
 * table_list +  8(12)为串口1读(写)队列首地址;
 * table_list + 16(20)为串口2读(写)队列首地址。*/
struct tty_queue * table_list[]={
    &tty_table[0].read_q, &tty_table[0].write_q,
    &tty_table[1].read_q, &tty_table[1].write_q,
    &tty_table[2].read_q, &tty_table[2].write_q
};

/* tty_init,
 * 初始化串口(UART)和控制台终端的通信方式(中断),激活键盘。*/
void tty_init(void)
{
    rs_init();
    con_init();
}

/* tty_intr,
 * 产生mask信号给tty所在进程组的进程。*/
void tty_intr(struct tty_struct * tty, int mask)
{
    int i;

    if (tty->pgrp <= 0)
        return;
    for (i=0;ipgrp==tty->pgrp)
            task[i]->signal |= mask;
}

/* sleep_if_empty,
 * 若当前进程无其他需处理的信号且queue指向的队列为空则进入睡眠。*/
static void sleep_if_empty(struct tty_queue * queue)
{
    cli(); /* 本进程睡眠过程中进制CPU处理中断 */
    while (!current->signal && EMPTY(*queue))
        /* 让本进程进入睡眠直到有其他进程调用
         * wake_up(&queue->proc_list)将本进程唤醒。
         *
         * interruptible_sleep_on跟sleep_on的区别为
         * 经interruptible_sleep_on函数睡眠的进程的
         * 状态为TASK_INTERRUPTIBLE,可被进程的signal
         * 将进程状态设置为TASK_RUNNING即重新运行;而
         * 后者只能通过显示设置进程状态为TASK_RUNNING
         * 时才能唤醒该进程。*/
        interruptible_sleep_on(&queue->proc_list);
    sti();
}

/* sleep_if_full,
 * 若当前进程无其他需处理的信号且queue所指队列已满则进入睡眠。*/
static void sleep_if_full(struct tty_queue * queue)
{
    if (!FULL(*queue))
        return;
    cli(); /* 睡眠过程中进制CPU处理本进程中断 */
    while (!current->signal && LEFT(*queue)<128)
        /* 进程无其他信号处理且队列空余数小于128时则睡眠
         * 直到被其它进程调用wake_up(&queue->proc_list)将
         * 本进程唤醒。interruptible_sleep_on跟sleep_on的
         * 区别为经interruptible_sleep_on函数睡眠的进程的
         * 状态为TASK_INTERRUPTIBLE,可被进程的signal将进程
         * 状态设置为TASK_RUNNING即重新运行;而后者只能通过
         * 显示设置进程状态为TASK_RUNNING时才能唤醒该进程。*/
        interruptible_sleep_on(&queue->proc_list);
    sti();
}

/* wait_for_keypress,
 * (让使用控制台终端的进程)等待键盘输入。*/
void wait_for_keypress(void)
{
    sleep_if_empty(&tty_table[0].secondary);
}

/* copy_to_cooked,
 * 将从字符设备所读字符(转换为规范模式)并存入辅助队列中。
 *
 * 字符设备控制器接收数据中断-->PIC-->CPU执行串口读中断
 * 处理函数(read_char)-->copy_to_cooked。当字符设备读队
 * 列中的所有字符被存入辅助队列中后,即可唤醒等待读字符设
 * 备辅助队列的进程,以接收到字符设备数据。*/
void copy_to_cooked(struct tty_struct * tty)
{
    signed char c;

    /* 字符设备读队列不为空且辅助队列非满 */
    while (!EMPTY(tty->read_q) && !FULL(tty->secondary)) {

        /* [1] 从字符设备读队列中读取1个字符 */
        GETCH(tty->read_q,c);

        /* [2] 若字符为回车且为设备设置了回车转换行标志,则转换字符,
         * 若设置了忽略回车字符则将其忽略,继续读取下一个字符;
         * 
         * 若字符为换行且设备设置了换行转回车标志则转换字符;
         * 若为设备设置了将字符转换为小写字符标志,则进行转换。*/
        if (c==13) /* 回车键 */
            if (I_CRNL(tty))
                c=10;
            else if (I_NOCR(tty))
                continue;
            else ;
        else if (c==10 && I_NLCR(tty))
            c=13;
        if (I_UCLC(tty))
            c=tolower(c);

        /* 若为字符设备开启了规范模式标志 */
        if (L_CANON(tty)) {
            /* 若当前字符为删除行的字符,则作删除当前行的处理 */
            if (c==KILL_CHAR(tty)) {
                /* deal with killing the input line */
                /* 若读取到删除当前行字符后,则删除当前行
                 * 字符直到遇到上一行回车或文件结束符。若
                 * 为设备设置了回显标志,则将删除字符写入设
                 * 备写的队列中(控制字符共2字节,需两个删除
                 * 字符), 删除辅助队列中1字符时,减少写队列
                 * 数据头索引。*/
                while(!(EMPTY(tty->secondary) ||
                        (c=LAST(tty->secondary))==10 ||
                        c==EOF_CHAR(tty))) {
                    if (L_ECHO(tty)) {
                        if (c<32)
                            PUTCH(127,tty->write_q);
                        PUTCH(127,tty->write_q);
                        tty->write(tty);
                    }
                    DEC(tty->secondary.head);
                }
                /* 当前行删除后,从读队列中读取下一个字符 */
                continue;
            }

            /* 若当前字符为删除字符, */
            if (c==ERASE_CHAR(tty)) {
                /* 若辅助队列为空或者辅助队列尾数据为换行字
                 * 符或为文件结束字符则继续读取下一个字符处理 */
                if (EMPTY(tty->secondary) ||
                   (c=LAST(tty->secondary))==10 ||
                   c==EOF_CHAR(tty))
                        continue;
                /* 若辅助队列非空数据尾字符非换行符或结束标志则向设备的
                 * 写队列中写入删除字符,对于控制字符则需写入两个删除字符 */
                if (L_ECHO(tty)) {
                    if (c<32)
                        PUTCH(127,tty->write_q);
                    PUTCH(127,tty->write_q);
                    tty->write(tty);
                }
                /* 删除辅助队列中数据头字符后减少辅助队列数据头索引,
                 * 并继续从设备读队列中读取下一字符以处理。*/
                DEC(tty->secondary.head);
                continue;
            }

            /* 若读取字符为停止控制字符则置位tty停止输出
             * 标志并继续处理读队列中的下1字符 */
            if (c==STOP_CHAR(tty)) {
                tty->stopped=1;
                continue;
            }
            /* 若读取字符为开始控制字符则复位tty停止输出
             * 标志并继续处理读队列中的下1字符 */
            if (c==START_CHAR(tty)) {
                tty->stopped=0;
                continue;
            }
        }

        /* 若没有为当前设备设置规范标志,则直接将读队列中的字符存储到到辅助队列中。*/
         
        /* 若为设备置位了ISIG标志,若收到INTR,QUIT,SUSP,DSUSP控制字符时,
         * 则向进程输出相应信号。若收到键盘中断控制符(^C)则向当前进程所
         * 在进程组中的所有进程发送中断信号。若收到退出符(^\)则向当前进
         * 程所在组的所有进程发送退出信号。做完这些处理后继续处理下1字符。*/
        if (L_ISIG(tty)) {
            if (c==INTR_CHAR(tty)) {
                tty_intr(tty,INTMASK);
                continue;
            }
            if (c==QUIT_CHAR(tty)) {
                tty_intr(tty,QUITMASK);
                continue;
            }
        }

        /* 若当前字符为换行符或文件结束符,
         * 表示已处理完一行字符,则将辅助队
         * 列成员data增1,以表示往辅助队列中
         * 又转换了一行输入。*/
        if (c==10 || c==EOF_CHAR(tty))
            tty->secondary.data++;

        /* 若为当前设备设置了回显标志,则将读入字符写回设备显示。
         * 若当前字符为换行符,则往设备回写换行符和回车符;若是控
         * 制字符,则将其写回设备显示(若为设备设置了回显控制符标
         * 志,则将控制字符转换为诸如^H形式显示)。*/
        if (L_ECHO(tty)) {
            if (c==10) {
                PUTCH(10,tty->write_q);
                PUTCH(13,tty->write_q);
            } else if (c<32) {
                if (L_ECHOCTL(tty)) {
                    PUTCH('^',tty->write_q);
                    PUTCH(c+64,tty->write_q);
                }
            } else
                PUTCH(c,tty->write_q);
            tty->write(tty);
        }
        PUTCH(c,tty->secondary);
    }
    /* 当字符设备读队列中的字符处理完毕后,即可唤醒等在辅助队列上的进程 */
    wake_up(&tty->secondary.proc_list);
}

/* tty_read,
 * 从当前进程的字符设备的辅助队列中读取nr字节数据到buf中,
 * channel = 0, 控制台终端,
 * channel = 1, 串口1终端,
 * channel = 2, 串口2终端。
 *
 * 系统调用读文件(read) --> 内核(sys_read) -->
 * 区分读字符设备(rw_char) --> 区分字符设备类型
 * (rw_ttyx,rw_tty) --> 从字符设备辅助队列中读取字符(tty_read)。
 *
 * 字符设备数据到辅助队列:当字符设备接收到字符时以中
 * 断方式通知CPU读取该字符到字符设备对应的读队列中,
 * 然后通过中断C处理函数将该字符读(转换)到辅助队列中。*/
int tty_read(unsigned channel, char * buf, int nr)
{
    struct tty_struct * tty;
    char c, * b=buf;
    int minimum,time,flag=0;
    long oldalarm;

    /* 根据channel获取 管理字符设备的结构体 */
    if (channel>2 || nr<0) return -1;
    tty = &tty_table[channel];

    /* 备份当前进程的定时值;
     * 获取为字符设备设置的超时值(0则未设置)和达到该超
     * 时值应读取的字符数,若当前进程没有设置超时值或者
     * 读取字符设备的超时值小于进程原设置的超时值,则用
     * 读取字符的超时值覆盖进程原超时值。待任务调度函数
     * 执行时(如当前进程进入睡眠后)会检查当前进程是否超
     * 时,若超时则会给任务置超时信号。*/
    oldalarm = current->alarm;
    time = 10L*tty->termios.c_cc[VTIME];
    minimum = tty->termios.c_cc[VMIN];
    if (time && !minimum) {
        minimum=1;
        if (flag=(!oldalarm || time+jiffiesalarm = time+jiffies;
    }
    if (minimum>nr)
        minimum=nr;

    /* 从字符设备辅助队列中读取nr个字符到buf中。
     *
     * 首先检查读取字符是否超时,若超时或进程有其他
     * 信号要处理则停止读取;若辅助队列不满足读取条
     * 件则尝试睡眠等待辅助队列满足被读条件;待辅助
     * 队列满足阅读条件时再检查读取字符是否超时,满
     * 足条件后从辅助队列中读取字符到buf中,直到遇到
     * 如文件结束符等结束标志。*/
    while (nr>0) {
        /* 若当前进程超时值为字符读取超时值(flag),检查
         * 本进程是否有超时信号需处理,若是则清除进程超
         * 时信号后退出循环。*/
        if (flag && (current->signal & ALRMMASK)) {
            current->signal &= ~ALRMMASK;
            break;
        }
        /* 若当前进程还有其他信号需处理则停止字符的继续读取 */
        if (current->signal)
            break;

        /* 若字符设备辅助队列空或者在为字符设备开启规范标志
         * 前提下(以行为单位进行读取),当辅助队列中的字符数少
         * 于1行且辅助队列剩余空间大于20则进入睡眠(在队列为空
         * 时sleep_if_empty才会真正地进入睡眠,这种情况只有等超
         * 时退出读取咯)。待真正进入睡眠时,待被其它进程唤醒后
         * 若未超时将继续读取。*/
        if (EMPTY(tty->secondary) || (L_CANON(tty) &&
        !tty->secondary.data && LEFT(tty->secondary)>20)) {
            sleep_if_empty(&tty->secondary);
            continue;
        }

        /* 从辅助队列中读取字符序列依次存到buf中,
         * 在字符设备开启规范标志时,若读到文件结束
         * 符,换行符时则结束读操作;或在读满nr字符或
         * 读完辅助队列内容方结束。*/
        do {
            GETCH(tty->secondary,c);
            if (c==EOF_CHAR(tty) || c==10)
                tty->secondary.data--;
            if (c==EOF_CHAR(tty) && L_CANON(tty))
                return (b-buf);
            else {
                put_fs_byte(c,b++);
                if (!--nr)
                    break;
            }
        } while (nr>0 && !EMPTY(tty->secondary));

        /* 检查并更新当前进程的超时值。
         * 
         * 若字符设备设置了超时时间且没有开启规范模式,
         * 若当前进程无超时值或其超时值比字符设备所设
         * 置的超时值要大时,则更新当前进程的超时值为
         * 字符设备所设置的超时值,否则恢复进程原本超时值。*/
        if (time && !L_CANON(tty))
            if (flag=(!oldalarm || time+jiffiesalarm = time+jiffies;
            else
                current->alarm = oldalarm;

        /* 在一次读取循环结束后,
         * 在规范模式下,只要读到字符便结束本次读取;
         * 在非规范模式下,当读取到超时所对应字符数时才停止本次读取。*/
        if (L_CANON(tty)) {
            if (b-buf)
                break;
        } else if (b-buf >= minimum)
            break;
    }
    
    /* 恢复进程的超时值,若读取超时且没有读取到任何
     * 字符则返回相应错误码,否则返回读取成功的字符数。*/
    current->alarm = oldalarm;
    if (current->signal && !(b-buf))
        return -EINTR;
    return (b-buf);
}

/* tty_write,
 * 
 *
 * 系统调用写文件(write)
 * --> 内核(sys_write)
 * --> 区分写字符设备(rw_char)
 * --> 区分字符设备类型(rw_ttyx,rw_tty)
 * --> 往字符设备写队列中写字符(tty_write)
 * --> 开启字符设备发送中断(rs_write)
 * <--> 字符设备发送中断处理函数发送字符(rs_interrupt,write_char)
 * --> 关字符设备发送中断。*/
int tty_write(unsigned channel, char * buf, int nr)
{
    static cr_flag=0;
    struct tty_struct * tty;
    char c, *b=buf;

    if (channel>2 || nr<0) return -1;
    tty = channel + tty_table;

    /* 向字符设备写队列中写入buf内存段的nr字节数据,直到
     * nr字节内容被完全写入或者当前进程收到其他处理信号。*/
    while (nr>0) {
        /* 若字符设备写队列为空则睡眠等待写队列非空 */
        sleep_if_full(&tty->write_q);
        if (current->signal)
            break;

        /* 当写队列非满时,将buf中的nr字符往写队列中。
         * 若设置了输出处理标志则根据相应输出处理标志
         * 对字符做转换后再写入。*/
        while (nr>0 && !FULL(tty->write_q)) {
            c=get_fs_byte(b);
            if (O_POST(tty)) {
                if (c=='\r' && O_CRNL(tty))
                    c='\n';
                else if (c=='\n' && O_NLRET(tty))
                    c='\r';
                if (c=='\n' && !cr_flag && O_NLCR(tty)) {
                    cr_flag = 1;
                    PUTCH(13,tty->write_q);
                    continue;
                }
                if (O_LCUC(tty))
                    c=toupper(c);
            }
            b++; nr--;
            cr_flag = 0;
            PUTCH(c,tty->write_q);
        }
        /* 当完成一次循环写入队列后,调用写队列后的回调函数;
         * 若还未写完nr字符但写队列已满时则调用任务调度函数
         * 切换任务,待切换到本任务时继续发送,在任务切换函数
         * 中有可能会为当前进程置某种信号(signal)。*/
        tty->write(tty);
        if (nr>0)
            schedule();
    }
    
    /* 返回写入字节数 */
    return (b-buf);
}

/*
 * Jeh, sometimes I really like the 386.
 * This routine is called from an interrupt,
 * and there should be absolutely no problem
 * with sleeping even in an interrupt (I hope).
 * Of course, if somebody proves me wrong, I'll
 * hate intel for all time :-). We'll have to
 * be careful and see to reinstating the interrupt
 * chips before calling this, though.
 *
 * I don't think we sleep here under normal circumstances
 * anyway, which is good, as the task sleeping might be
 * totally innocent.
 */
/* do_tty_interrupt,
 * 字符设备读中断C处理函数,
 * 将所读字符存储到字符设备的辅助队列中。
 * 
 * 由字符设备接收中断处理函数read_char调用。*/
void do_tty_interrupt(int tty)
{
    copy_to_cooked(tty_table+tty);
}

void chr_dev_init(void)
{
}
rs_io.s
/*
 *  linux/kernel/rs_io.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * rs_io.s
 *
 * This module implements the rs232 io interrupts.
 */
/* rs_io.s
 * 本程序实现了 rs232 I/O中断处理程序。*/

.text
.globl _rs1_interrupt,_rs2_interrupt

size = 1024 /* must be power of two !
               and must match the value
               in tty_io.c!!! */

/* these are the offsets into the read/write buffer structures */
/* 串口读写队列结构体中各成员偏移量 */
rs_addr = 0
head = 4
tail = 8
proc_list = 12
buf = 16

startup = 256 /* chars left in write queue when we restart it */

/*
 * These are the actual interrupt routines. They look where
 * the interrupt is coming from, and take appropriate action.
 */
.align 2
_rs1_interrupt:
    pushl $_table_list+8 /* table_list第3个元素的地址 */
    jmp rs_int
.align 2
_rs2_interrupt:
    pushl $_table_list+16 /* table_list第5个元素的地址 */
rs_int:
    pushl %edx
    pushl %ecx
    pushl %ebx
    pushl %eax
    push %es
    push %ds    /* as this is an interrupt, we cannot */
    pushl $0x10 /* know that bs is ok. Load it */
    pop %ds     /* es=ds=10h, 加载内核数据段到数据段寄存器中 */
    pushl $0x10
    pop %es
    movl 24(%esp),%edx /* edx=table_list + 8 or 16 */
    movl (%edx),%edx   /* 取串口1或串口2读队列内存首地址 */
    movl rs_addr(%edx),%edx /* 取读队列data成员即串口1或2的起始端口(0x3f8, 0x2f8) */
    addl $2,%edx /* interrupt ident. reg  0x3fa(0x2fa) */
rep_int:
    xorl %eax,%eax
    inb %dx,%al  /* 读0x3fa(0x2fa)即读中断标识寄存器以判断串口中断类型 */
    testb $1,%al /* bit[1]=1则表示无中断 */
    jne end
    cmpb $6,%al /* 接收状态有错,this shouldn't happen, but ... */
    ja end
    movl 24(%esp),%ecx /* ecx=table_list + 8 or 16 */
    pushl %edx
    subl $2,%edx /* 传递给子程序的参数edx=0x3f8(0x2f8) */
    /* al=110,接收状态有错,奇偶错etc,读线路状态寄存器复位;
     * al=100,接收数据就绪,接收器数据有效,读接收数据寄存器复位;
     * al=010,发送保持寄存器空,发送器准备就绪,写入发送保持寄存器;
     * al=000,MODEM状态有变化,输入状态有变化,读MODEM状态寄存器;
     * 
     * 根据串口中断类型跳转执行jmp_table表中相应的中断中断处理函数。 */
    call jmp_table(,%eax,2) /* NOTE! not *4, bit0 is 0 already */
    popl %edx
    jmp rep_int /* 直到串口无中断方结束 */
end:    movb $0x20,%al
    outb %al,$0x20  /* EOI,向PIC发送结束中断命令 */
    pop %ds
    pop %es
    popl %eax
    popl %ebx
    popl %ecx
    popl %edx
    addl $4,%esp # jump over _table_list entry
    iret

/* 各串扣中断对应的中断处理程序表,他们分别是
 * MODEM寄存器有变化,发送保持寄存器空,接收数据,接收状态错误
 * 的中断处理程序。*/
jmp_table:
    .long modem_status,write_char,read_char,line_status

.align 2
modem_status: /* 读MODEM状态寄存器(0x3fe或0x2fe)以让MODEM寄存器复位 */
    addl $6,%edx /* clear intr by reading modem status reg */
    inb %dx,%al
    ret

.align 2
line_status: /* 读线路状态寄存器(0x3fd或0x2fd)以让MODEM寄存器复位 */
    addl $5,%edx /* clear intr by reading line status reg. */
    inb %dx,%al
    ret

/* 读串口所接收到的数据 */
.align 2
read_char:
    inb %dx,%al    /* 读0x3f8(0x2f8), 读接收数据寄存器 */
    movl %ecx,%edx /* edx=ecx=table_list+8 or +16 */
    subl $_table_list,%edx  /* edx=8 or 16 */
    shrl $3,%edx            /* edx=1 or 2 */
    
    /* ecx=table_list[8 or 16)]即&tty_table[1 or 2].read_q,
     * 获取串口读队列地址赋给ecx。*/
    movl (%ecx),%ecx

    /* movl (%ecx+head), ebx,
     * ebx=*( (long *)(&tty_table[1 or 2].read_q + 4) )即
     * tty_table[1 or 2].read_q.head,将串口读队列中head成员赋给ebx寄存器中。*/
    movl head(%ecx),%ebx

    /* 将从串口中所读数据写入*( (char *)(&tty_table[1 or 2].read_q + 16 + head) )
     * 即tty_table[1 or 2].read_q.buf[ebx]中,即将从串口所读数据写入串口读队列的buf成员中。*/
    movb %al,buf(%ecx,%ebx)

    incl %ebx /* tty_table[1 or 2].read_q.buf数据头索引增1 */
    andl $size-1,%ebx      /* ebx=ebx & size - 1,即以循环队列的方式使用队列中的buf */
    cmpl tail(%ecx),%ebx   /* 判断tty_table[1 or 2].read_q.tail是否等于ebx即buf中数据头索引 */
    je 1f /* 若数据头索引等于数据尾索引表示buf已满则向前跳转标号1处, */
    movl %ebx,head(%ecx)   /* 将数据头索引赋值给tty_table[1 or 2].read_q.head */
1: pushl %edx              /* table_list读队列下标(1 or 2)作为do_tty_interrupt函数的参数 */
    call _do_tty_interrupt /* kernel/chr_drv/tty_io.c */
    addl $4,%esp           /* 清参数edx的栈内存 */
    ret

.align 2
write_char:
    movl 4(%ecx),%ecx /* ecx=&tty_table[1 or 2].write_q */

    /* ebx=*( (long *)(&tty_table[1 or 2].write_q + 4) )即
     * ebx=串口写队列head成员 */
    movl head(%ecx),%ebx

    /* ebx=写队列中的数据个数 */
    subl tail(%ecx),%ebx
    andl $size-1,%ebx # nr chars in queue

    je write_buffer_empty /* 若写队列为空则跳转write_buffer_empty处 */
    cmpl $startup,%ebx    /* if (写队列数据元素 < 256) 则向前跳转标号1处 */
    ja 1f

    /* ebx=*( (long *)(&tty_table[1 or 2].write_q + 12) )即
     * 取串口写队列任务指针成员proc_list赋值给ebx。*/
    movl proc_list(%ecx),%ebx # wake up sleeping process

    /* 若任务指针值为空则向前跳转到标号1处 */
    testl %ebx,%ebx # is there any?
    je 1f

    /* movl $0, *proc_list 即proc_list所指内存段首4字节置为0,
     * 即将proc_list所指任务的state成员置位0-将所指任务置为就绪状态 */
    movl $0,(%ebx)

    /* 将写队列数据尾的数据写往al,然后将其 */
1:  movl tail(%ecx),%ebx
    movb buf(%ecx,%ebx),%al
    outb %al,%dx /* 写发送器保持寄存器0x3f8(0x2f8) */
    incl %ebx    /* 数据尾索引增1 */
    andl $size-1,%ebx /* 以循环队列的方式使用队列中的buf */
    movl %ebx,tail(%ecx) /* 将数据尾索引赋给串口写队列tail成员 */
    cmpl head(%ecx),%ebx /* 比较串口写队列中指向数据头和数据尾成员,若两者相等表示队列空 */
    je write_buffer_empty /* 队列空则跳转write_buffer_empty处 */
    ret /* 将串口写队列中数据传输一个到串口让其发送出去 */
.align 2
write_buffer_empty:
    /* 将串口写队列的proc_list成员赋值给ebx,
     * 检查ebx是否为空,为空则向前跳转标号1处,
     * 若不为空则赋值0给proc_list所指任务的state成员,
     * 即将该任务置位可运行状态,即唤醒该任务。*/
    movl proc_list(%ecx),%ebx # wake up sleeping process
    testl %ebx,%ebx # is there any?
    je 1f
    movl $0,(%ebx)
    
1:  incl %edx   /* edx=0x3f9(0x2f9) */
    inb %dx,%al /* 读中断允许标志寄存器 */
    jmp 1f
1: jmp 1f
1: andb $0xd,%al /* disable transmit interrupt */
    outb %al,%dx /* 禁止串口发送寄存器空中断,因为此时写队列中的数据已发送完毕 */
    ret
serial.c
/*
 *  linux/kernel/serial.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * serial.c
 *
 * This module implements the rs232 io functions
 * void rs_write(struct tty_struct * queue);
 * void rs_init(void);
 * and all interrupts pertaining to serial IO.
 */

#include 
#include 
#include 
#include 

#define WAKEUP_CHARS (TTY_BUF_SIZE/4)

/* 以typedef void (fun)(void)函数类型
 * 声明以下符号,其定义在rs_io.s中。*/
extern void rs1_interrupt(void);
extern void rs2_interrupt(void);

/* init,
 * 初始化串口通信模式,如串口
 * 终端设备以中断方式和CPU通信,数据速率=2400bps。*/
static void init(int port)
{
/* PC/AT分配给UART2和UART1的端口地址空间分别为
 * [0x2f0, 0x2ff]和[0x3f8, 0x3ff]。实际芯片使用
 * 端口低3位用于寄存器寻址,UART2只使用了[0x2f8, 0x2fe],
 * UART1只使用了[0x3f8, 0x3fe]。*/
 
    /* 写3FBH(2FBH) 0x80,
     * 写线路控制寄存器, 确定异步通信的数据格式,
     * DLAB=1, 无奇偶, 停止位为1位, 数据5位。
     *
     * DLAB=1, 写3F8H(2F8H)/3F9H(2F9H) 0x30/0x00,
     * 写波特率因子LSB/MSB,
     * MSB=0x00, LSB=0x30 --> 数据速率=2400bps。
     * (波特率因子: 接收/发送一个bit所需时钟数,
     * 由此顺便计算下此时的时钟频率=2400bps * 48 = 115200Hz)
     *
     * 写3FBH(2FBH) - 写线路控制寄存器 0x03,
     * DLAB = 0, 无奇偶, 数据位为8位。
     *
     * 写3FCH(2FCH) - 写MODEM控制寄存器 0x0b, (RS-232)
     * bit[3]=1, UART为中断I/O方式, 
     * bit[1]=1, 数据终端就绪, DTR输出有效,
     * bit[0]=1, 请求发送, RTS输出有效。
     *
     * DLAB=0,写3F9H(2F9H) 0x0d,
     * 写中断允许寄存器,
     * bit[3]=1, 允许MODEM状态变化中断, 
     * bit[2]=1, 允许接收有错或间断条件中断,
     * bit[1]=0, 禁止发送器保持寄存器空中断,
     * bit[0]=1, 允许接收器数据就绪中断,
     * 当UART为中断I/O方式, 满足以上某中断条件时, 
     * 芯片的INTRPT(中断请求)端输出高电平向8259A(IRQ4/IRQ3中断发生),
     * 并在中断标识寄存器中设置相应标识位标识当前中断。
     *
     * DLAB=0,
     * 读3F8H(2F8H), 读接收数据寄存器, 
     * 将接收数据寄存器的内容读出以恢复接收数据寄存器无数据状态?*/
    outb_p(0x80,port+3); /* set DLAB of line control reg */
    outb_p(0x30,port);   /* LS of divisor (48 -> 2400 bps */
    outb_p(0x00,port+1); /* MS of divisor */
    outb_p(0x03,port+3); /* reset DLAB */
    outb_p(0x0b,port+4); /* set DTR,RTS, OUT_2 */
    outb_p(0x0d,port+1); /* enable all intrs but writes */
    (void)inb(port); /* read data port to reset things (?) */
}
 
/* rs_init,
 * UART初始化,
 * 在IDT中设置串口中断处理入口程序,
 * 设置PIC使能串口中断,并设置串口通信相关参数。*/
void rs_init(void)
{
    /* 在IDT[24h..23h]中设置串口2和串口1
     * 的中断处理入口程序。*/
    set_intr_gate(0x24,rs1_interrupt);
    set_intr_gate(0x23,rs2_interrupt);

    /* 编程设置UART,建立异步串行通信模式。
     * 以中断方式和CPU通信,数据传输速率为2400bps。*/
    init(tty_table[1].read_q.data); /* 0x3f8 */
    init(tty_table[2].read_q.data); /* 0x2f8 */

    /* 设置PIC允许IRQ3和IRQ4即串口2和串口1中断 */
    outb(inb_p(0x21)&0xE7,0x21);
}

/*
 * This routine gets called when tty_write has put something into
 * the write_queue. It must check wheter the queue is empty, and
 * set the interrupt register accordingly
 *
 * void _rs_write(struct tty_struct * tty);
 */
/* rs_write,
 * 设置tty对应的UART允许其发送保持寄存器空闲时的中断。
 * 
 * 该函数在tty_write往写队列tty->write_queue写一些数
 * 据后被调用,当UART发送保持寄存器空闲时就会向PIC输出
 * 中断从而让CPU执行串口中断处理入口程序rs*_interrupt,
 * 待tty->write_queue队列中无数据时将再禁止UART发送保
 * 持寄存器空闲时中断。*/
void rs_write(struct tty_struct * tty)
{
    cli();
    /* 当串口写队列不为空时,
     * 通过0x3f9(0x2f9)写UART发送保持寄存器
     * bit[1]以允许为发送保持寄存器空时中断。*/
    if (!EMPTY(tty->write_q))
        outb(inb_p(tty->write_q.data+1)|0x02,tty->write_q.data+1);
    sti();
}
console.c
/*
 *  linux/kernel/console.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * console.c
 *
 * This module implements the console io functions
 * 'void con_init(void)'
 * 'void con_write(struct tty_queue * queue)'
 * Hopefully this will be a rather complete VT102 implementation.
 *
 * Beeping thanks to John T Kohl.
 */
/* 本文件实现了控制台I/O操作函数
 * 'void con_init(void)'
 * 'void con_write(struct tty_queue * queue)'
 * 希望这是一个相当完整的VT102版实现。*/

/*
 *  NOTE!!! We sometimes disable and enable interrupts for a short while
 * (to put a word in video IO), but this will work even for keyboard
 * interrupts. We know interrupts aren't enabled when getting a keyboard
 * interrupt, as we use trap-gates. Hopefully all is well.
 */
/* 注,在放置数据到显卡I/O时会禁止CPU处理中断,这些代码也会在键盘中断中运行。
 * 由于使用陷阱门,所以其实在键盘中断中没有使能CPU处理中断。希望这种重复禁止
 * 的方式可以正常运行。*/
 
/*
 * Code to check for different video-cards mostly by Galen Hunt,
 * 
 */

#include 
#include 
#include 
#include 

/*
 * These are set up by the setup-routine at boot-time:
 */
/* 以下这些宏读取在setup.s中通过BIOS获取并存储的跟显示相关的信息 */
#define ORIG_X          (*(unsigned char *)0x90000) /* 光标x方向位置 */
#define ORIG_Y          (*(unsigned char *)0x90001) /* 光标y方向位置 */
#define ORIG_VIDEO_PAGE (*(unsigned short *)0x90004) /* 当前显示页 */
#define ORIG_VIDEO_MODE ((*(unsigned short *)0x90006) & 0xff) /* 显示模式 */
#define ORIG_VIDEO_COLS (((*(unsigned short *)0x90006) & 0xff00) >> 8) /* 窗口宽度 */
#define ORIG_VIDEO_LINES    (25)
#define ORIG_VIDEO_EGA_AX   (*(unsigned short *)0x90008) /**/
#define ORIG_VIDEO_EGA_BX   (*(unsigned short *)0x9000a) /* EGA显存大小 */
#define ORIG_VIDEO_EGA_CX   (*(unsigned short *)0x9000c) /* 属性等设置 */

#define VIDEO_TYPE_MDA  0x10 /* Monochrome Text Display */
#define VIDEO_TYPE_CGA  0x11 /* CGA Display */
#define VIDEO_TYPE_EGAM 0x20 /* EGA/VGA in Monochrome Mode */
#define VIDEO_TYPE_EGAC 0x21 /* EGA/VGA in Color Mode */

#define NPAR 16

extern void keyboard_interrupt(void);

static unsigned char  video_type;        /* Type of display being used */
static unsigned long  video_num_columns; /* Number of text columns */
static unsigned long  video_size_row;    /* Bytes per row */
static unsigned long  video_num_lines;   /* Number of test lines */
static unsigned char  video_page;        /* Initial video page */
static unsigned long  video_mem_start;   /* Start of video RAM */
static unsigned long  video_mem_end;     /* End of video RAM (sort of) */
static unsigned short video_port_reg;    /* Video register select port */
static unsigned short video_port_val;    /* Video register value port */
static unsigned short video_erase_char;  /* Char+Attrib to erase with */

/* 跟显存地址对应的屏幕位置 */
static unsigned long origin;  /* 屏幕内容左上角对应的显存地址 */
static unsigned long scr_end; /* 屏幕内容末端内容对应的显存地址 */
static unsigned long pos;     /* 屏幕坐标(x,y)对应的显存地址 */
static unsigned long x,y;     /* 屏幕坐标 */
static unsigned long top,bottom; /* 屏幕内容的顶端和底部坐标 */
static unsigned long state=0; /* 解析终端写队列中数据的状态/步骤 */
static unsigned long npar,par[NPAR];
static unsigned long ques=0;
static unsigned char attr=0x07;

static void sysbeep(void);

/*
 * this is what the terminal answers to a ESC-Z or csi0c
 * query (= vt100 response).
 */
#define RESPONSE "\033[?1;2c"

/* NOTE! gotoxy thinks x==video_num_columns is ok */
/* gotoxy,
 * 更新光标在屏幕上的位置(x,y),并计算该位置所对应的显存地址。*/
static inline void gotoxy(unsigned int new_x,unsigned int new_y)
{
    if (new_x > video_num_columns || new_y >= video_num_lines)
        return;
    /* x,y用于记录光标在屏幕上的坐标 */
    x=new_x;
    y=new_y;
    /* 计算屏幕坐标(x,y)对应的显存地址(1列用2字节显存表示) */
    pos=origin + y*video_size_row + (x<<1);
}

/* set_origin,
 * 设置终端屏幕起始显存地址,以将整屏对应的内容显示在终端上。*/
static inline void set_origin(void)
{
    cli();
    /* 选择显示控制数据寄存器r12,
     * 写入终端将要显示内容所在显存中的偏移地址的高字节。*/
    outb_p(12, video_port_reg);
    outb_p(0xff&((origin-video_mem_start)>>9), video_port_val);

    /* 选择显示控制数据寄存器r3,
     * 写入终端将要显示内容所在显存中的偏移地址的低字节(1列2字节)。*/
    outb_p(13, video_port_reg);
    outb_p(0xff&((origin-video_mem_start)>>1), video_port_val);
    sti();
}

/* scrup,
 * 将终端窗口向上移动一行。
 * 
 * 终端内容向下继续显示一行,
 * 若终端屏幕已满,则向上移动一行,
 * 终端屏幕下方出现的新行用空格填充。*/
static void scrup(void)
{
    /* EGA显卡支持终端区域和整屏窗口移动, */
    if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
    {
        /* 若终端已全屏显示,*/
        if (!top && bottom == video_num_lines) {
            /* 则将终端内容左上角,坐标位置,终端内容末端
             * 的显存地址更新到下一行 */
            origin += video_size_row;
            pos += video_size_row;
            scr_end += video_size_row;

            /* 若控制终端内容末端显存地址已超过显存末尾地址,
             * 则将终端除第1行以外的内容重新写入显存起始
             * 地址处(新行用空格填充),并更新终端内容对应的显存内存段。*/
            if (scr_end > video_mem_end) {
                __asm__("cld\n\t"
                    "rep\n\t"
                    "movsl\n\t"
                    "movl _video_num_columns,%1\n\t"
                    "rep\n\t"
                    "stosw"
                    ::"a" (video_erase_char),
                    "c" ((video_num_lines-1)*video_num_columns>>1),
                    "D" (video_mem_start),
                    "S" (origin)
                    :"cx","di","si");
                scr_end -= origin-video_mem_start;
                pos -= origin-video_mem_start;
                origin = video_mem_start;
            /* 若控制终端内容末端还未超出显存末端则用空格填充新行*/
            } else {
                __asm__("cld\n\t"
                    "rep\n\t"
                    "stosw"
                    ::"a" (video_erase_char),
                    "c" (video_num_columns),
                    "D" (scr_end-video_size_row)
                    :"cx","di");
            }
            /* 将屏幕窗口内容对应的显存段写往显示控制
             * 器中,以将指定显存段内容显示在控制终端上 */
            set_origin();
            
        /* 若EGA显卡下,终端内容未满屏,不用整屏移动,
         * 此时将开始于top+1到bottom区域中的内容向
         * 上移动一行,用空格填充新出现的行。*/
        } else {
            __asm__("cld\n\t"
                "rep\n\t"
                "movsl\n\t"
                "movl _video_num_columns,%%ecx\n\t"
                "rep\n\t"
                "stosw"
                ::"a" (video_erase_char),
                "c" ((bottom-top-1)*video_num_columns>>1),
                "D" (origin+video_size_row*top),
                "S" (origin+video_size_row*(top+1))
                :"cx","di","si");
        }
    }
    /* 非EGA显卡, 诸如MDA显卡控制器只支持整屏滚动,
     * 但其会自动调整超出显存范围的情况。*/
    else    /* Not EGA/VGA */
    {
        __asm__("cld\n\t"
            "rep\n\t"
            "movsl\n\t"
            "movl _video_num_columns,%%ecx\n\t"
            "rep\n\t"
            "stosw"
            ::"a" (video_erase_char),
            "c" ((bottom-top-1)*video_num_columns>>1),
            "D" (origin+video_size_row*top),
            "S" (origin+video_size_row*(top+1))
            :"cx","di","si");
    }
}

/* scrdown,
 * 将终端窗口向下移动一行。
 * 
 * 终端内容向上继续显示一行,
 * 若终端屏幕已满,则向下移动一行,
 * 终端屏幕上方出现的新行用空格填充。*/
static void scrdown(void)
{
    if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
    {
        __asm__("std\n\t"
            "rep\n\t"
            "movsl\n\t"
            "addl $2,%%edi\n\t"	/* %edi has been decremented by 4 */
            "movl _video_num_columns,%%ecx\n\t"
            "rep\n\t"
            "stosw"
            ::"a" (video_erase_char),
            "c" ((bottom-top-1)*video_num_columns>>1),
            "D" (origin+video_size_row*bottom-4),
            "S" (origin+video_size_row*(bottom-1)-4)
            :"ax","cx","di","si");
    }
    else    /* Not EGA/VGA */
    {
        __asm__("std\n\t"
            "rep\n\t"
            "movsl\n\t"
            "addl $2,%%edi\n\t"	/* %edi has been decremented by 4 */
            "movl _video_num_columns,%%ecx\n\t"
            "rep\n\t"
            "stosw"
            ::"a" (video_erase_char),
            "c" ((bottom-top-1)*video_num_columns>>1),
            "D" (origin+video_size_row*bottom-4),
            "S" (origin+video_size_row*(bottom-1)-4)
            :"ax","cx","di","si");
    }
}

/* lf,
 * 针对输出的换行符进行换行。*/
static void lf(void)
{
    /* 若还未到屏幕底部则
     * 更新y方向坐标和对应的显存地址 */
    if (y+1top) {
        y--;
        pos -= video_size_row;
        return;
    }
    /* 若光标在终端内容顶端,
     * 则将终端窗口向下移动一行。*/
    scrdown();
}

/* cr,
 * 将光标置于行首。*/
static void cr(void)
{
    /* 显存地址减去 
     * 行首到x位置处的显存字节数 */
    pos -= x<<1;
    x=0;
}

/* del,
 * 更新删除1字符后光标的位置。*/
static void del(void)
{
    if (x) {
        pos -= 2;
        x--;
        *(unsigned short *)pos = video_erase_char;
    }
}

/* csi_J,
 * 以光标为基准,删除终端上的内容。
 *
 * 'ESC [par J',
 * par=0, 删除光标处到终端底端的内容;
 * par=1, 删除终端开始到光标处的内容;
 * par=2, 删除终端整屏。*/
static void csi_J(int par)
{
    long count __asm__("cx");
    long start __asm__("di");

    /* 根据par参数值,计算删除字符数及对应显存区域 */
    switch (par) {
        case 0: /* erase from cursor to end of display */
            count = (scr_end-pos)>>1;
            start = pos;
            break;
        case 1: /* erase from start to cursor */
            count = (pos-origin)>>1;
            start = origin;
            break;
        case 2: /* erase whole display */
            count = video_num_columns * video_num_lines;
            start = origin;
            break;
        default:
            return;
    }
    /* 用擦除字符video_erase_char填充终端上指定区域的字符 */
    __asm__("cld\n\t"
        "rep\n\t"
        "stosw\n\t"
        ::"c" (count),
        "D" (start),"a" (video_erase_char)
        :"cx","di");
}

/* csi_K,
 * 以光标位置为基准,删除光标所在行的内容。
 *
 * 'ESC [par K',
 * par=0,删除光标到行尾内容;
 * par=1,行首到光标段内容;
 * par=2,删除光标所在行。*/
static void csi_K(int par)
{
    long count __asm__("cx");
    long start __asm__("di");

    /* 根据par值计算删除的字符数及对应的显存区域 */
    switch (par) {
        case 0: /* erase from cursor to end of line */
            if (x>=video_num_columns)
                return;
            count = video_num_columns-x;
            start = pos;
            break;
        case 1:	/* erase from start of line to cursor */
            start = pos - (x<<1);
            count = (x>9), video_port_val);
    
    /* 选择显示控制器数据寄存器r14写入鼠标在显存中偏移的低字节*/
    outb_p(15, video_port_reg);
    outb_p(0xff&((pos-video_mem_start)>>1), video_port_val);
    sti();
}

/* respond,
 * 向主机响应终端的设备属性(主机通过'ESC Z'等控制序列请求)。*/
static void respond(struct tty_struct * tty)
{
    char * p = RESPONSE;

    cli();
    /* 将应答序列放入读队列中, */
    while (*p) {
        PUTCH(*p,tty->read_q);
        p++;
    }
    sti();
    /* 将含应答序列读队列中的内容转换到辅助队列中 */
    copy_to_cooked(tty);
}

/* insert_char,
 * 在光标位置插入擦除字符,将光标原后续字符皆后移。*/
static void insert_char(void)
{
    int i=x;
    unsigned short tmp, old = video_erase_char; /* 擦除字符 */
    unsigned short * p = (unsigned short *) pos; /* 光标对应显存地址 */

    /* 将擦除字符插入光标位置处 */
    while (i++=video_num_columns)
        return;
    /* 将光标之后字符依次左移,在行尾用擦除字符填充 */
    i = x;
    while (++i < video_num_columns) {
        *p = *(p+1);
        p++;
    }
    *p = video_erase_char;
}

/* delete_line,
 * 删除光标所在行。*/
static void delete_line(void)
{
    int oldtop,oldbottom;

    oldtop=top;
    oldbottom=bottom;

    /* 从光标所在行开始,将中断窗口上移一行 */
    top=y;
    bottom = video_num_lines;
    scrup();

    /* 恢复光标位置 */
    top=oldtop;
    bottom=oldbottom;
}

/* csi_at,
 * 在光标处插入nr个擦除字符。
 * 光标右边的字符往右移,超过终端右边界即列数的字符将消失。
 * 
 * 'ESC [nr @',nr为插入字符个数。*/
static void csi_at(unsigned int nr)
{
    if (nr > video_num_columns)
        nr = video_num_columns;
    else if (!nr)
        nr = 1;
    while (nr--)
        insert_char();
}

/* csi_L,
 * 在光标位置处插入nr行。
 *
 * 'ESC [nr L'。*/
static void csi_L(unsigned int nr)
{
    if (nr > video_num_lines)
        nr = video_num_lines;
    else if (!nr)
        nr = 1;
    while (nr--)
        insert_line();
}

/* csi_P,
 * 删除光标处的nr个字符。
 *
 * 'ESC [nr P'。*/
static void csi_P(unsigned int nr)
{
    if (nr > video_num_columns)
        nr = video_num_columns;
    else if (!nr)
        nr = 1;
    while (nr--)
        delete_char();
}

/* csi_M,
 * 删除光标处的nr行。
 *
 * 'ESC [ nr M'。*/
static void csi_M(unsigned int nr)
{
    if (nr > video_num_lines)
        nr = video_num_lines;
    else if (!nr)
        nr=1;
    while (nr--)
        delete_line();
}

/* 用于保存光标行和列号 */
static int saved_x=0;
static int saved_y=0;

/* save_cur,
 * 同步光标当前位置。*/
static void save_cur(void)
{
    saved_x=x;
    saved_y=y;
}

/* restore_cur,
 * 恢复所保存的光标位置。*/
static void restore_cur(void)
{
    gotoxy(saved_x, saved_y);
}

/* con_write,
 * 往控制终端写队列中写入数据后的回调函数。
 *
 * 解析终端写队列中的数据,若是控制字符,转义字符,
 * 控制序列则在终端实现这些字符对应的功能。*/
void con_write(struct tty_struct * tty)
{
    int nr;
    char c;

    /* 控制终端写队列中的字符数 */
    nr = CHARS(tty->write_q);

    while (nr--) {
        /* 从写队列中读取1个字符 */
        GETCH(tty->write_q,c);

        switch(state) {
            case 0:
                /* 在state=0阶段,若读取到普通字符
                 * 则直接显示在光标位置处,若已到行
                 * 尾则将字符写到下一行。显示字符后
                 * 更新光标和其对应的显存地址。*/
                if (c>31 && c<127) {
                    if (x>=video_num_columns) {
                        x -= video_num_columns;
                        pos -= video_size_row;
                    lf();
                    }
                    /* 将字符显示在经计算的位置 */
                    __asm__("movb _attr,%%ah\n\t"
                        "movw %%ax,%1\n\t"
                        ::"a" (c),"m" (*(short *)pos)
                        :"ax");
                    pos += 2;
                    x++;

                /* 为转义字符时,置state=1 */
                } else if (c==27)
                    state=1;
                /* 若为换行符,纵向制表符,换页符则将光标移到下一行 */
                else if (c==10 || c==11 || c==12)
                    lf();
                /* 若为回车符, 则将光标移到行首 */
                else if (c==13)
                    cr();
                /* 若是擦除字符,则擦除光标前1字符 */
                else if (c==ERASE_CHAR(tty))
                    del();
                /* 若是退格字符,则左移光标1字符位置 */
                else if (c==8) {
                    if (x) {
                        x--;
                        pos -= 2;
                    }
                /* 若字符为水平制表符则将光标移到最近列数为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;
                /* 若为响铃字符则调用蜂鸣函数蜂鸣一下 */
                } else if (c==7)
                    sysbeep();
                break;
            /* 若在state=0阶段解析到转义字符序列,*/
            case 1:
                state=0; /* 恢复state 0状态 */
                /* 若继state=0解析到ESC后又解析到'['则置state=2 */
                if (c=='[')
                    state=2;
                else if (c=='E') /* ESC 'E' */
                    gotoxy(0,y+1);
                else if (c=='M') /* ESC 'M'*/
                    ri();
                else if (c=='D') /* ESC 'D'*/
                    lf();
                else if (c=='Z') /* ESC 'Z'*/
                    respond(tty);
                else if (x=='7') /* ESC '7'*/
                    save_cur();
                else if (x=='8') /* ESC '8'*/
                    restore_cur();
                break;
            /* ESC [ */
            case 2:
                /* 初始化par数组供case 3使用 */
                for(npar=0;npar='0' && c<='9') {
                    par[npar]=10*par[npar]+c-'0';
                    break;
                /* 字符不为';'或数字字符则置state=4 */
                } else state=4;

            /* 解析转义字符序列的最后一个字符,该字符表示具体命令 */
            case 4:
                state=0; /* 复位state=0 */
                switch(c) {
                    /* ESC [ par 'G'或'`',光标水平移动 */
                    case 'G': case '`':
                        if (par[0]) par[0]--;
                        gotoxy(par[0],y);
                        break;
                    /* ESC [ par 'A',光标上移 */
                    case 'A':
                        if (!par[0]) par[0]++;
                        gotoxy(x,y-par[0]);
                        break;
                    /* ESC [ par 'B' 或 'e', 光标下移 */
                    case 'B': case 'e':
                        if (!par[0]) par[0]++;
                        gotoxy(x,y+par[0]);
                        break;
                    /* ESC [ par 'C' 或 'a', 光标右移 */
                    case 'C': case 'a':
                        if (!par[0]) par[0]++;
                        gotoxy(x+par[0],y);
                        break;
                    /* ESC [ par 'D', 光标左移 */
                    case 'D':
                        if (!par[0]) par[0]++;
                        gotoxy(x-par[0],y);
                        break;
                    /* ESC [ par 'E', 光标下移并回行首 */
                    case 'E':
                        if (!par[0]) par[0]++;
                        gotoxy(0,y+par[0]);
                        break;
                    /* ESC [ par 'F', 光标上移并回行首 */
                    case 'F':
                        if (!par[0]) par[0]++;
                        gotoxy(0,y-par[0]);
                        break;
                    /* ESC [ par 'd', 当前列设置行位置 */
                    case 'd':
                        if (par[0]) par[0]--;
                        gotoxy(x,par[0]);
                        break;
                    /* ESC [ par 'H' 或 'f', 光标定位 */
                    case 'H': case 'f':
                        if (par[0]) par[0]--;
                        if (par[1]) par[1]--;
                        gotoxy(par[1],par[0]);
                        break;
                    /* ESC [ par 'j', 删除操作 */
                    case 'J':
                        csi_J(par[0]);
                        break;
                    /* ESC [ par 'K', 行内删除 */
                    case 'K':
                        csi_K(par[0]);
                        break;
                    /* ESC [ par 'L', 插入行 */
                    case 'L':
                        csi_L(par[0]);
                        break;
                    /* ESC [ par 'M', 删除行 */
                    case 'M':
                        csi_M(par[0]);
                        break;
                    /* ESC [ par 'P', 删除字符 */
                    case 'P':
                        csi_P(par[0]);
                        break;
                    /* ESC [ par '@', 插入字符 */
                    case '@':
                        csi_at(par[0]);
                        break;
                    /* ESC [ par 'm', 设置显示字符属性 */
                    case 'm':
                        csi_m();
                        break;
                    /* ESC [ par '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;
                    /* ESC [ par 's', 保存光标位置 */
                    case 's':
                        save_cur();
                        break;
                    /* ESC [ par '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.
 */
/* con_init,
 * 初始化控制台终端。
 *
 * 初始化画面显示信息,光标位置,键盘中断,使能键盘等。*/
void con_init(void)
{
    register unsigned char a;
    char *display_desc = "????";
    char *display_ptr;
    
    /* 获取在setup.s通过BIOS所获取到的显卡
     * 所支持的显示参数并保存在全局变量中。*/
    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;

    /* 判断所设置显卡的显示模式,并根据显示模式作相应设置 */

    /* 单色模式 */
    if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */
    {
        video_mem_start = 0xb0000;
        video_port_reg = 0x3b4;
        video_port_val = 0x3b5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
        {
            video_type = VIDEO_TYPE_EGAM;
            video_mem_end = 0xb8000;
            display_desc = "EGAm";
        }
        else
        {
            video_type = VIDEO_TYPE_MDA;
            video_mem_end = 0xb2000;
            display_desc = "*MDA";
        }
    }
    else /* 彩色模式 */
    {
        video_mem_start = 0xb8000;
        video_port_reg  = 0x3d4;
        video_port_val  = 0x3d5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
        {
            video_type    = VIDEO_TYPE_EGAC;
            video_mem_end = 0xbc000;
            display_desc  = "EGAc";
        }
        else
        {
            video_type = VIDEO_TYPE_CGA;
            video_mem_end = 0xba000;
            display_desc = "*CGA";
        }
    }

    /* Let the user known what kind of display driver we are using */
    /* 将当前显示模式显示在右上角 */
    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 (mos

相关文章