tty_io.c

1887阅读 0评论2009-08-11 jhluroom
分类:LINUX

/*
 * 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 <ctype.h>
#include <errno.h>
#include <signal.h>

#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))//tty发出的停止进程信号屏蔽位


#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/segment.h>
#include <asm/system.h>
//获取三种模式标志集之一,或者判断一个标志集是否有置位标志

#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)//输出模式

//取本地模式 标志集中的一个标志

#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)
//取输入模式 标志集中的一个标志

#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)
//取输出模式 标志集中的一个标志

#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)
//有3个,分别为串口1和串口2

struct tty_struct tty_table[] = {
    {
        {ICRNL,        /* change incoming CR to NL *///输入模式

        OPOST|ONLCR,    /* change outgoing NL to CRNL *///输出模式

        0,//控制模式

        ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,//本地模式

        0,        /* console termio *///线路规程0--TTY

        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,
        0,
        0,
        INIT_C_CC},
        0,
        0,
        rs_write,
        {0x3f8,0,0,0,""},        /* rs 1 */
        {0x3f8,0,0,0,""},
        {0,0,0,0,""}
    },{
        {0, /* no translation */
        0, /* no translation */
        B2400 | CS8,
        0,
        0,
        INIT_C_CC},
        0,
        0,
        rs_write,
        {0x2f8,0,0,0,""},        /* rs 2 */
        {0x2f8,0,0,0,""},
        {0,0,0,0,""}
    }
};//3


/*
 * these are the tables used by the machine code handlers.
 * you can implement pseudo-tty's or something by changing
 * them. Currently not done.
 */

struct tty_queue * table_list[]={//由key_board.S line 91 调用,取得读写缓冲区队列地址

    &tty_table[0].read_q, &tty_table[0].write_q,//控制台

    &tty_table[1].read_q, &tty_table[1].write_q,//串口1

    &tty_table[2].read_q, &tty_table[2].write_q//串口2

    };

void tty_init(void)//tyy终端初始化函数,由main.c line 130 调用

{
    rs_init();//初始化串口

    con_init();//初始化控制台

}

void tty_intr(struct tty_struct * tty, int mask)//tty键盘中断字符(ctrl C)处理函数,mask通常为SIGINT

{
    int i;

    if (tty->pgrp <= 0)//TTY里程组号小于0不存在,等于0时为初始INIT进程,不可能发生SIGINT

        return;
    for (i=0;i<NR_TASKS;i++)//向进程组中每个进程的组进程号为TTY的组进程号的发送指定的信号mask

        if (task[i] && task[i]->pgrp==tty->pgrp)
            task[i]->signal |= mask;
}

static void sleep_if_empty(struct tty_queue * queue)//如果队列缓冲区空则让进程进入可中断睡眠状态

{
    cli();//关中断

    while (!current->signal && EMPTY(*queue))//当前进程没有信号要处理,并且指定的队列缓冲区为空

        interruptible_sleep_on(&queue->proc_list);//将当前任务置为可中断的等待状态,并将此放入等待队列queue->proc_list中,因为当前的缓冲区为空,没有数据可读,所以要将此进程睡眠,等待有数据可读时再唤醒

    sti();//开中断

}

static void sleep_if_full(struct tty_queue * queue)//如果队列缓冲区满则让进程可中断睡眠状态

{
    if (!FULL(*queue))
        return;
    cli();
    while (!current->signal && LEFT(*queue)<128)//当前进程没有信号要处理,并且指定的队列缓冲区当前长度小于128

        interruptible_sleep_on(&queue->proc_list);//将当前任务置为可中断的等待状态,并将此放入等待队列queue->proc_list中,因为当前的缓冲区为满时,没有地方可放数据,所以要将此进程睡眠,等待有空闲空间放数据 时再唤醒

    sti();
}

void wait_for_keypress(void)//等待按键

{
    sleep_if_empty(&tty_table[0].secondary);//如果队列缓冲区空则让进程进入可中断睡眠状态

}
//复制成规范模式字符序列,从缓冲区读队列取数据 ,处理完后放入辅助队列中,有必要还要放写队列中

void copy_to_cooked(struct tty_struct * tty)
{
    signed char c;

    while (!EMPTY(tty->read_q) && !FULL(tty->secondary)) {//读队列不空或辅助队列不满

        GETCH(tty->read_q,c);//从读队列中取一个字符到C 中

        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)) {//键盘终止控制字符KILL (ctrl U)

                /* deal with killing the input line */
                while(!(EMPTY(tty->secondary) ||
                 (c=LAST(tty->secondary))==10 ||
                 c==EOF_CHAR(tty))) {//文件结束符(ctrl D)

                    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)) {//删除控制字符EARSE (ctrl H)

                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;
            }
            if (c==STOP_CHAR(tty)) {//停止控制字符STOP (ctrl S)

                tty->stopped=1;
                continue;
            }
            if (c==START_CHAR(tty)) {//开始字符(ctrl Q)

                tty->stopped=0;
                continue;
            }
        }
        if (L_ISIG(tty)) {//本地模式信号置位

            if (c==INTR_CHAR(tty)) {//ctrl C 中断信号

                tty_intr(tty,INTMASK);
                continue;
            }
            if (c==QUIT_CHAR(tty)) {// ctrl \ 退出信号

                tty_intr(tty,QUITMASK);
                continue;
            }
        }
        if (c==10 || c==EOF_CHAR(tty))//ctrl D 文件结束符

            tty->secondary.data++;
        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);//唤醒等待该辅助队列的进程

}

int tty_read(unsigned channel, char * buf, int nr)//channel 子设备号,从辅助队列中读取指定数量的字符放到用户指定的缓冲区

{
    struct tty_struct * tty;
    char c, * b=buf;
    int minimum,time,flag=0;
    long oldalarm;

    if (channel>2 || nr<0) return -1;//判断参数的有效性

    tty = &tty_table[channel];//tty指向子设备号对应tty结构上

    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+jiffies<oldalarm))
            current->alarm = time+jiffies;
    }
    if (minimum>nr)
        minimum=nr;//确切的至少需要读取 擦子符个数

    while (nr>0) {//从辅助队列中循环读取字符放到用户缓冲区buf中

        if (flag && (current->signal & ALRMMASK)) {//flag已设置且进程此时已收到定时报警信号

            current->signal &= ~ALRMMASK;//复位进程的定时报警信号

            break;
        }
        if (current->signal)//进程 定时到,或收到其他信号

            break;
        if (EMPTY(tty->secondary) || (L_CANON(tty) &&
        !tty->secondary.data && LEFT(tty->secondary)>20)) {//辅助队列 为空或(置为本地规范模式且辅助队列空闲队列大于20),则让当前进程进入可中断睡眠状态

            sleep_if_empty(&tty->secondary);
            continue;
        }
        do {
            GETCH(tty->secondary,c);//取一字符

            if (c==EOF_CHAR(tty) || c==10)//不为结束符,也不为换行符

                tty->secondary.data--;//字符行减1

            if (c==EOF_CHAR(tty) && L_CANON(tty))//在本地规范模式下且是换行符也退出

                return (b-buf);
            else {
                put_fs_byte(c,b++);//放入buf 中

                if (!--nr)
                    break;
            }
        } while (nr>0 && !EMPTY(tty->secondary));//

        if (time && !L_CANON(tty))
            if (flag=(!oldalarm || time+jiffies<oldalarm))
                current->alarm = 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);//返回读的个数

}

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;//tty指向对应设备的tty结构处

    while (nr>0) {
        sleep_if_full(&tty->write_q);//如果队列缓冲区满则让进程可中断睡眠状态

        if (current->signal)//当前进程有信号要处理则退出循环

            break;
        while (nr>0 && !FULL(tty->write_q)) {//要取的还大于0,且写队列不满时

            c=get_fs_byte(b);//从缓冲区取一个字符

            if (O_POST(tty)) {//l输出模式,执行输出处理标志POST 置位

                if (c=='\r' && O_CRNL(tty))//c=回车符 回车符转换行符标志置位

                    c='\n';
                else if (c=='\n' && O_NLRET(tty))////c=换行符 换行符转回车符标志置位

                    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);//放入写队列中

        }
        tty->write(tty);//调用对应的TTY写函数,或者把写队列中的数据显示在控制台屏幕上(con_write),或者通过串行端口发出去(rs_write)

        if (nr>0)//如果还有字符要写(此时写队列已满),则等待写队列中的字符取走,所以这里调用schedule()函数,先去执行其他任务

            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.
 */

void do_tty_interrupt(int tty)//tty中断处理调用函数

{
    copy_to_cooked(tty_table+tty);
}

void chr_dev_init(void)//字符设备初始化函数

{
}

上一篇:keyboard.S
下一篇:console.c