/* * 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)//字符设备初始化函数
{ }
|