控制台终端
当用户通过键盘输入时,键盘中断程序会将接收到的扫描码进行转义(有些是显示字符,可以直接显示在屏幕上;有些是控制字符,控制序列,转义序列什么的),写入read_q队列中。然后调用tty_do_interrupt来进一步调用copy_to_cooked,将read_q中的内容读出。这里分两部走:1)根据规范模式进行基于字符行的转换操作,并写入secondary队列中,供上层程序读取使用。如tty_read,会把secondary中的内容copy给用户指定的缓冲区中。2)如果需要回显(echo),则将转换操作结果写入write_q中,并调用con_write输出到屏幕上。屏幕输出无非就是把字符数据(包括字符属性)写到对应的显示内存位置上去。
另一种情况就是用户的程序经过系统调用,间接调用到tty_write,将要输出的数据写入write_q,然后tty_write进一步调用con_write输出到当前终端的显示屏幕上。
串行终端
当串行终端有数据要被接收时,会产生串行设备的中断。linux0.12的串行中断处理程序会判断中断来源。如果是接收数据中断,那么就读取RBR接收缓存寄存器,将接收到的字符写入read_q中。之后的过程类似于控制台终端的情况,只是调用的子程序会有些不同。同样,中断处理程序会调用tty_do_interrupt来进一步调用copy_to_cooked,将read_q中的内容读出。同样分两步走。1)规范模式转换结果,依旧是放入secondary队列中,供上层的tty_read获取数据。2)如果要回显,则写回write_q中,并通过调用rs_write来将数据发送回串行终端。这里要注意的是,rs_write并不是真正干发送操作的地方,它仅仅是打开了发送保持寄存器,以允许空时中断。这里很绕口,其实就是这个寄存器,当它是空的时候会发生中断。所以平时他所对应的中断源是被禁止掉的。而rs_write的作用就是在发送之前打开这个中端。这样,串行中断处理程序会被调用,这次的中断源变成了“设置发送保持寄存器中断允许标志”,然后调用write_char子程序,将write_q中的数据送往发送保持寄存器,从而发送出去。
发送串行数据和控制台一样,通过tty_write,将要发送的数据写入write_q,然后tty_write进一步调用write_char来发送。
伪终端(主/从配对)
伪终端是最有意思的一个,也是实现上最简单的一个,所以书上没有列出它的源代码,但是自己看看就能够懂了。伪终端派什么用?其实就是用来进程间通信的。我们经常会需要在自己的程序中调用一个shell命令,然后读入shell命令的处理结果,然后进行分析。要注意的是,伪终端永远都是配对使用的!对一个伪终端进行读操作,就是接收它所配对的那个伪终端写入的数据。从实现上来看,当前伪终端tty_read读操作,肯定是读自己的secondary队列,它的数据来自于自己的read_q。自己read_q的内容就是配对的那个伪终端写入的数据,也就是它write_q中的数据。这样看来,就是一个数据的copy操作而已,从从伪终端中write_q,copy到自己的read_q中,然后调用copy_to_cooked来规范模式处理。linux0.12就是这么做的。
几个Sleep/Wakeup点
在tty_write中写入数据之前,如果write_q已经满了,那么当前调用tty_write的进程就会睡眠等待,这里是可中断的等待。在write_char中和pty.c中会唤醒等待在write_q上的进程。奇怪的是con_write中没有。难道是bug?这个我还没调试过。
tty_read只从secondary中获取提供给上层程序的数据。如果secondary为空,则睡眠等待。在copy_to_cooked会唤醒。
对于read_q,我只看到两处对于进程唤醒的操作:tty_read中和中断处理程序的put_queue中。但是从来没看到过有进程为sleep在它上面,