Summary on char dev driver in Linux0.12

控制台终端

当用户通过键盘输入时,键盘中断程序会将接收到的扫描码进行转义(有些是显示字符,可以直接显示在屏幕上;有些是控制字符,控制序列,转义序列什么的),写入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在它上面,

Why link with .exp file?

对于.exp文件(导出文件),我们已经都很熟悉了。每次build一个dll的时候,目录中总会有这个文件被生成。一般来说,这个文件没什么用处,基本是直接被无视的。因为当我们发布dll出去时,只需要它对应的.lib导入库。顶多再加个.pdb调试文件。但是我在最近的工作中发现,有些时候.exp也是相当重要的,需要被用来参与构建,就是说要放到link命令行上去被使用。这里所谓有些时候就是当几个dll之间需要循环引用的时候。我很讨厌循环引用,所以会尽量不用这种开发方式,所以造成了对于.exp文件使用上的无知。

首先要说的是,.exp文件的使用时机。.exp中包含导出符号和其他一些信息(一些值,将来会作为link的命令行参数),它会在构建导出这些符号的dll/exe时被链接进来,然而就像上面说的,引用这个dll/exe时,并不需要它。其次,有两种方式可以产生.exp文件。第一种,使用 lib /def:<.def文件>。这种方式下,lib工具可以在源代码还没编译之前,就可以根据.def来产生.exp文件和导入库.lib文件。第二种方式是使用link,在链接产生一个dll/exe的时候,自动生成对应的.exp和.lib。第一种方式就可以被用于循环引用的情况中。既然循环引用是个鸡和蛋之间的关系,个么我们就要打破这种循环关系,先造个假蛋来造鸡,再用鸡来生个真蛋来替换假蛋。

依旧,用一个例子来说明循环引用的整个过程,以及.exp的使用。2个互相引用的dll,一个exe。代码如下:

Circular1.cpp必须使用.def文件来导出:LIBRARY “circular1”

EXPORTS
circular1_func1 @1
circular1_func2 @2

extern “C” {
          __declspec(dllimport) void circular2_func2();
          /*__declspec(dllexport)*/ void circular1_func1();
          /*__declspec(dllexport)*/ void circular1_func2();
}void circular1_func1() {
          circular2_func2();
          printf(“circular1_func1\n”);
}void circular1_func2() {
          printf(“circular1_func2\n”);
}
Circular2.cpp extern “C” {         
__declspec(dllimport) void circular1_func2();
          __declspec(dllexport) void circular2_func1();
          __declspec(dllexport) void circular2_func2();
}void circular2_func1() {
          circular1_func2();
          printf(“circular2_func1\n”);
}void circular2_func2() {
          printf(“circular2_func2\n”);
}
Testmain.cpp extern “C” {         
__declspec(dllimport) void circular1_func1();
          __declspec(dllimport) void circular1_func2();
          __declspec(dllimport) void circular2_func1();
          __declspec(dllimport) void circular2_func2();
}void main() {
          printf(“——————–\n”);
          circular1_func1();
          printf(“——————–\n”);
          circular1_func2();
          printf(“——————–\n”);
          circular2_func1();
          printf(“——————–\n”);
          circular2_func2();
          printf(“\n”);
}

然后使用如下命令来构建(这里使用C导出,以减少编写.def文件的复杂度,否则自己用dumpbin来折腾吧):

> lib /def:circular1.def /machine:i386
> cl /ZI /c circular2.cpp
> link /DEBUG /DLL circular2.obj circular1.lib
> cl /ZI /c circular1.cpp
> link /DEBUG /DLL circular1.obj circular1.exp circular2.lib
> cl /ZI /c testmain.cpp
> link /DEBUG testmain.obj circular1.lib circular2.lib

编译链接后运行,输出结果如下:

D:\Visual Studio 2008\exptest\test>testmain.exe
——————–
circular2_func2
circular1_func1
——————–
circular1_func2
——————–
circular1_func2
circular2_func1
——————–
circular2_func2

更多信息可以查看MSDN。都是纯文本的描述,自己看着办吧。

http://msdn.microsoft.com/en-us/library/se8y7dcs(VS.80).aspx
http://msdn.microsoft.com/en-us/library/kkt2hd12(v=VS.71).aspx
http://msdn.microsoft.com/en-us/library/f0z8kac4(v=vs.71).aspx

有兴趣的人,可以再复习下链接器的原理。不用太深入,你又不靠写链接起来混饭吃:-)

http://blog.csdn.net/soloist/archive/2005/09/30/493238.aspx

Austin.D @ Autodesk, ACRD CPG CER/CIP Team