ldd3之模块学习总结(页 1) - 文档专区 - 无分类文档 - Linux论坛 - p...
来源:百度文库 编辑:神马文学网 时间:2024/06/03 13:24:41
china_longldd3之模块学习总结
ldd3之模块学习总结
一个学习Linux设备驱动程序都会碰到的第一个例程:
#include linux/init.h>
#include linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, Tekkaman Ninja !\n");
return 0;
}
static [color=#ff0000]void[/color] hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n");
}
module_init(hello_init);
module_exit(hello_exit);
1、Makefile书写:
KERNELDIR = /usr/src/linux_2.6.26/linux-2.6.26 #内核原代码树位置
PWD = $(shell pwd)
INSTALLDIR = /lib/modules/2.6.26linux2.6.26
#1 obj-m := hello.o 模块只有一个文件
#2 obj-m := hello.o
hello-objs := one.o two.o three.o 模块有多个文件
#3 如果模块是由一个目录(online)组成的写法如下
a、在这个目录的上一级目录中的makefile中加入
[color=#000000]obj-m += online/ #告诉在编译的时候找到online目录[/color]
[color=#000000] obj-$(CONFIG_USB_GADGET_ONLINE) += online/ 这种方式是用配置选项(kconfig文件中添加配置选项)来判断是否编译这个子目录(即模块)[/color]
[color=#000000] b、最后,我们在online/下创建新文件Makefile,并且添加下面一行到其内。
obj-m += netmeeting.o[/color]
[color=#000000] netmeeting-objs := one.o two.o three.o 如果有多个文件[/color]
modules:
[TAB] $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
[TAB] cp hello.ko $(INSTALLDIR)
clean:
[TAB] rm -rf *.o *.ko *.mod.c *.o.cmd .tmp_versions *.mod.o .cmd
.PHONY :modules modules_install clean
2、编译运行
insmod
rmmod
lsmod
3、调试查看输出信息
a、在模块中添加调试输出语句 printk
cat /proc/kmsg (rsyslog没有运行)
dmesg (syslogd进程已经运行 /etc/rc.d/init/rsyslog 开启)
cat /var/log/message
注意:
[color=#000000]printk[/color]([color=#000000]KERN_ALERT[/color]"xxxx“);
宏的定义等级如下(printk.c):
#define KERN_EMERG "" /* system is unusable */
#define KERN_ALERT "" /* action must be taken immediately */
#define KERN_CRIT "" /* critical conditions */
#define KERN_ERR "" /* error conditions */
#define KERN_WARNING "" /* warning conditions */
#define KERN_NOTICE "" /* normal but significant condition */
#define KERN_INFO "" /* informational */
#define KERN_DEBUG "" /* debug-level messages */
当指定的等级小于console_level(默认4)时候,就可以直接在tty上面打印输出语句
console_level设置方式:
通过对/proc/sys/kernel/printk的访问来改变[color=#000102]console_loglevel的值:[/color]
[Tekkaman2440@SBC2440V4]#echo 1 > /proc/sys/kernel/printk
[Tekkaman2440@SBC2440V4]#cat /proc/sys/kernel/printk
1 4 1 7
[Tekkaman2440@SBC2440V4]#insmod hello.ko
[Tekkaman2440@SBC2440V4]#rmmod hello
[Tekkaman2440@SBC2440V4]#echo 7 > /proc/sys/kernel/printk
[Tekkaman2440@SBC2440V4]#cat /proc/sys/kernel/printk7 4 1 7
[Tekkaman2440@SBC2440V4]#insmod hello.ko
Hello, Tekkaman Ninja !
[Tekkaman2440@SBC2440V4]#rmmod hello
Goodbye, Tekkaman Ninja !
Love Linux !Love ARM ! Love KeKe !
四个数字的含义:当前的loglevel、默认loglevel、最小允许的loglevel、引导时的默认loglevel。
4、模块中使用的头文件
#include linux/init.h>
#include linux/module.h>
5、模块中使用的描述性定义
MODULE_AUTHOR("");
MODULE_DESCRIPTION("");
MODULE_VERSION("");
MODULE_ALIAS("");
MODULE_DEVICE_TABLE("");
描绘性定义一般放在最后面
6、模块中的参数
(内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变[color=#000000]。 example : insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1[/color])
[color=#000000]对于如何向模块传递参数,Linux kernel 提供了一个简单的框架。其允许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,在驱动程序里,参数的用法如同全局变量。这些模块参数也能够在sysfs中显示出来。结果,有许许多多的方法用来创建和管理模块参数。
通过宏module_param()定义一个模块参数:
module_param(name, type, perm);
这里,name既是用户看到的参数名,又是模块内接受参数的变量; type表示参数的数据类型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool。这些类型分别是:a byte, a short integer, an unsigned short integer, an integer, an unsigned integer, a long integer, an unsigned long integer, a pointer to a char, a Boolean, a Boolean whose value is inverted from what the user specifies. The byte type is stored in a single char and the Boolean types are stored in variables of type int. The rest are stored in the corresponding primitive C types. 最后,perm指定了在sysfs中相应文件的访问权限。访问权限用通常的八进制格式来表示,例如,用0644(表示ower具有读写权限,group和everyone只读权限), 或者用通常的S_Ifoo定义,例如,S_IRUGO | S_IWUSR (表示everyone具有读权限,用户具有写权限)。用0表示完全关闭在sysfs中相对应的项。
其实宏不会声明变量,因此在使用宏之前,必须声明变量。所以,典型地用法如下:
static unsigned int use_acm = 0;
module_param(use_acm, uint, S_IRUGO);
这些必须写在模块源文件的开头部分。即use_acm是全局的。
我们也可以使模块源文件内部的变量名与外部的参数名有不同的名字。这通过宏module_param_named()定义。
module_param_named(name, variable, type, perm);
这里name是外部可见的参数名,variable是源文件内部的全局变量名。例如:
static unsigned int max_test = 9;
module_param_name(maximum_line_test, max_test, int, 0);
如果模块参数是一个字符串时,通常使用charp类型定义这个模块参数。内核复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。例如:
static char *name;
module_param(name, charp, 0);
另一种方法是通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内。
module_param_string(name, string, len, perm);
这里,name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小(可以小于buffer的大小,但是没有意义),perm表示sysfs的访问权限(或者perm是零,表示完全关闭相对应的sysfs项)。例如:
static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);
上面说得只是给模块传入一个参数的情况,如果给模块传入多个参数,那该怎么办呢?可以通过宏module_param_array()给模块传入多个参数。 用法如下:
module_param_array(name, type, nump, perm);
这里,name既是外部模块的参数名又是程序内部的变量名,type是数据类型,perm是sysfs的访问权限。指针nump指向一个整数,其值表示有多少个参数存放在数组name中。值得注意是name数组必须静态分配。例如:
static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(fish, int, &nr_fish, 0444);
通过宏module_param_array_named()使得内部的数组名与外部的参数名有不同的名字。例如:
module_param_array_named(name, array, type, nump, perm);
这里的参数意义与其它宏一样。
最后,通过宏MODULE_PARM_DESC()对参数进行说明:
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, “The size in inches of the fishing pole” \
“connected to this computer.” );
使用这些宏时需要包含头文件。[/color]
[color=#ff0000]注意:[/color]
由于模块参数指的是外部向模块传递参数,模块声明的变量能否被内核其他代码和模块使用呢?(是不是只要是非静态的且导出符号,内核和其他模块就能共享这个变量呢?)
example code :
#include linux/init.h>
#include linux/module.h>
#include linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "Tekkaman Ninja";
static int howmany = 1;
static int TNparam[] = {1,2,3,4};
static int TNparam_nr = 4;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
static int hello_init(void)
{
int i;
for (i = 0; i howmany; i++)
printk(KERN_ALERT "(%d) Hello, %s !\n", i, whom);
for (i = 0; i ; i++)
printk(KERN_ALERT "TNparam[%d] : %d \n", i, TNparam[i]);
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n");
}
module_init(hello_init);
module_exit(hello_exit);
编译完成后
测试1
insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1
测试2
[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8
TNparam: can only take 4 arguments
hello_param: `4' invalid for parameter `TNparam'
insmod: cannot insert 'hello-param.ko': Invalid parameters (-1): Invalid argument
注意:
这个测试说明module_param_array(TNparam , int , &TNparam_nr , S_IRUGO),参数[color=#ff0102]TNparam_nr不能自己限制参数的个数,而需要程序员自己确定参数的个数,否则数组[/color][color=#0000cc]TNparam可能会越界。(个人认为如果传送数组形式的参数最好再定义一个宏来确定数组的大小)[/color]
7、导出符号
[color=#000000] 当装载模块的时候,模块动态地链接入内核之中。动态链接的二进制代码只能调用外部函数,然而,外部函数必须明确地输出,在内核中,通过EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL来达到这个目的。
输出的函数可以被其它模块调用。没有输出过的函数不能被其它模块调用。模块比核心内核映像代码具有更严格的链接和调用规则。因为所有核心源文件链接成一个单一的作为基础的映像,因此在内核中核心代码可以调用任何非静态的接口。当然,输出符号也必须是非静态属性。
一套输出的内核符号称之为输出的内核接口,甚至称之为kernel API。
输出一个内核符号是举手之劳之事。当函数声明之时,在其后用EXPORT_SYMBOL()把函数输出。
例如:
/* it will receive control requests including set_configuration(), which enables non-control requests.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
…
}
EXPORT_SYMBOL(usb_gadget_register_driver) ;
从此以后,任何模块都可以调用函数usb_gadget_register_driver(),只要在源文件中包含声明这个函数的头文件,或者extern这个函数的声明。
有些开发者希望他们的接口只让遵从GPL的模块调用。通过MODULE_LICENSE()的使用,内核链接器能够强制保证做到这点。如果你希望前面的函数仅被标有GPL许可证的模块访问,那么你可以用如下方式输出符号:
EXPORT_SYMBOL_GPL(usb_gadget_register_driver);
如果你的代码配置为模块方式,那么必须确保:源文件中使用的所有接口必须是已经输出的符号,否则导致在装载时链接错误。[/color]
[b]本文来自ChinaUnix博客,如果查看原文请点:[/b][url]http://blog.chinaunix.net/u2/83200/showart_1679685.html[/url]
ldd3之模块学习总结
一个学习Linux设备驱动程序都会碰到的第一个例程:
#include linux/init.h>
#include linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, Tekkaman Ninja !\n");
return 0;
}
static [color=#ff0000]void[/color] hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n");
}
module_init(hello_init);
module_exit(hello_exit);
1、Makefile书写:
KERNELDIR = /usr/src/linux_2.6.26/linux-2.6.26 #内核原代码树位置
PWD = $(shell pwd)
INSTALLDIR = /lib/modules/2.6.26linux2.6.26
#1 obj-m := hello.o 模块只有一个文件
#2 obj-m := hello.o
hello-objs := one.o two.o three.o 模块有多个文件
#3 如果模块是由一个目录(online)组成的写法如下
a、在这个目录的上一级目录中的makefile中加入
[color=#000000]obj-m += online/ #告诉在编译的时候找到online目录[/color]
[color=#000000] obj-$(CONFIG_USB_GADGET_ONLINE) += online/ 这种方式是用配置选项(kconfig文件中添加配置选项)来判断是否编译这个子目录(即模块)[/color]
[color=#000000] b、最后,我们在online/下创建新文件Makefile,并且添加下面一行到其内。
obj-m += netmeeting.o[/color]
[color=#000000] netmeeting-objs := one.o two.o three.o 如果有多个文件[/color]
modules:
[TAB] $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
[TAB] cp hello.ko $(INSTALLDIR)
clean:
[TAB] rm -rf *.o *.ko *.mod.c *.o.cmd .tmp_versions *.mod.o .cmd
.PHONY :modules modules_install clean
2、编译运行
insmod
rmmod
lsmod
3、调试查看输出信息
a、在模块中添加调试输出语句 printk
cat /proc/kmsg (rsyslog没有运行)
dmesg (syslogd进程已经运行 /etc/rc.d/init/rsyslog 开启)
cat /var/log/message
注意:
[color=#000000]printk[/color]([color=#000000]KERN_ALERT[/color]"xxxx“);
宏的定义等级如下(printk.c):
#define KERN_EMERG "" /* system is unusable */
#define KERN_ALERT "" /* action must be taken immediately */
#define KERN_CRIT "" /* critical conditions */
#define KERN_ERR "" /* error conditions */
#define KERN_WARNING "" /* warning conditions */
#define KERN_NOTICE "" /* normal but significant condition */
#define KERN_INFO "" /* informational */
#define KERN_DEBUG "" /* debug-level messages */
当指定的等级小于console_level(默认4)时候,就可以直接在tty上面打印输出语句
console_level设置方式:
通过对/proc/sys/kernel/printk的访问来改变[color=#000102]console_loglevel的值:[/color]
[Tekkaman2440@SBC2440V4]#echo 1 > /proc/sys/kernel/printk
[Tekkaman2440@SBC2440V4]#cat /proc/sys/kernel/printk
1 4 1 7
[Tekkaman2440@SBC2440V4]#insmod hello.ko
[Tekkaman2440@SBC2440V4]#rmmod hello
[Tekkaman2440@SBC2440V4]#echo 7 > /proc/sys/kernel/printk
[Tekkaman2440@SBC2440V4]#cat /proc/sys/kernel/printk7 4 1 7
[Tekkaman2440@SBC2440V4]#insmod hello.ko
Hello, Tekkaman Ninja !
[Tekkaman2440@SBC2440V4]#rmmod hello
Goodbye, Tekkaman Ninja !
Love Linux !Love ARM ! Love KeKe !
四个数字的含义:当前的loglevel、默认loglevel、最小允许的loglevel、引导时的默认loglevel。
4、模块中使用的头文件
#include linux/init.h>
#include linux/module.h>
5、模块中使用的描述性定义
MODULE_AUTHOR("");
MODULE_DESCRIPTION("");
MODULE_VERSION("");
MODULE_ALIAS("");
MODULE_DEVICE_TABLE("");
描绘性定义一般放在最后面
6、模块中的参数
(内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变[color=#000000]。 example : insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1[/color])
[color=#000000]对于如何向模块传递参数,Linux kernel 提供了一个简单的框架。其允许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,在驱动程序里,参数的用法如同全局变量。这些模块参数也能够在sysfs中显示出来。结果,有许许多多的方法用来创建和管理模块参数。
通过宏module_param()定义一个模块参数:
module_param(name, type, perm);
这里,name既是用户看到的参数名,又是模块内接受参数的变量; type表示参数的数据类型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool。这些类型分别是:a byte, a short integer, an unsigned short integer, an integer, an unsigned integer, a long integer, an unsigned long integer, a pointer to a char, a Boolean, a Boolean whose value is inverted from what the user specifies. The byte type is stored in a single char and the Boolean types are stored in variables of type int. The rest are stored in the corresponding primitive C types. 最后,perm指定了在sysfs中相应文件的访问权限。访问权限用通常的八进制格式来表示,例如,用0644(表示ower具有读写权限,group和everyone只读权限), 或者用通常的S_Ifoo定义,例如,S_IRUGO | S_IWUSR (表示everyone具有读权限,用户具有写权限)。用0表示完全关闭在sysfs中相对应的项。
其实宏不会声明变量,因此在使用宏之前,必须声明变量。所以,典型地用法如下:
static unsigned int use_acm = 0;
module_param(use_acm, uint, S_IRUGO);
这些必须写在模块源文件的开头部分。即use_acm是全局的。
我们也可以使模块源文件内部的变量名与外部的参数名有不同的名字。这通过宏module_param_named()定义。
module_param_named(name, variable, type, perm);
这里name是外部可见的参数名,variable是源文件内部的全局变量名。例如:
static unsigned int max_test = 9;
module_param_name(maximum_line_test, max_test, int, 0);
如果模块参数是一个字符串时,通常使用charp类型定义这个模块参数。内核复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。例如:
static char *name;
module_param(name, charp, 0);
另一种方法是通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内。
module_param_string(name, string, len, perm);
这里,name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小(可以小于buffer的大小,但是没有意义),perm表示sysfs的访问权限(或者perm是零,表示完全关闭相对应的sysfs项)。例如:
static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);
上面说得只是给模块传入一个参数的情况,如果给模块传入多个参数,那该怎么办呢?可以通过宏module_param_array()给模块传入多个参数。 用法如下:
module_param_array(name, type, nump, perm);
这里,name既是外部模块的参数名又是程序内部的变量名,type是数据类型,perm是sysfs的访问权限。指针nump指向一个整数,其值表示有多少个参数存放在数组name中。值得注意是name数组必须静态分配。例如:
static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(fish, int, &nr_fish, 0444);
通过宏module_param_array_named()使得内部的数组名与外部的参数名有不同的名字。例如:
module_param_array_named(name, array, type, nump, perm);
这里的参数意义与其它宏一样。
最后,通过宏MODULE_PARM_DESC()对参数进行说明:
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, “The size in inches of the fishing pole” \
“connected to this computer.” );
使用这些宏时需要包含头文件。[/color]
[color=#ff0000]注意:[/color]
由于模块参数指的是外部向模块传递参数,模块声明的变量能否被内核其他代码和模块使用呢?(是不是只要是非静态的且导出符号,内核和其他模块就能共享这个变量呢?)
example code :
#include linux/init.h>
#include linux/module.h>
#include linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "Tekkaman Ninja";
static int howmany = 1;
static int TNparam[] = {1,2,3,4};
static int TNparam_nr = 4;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
static int hello_init(void)
{
int i;
for (i = 0; i howmany; i++)
printk(KERN_ALERT "(%d) Hello, %s !\n", i, whom);
for (i = 0; i ; i++)
printk(KERN_ALERT "TNparam[%d] : %d \n", i, TNparam[i]);
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n");
}
module_init(hello_init);
module_exit(hello_exit);
编译完成后
测试1
insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1
测试2
[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8
TNparam: can only take 4 arguments
hello_param: `4' invalid for parameter `TNparam'
insmod: cannot insert 'hello-param.ko': Invalid parameters (-1): Invalid argument
注意:
这个测试说明module_param_array(TNparam , int , &TNparam_nr , S_IRUGO),参数[color=#ff0102]TNparam_nr不能自己限制参数的个数,而需要程序员自己确定参数的个数,否则数组[/color][color=#0000cc]TNparam可能会越界。(个人认为如果传送数组形式的参数最好再定义一个宏来确定数组的大小)[/color]
7、导出符号
[color=#000000] 当装载模块的时候,模块动态地链接入内核之中。动态链接的二进制代码只能调用外部函数,然而,外部函数必须明确地输出,在内核中,通过EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL来达到这个目的。
输出的函数可以被其它模块调用。没有输出过的函数不能被其它模块调用。模块比核心内核映像代码具有更严格的链接和调用规则。因为所有核心源文件链接成一个单一的作为基础的映像,因此在内核中核心代码可以调用任何非静态的接口。当然,输出符号也必须是非静态属性。
一套输出的内核符号称之为输出的内核接口,甚至称之为kernel API。
输出一个内核符号是举手之劳之事。当函数声明之时,在其后用EXPORT_SYMBOL()把函数输出。
例如:
/* it will receive control requests including set_configuration(), which enables non-control requests.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
…
}
EXPORT_SYMBOL(usb_gadget_register_driver) ;
从此以后,任何模块都可以调用函数usb_gadget_register_driver(),只要在源文件中包含声明这个函数的头文件,或者extern这个函数的声明。
有些开发者希望他们的接口只让遵从GPL的模块调用。通过MODULE_LICENSE()的使用,内核链接器能够强制保证做到这点。如果你希望前面的函数仅被标有GPL许可证的模块访问,那么你可以用如下方式输出符号:
EXPORT_SYMBOL_GPL(usb_gadget_register_driver);
如果你的代码配置为模块方式,那么必须确保:源文件中使用的所有接口必须是已经输出的符号,否则导致在装载时链接错误。[/color]
[b]本文来自ChinaUnix博客,如果查看原文请点:[/b][url]http://blog.chinaunix.net/u2/83200/showart_1679685.html[/url]
ldd3之模块学习总结(页 1) - 文档专区 - 无分类文档 - Linux论坛 - p...
在autoconf/automake项目中禁止优化 - 程序开发 - 文档专区 - Linux论坛
NS2脚本中如何使用God - 程序开发 - 文档专区 - Linux论坛
学习Linux系统的十一点建议 , linux,命令,学习,命令行,工具书,文档,系统,问题,安装,版本, ,
文档1
TDS学习文档1——入门
struts1.0学习文档
Word文档无响应现象
学习Linux系统的十一点建议 , linux,命令,学习,命令行,工具书,文档,系统,问题,安装,版本, , - CSDN 新闻
Linuxeden.com---自由文档- 一个LINUX学习高手写给初学者的话
我的计划--嵌入式学习路径 - 技术文档 - 程序开发 Linux时代 - 开源、自由、共...
我的计划--嵌入式学习路径 - 技术文档 - 程序开发 Linux时代 - 开源、自由、共...
非常全面的NFS文档(Linux新手入门与安装配置论坛)
linux学习笔记总结
文档之战还远没有结束
文档之战还远没有结束
WORD文档与PDF文档相互转换之全攻略
Java视线论坛--DWR学习--读了遍1.1官方文档,提取的些东西。
文件格式汇编—技术文档(学习资料)
Lucene-2.0学习文档(一)_codeproject
小学高年级语文综合性学习 - 文档搜索
学习用 doxygen 生成源码文档-Linux伊甸园----Linux|Unix|新闻|下载|论坛|人才|教程|自由软件|嵌入式|安装|开源|qq|RedHat|SUSE|命令|Mandriva|
论坛风格优化方案策划文档
MySQL优化 之 Discuz论坛优化 | MySQL 中文网|MySQL 文档|MyS...