2008年10月18日星期六
我的slitaz已安装软件清单
中文输入:fcitx,gcin
网页浏览:opera9.6,firefox3.03, midori, links
文件查找及桌面搜索:searchmonkey
邮件:opera,claws mail,ghost in the mail
rss阅读:google reader,opera
pdf阅读:epdfview, foxit reader
chm阅读: xchm
终端:xterm,sakura,aterm,rxvt-unicode,
代码编辑及阅读:vim+ctags+taglist
文本编辑:leafpad
查字典:opera + dict字典搜索引擎
媒体播放:xine,mplayer,realplayer
音乐播放:alsaplayer
在线电视:qsopcast
网络聊天:pidgin,QQ
文件管理:pcmanfm
图片浏览:gpicview
抓屏:grab screeshot
http下载:wget
ftp下载:gftp
bt下载:benliud,Torrentbox
压缩解压缩:zip,unzip,rar,xarchive,7zr
推荐7-zip,windows和linux都一样用,况且可以支持tar,bz2,rar,zip等等多种格式,使用方法:
7z a -rp test.7z /home/wandering/test/
7z x test.7z
excel文件编辑查看:Gnumeric
计算器:galculator
游戏:连连看
任务管理器:htop processes viewer
......
如何编写Linux设备驱动程序[转载]
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它与dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,获得了一些经验,愿与Linux
fans共享,有不当之处,请予指正。
以下的一些文字主要来源于khg,johnsonm的Write linux device
driver,Brennan's Guide to Inline Assembly,The Linux
A-Z,还有清华BBS上的有关device driver的一些资料.
这些资料有的已经过时,有的还有一些错误,我依据自己的试验结果进行了修正.
一、Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,
应用程序可以象操作普通文件一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能:
1.对设备初始化和释放.
2.把数据从内核传送到硬件和从硬件读取数据.
3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
4.检测和处理设备出现的错误.
在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待.
已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序.
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck.//hehe
读/写时,它首先察看缓冲区的内容,如果缓冲区的数据(UNIX Reference注:
此处不完整,由于找不到原始的文字,所以无法进行修正,请谅解。)
二、实例剖析
我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel上可能会出现问题,我还没测试过.//xixi
#define __NO_VERSION__
#include <linux/modules.h>
#include <linux/version.h>
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少.Johnsonm说所有的驱动程序的开头都要包含<linux/config.h>,但我看倒是未必.
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如
open,read,write,close...., 注意,不是fopen,
fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:
struct file_operations
{
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned
long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域.
相当简单,不是吗? 下面就开始写子程序.
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
unsigned int test_major = 0;
static int read_test(struct inode *node, struct file *file, char *buf, int
count)
{
int left;
if (verify_area(VERIFY_WRITE, buf, count) == -EFAULT )
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1, buf, 1);
buf++;
}
return count;
}
这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的缓冲区全部写1.buf
是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用。
这就用到函数verify_area.
static int write_tibet(struct inode *inode, struct file *file, const char
*buf, int count)
{
return count;
}
static int open_tibet(struct inode *inode, struct file *file )
{
MOD_INC_USE_COUNT;
return 0;
}
static void release_tibet(struct inode *inode, struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空操作.实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。
struct file_operations test_fops =
{
NULL,
read_test,
write_test,
NULL, /* test_readdir */
NULL,
NULL, /* test_ioctl */
NULL, /* test_mmap */
open_test,
release_test, NULL, /* test_fsync */
NULL, /* test_fasync */
/* nothing more, fill with NULLs */
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。
int init_module(void)
{
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0)
{
printk(KERN_INFO "test: can't get major number\n");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_module
函数被调用。在这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。 void
cleanup_module(void)
{
unregister_chrdev(test_major, "test");
}
在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld -r file1.o file2.o -o modulename.
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod -f test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。
要卸载的话,运行
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices | awk "\\$2==\"test\" {print \\$1}"
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test", O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev, buf, 10);
for (i = 0; i < 10;i++)
printf("%d\n", buf[i]);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O
port等问题。这些才是真正的难点。请看下节,实际情况的处理。
三、设备驱动程序中的一些具体问题
1. I/O Port.
和硬件打交道离不开I/O
Port,老的ISA设备经常是占用实际的I/O端口,在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:io端口的基地址,
参数2:io端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,(之所以不说是dos操作系统是因为我认为DOS根本就不是一个操作系统,它实在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk提供了一个vmm
调用
_MapLinearToPhys,用以把线性地址转化为物理地址。但在Linux中是怎样做的呢?
2.内存操作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages.
请注意,kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理地址的转换是由386cpu硬件完成的,那样汇编指令的操作数应该是线性地址,驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法不知对不对,还请高手指教。
言归正传,要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。kmalloc用法参见khg.
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap之后就可以为DMA所用了。
请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。
3.中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq , void(*handle)(int, void *, struct
pt_regs *), unsigned int long flags, const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断, 0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的中断。
4.一些常见的问题。
对硬件操作,有时时序很重要。但是如果用C语言写一些低级的硬件操作的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现出来。
驱动函数中变量的高效分配
1. 频繁地为参数分配栈内存是一种低效的做法;
2.
使用动态内存好象可以解决上面的问题,但它没有考虑到频繁地使用malloc-free会产生大量的内存碎片。在嵌入式系统环境中,一般内存大小有限,所以这种做法最终会导致分配失败。
3.
对于处理大流量数据问题,一种比较常用的高效方法就是在函数内部使用静态数组(全局静态数组在这个应用中不建议使用,因为全局变量会增加函数间的耦合度)。
关键字static有以下明显的作用:
(1).static变量分配到静态内存中,这一点和全局非静态变量相同。
(2).在函数体,static变量只要不进行修改操作,在被调用过程中其值将保持不变。
(3).在模块内,全局static变量可以被模块内所有函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量,具有私有特点。
(4).在模块内,一个static函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用,也具有私有特点。
But,千万要小心"代码可重入(reentrance)"可能产生的问题!
使用静态数组的确不用频繁地分配动态或栈内存,但同时引入了代码不可重入的问题。因为函数内的static变量分配在静态内存区,供所有对象共用。在多任务系统中,如果有一个以上的任务同时访问该内存,很可能会出现问题。因此必须要用其它手段来消除这个不可重入问题。使用信号量semaphore是一个很好解决不可重入问题的方法。
在编写设备驱动程序时,也有此问题。驱动功能主要有两个:操作底层硬件和提供上层接口。不推荐直接在驱动硬件层使用semaphore进行代码可重入保护,因为semaphore是操作系统提供的。推荐将驱动分层:硬件相关层和硬件无关层。硬件相关层主要处理硬件设备初始化及具体操作;硬件无关层来处理防止函数重入/封装接口/同异步调用/调用排队等操作。
2008年10月17日星期五
人情世故
1、即使不是对大人物,我们也要用请教的态度口吻而不是傲慢的姿态与他们说话,因为人不可貌相,很多实用的良师益友往往来自不起眼的生活与工作中。
2、在吃饭的场合做主动点菜者,不适合请主人与主宾点菜,因为那不是尊贵者通常做的事情,但是请注意询问他们的喜好,而不是只管点自己爱吃的东西。但是这需要平时研究菜单,积累点菜的经验。上桌的时候要尊重主人的安排,不要贸然先行入席。
3、经常找到朋友、伙伴与同事(甚至小孩子)值得肯定的方面,注意哦,即使老板也需要被你肯定,但是对上者的赞扬应尽量在私下场合,而对于一般朋友与同事则应公开赞扬。
4、在受到别人对自己的相貌、事情、人品赞扬时,不要表现出理所当然的样子,也不要假意否认,合适的方式是表示感谢,尤其感谢朋友的肯定与支持。
5、学会使用便条,包括借条、领条、请假条、申请信,如果你很主动地使用这些便条会让其他人感到你很规范,而且如果你懂得请其他人这样做,你未来就能更好与他们有凭有据地打交道。会写便条会让别人刮目相看。
6、即使你不是服务人员,在朋友或者同事有客人来的时候主动倒水,会让朋友与同事很有面子,也会让客人觉得你的朋友与同事很有威望。这会让你的朋友与同事特别感谢你的姿态。
7、虽然你觉得你是新手或者地位比较低的人,但是你要勇于不耻下问,也要做到主动询问别人的需要,而不要等领导或者资深的同事对你表现出亲和,因为他们这样表现往往需要特别的努力。
8、记得在别人不在座位的时候很热情地帮助接听与记录电话、接受信件、传递信息,对团队的同事与同学,提醒他们一些你知道的重要日程。
9、在征询了别人意见之后才进入别人的房间,阅览别人的书架或者室内物品,在经别人同意的情况下才用别人的电脑,坐在别人的私人座位上,但通常不去翻动别人的笔记本。
10、出席别人的活动需要有邀请,如果不能出席应提前通知,迟到的话要在适当的时间点上通知主人,到了以后要解释,带未经邀请的朋友要事前通知主人。
11、不适合向别人索要礼物,收到别人的礼品不管是不是喜欢要表示感谢,因为送礼者会很在乎你的反应,不适合把一处的礼物转送给另一处,尤其还保留原来送礼者的符号与痕迹就显得很没有礼貌了。
12、在有多个出席者的场合,主动介绍自己的朋友给其他人,或者主动在你认识的朋友之间穿针引线。那些被缓解了陌生感的朋友会特别感谢你。
13、 有不同地位的朋友在的场合,都要保持微笑,体贴地招呼下那些内向的、不为人注意的、可能有点自卑感的朋友,在社交中对弱势者的帮助会得到别人特别的感激。 对于社会地位较低者,尤其在有你不能适应的生活条件与生活习惯时,要克制自己所想表现出的不适感与负面表情,尽量主动先打招呼。
14、有好东西吃的时候不要吃独食,主动地告诉他们你知道的好消息,在有好事情的时候能想到别人会让别人觉得你把他们当好朋友。
15、 有人做错了事,不要用情绪性的方式加以批评,尤其要注意就事论事,避免评价了别人的人格、个性与家庭教养。批评时能提出解决方案就更有建设性。批评时应不 忘肯定别人的长处。如批评时能较幽默,往往负面效果就更少。被批评或者遇到尴尬的时候能幽默自嘲,也能提高交流的建设性。
16、好汉不吃眼前亏。但是在另外很多时候,如果问题争执不下,也不要继续火上浇油,冷静下来,多收集一些数据材料与想得更明白点再说。
17、在你不能有充分把握的时候,用"争取"与"尽量"这样的口吻回答别人的邀约,承诺了就要最大限度去履行。诺言是指100%做到的事情。如果你有了三次甚至更多的对同一个人没有履行诺言的记录,那个人通常就不再认真地对待你的约定,这就是所谓的信用问题。
18、我们要尽量不谈回报地先为别人做点什么,这样就赢得了在心理上比别人优越的债权感。一个人的社会地位是别人对他负有的社会债务感的总和。
Linux连接库的生成
libhello.a
为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如:
libhello.so.1.0,由于程序连接默认以。so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。
ln -s libhello.so.1.0 libhello.so.1
ln -s libhello.so.1 libhello.so
使用库
当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记'指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。
现在假设有一个叫hello的程序开发包,它提供一个静态库libhello.a
一个动态库libhello.so,一个头文件hello.h,头文件中提供sayhello()这个函数
/* hello.h */
void sayhello();
另外还有一些说明文档。这一个典型的程序开发包结构
1.与动态库连接
linux默认的就是与动态库连接,下面这段程序testlib.c使用hello库中的sayhello()函数
/*testlib.c*/
#include
#include
int main()
{
sayhello();
return 0;
}
使用如下命令进行编译
$gcc -c testlib.c -o testlib.o
用如下命令连接:
$gcc testlib.o -lhello -o testlib
在连接时要注意,假设libhello.o
和libhello.a都在缺省的库搜索路径下/usr/lib下,如果在其它位置要加上-L参数
2. 与静态库连接麻烦一些,主要是参数问题。还是上面的例子:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello
注:这个特别的"-WI,-Bstatic"参数,实际上是传给了连接器ld.
指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数了。
如果要和多个库相连接,而每个库的连接方式不一样,比如上面的程序既要和libhello进行静态连接,又要和libbye进行动态连接,其命令应为:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic –lbye
3.动态库的路径问题
为了让执行程序顺利找到动态库,有三种方法:
(1)把库拷贝到/usr/lib和/lib目录下。
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。例如动态库libhello.so在/home/ting/lib目录下,以bash为例,使用命令:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
(3)
修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见。
4.查看库中的符号
有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种,一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;一种是库中定义的函数,用T表示,这是最常见的;另外一种是所谓的"弱态"符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。例如,假设开发者希望知道上文提到的hello库中是否定义了
printf():
$nm libhello.so |grep printf
U printf
U表示符号printf被引用,但是并没有在函数内定义,由此可以推断,要正常使用hello库,必须有其它库支持,再使用ldd命令查看hello依赖于哪些库:
$ldd hello
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on
生成库
第一步要把源代码编绎成目标代码。以下面的代码为例,生成上面用到的hello库:
/* hello.c */
#include
void sayhello()
{
printf("hello,world\n");
}
用gcc编绎该文件,在编绎时可以使用任何全法的编绎参数,例如-g加入调试代码等:
gcc -c hello.c -o hello.o
(1)。连接成静态库
连接成静态库使用ar命令,其实ar是archive的意思
$ar cqs libhello.a hello.o
(2)。连接成动态库
生成动态库用gcc来完成,由于可能存在多个版本,因此通常指定版本号:
$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o
另外再建立两个符号连接:
$ln -s libhello.so.1.0 libhello.so.1
$ln -s libhello.so.1 libhello.so
这样一个libhello的动态连接库就生成了。最重要的是传gcc -shared
参数使其生成是动态库而不是普通执行程序。
-Wl
表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。实际上,每一个库都有一个soname,当连接器发现它正在查找的程序库中有这样一个名称,连接器便会将soname嵌入连结中的二进制文件内,而不是它正在运行的实际文件名,在程序执行期间,程序会查找拥有
soname名字的文件,而不是库的文件名,换句话说,soname是库的区分标志。
这样做的目的主要是允许系统中多个版本的库文件共存,习惯上在命名库文件的时候通常与soname相同
libxxxx.so.major.minor
其中,xxxx是库的名字,major是主版本号,minor 是次版本号。
2008年10月15日星期三
Foxit Reader for Desktop Linux
在linux下的几个浏览器,如evince,xpdf虽然也小巧,但是却经常有各种显示中文的问题。对字体的选择也不尽如人意;官方的acroread
却又太臃肿,打开一个文件要很长的时间,速度更是接近无法忍受~
下面介绍在linux下的原生foxit
首先下载foxit reader for Desktop Linux,并解压,放入自己的bin目录。 代码:
cd
wget -c http://www.foxitsoftware.com/foxitreader/FoxitReaderLinux.tar.gz
tar zxvf FoxitReaderLinux.tar.gz
mv ReaderLinux bin/
直接执行文件的结果肯定是段错误。从dapper时就是如此,之后的edgy,feisty,gutsy都是段错误。
这主要是由于foxit编译的环境并不是ubuntu/debian,并且年代也比较久了(2005年),编译时的参数都不一致,所以~~唉~~难道就没有办法了吗?呵呵,不是的,很幸运的,foxit
reader是采用的动态连接,所以我们可以创造一个干净的环境。
到下面的站点下载loki compact libraries.
wget -c http://www.swanson.ukfsn.org/loki/loki_compat_libs-1.2.tar.bz2
将loki-compat-library下载解压到指定位置/usr/local/games/Loki_Compat。
编辑~/.bashrc
添加 代码:
alias foxitreader='LD_LIBRARY_PATH=/lib:/usr/local/games/Loki_Compat
ReaderLinux'
foxit
reader就可以正常使用了。foxit的渲染速度可真是没的说的,而且对中文的处理也不用多麻烦,呵呵,毕竟就一个可执行文件,也不需要什么配置~~
总之,由于它的开发基础是foxit
sdk,完全独立,所以,我觉得对pdf,也应该有三套马车~~
官方的acroread
轻灵的foxit
Free的evince/xpdf

