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

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

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

回想在[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 <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))#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/segment.h>
#include <asm/system.h>/* 判断字符设备本地模式,输入模式,* 输出模式中是否设置了标志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;i<NR_TASKS;i++)if (task[i] && task[i]->pgrp==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);}} elsePUTCH(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+jiffies<oldalarm))current->alarm = 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+jiffies<oldalarm))current->alarm = time+jiffies;elsecurrent->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_interruptsize = 1024 /* must be power of two !and must match the valuein tty_io.c!!! *//* these are the offsets into the read/write buffer structures */
/* 串口读写队列结构体中各成员偏移量 */
rs_addr = 0
head = 4
tail = 8
proc_list = 12
buf = 16startup = 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 %edxpushl %ecxpushl %ebxpushl %eaxpush %espush %ds    /* as this is an interrupt, we cannot */pushl $0x10 /* know that bs is ok. Load it */pop %ds     /* es=ds=10h, 加载内核数据段到数据段寄存器中 */pushl $0x10pop %esmovl 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,%eaxinb %dx,%al  /* 读0x3fa(0x2fa)即读中断标识寄存器以判断串口中断类型 */testb $1,%al /* bit[1]=1则表示无中断 */jne endcmpb $6,%al /* 接收状态有错,this shouldn't happen, but ... */ja endmovl 24(%esp),%ecx /* ecx=table_list + 8 or 16 */pushl %edxsubl $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 %edxjmp rep_int /* 直到串口无中断方结束 */
end:    movb $0x20,%aloutb %al,$0x20  /* EOI,向PIC发送结束中断命令 */pop %dspop %espopl %eaxpopl %ebxpopl %ecxpopl %edxaddl $4,%esp # jump over _table_list entryiret/* 各串扣中断对应的中断处理程序表,他们分别是* 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,%alret.align 2
line_status: /* 读线路状态寄存器(0x3fd或0x2fd)以让MODEM寄存器复位 */addl $5,%edx /* clear intr by reading line status reg. */inb %dx,%alret/* 读串口所接收到的数据 */
.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),%ebxandl $size-1,%ebx # nr chars in queueje 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),%ebxmovb buf(%ecx,%ebx),%aloutb %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 processtestl %ebx,%ebx # is there any?je 1fmovl $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 <linux/tty.h>
#include <linux/sched.h>
#include <asm/system.h>
#include <asm/io.h>#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,* <g-hunt@ee.utah.edu>*/#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/io.h>
#include <asm/system.h>/** 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 16extern 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+1<bottom) {y++;pos += video_size_row;return;}/* 将终端窗口向上移动一行 */scrup();
}/* ri,* 保持列不变,将光标移动上一行。*/
static void ri(void)
{/* 若光标不在终端顶端则直接* 更新光标坐标和对应的显存地址*/if (y>top) {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<video_num_columns)?x:video_num_columns;break;case 2: /* erase whole line */start = pos - (x<<1);count = video_num_columns;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_m,* 设置显示字符属性。** 'ESC [par m',* par=0,默认属性(0x07);* par=1,加粗(0x0f);* par=4,加下划线;* par=7,反显(0x70);* par=27,正显(0x07)。*/
void csi_m(void)
{int i;for (i=0;i<=npar;i++)switch (par[i]) {case 0:attr=0x07;break;case 1:attr=0x0f;break;case 4:attr=0x0f;break;case 7:attr=0x70;break;case 27:attr=0x07;break;}
}/* set_cursor,* 根据光标显存地址pos显示光标。*/
static inline void set_cursor(void)
{cli();/* 选择显示控制器数据寄存器r14写入鼠标在显存中偏移的高字节 */outb_p(14, video_port_reg);outb_p(0xff&((pos-video_mem_start)>>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) {tmp=*p;*p=old;old=tmp;p++;}
}/* insert_line,* 在光标位置处插入一行内容。*/
static void insert_line(void)
{int oldtop,oldbottom;oldtop=top;oldbottom=bottom;/* 从光标所在行让终端窗口向下移动一行 */top=y;bottom = video_num_lines;scrdown();/* 恢复光标位置 */top=oldtop;bottom=oldbottom;
}/* delete_char,* 删除光标处字符,原光标后续字符向左移一个字符位置。*/
static void delete_char(void)
{int i;unsigned short * p = (unsigned short *) pos;if (x>=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<NPAR;npar++)par[npar]=0;npar=0;state=3; /* 置3表明下一个字符为 'ESC ['序列中的字符*/if (ques=(c=='?')) /* 是否有'?'*/break;/* 在case 3中解析转义字符序列 */case 3:/* 解析到';'则增加par索引并退出 */if (c==';' && npar<NPAR-1) {npar++;break;/* 将数字字符转换为数字存储在par数组中 */} else if (c>='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 (mostly 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);/* 在IDT[21h]中设置键盘中断处理入口函数,* 设置PIC允许IRQ1即键盘中断。*/set_trap_gate(0x21,&keyboard_interrupt);outb_p(inb_p(0x21)&0xfd,0x21);/* 通过端口地址61h(8255A)激活键盘 */a=inb_p(0x61);outb_p(a|0x80,0x61);outb(a,0x61);
}
/* from bsd-net-2: *//* sysbeepstop,* 停止蜂鸣声。*/
void sysbeepstop(void)
{/* disable counter 2 */outb(inb_p(0x61)&0xFC, 0x61);
}int beepcount = 0;/* sysbeep,* 使能蜂鸣功能。* * 8255A PB bit[1]=1时开启扬声器,* PB bit[0]=1时开启8253定时器2,* 通过61h设置PB bit[1..0]=(11)2时,* 在8253定时器2开通时,扬声器以8253* 定时器2的频率蜂鸣。*/
static void sysbeep(void)
{/* enable counter 2 */outb_p(inb_p(0x61)|3, 0x61);/* set command for counter 2, 2 byte write */outb_p(0xB6, 0x43);/* send 0x637 for 750 HZ */outb_p(0x37, 0x42);outb(0x06, 0x42);/* 1/8 second */beepcount = HZ/8;
}
keyboard.S
/**  linux/kernel/keyboard.S**  (C) 1991  Linus Torvalds*//** Thanks to Alfred Leung for US keyboard patches*  Wolfgang Thiel for German keyboard patches*  Marc Corsini for the French keyboard*/
/* 感谢 Alfred Leung      为美式键盘相关程序的修正;*      Wolfgang Thiel  为德式键盘相关程序的修正;*      Marc Corsini    为法式键盘做的贡献。*/#include <linux/config.h>.text
.globl _keyboard_interrupt/** these are for the keyboard read functions*/
size = 1024  /* must be a power of two ! And MUST be the sameas in tty_io.c !!!! */
head = 4
tail = 8
proc_list = 12
buf = 16mode:   .byte 0 /* caps, alt, ctrl and shift mode */
leds:   .byte 2 /* num-lock, caps, scroll-lock mode (nom-lock on) */
e0: .byte 0/**  con_int is the real interrupt routine that reads the*  keyboard scan-code and converts it into the appropriate*  ascii character(s).*/
/* con_int 是读取键盘扫描码并将键盘扫描码转换为对应 asscii 字符(集)的中断C处理程序。*//* _keyboard_interrupt,* 键盘(8255A)中断处理入口程序。** 键盘输入 -> 键盘控制器 -> PIC -> CPU调用IDT[21h]中处理程序* _keyboard_interrupt。_keyboard_interrupt从键盘控制器输出缓* 冲器中读取键盘输入码,并根据键盘输入码调用相应子程序将键盘码* 存入控制台终端读队列中,然后结束键盘中断并调用键盘中断C处理函* 数do_tty_interrupt将键盘码转换到控制台终端辅助队列中。当键盘* 码为0xe0或0xe1时直接结束键盘中断而将读队列中的键盘码转换到辅助队列中。*/
_keyboard_interrupt:
/* 在栈中备份键盘中断前程序所使用的寄存器 */pushl %eaxpushl %ebxpushl %ecxpushl %edxpush %dspush %es/* 将内核数据段加载给数据段寄存器 */movl $0x10,%eaxmov %ax,%dsmov %ax,%esxorl %al,%al    /* %eax is scan code *//* 读键盘输出缓冲器,* 内容为0xe0时则提前跳转set_e0处处理;* 内容为0xe1时则提前跳转set_e1处处理。*/inb $0x60,%alcmpb $0xe0,%alje set_e0cmpb $0xe1,%alje set_e1/* 若键盘输入为其他字符,* 则调用eax * 4 + key_table处函数处理。*/call key_table(,%eax,4)movb $0,e0 /* 恢复e0值 *//* 通过8255A 61h端口对键盘进行复位再* 使能,以对收到键盘扫描码作出应答。*/
e0_e1:  inb $0x61,%aljmp 1f
1:  jmp 1f
1:  orb $0x80,%aljmp 1f
1:  jmp 1f
1:  outb %al,$0x61jmp 1f
1:  jmp 1f
1:  andb $0x7F,%aloutb %al,$0x61movb $0x20,%aloutb %al,$0x20 /* 向PIC发送EOI结束键盘中断 *//* 调用键盘中断C处理函数do_tty_interrupt* 将键盘读队列中的字符转换到其辅助队列中。*/pushl $0call _do_tty_interruptaddl $4,%esp /* do_tty_interrrupt参数回收 */pop %espop %dspopl %edxpopl %ecxpopl %ebxpopl %eaxiret/* 键盘扫描码为0xe0和0xe1时,将* 该扫描码后跟随的字符数写在e0* 处,然后跳转e0_e1处继续执行。*/
set_e0: movb $1,e0jmp e0_e1
set_e1: movb $2,e0jmp e0_e1/** This routine fills the buffer with max 8 bytes, taken from* %ebx:%eax. (%edx is high). The bytes are written in the* order %al,%ah,%eal,%eah,%bl,%bh ... until %eax is zero.*/
/* put_queue,* 将存在寄存器中的键盘输入写入控制台终端读队列中。*/
put_queue:pushl %ecxpushl %edx/* edx = table_list地址中的内容(直接寻址),即中端读队列首地址;* ecx = *((long *)(edx+head))即为终端读队列中的head成员值;* * *((char *)(edx+buf+ecx))=al即read_q.buf[head]=al* 即将字符al写入终端读队列buf成员的head偏移处。*/movl _table_list,%edx   # read-queue for consolemovl head(%edx),%ecx
1: movb %al,buf(%edx,%ecx)/* read_q.head++;以循环队列方式使用buf */incl %ecxandl $size-1,%ecx/* 若read_q.head == read_q.tail(队列满)则向前跳转3f处 */cmpl tail(%edx),%ecx # buffer full - discard everythingje 3f/* eax = (ebx << 32 + eax) >> 8,* 若eax=0则表明无字符则向前跳转到2f处 */shrdl $8,%ebx,%eaxje 2f/* ebx = ebx >> 8,* 向后跳转1b标号处继续将字符保存在read_q.buf中 */shrl $8,%ebx jmp 1b/* 更新read_q.head成员值,* ecx=read_q.pro_lsit,* 若等待reqd_q的进程不为空则将该进程的* state成员置为0即将该进程置为可运行状态。*/
2:  movl %ecx,head(%edx)movl proc_list(%edx),%ecxtestl %ecx,%ecxje 3fmovl $0,(%ecx)3:  popl %edxpopl %ecxret/* 处理各键盘码输入的子程序 *//* ctrl && alt 按下键码 处理子程序 */
ctrl: movb $0x04,%al /* 左ctrl */jmp 1f
alt:  movb $0x10,%al /* 左alt */
1: cmpb $0,e0 /* 若e0 != 0则表明收到右ctrl或右alt */je 2faddb %al,%al /* 右ctrl或右alt */
2: orb %al,mode  /* 记录当前收到的ctrl或alt键是键盘左边的还是右边的 */ret/* ctrl && alt松开键码 处理子程序 */
unctrl: movb $0x04,%al /* 左ctrl */jmp 1f
unalt: movb $0x10,%al  /* 左alt */
1: cmpb $0,e0 /* e0处被置位表右ctrl或右alt */je 2faddb %al,%al
2: notb %al /* ctrl或alt键被松开,复位mode相应位 */andb %al,moderet/* 左右 shift按下松开键码处理程序 */
lshift:orb $0x01,mode /* 置mode bit[0]标识左shift按下 */ret
unlshift:andb $0xfe,mode /* 左shift松开则复位mode bit[0] */ret
rshift:orb $0x02,mode /* 置mode bit[1]标识右shift按下 */ret
unrshift:andb $0xfd,mode /* 右shift松开则复位mode bit[1] */ret/* CapsLock键码处理子程序 */
caps: testb $0x80,mode /* CapsLock是否被按下,若按下则向前跳转1f处 */jne 1fxorb $4,leds    /* CapsLock没有处于被按下状态则翻转leds bit[2]以记录led灯的亮灭 */xorb $0x40,mode /* 翻转设置mode bit[6]以记录CapsLock锁定与否 */orb $0x80,mode  /* 设置mode bit[7]以记录capslock处于按下状态 */
/* 根据leds处的标志位,设置led指示灯 */
set_leds:call kb_wait /* 等待键盘控制器输入缓冲器空闲 */movb $0xed,%al  /* set leds command */outb %al,$0x60call kb_waitmovb leds,%al   /* 发送命令参数以设置led灯 */outb %al,$0x60ret
/* capslock键松开则复位mode bit[7] */
uncaps: andb $0x7f,moderet/* scroll键按下则翻转leds bit[0]并设置led灯状态 */
scroll:xorb $1,ledsjmp set_leds
/* 小键盘中的num键被按下则翻转led bit[1]并设置led灯状态 */
num: xorb $2,ledsjmp set_leds/**  curosr-key/numeric keypad cursor keys are handled here.*  checking for numeric keypad etc.*/
/* 方向键以及小键盘按键处理子程序 */
cursor:/* 47h <= 数字键码 <= 53h */subb $0x47,%aljb 1fcmpb $12,%al  ja 1fjne cur2    /* check for ctrl-alt-del */testb $0x0c,mode /* del and ctrl? */je cur2testb $0x30,mode /* del and ctrl and alt:重启 */jne reboot
cur2: cmpb $0x01,e0 /* e0 forces cursor movement */je curtestb $0x02,leds    /* not num-lock forces cursor */je curtestb $0x03,mode    /* shift forces cursor */jne curxorl %ebx,%ebxmovb num_table(%eax),%al /* 取数字键码于al中 */jmp put_queue /* 将得到的数字键存入读终端对队列中 */
1:  ret/* 光标移动&&插入&&删除按键处理子程序 */
cur: movb cur_table(%eax),%al /* 光标字符于al */cmpb $'9,%al ja ok_curmovb $'~,%ah
ok_cur: shll $16,%eaxmovw $0x5b1b,%axxorl %ebx,%ebxjmp put_queue /* 将eax中的2个字符存入终端读队列中 */#if defined(KBD_FR)
num_table: /* 数字小键盘上数字键ASCII码 */.ascii "789 456 1230."
#else
num_table:.ascii "789 456 1230,"
#endif
cur_table: /* 小键盘上方向键 插入 删除键表征的移动功能的字符表 */.ascii "HA5 DGC YB623"/** this routine handles function keys*/
/* 功能键处理子程序 */
func:pushl %eaxpushl %ecxpushl %edxcall _show_statpopl %edxpopl %ecxpopl %eaxsubb $0x3B,%al /* F1 */jb end_funccmpb $9,%al /* F1-F10 */jbe ok_funcsubb $18,%alcmpb $10,%al /* F11 */jb end_funccmpb $11,%al /* F12 */ja end_func
ok_func:cmpl $4,%ecx    /* check that there is enough room */jl end_funcmovl func_table(,%eax,4),%eax /* 取功能键对应序列 */xorl %ebx,%ebxjmp put_queue
end_func:ret/** function keys send F1:'esc [ [ A' F2:'esc [ [ B' etc.*/
/* 功能键序列表,F1-F10:'esc [[ A-J' */
func_table:.long 0x415b5b1b,0x425b5b1b,0x435b5b1b,0x445b5b1b.long 0x455b5b1b,0x465b5b1b,0x475b5b1b,0x485b5b1b.long 0x495b5b1b,0x4a5b5b1b,0x4b5b5b1b,0x4c5b5b1b/* ASSCII字符映射表 */
#if defined(KBD_FINNISH) /* 芬兰语键盘扫描码映射表 */
key_map:.byte 0,27 /* 按键扫描码0,1对应的ASCII */.ascii "1234567890+'" /* 2,3..0xd对应ASCII,...*/.byte 127,9.ascii "qwertyuiop}".byte 0,13,0.ascii "asdfghjkl|{".byte 0,0.ascii "'zxcvbnm,.-".byte 0,'*,0,32      /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '<.fill 10,1,0/* 同时按下shift按键的ASCII映射表 */
shift_map:.byte 0,27.ascii "!\"#$%&/()=?`".byte 127,9.ascii "QWERTYUIOP]^".byte 13,0.ascii "ASDFGHJKL\\[".byte 0,0.ascii "*ZXCVBNM;:_".byte 0,'*,0,32       /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '>.fill 10,1,0/* 同时按下alt的ASCII映射表 */
alt_map:.byte 0,0.ascii "\0@\0$\0\0{[]}\\\0".byte 0,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte '~,13,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0,0,0   /* 36-39 */.fill 16,1,0    /* 3A-49 */.byte 0,0,0,0,0 /* 4A-4E */.byte 0,0,0,0,0,0,0 /* 4F-55 */.byte '|.fill 10,1,0#elif defined(KBD_US) /* 美式键盘扫描码同ASCCI映射表 */key_map:.byte 0,27.ascii "1234567890-=".byte 127,9.ascii "qwertyuiop[]".byte 13,0.ascii "asdfghjkl;'".byte '`,0.ascii "\\zxcvbnm,./".byte 0,'*,0,32       /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '<.fill 10,1,0shift_map:.byte 0,27.ascii "!@#$%^&*()_+".byte 127,9.ascii "QWERTYUIOP{}".byte 13,0.ascii "ASDFGHJKL:\"".byte '~,0.ascii "|ZXCVBNM<>?".byte 0,'*,0,32     /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '>.fill 10,1,0alt_map:.byte 0,0.ascii "\0@\0$\0\0{[]}\\\0".byte 0,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte '~,13,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0,0,0        /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte 0,0,0,0,0      /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '|.fill 10,1,0#elif defined(KBD_GR) /* 德语键盘扫描码同ASCII映射表 */key_map:.byte 0,27.ascii "1234567890\\'".byte 127,9.ascii "qwertzuiop@+".byte 13,0.ascii "asdfghjkl[]^".byte 0,'#.ascii "yxcvbnm,.-".byte 0,'*,0,32        /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '<.fill 10,1,0shift_map:.byte 0,27.ascii "!\"#$%&/()=?`".byte 127,9.ascii "QWERTZUIOP\\*".byte 13,0.ascii "ASDFGHJKL{}~".byte 0,''.ascii "YXCVBNM;:_".byte 0,'*,0,32     /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '>.fill 10,1,0alt_map:.byte 0,0.ascii "\0@\0$\0\0{[]}\\\0".byte 0,0.byte '@,0,0,0,0,0,0,0,0,0,0.byte '~,13,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0,0,0     /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte 0,0,0,0,0      /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '|.fill 10,1,0#elif defined(KBD_FR) /* 法语键盘...*/key_map:.byte 0,27.ascii "&{\"'(-}_/@)=".byte 127,9.ascii "azertyuiop^$".byte 13,0.ascii "qsdfghjklm|".byte '`,0,42       /* coin sup gauche, don't know, [*|mu] */.ascii "wxcvbn,;:!".byte 0,'*,0,32     /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '<.fill 10,1,0shift_map:.byte 0,27.ascii "1234567890]+".byte 127,9.ascii "AZERTYUIOP<>".byte 13,0.ascii "QSDFGHJKLM%".byte '~,0,'#.ascii "WXCVBN?./\\".byte 0,'*,0,32        /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte '-,0,0,0,'+ /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '>.fill 10,1,0alt_map:.byte 0,0.ascii "\0~#{[|`\\^@]}".byte 0,0.byte '@,0,0,0,0,0,0,0,0,0,0.byte '~,13,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0.byte 0,0,0,0,0,0,0,0,0,0,0.byte 0,0,0,0        /* 36-39 */.fill 16,1,0     /* 3A-49 */.byte 0,0,0,0,0      /* 4A-4E */.byte 0,0,0,0,0,0,0  /* 4F-55 */.byte '|.fill 10,1,0#else
#error "KBD-type not defined"
#endif
/** do_self handles "normal" keys, ie keys that don't change meaning* and which have just one character returns.*/
/* do_self,* 接收普通按键的子程序。** 根据mode中的位标志分别从alt_map或shift_map或key_map* 键盘码映射表中取相应按键扫描码的ASCII,再根据诸如caplock* 是否处于锁定状态而将字符转换为大写字符等得到最终字符,* 最后将其存入终端读队列的buf中。*/
do_self:lea alt_map,%ebxtestb $0x20,mode /* alt-gr */jne 1flea shift_map,%ebxtestb $0x03,modejne 1flea key_map,%ebx
1: movb (%ebx,%eax),%alorb %al,%alje nonetestb $0x4c,mode /* ctrl or caps */je 2fcmpb $'a,%aljb 2fcmpb $'},%alja 2fsubb $32,%al
2: testb $0x0c,mode /* ctrl */je 3fcmpb $64,%aljb 3fcmpb $64+32,%aljae 3fsubb $64,%al
3: testb $0x10,mode /* left alt */je 4forb $0x80,%al
4: andl $0xff,%eaxxorl %ebx,%ebxcall put_queue
none: ret/** minus has a routine of it's own, as a 'E0h' before* the scan code for minus means that the numeric keypad* slash was pushed.*/
/* 处理减号的子程序 */
minus: cmpb $1,e0jne do_selfmovl $'/,%eaxxorl %ebx,%ebxjmp put_queue/** This table decides which routine to call when a scan-code has been* gotten. Most routines just call do_self, or none, depending if* they are make or break.*/
/* 键盘码处理子程序跳转表。* 键盘码将作为跳转表中各子程序的索引。*/
key_table:.long none,do_self,do_self,do_self  /* 00-03 s0 esc 1 2 */.long do_self,do_self,do_self,do_self   /* 04-07 3 4 5 6 */.long do_self,do_self,do_self,do_self   /* 08-0B 7 8 9 0 */.long do_self,do_self,do_self,do_self   /* 0C-0F + ' bs tab */.long do_self,do_self,do_self,do_self   /* 10-13 q w e r */.long do_self,do_self,do_self,do_self   /* 14-17 t y u i */.long do_self,do_self,do_self,do_self   /* 18-1B o p } ^ */.long do_self,ctrl,do_self,do_self  /* 1C-1F enter ctrl a s */.long do_self,do_self,do_self,do_self   /* 20-23 d f g h */.long do_self,do_self,do_self,do_self   /* 24-27 j k l | */.long do_self,do_self,lshift,do_self    /* 28-2B { para lshift , */.long do_self,do_self,do_self,do_self   /* 2C-2F z x c v */.long do_self,do_self,do_self,do_self   /* 30-33 b n m , */.long do_self,minus,rshift,do_self  /* 34-37 . - rshift * */.long alt,do_self,caps,func /* 38-3B alt sp caps f1 */.long func,func,func,func   /* 3C-3F f2 f3 f4 f5 */.long func,func,func,func   /* 40-43 f6 f7 f8 f9 */.long func,num,scroll,cursor    /* 44-47 f10 num scr home */.long cursor,cursor,do_self,cursor  /* 48-4B up pgup - left */.long cursor,cursor,do_self,cursor  /* 4C-4F n5 right + end */.long cursor,cursor,cursor,cursor   /* 50-53 dn pgdn ins del */.long none,none,do_self,func    /* 54-57 sysreq ? < f11 */.long func,none,none,none   /* 58-5B f12 ? ? ? */.long none,none,none,none   /* 5C-5F ? ? ? ? */.long none,none,none,none   /* 60-63 ? ? ? ? */.long none,none,none,none   /* 64-67 ? ? ? ? */.long none,none,none,none   /* 68-6B ? ? ? ? */.long none,none,none,none   /* 6C-6F ? ? ? ? */.long none,none,none,none   /* 70-73 ? ? ? ? */.long none,none,none,none   /* 74-77 ? ? ? ? */.long none,none,none,none   /* 78-7B ? ? ? ? */.long none,none,none,none   /* 7C-7F ? ? ? ? */.long none,none,none,none   /* 80-83 ? br br br */.long none,none,none,none   /* 84-87 br br br br */.long none,none,none,none   /* 88-8B br br br br */.long none,none,none,none   /* 8C-8F br br br br */.long none,none,none,none   /* 90-93 br br br br */.long none,none,none,none   /* 94-97 br br br br */.long none,none,none,none   /* 98-9B br br br br */.long none,unctrl,none,none /* 9C-9F br unctrl br br */.long none,none,none,none   /* A0-A3 br br br br */.long none,none,none,none   /* A4-A7 br br br br */.long none,none,unlshift,none   /* A8-AB br br unlshift br */.long none,none,none,none   /* AC-AF br br br br */.long none,none,none,none   /* B0-B3 br br br br */.long none,none,unrshift,none   /* B4-B7 br br unrshift br */.long unalt,none,uncaps,none    /* B8-BB unalt br uncaps br */.long none,none,none,none   /* BC-BF br br br br */.long none,none,none,none   /* C0-C3 br br br br */.long none,none,none,none   /* C4-C7 br br br br */.long none,none,none,none   /* C8-CB br br br br */.long none,none,none,none   /* CC-CF br br br br */.long none,none,none,none   /* D0-D3 br br br br */.long none,none,none,none   /* D4-D7 br br br br */.long none,none,none,none   /* D8-DB br ? ? ? */.long none,none,none,none   /* DC-DF ? ? ? ? */.long none,none,none,none   /* E0-E3 e0 e1 ? ? */.long none,none,none,none   /* E4-E7 ? ? ? ? */.long none,none,none,none   /* E8-EB ? ? ? ? */.long none,none,none,none   /* EC-EF ? ? ? ? */.long none,none,none,none   /* F0-F3 ? ? ? ? */.long none,none,none,none   /* F4-F7 ? ? ? ? */.long none,none,none,none   /* F8-FB ? ? ? ? */.long none,none,none,none   /* FC-FF ? ? ? ? *//** kb_wait waits for the keyboard controller buffer to empty.* there is no timeout - if the buffer doesn't empty, we hang.*/
/* 等待键盘控制器输入缓冲器为空 */
kb_wait:pushl %eax
1: inb $0x64,%altestb $0x02,%aljne 1bpopl %eaxret
/** This routine reboots the machine by asking the keyboard* controller to pulse the reset-line low.*/
/* 在向键盘控制器复位线输出负脉冲重启系统之前,* 先在物理内存0x472即启动模式标志内存处写入* 0x1234(热启动-不进行内存检测等过程;0-冷启动)* 最后进入linux0.11的死机程序中以等待系统热启动。*/
reboot:call kb_waitmovw $0x1234,0x472 /* don't do memory check */movb $0xfc,%al /* pulse reset and A20 low */outb %al,$0x64
die:    jmp die/* 哈哈,在linux0.11字符设备驱动及访问请求管理程序中,* 比如keyboard.S* 就是属于主参考《linux0.11内核完全注释》的典型文件,* 这部分内容跟键盘紧密相关,感觉不太好找准资料。* * 注: 一旦形成主参考《linux0.11内核完全注释》一书时,* 就有了一种潜在的依赖感+一种完全停不下来的感觉。下* 一步将粗略阅读linux0.11的进程管理程序,此文将恢复最* 初自我的风格,会停止参考本书,以免形成过强依赖而导致* 丧失自己的理解(请允许凡人出错^_^)。*/* 另外,学习字符设备管理程序后可粗略理解计算机(键盘)数据输入流向。*   |------------|*   |  terminal  |*   |------------|*   |UART|network|*   |------------|*         ^*         |写往串口/网口控制器发送给其他设备*         |*     |------|*     |  CPU | to ECHO |----------|   |-------|*     |------| ------> |video card|-->|monitor|*     |memory|         |----------|   |-------|*     |------|*        ^*        |键盘输入*        |* |--------------------------|* |keyboard && its controller|* |--------------------------| */
tty_ioctl.c
/**  linux/kernel/chr_drv/tty_ioctl.c**  (C) 1991  Linus Torvalds*/#include <errno.h>
#include <termios.h>#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/tty.h>#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>/* 波特率因子数组。* 波特率=1.8432MHz / (16 * 波特率因子)。* 如 波特率因子48对应的波特率=1843200 / (16 * 48) = 2400. */
static unsigned short quotient[] = {0, 2304, 1536, 1047, 857,768, 576, 384, 192, 96,64, 48, 24, 12, 6, 3
};/* change_speed,* 根据tty中的控制模式成员,修改tty对应字符设备传输波特率。*/
static void change_speed(struct tty_struct * tty)
{unsigned short port,quot;/* 判断tty对应字符设备是否为串口终端,* 串口终端读队列data成员保存了串口端* 口起始地址,控制终端读队列data成员用* 于记录读队列数据的行数,初始值为0。*/if (!(port = tty->read_q.data))return;/* 根据tty控制模式成员获取波特率因子索引,* 从而从quotient数组中得到波特率因子。*/quot = quotient[tty->termios.c_cflag & CBAUD];/* 将波特率因子写入串口控制芯片中(见serial.c/init()) */cli();outb_p(0x80,port+3);        /* set DLAB */outb_p(quot & 0xff,port);   /* LS of divisor */outb_p(quot >> 8,port+1);   /* MS of divisor */outb(0x03,port+3);          /* reset DLAB */sti();
}/* flush,* 清空queue所指字符设备队列,即将队列尾索引赋给队列头索引。*/
static void flush(struct tty_queue * queue)
{cli();queue->head = queue->tail;sti();
}static void wait_until_sent(struct tty_struct * tty)
{/* do nothing - not implemented */
}static void send_break(struct tty_struct * tty)
{/* do nothing - not implemented */
}/* get_termios,* 获取tty字符设备的模式信息存于termios所指内存段中。*/
static int get_termios(struct tty_struct * tty, struct termios * termios)
{int i;/* 保证termios所指内存段组否 */verify_area(termios, sizeof (*termios));/* 将ttytermios成员数据拷贝到termios所指内存中 */for (i=0 ; i< (sizeof (*termios)) ; i++)put_fs_byte( ((char *)&tty->termios)[i] , i+(char *)termios );return 0;
}/* set_termios,* 将termios所指内存段中的模式信息设置到tty的模式成员中,* 并随之更新字符设备波特率(因为模式信息的控制成员也被设置了)。*/
static int set_termios(struct tty_struct * tty, struct termios * termios)
{int i;for (i=0 ; i< (sizeof (*termios)) ; i++)((char *)&tty->termios)[i]=get_fs_byte(i+(char *)termios);change_speed(tty);return 0;
}/* get_termio,* 获取tty所对应字符设备的模式信息于termio所指内存中。*/
static int get_termio(struct tty_struct * tty, struct termio * termio)
{int i;struct termio tmp_termio;/* 保证termio所指内存段组否 */verify_area(termio, sizeof (*termio));/* struct termio中数据成员的数据类型是* struct termios中数据成员数据类型的一半,* 先通过各成员的一一赋值以截断各数据成员。*/tmp_termio.c_iflag = tty->termios.c_iflag;tmp_termio.c_oflag = tty->termios.c_oflag;tmp_termio.c_cflag = tty->termios.c_cflag;tmp_termio.c_lflag = tty->termios.c_lflag;tmp_termio.c_line = tty->termios.c_line;for(i=0 ; i < NCC ; i++)tmp_termio.c_cc[i] = tty->termios.c_cc[i];/* 然后将正确的tmp_termio拷贝到termio所指内存中 */for (i=0 ; i< (sizeof (*termio)) ; i++)put_fs_byte( ((char *)&tmp_termio)[i] , i+(char *)termio );return 0;
}/** This only works as the 386 is low-byt-first*/
/* set_termio,* 将termio所指内存段中的模式信息设置到tty所指字符设备的模式成员中。* 该函数只能在低字节在低地址的386的计算机上正确运行。*/
static int set_termio(struct tty_struct * tty, struct termio * termio)
{int i;struct termio tmp_termio;/* struct termio中数据成员的数据类型是* struct termios中数据成员数据类型的一半,* 各成员需一对一赋值,不能以内存段的方式进行拷贝。*/for (i=0 ; i< (sizeof (*termio)) ; i++)((char *)&tmp_termio)[i]=get_fs_byte(i+(char *)termio);*(unsigned short *)&tty->termios.c_iflag = tmp_termio.c_iflag;*(unsigned short *)&tty->termios.c_oflag = tmp_termio.c_oflag;*(unsigned short *)&tty->termios.c_cflag = tmp_termio.c_cflag;*(unsigned short *)&tty->termios.c_lflag = tmp_termio.c_lflag;tty->termios.c_line = tmp_termio.c_line;for(i=0 ; i < NCC ; i++)tty->termios.c_cc[i] = tmp_termio.c_cc[i];/* 控制模式成员被改变,所以需重新设置波特率 */change_speed(tty);return 0;
}/* tty_ioctl,* 根据设置命令cmd和参数arg完成字符设备dev输入输出设置。* * 其中的一些功能在linux0.11中还未实现。*/
int tty_ioctl(int dev, int cmd, int arg)
{struct tty_struct * tty;/* 主设备5为控制终端字符设备,* 其进程tty字段为tty次设备号,-1表示无控制终端;* 串口主设备号为4,其次设备号就在dev中;0,控制终端;* 1,串口1终端;2,串口2终端。根据次设备号索引到管理* 该设备数据通信的结构体tty_table[dev]。*/if (MAJOR(dev) == 5) {dev=current->tty;if (dev<0)panic("tty_ioctl: dev<0");} elsedev=MINOR(dev);tty = dev + tty_table;switch (cmd) {case TCGETS:/* 获取字符设备的模式信息于arg参数所指内存段中 */return get_termios(tty,(struct termios *) arg);/* case TCSETSF-TCSETS: */case TCSETSF: /* 情况字符设备读队列后再设置字符设备模式标志 */flush(&tty->read_q); /* fallthrough */case TCSETSW:wait_until_sent(tty); /* fallthrough *//* 将arg所指内存段的模式信息设置到字符设备模式成员中 */case TCSETS:return set_termios(tty,(struct termios *) arg);case TCGETA:/* 获取字符设备模式信息于arg所指内存段中(struct termio) */return get_termio(tty,(struct termio *) arg);case TCSETAF: /* 清空字符设备读队列后再设置字符设备标志信息 */flush(&tty->read_q); /* fallthrough */case TCSETAW:wait_until_sent(tty); /* fallthrough */case TCSETA: /* 设置字符设备模式信息(struct termio) */return set_termio(tty,(struct termio *) arg);case TCSBRK:if (!arg) {wait_until_sent(tty);send_break(tty);}return 0;case TCXONC:return -EINVAL; /* not implemented */case TCFLSH: /* 刷新字符设备指定队列 */if (arg==0)flush(&tty->read_q);else if (arg==1)flush(&tty->write_q);else if (arg==2) {flush(&tty->read_q);flush(&tty->write_q);} elsereturn -EINVAL;return 0;case TIOCEXCL:return -EINVAL; /* not implemented */case TIOCNXCL:return -EINVAL; /* not implemented */case TIOCSCTTY:return -EINVAL; /* set controlling term NI */case TIOCGPGRP:verify_area((void *) arg,4);put_fs_long(tty->pgrp,(unsigned long *) arg);return 0;case TIOCSPGRP:tty->pgrp=get_fs_long((unsigned long *) arg);return 0;case TIOCOUTQ: /* 获取字符设备所在进程组组号 */verify_area((void *) arg,4);put_fs_long(CHARS(tty->write_q),(unsigned long *) arg);return 0;case TIOCINQ: /* 获取辅助队列还未被处理的字符数 */verify_area((void *) arg,4);put_fs_long(CHARS(tty->secondary),(unsigned long *) arg);return 0;case TIOCSTI:return -EINVAL; /* not implemented */case TIOCGWINSZ:return -EINVAL; /* not implemented */case TIOCSWINSZ:return -EINVAL; /* not implemented */case TIOCMGET:return -EINVAL; /* not implemented */case TIOCMBIS:return -EINVAL; /* not implemented */case TIOCMBIC:return -EINVAL; /* not implemented */case TIOCMSET:return -EINVAL; /* not implemented */case TIOCGSOFTCAR:return -EINVAL; /* not implemented */case TIOCSSOFTCAR:return -EINVAL; /* not implemented */default:return -EINVAL;}
}
tty.h
/** 'tty.h' defines some structures used by tty_io.c and some defines.** NOTE! Don't touch this without checking that nothing in rs_io.s or* con_io.s breaks. Some constants are hardwired into the system (mainly* offsets into 'tty_queue'*/
/* tty.h 为tty_io.c定义了一些数据结构体,还定义了一些宏常量。** 注,在没有了解/修改 rs_io.s 或 con_io.s 之前,不要修改此文件。一些宏常量* 是直接写在代码中的(如 tty_queue 的初始化)。*/#ifndef _TTY_H
#define _TTY_H#include <termios.h>#define TTY_BUF_SIZE 1024
/* struct tty_queue,* 缓冲字符设备数据(接收,发送)的队列结构体类型 */
struct tty_queue {unsigned long data; /* 存串口控制器的起始端口地址;计数队列所含行数 */unsigned long head; /* buf头部数据的索引 */unsigned long tail; /* buf尾部数据的索引 */struct task_struct * proc_list; /* 用于同步其他进程对本队列的访问 */char buf[TTY_BUF_SIZE]; /* 用于缓存数据 */
};#define INC(a) ((a) = ((a)+1) & (TTY_BUF_SIZE-1))
#define DEC(a) ((a) = ((a)-1) & (TTY_BUF_SIZE-1))
#define EMPTY(a) ((a).head == (a).tail)
#define LEFT(a) (((a).tail-(a).head-1)&(TTY_BUF_SIZE-1))
#define LAST(a) ((a).buf[(TTY_BUF_SIZE-1)&((a).head-1)])
#define FULL(a) (!LEFT(a))
#define CHARS(a) (((a).head-(a).tail)&(TTY_BUF_SIZE-1))
/* GETCH(queue, c),* 从队列queue的buf中读取数据尾部数据保存到c中,* 并将数据尾部数据索引增1. */
#define GETCH(queue,c) \
(void)({c=(queue).buf[(queue).tail];INC((queue).tail);})/* PUTCH(c,queue),* 从将字符c写入queue的buf中并将指向数据头部数据索引增1. */
#define PUTCH(c,queue) \
(void)({(queue).buf[(queue).head]=(c);INC((queue).head);})#define INTR_CHAR(tty) ((tty)->termios.c_cc[VINTR])
#define QUIT_CHAR(tty) ((tty)->termios.c_cc[VQUIT])
#define ERASE_CHAR(tty) ((tty)->termios.c_cc[VERASE])
#define KILL_CHAR(tty) ((tty)->termios.c_cc[VKILL])
#define EOF_CHAR(tty) ((tty)->termios.c_cc[VEOF])
#define START_CHAR(tty) ((tty)->termios.c_cc[VSTART])
#define STOP_CHAR(tty) ((tty)->termios.c_cc[VSTOP])
#define SUSPEND_CHAR(tty) ((tty)->termios.c_cc[VSUSP])/* struct tty_struct,* 管理字符设备读写的结构体类型。*/
struct tty_struct {struct termios termios;int pgrp; /* 字符设备进程组id */int stopped; /* 停止标志 *//* 写队列函数指针 */void (*write)(struct tty_struct * tty);/* 读队列;写队列;辅助队列(存放读队列规范字符序列) */struct tty_queue read_q;struct tty_queue write_q;struct tty_queue secondary;
};extern struct tty_struct tty_table[];/*  intr=^C     quit=^|     erase=del   kill=^Ueof=^D      vtime=\0    vmin=\1     sxtc=\0start=^Q    stop=^S     susp=^Z     eol=\0reprint=^R  discard=^U  werase=^W   lnext=^Veol2=\0
*/
/* 控制字符序列定义 */
#define INIT_C_CC "\003\034\177\025\004\0\1\0\021\023\032\0\022\017\027\026\0"void rs_init(void);
void con_init(void);
void tty_init(void);int tty_read(unsigned c, char * buf, int n);
int tty_write(unsigned c, char * buf, int n);void rs_write(struct tty_struct * tty);
void con_write(struct tty_struct * tty);void copy_to_cooked(struct tty_struct * tty);#endif
termios.h
#ifndef _TERMIOS_H
#define _TERMIOS_H#define TTY_BUF_SIZE 1024/* 0x54 is just a magic number to make these relatively uniqe ('T') */
/* struct termios 结构体模式信息操作标志 */
#define TCGETS  0x5401 /* 获取字符设备的模式标志 */
#define TCSETS  0x5402 /* 设置字符设备的模式标志 */
#define TCSETSW 0x5403
#define TCSETSF 0x5404 /* 情况字符设备读队列后再设置字符设备模式标志 */
#define TCGETA  0x5405 /* 获取字符设备模式标志信息(struct termio) */
#define TCSETA  0x5406 /* 设置字符设备模式标志信息((struct termio))*/
#define TCSETAW 0x5407
#define TCSETAF 0x5408 /* 清空字符设备读队列后再设置字符设备标志信息 */
#define TCSBRK  0x5409
#define TCXONC  0x540A
#define TCFLSH  0x540B /* 刷新字符设备对垒 */
#define TIOCEXCL    0x540C
#define TIOCNXCL    0x540D
#define TIOCSCTTY   0x540E
#define TIOCGPGRP   0x540F
#define TIOCSPGRP   0x5410
#define TIOCOUTQ    0x5411 /* 获取字符设备所在进程组组号 */
#define TIOCSTI     0x5412
#define TIOCGWINSZ  0x5413
#define TIOCSWINSZ  0x5414
#define TIOCMGET    0x5415
#define TIOCMBIS    0x5416
#define TIOCMBIC    0x5417
#define TIOCMSET    0x5418
#define TIOCGSOFTCAR    0x5419
#define TIOCSSOFTCAR    0x541A
#define TIOCINQ 0x541B /* 获取辅助队列还未被读取的字符数 */struct winsize {unsigned short ws_row;unsigned short ws_col;unsigned short ws_xpixel;unsigned short ws_ypixel;
};#define NCC 8
struct termio {unsigned short c_iflag;  /* input mode flags */unsigned short c_oflag;  /* output mode flags */unsigned short c_cflag;  /* control mode flags */unsigned short c_lflag;  /* local mode flags */unsigned char c_line;    /* line discipline */unsigned char c_cc[NCC]; /* control characters */
};#define NCCS 17
struct termios {unsigned long c_iflag;    /* input mode flags */unsigned long c_oflag;    /* output mode flags */unsigned long c_cflag;    /* control mode flags */unsigned long c_lflag;    /* local mode flags */unsigned char c_line;     /* line discipline,线路速率 */unsigned char c_cc[NCCS]; /* control characters */
};/* c_cc characters */
/* struct termios结构体类型中控制字符数组c_cc种控制字符的索引 */
#define VINTR 0  /* c_cc[VINTR] =^C, \003,中断 */
#define VQUIT 1  /* c_cc[VQUIT] =^\, \034,退出 */
#define VERASE 2 /* c_cc[VERASE]=^H, \177,删除字符 */
#define VKILL 3  /* c_cc[VKILL] =^U, \025,删除行 */
#define VEOF 4   /* c_cc[VEOF]  =^D, \004,文件结束字符 */
#define VTIME 5  /* c_cc[VTIME] =,      1/10秒定时值 */
#define VMIN 6   /* c_cc[VMIN]  =,      应读取最少字符个数 */
#define VSWTC 7  /* c_cc[VSWTC] =\0,     交换字符 */
#define VSTART 8 /* c_cc[VSTART]=^Q, \021,开始字符 */
#define VSTOP 9  /* c_cc[VSTOP] =^S, \023,停止字符 */
#define VSUSP 10 /* c_cc[VSUSP] =^Z, \032,挂起字符 */
#define VEOL 11  /* c_cc[VSUSP] =\0, \032,行结束字符 */
#define VREPRINT 12 /* c_cc[VREPRINT]=^R, \022,重显字符 */
#define VDISCARD 13 /* c_cc[VDISCARD]=^0, \017,丢弃字符 */
#define VWERASE 14  /* c_cc[VWERASE] =^W, \027,单词擦除字符 */
#define VLNEXT 15   /* c_cc[VLNEXT]  =^V, \026,下一行字符 */
#define VEOL2 16    /* c_cc[VEOL2]   =\0,     ,行结束符2 *//* c_iflag bits */
/* struct termios结构体类型中输入模式标志位 */
#define IGNBRK 0000001 /* 输入时忽略BREAK标志 */
#define BRKINT 0000002 /* 输入BREAK时产生SIGINT信号 */
#define IGNPAR 0000004 /* 忽略校验出错字符标志 */
#define PARMRK 0000010 /* 标记奇偶校验错标志 */
#define INPCK  0000020 /* 允许输入奇偶校验标志 */
#define ISTRIP 0000040 /* 屏蔽字符第8位标志 */
#define INLCR  0000100 /* 将输入的换行符转回车键标志 */
#define IGNCR  0000200 /* 忽略输入回车键标志 */
#define ICRNL  0000400 /* 将输入的回车键转换为换行符标志 */
#define IUCLC  0001000 /* 将输入转换为小写字符标志 */
#define IXON   0002000 /* 允许开始/停止输出控制标志 */
#define IXANY  0004000 /* 允许任何字符重启输出标志 */
#define IXOFF  0010000 /* 允许开始/停止输入控制标志 */
#define IMAXBEL 0020000 /* 输入队列满时响铃标志 *//* c_oflag bits */
/* struct termios 结构体类型中输出模式标志位 */
#define OPOST   0000001 /* 输出处理置位标志 */
#define OLCUC   0000002 /* 输出时将小写字符转为大写字符标志 */
#define ONLCR   0000004 /* 输出时将换行符转换为回车和换行符的标志 */
#define OCRNL   0000010 /* 输出时将回车转换为换行符标志 */
#define ONOCR   0000020 /* 行首不输出回车符标志 */
#define ONLRET  0000040 /* 遇到换行符时当回车符处理标志 */
#define OFILL   0000100 /* 延迟时使用填充字符而不使用时间延迟标志 */
#define OFDEL   0000200 /* 填充字符为ASCII DEL码,未设置时为ASCII NULL */
#define NLDLY   0000400 /* 选择换行延迟标志 */
#define   NL0   0000000 /* 换行延迟类型0标志 */
#define   NL1   0000400 /* 换行延迟类型1标志 */
#define CRDLY   0003000 /* 使用回车延迟标志 */
#define   CR0   0000000 /* 回车延迟类型0标志 */
#define   CR1   0001000 /* 回车延迟类型1标志 */
#define   CR2   0002000 /* 回车延迟类型2标志 */
#define   CR3   0003000 /* 回车延迟类型3标志 */
#define TABDLY  0014000 /* 使用TAB延迟标志 */
#define   TAB0  0000000 /* TAB延迟类型0标志 */
#define   TAB1  0004000 /* TAB延迟类型1标志 */
#define   TAB2  0010000 /* TAB延迟类型2标志 */
#define   TAB3  0014000 /* TAB延迟类型3标志 */
#define   XTABS 0014000 /* TAB转换为空格标志 */
#define BSDLY   0020000 /* 使用退格延迟标志 */
#define   BS0   0000000 /* 退格延迟类型0标志 */
#define   BS1   0020000 /* 退格延迟类型1标志 */
#define VTDLY   0040000 /* 使用纵向制表延迟标志 */
#define   VT0   0000000 /* 纵向制表延迟类型0标志 */
#define   VT1   0040000 /* 纵向制表延迟类型1标志 */
#define FFDLY   0040000 /* 使用换页延迟标志 */
#define   FF0   0000000 /* 换页延迟类型0标志 */
#define   FF1   0040000 /* 换页延迟类型1标志*//* c_cflag bit meaning */
/* struct termios 结构体类型中控制模式标志位 */
#define CBAUD   0000017 /* 传输速率屏蔽码 */
#define  B0     0000000 /* 挂断线路标志 */
#define  B50    0000001 /* 波特率50 */
#define  B75    0000002 /* 波特率75 */
#define  B110   0000003 /* 波特率110 */
#define  B134   0000004 /* 波特率134 */
#define  B150   0000005 /* 波特率150 */
#define  B200   0000006 /* 波特率200 */
#define  B300   0000007 /* 波特率300 */
#define  B600   0000010 /* 波特率600 */
#define  B1200  0000011 /* 波特率1200 */
#define  B1800  0000012 /* 波特率1800 */
#define  B2400  0000013 /* 波特率2400 */
#define  B4800  0000014 /* 波特率4800 */
#define  B9600  0000015 /* 波特率9600 */
#define  B19200 0000016 /* 波特率19200 */
#define  B38400 0000017 /* 波特率38400 */
#define EXTA    B19200  /* 扩展波特率A */
#define EXTB    B38400  /* 扩展波特率B */
#define CSIZE   0000060 /* 字符位宽屏蔽码 */
#define   CS5   0000000 /* 每字符5位 */
#define   CS6   0000020 /* 每字符6位 */
#define   CS7   0000040 /* 每字符7位 */
#define   CS8   0000060 /* 每字符8位 */
#define CSTOPB  0000100 /* 设置两个停止位 */
#define CREAD   0000200 /* 使能接收 */
#define CPARENB 0000400 /* 输出时产生奇偶位,输入时奇偶校验 */
#define CPARODD 0001000 /* 输入/输出校验为奇校验 */
#define HUPCL   0002000 /* 进程关闭后挂断标志 */
#define CLOCAL  0004000 /* 忽略modem控制线路 */
#define CIBAUD  03600000     /* input baud rate (not used) */
#define CRTSCTS 020000000000 /* flow control */#define PARENB CPARENB
#define PARODD CPARODD/* c_lflag bits */
/* struct termios结构体类型中本地模式标志位 */
#define ISIG    0000001 /* 当收到INTR,QUIT等字符时产生对应信号给进程 */
#define ICANON  0000002 /* 规范模式(熟模式)开启标志 */
#define XCASE   0000004 /* ICANON设置前提下,回显大写字符 */
#define ECHO    0000010 /* 回显标志 */
#define ECHOE   0000020 /* ICANON设置前提下,ERASE/WERASE擦除前1字符或单词 */
#define ECHOK   0000040 /* ICANON设置前提下,收到KILL字符时将删除当前行 */
#define ECHONL  0000100 /* ICANON设置前提下,显示换行字符 */
#define NOFLSH  0000200 /* 产生SIGINT和SIGQUIT信号时不刷新队列,产生SIGSUSP则刷新 */
#define TOSTOP  0000400 /* 发送SIGTTOU信号到准备写控制终端的后台进程的进程组 */
#define ECHOCTL 0001000 /* ECHO设置前提下,除TAB NL START STOP以外控制字符被回显成^X, X为控制字符+40h */
#define ECHOPRT 0002000 /* ICANON和IECHO设置前提下,擦除字符时回显 */
#define ECHOKE  0004000 /* ICANON设置前提下,KILL删除当前行将回显 */
#define FLUSHO  0010000 /* 刷新输出 */
#define PENDIN  0040000 /* 收到读字符时重现所有字符 */
#define IEXTEN  0100000 /* 开启实时定义的输出处理 *//* modem lines */
#define TIOCM_LE    0x001
#define TIOCM_DTR   0x002
#define TIOCM_RTS   0x004
#define TIOCM_ST    0x008
#define TIOCM_SR    0x010
#define TIOCM_CTS   0x020
#define TIOCM_CAR   0x040
#define TIOCM_RNG   0x080
#define TIOCM_DSR   0x100
#define TIOCM_CD    TIOCM_CAR
#define TIOCM_RI    TIOCM_RNG/* tcflow() and TCXONC use these */
#define TCOOFF      0
#define TCOON       1
#define TCIOFF      2
#define TCION       3/* tcflush() and TCFLSH use these */
#define TCIFLUSH    0
#define TCOFLUSH    1
#define TCIOFLUSH   2/* tcsetattr uses these */
#define TCSANOW     0
#define TCSADRAIN   1
#define TCSAFLUSH   2typedef int speed_t;extern speed_t cfgetispeed(struct termios *termios_p);
extern speed_t cfgetospeed(struct termios *termios_p);
extern int cfsetispeed(struct termios *termios_p, speed_t speed);
extern int cfsetospeed(struct termios *termios_p, speed_t speed);
extern int tcdrain(int fildes);
extern int tcflow(int fildes, int action);
extern int tcflush(int fildes, int queue_selector);
extern int tcgetattr(int fildes, struct termios *termios_p);
extern int tcsendbreak(int fildes, int duration);
extern int tcsetattr(int fildes, int optional_actions,struct termios *termios_p);#endif
io.h
/* 目前还未在程序中使用过该宏, 顺便也阅读下outb宏吧。* * outb(value, port),* 将1字节数据value输出到端口地址port处。* * 内联汇编输入。* "a"(value), 将value赋给eax;* "d"(port),  将port赋给edx;* * outb %%al, %%dx, 将al赋给dx值表示的端口处。* */
#define outb(value,port) \
__asm__ ("outb %%al,%%dx"::"a" (value),"d" (port))/* 功能: 读端口。** 参数:* port - 端口地址,* * 内联汇编指令描述。* 输入: edx = port,* 输出: _v = eax。** 将port输入edx, 读端口dx数据到al, * 将eax输出到_v, 并将_v作为"返回值"。*/
#define inb(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al":"=a" (_v):"d" (port)); \
_v; \
})/* outb_p(value, port),* 写1字节数据value到指定端口地址port处。*** 内联汇编。* edx = port, eax = value,* 将al写往端口dx, 向前跳转执行标号1。** 跳转语句用作延时供写端口操作完成/稳定,* outb_p用于之后会紧跟与port相关的i/o指令。*/
#define outb_p(value,port) \
__asm__ ("outb %%al,%%dx\n" \"\tjmp 1f\n" \"1:\tjmp 1f\n" \"1:"::"a" (value),"d" (port))/* inb_p(port),* 从指定端口地址port处读取1字节内容。** 内联汇编。* volatile其告知编译器不要优化内联汇编中的代码* (如不共享寄存器, 不改变指令顺序等, 不删除未使用的函数)。* * 内联汇编指令。* edx = port,* 读端口地址dx处内容到al,* 向前跳转执行标号1(用作延时待读操作完成/稳定)* _v = eax.** _v为表达式最终值。*/
#define inb_p(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al\n" \"\tjmp 1f\n" \"1:\tjmp 1f\n" \"1:":"=a" (_v):"d" (port)); \
_v; \
})

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

  1. linux0.11磁盘映像制作及其剩余程序阅读注释笔记

    [ 1] linux0.11引导程序阅读注释. [ 2] linux0.11由实模式进入保护模式程序阅读注释 . [ 3] linux0.11护模式初始化程序阅读注释. [ 4] linux0.11主 ...

  2. linux0.11多任务管理程序阅读注释笔记

    [ 1] linux0.11引导程序阅读注释. [ 2] linux0.11由实模式进入保护模式程序阅读注释 . [ 3] linux0.11护模式初始化程序阅读注释. [ 4] linux0.11主 ...

  3. linux0.11缓冲区管理程序阅读注释笔记

    [ 1] linux0.11引导程序阅读注释. [ 2] linux0.11由实模式进入保护模式程序阅读注释 . [ 3] linux0.11护模式初始化程序阅读注释. [ 4] linux0.11主 ...

  4. 深入浅出:Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  5. Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

    前面在 Linux 字符设备驱动开发基础 (三)-- 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另 ...

  6. 蜕变成蝶~Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  7. 第六讲 Linux字符设备驱动1

    六.字符设备驱动 6.1.基本概念 Linux操作系统思想:一切皆文件 对设备的操作,也是将设备抽象成设备文件,应用层通过文件IO对设备文件进行操作,其最终结 果,是在操作设备. Linux操作系统, ...

  8. Linux设备驱动程序 三 字符设备驱动

    Linux设备驱动程序 三 字符设备驱动 笔记 第三章 字符驱动设备 本章会编写一个完整的字符设备,字符设备简单,易于理解, 名字是scull:Simple Caracter Utility for ...

  9. 字符设备驱动代码完整分析

    1.编译.安装驱动程序 Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此编译.安装驱动程序实质是编译.安装内核模块 memdev.c #include <linux/modu ...

最新文章

  1. 逆矩阵 与行列式的关系
  2. java如何构造ajax回调参数,jQuery实现ajax回调函数带入参数的方法示例
  3. JAVA学习篇--JSP实现原理
  4. java用的原码还是反码_java 原码 反码 和补码
  5. Windows 7 设置devenv.exe启动版本
  6. 对多用户分时系统最重要_互联网搜索引擎:让你的产品在最显眼的位置摆摊
  7. Unity3D基础5:摄像机与Game视图
  8. php 实现柱状图,PHP动态柱状图实现方法_PHP
  9. Julia : csv =hdf5
  10. cvMatchTemplate() 模板匹配
  11. 计算机网络工程师多久过期,软考网络工程师证书有效期
  12. 逆clarke变换_克拉克(CLARKE)及帕克(PARK)变换.pdf
  13. 2D游戏新手引导点光源和类迷雾实现
  14. dtu连接mysql_Azure SQL 数据库中的DTU和eDTU是什么
  15. CTEX 各种命令、符号
  16. python布尔系列_python-布尔运算
  17. consulandnacos
  18. lombok使用详细教程
  19. FreeType 管理字形
  20. 数据中心服务器冷却技术,盘点数据中心液体冷却系统

热门文章

  1. 卡方检验 java_卡方检验文本特征选择
  2. 合同法律风险管理 合同签字形式
  3. 推开混合云市场大门,Lenovo xCloud的破局之道
  4. Linux学习:Linux的发展历史及特点
  5. jsp include指令元素
  6. Libero使用教程(新建,仿真,下载)
  7. 内联注释 sql注释_SQL注释
  8. js匿名函数和立即执行函数
  9. java8 metaspacesize_JVM --- MetaspaceSize理解
  10. 【谷粒商城基础篇】基础环境搭建