??xml version="1.0" encoding="utf-8" standalone="yes"?>久久久久久A亚洲欧洲AV冫,久久精品国产清自在天天线,久久久久久久综合综合狠狠http://www.shnenglu.com/deane/category/13181.html q逐梦惻I怸停步......zh-cnFri, 17 Feb 2012 15:57:52 GMTFri, 17 Feb 2012 15:57:52 GMT60Linux 信号 (?http://www.shnenglu.com/deane/articles/165309.html李阳李阳Fri, 10 Feb 2012 09:49:00 GMThttp://www.shnenglu.com/deane/articles/165309.htmlhttp://www.shnenglu.com/deane/comments/165309.htmlhttp://www.shnenglu.com/deane/articles/165309.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/165309.htmlhttp://www.shnenglu.com/deane/services/trackbacks/165309.html
信号与进E是分不开的,而把信号与进E的W记分开来写Q是因ؓ(f)我觉得这个信号太难搞懂了Q特别是APUE信号q一章还把信L(fng)合历史来介绍弄的我云里雾里?nbsp;

信号本质上是在Y件层ơ上对中断机制的一U模拟,他有几种产生方式和处理方?APUE有介l?Q下面带着疑惑从几个角度对信号q行介绍 


Q一Q?nbsp;   站在q程的角?/strong>

q程发现和接受信?/strong>

我们知道Q信h异步的,一个进E不可能{待信号的到来,也不知道信号?x)到来,那么Q进E是如何发现和接受信号呢Q实际上Q信L(fng)接收不是q戯E来完成的,而是由内总理。当一个进EP2向另一个进EP1发送信号后Q内核接受到信号Qƈ其攑֜P1的信号队列当中。当P(yng)1再次陷入内核态时Q会(x)(g)查信号队列,q根据相应的信号调取相应的信号处理函?/p>

Task _struct 是进E控制块(PCB),详见          http://oss.org.cn/kernel-book/ch04/4.3.htm

 

信号(g)和响应时机

刚才我们_(d)当P(yng)1再次陷入内核Ӟ?x)检查信号队列。那么,P1什么时候会(x)再次陷入内核呢?陷入内核后在什么时Z(x)(g)信号队列呢Q?/p>

1.      当前q程׃pȝ调用、中断或异常而进入系l空间以后,从系l空间返回到用户I间的前夕?/p>

2.      当前q程在内怸q入睡眠以后刚被唤醒的时候(必定是在pȝ调用中)(j)Q或者由于不可忽略信L(fng)存在而提前返回到用户I间


q入信号处理函数

 

 


对于sigprocmask ?x)进入内核空间、pause需要从q入睡眠q两者都W合(g)处理信号函数的条gQ所以存在忽略信L(fng)情况Q而APUE讲sigsuspend的之后真是晦涩难懂,其实他主要做的工作就是等待一个中断然后执行相应的handle处理

所以我感觉例子中的

sigsuspend(&zeromask)Q?/p>

sigprocmask(SIG_SETMASK, &oldmask,NULL)Q?/p>

是不是可以直接替换ؓ(f)

Sigsuspend(&oldmask)

因ؓ(f)试情况难以出现Q这里只是个人理解ƈ未得到验?/p>

 

 

Q二Q?nbsp; 站在信号自n的角?/strong>

 

信号生命周期:

 对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完?来说Q可以分Z个重要的阶段Q这三个阶段由四个重要事件来ȝQ?/p>

1.信号诞生Q?/p>

2.信号在进E中注册完毕Q?/p>

3.信号在进E中的注销完毕Q?/p>

4.信号处理函数执行完毕?/p>

盔R两个事g的时间间隔构成信L(fng)命周期的一个阶Dc(din)?/p>

 

    详细描述各个生命周期

1.     信号"诞生"?/strong>

信号的诞生指的是触发信号的事件发生(如检到g异常、定时器时以及(qing)调用信号发送函数kill()或sigqueue(){)(j)?nbsp;

2.      信号在目标进E中"注册"?/strong>

q里注册定义不是由signal或者sigaction实现的,而是说信号发生之后内怸自动对信L(fng)注册保存?/p>

q程的task_structl构中有关于本进E中未决信号的数据成员:(x) 

struct sigpending pending;

struct sigpending

{

    struct sigqueue *head, **tail;

    sigset_t signal;

};

W一、第二个成员分别指向一个sigqueuecd的结构链Q称之ؓ(f)"未决信号信息?Q的首尾Q第三个成员是进E中所有未决信号集Q信息链中的每个sigqueuel构体刻M个特定信h携带的信息,q指向下一个sigqueuel构:

struct sigqueue

{

    struct sigqueue *next;

    siginfo_t info;

};

    信号在进E中注册指的是信号值加入到q程的未决信号集中(sigpendingl构的第二个成员sigset_t signalQ,q且信号所携带的信息被保留到未决信号信息链的某个sigqueuel构中。只要信号在q程的未决信号集中,表明q程已经知道q些信号的存在,但还没来得及(qing)处理Q或者该信号被进E阻塞?/p>

注:(x) 

    当一个实时信号发送给一个进E时Q不该信号是否已经在进E中注册Q都?x)被再注册一ơ,因此Q信号不?x)丢失,因此Q实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进E的未决信号信息链中占有多个sigqueuel构Q进E每收到一个实时信P都会(x)为它分配一个结构来登记该信号信息,q把该结构添加在未决信号铑ְQ即所有诞生的实时信号都会(x)在目标进E中注册Q;

当一个非实时信号发送给一个进E时Q如果该信号已经在进E中注册Q则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信?。这意味着同一个非实时信号在进E的未决信号信息链中Q至多占有一个sigqueuel构。一个非实时信号诞生后,Q?Q、如果发现相同的信号已经在目标结构中注册Q则不再注册Q对于进E来_(d)相当于不知道本次信号发生Q信号丢失;Q?Q、如果进E的未决信号中没有相同信P则在q程中注册自己。在APUE的不可靠信号章节中需要每ơ重新声明sinal_hanle函数Q这个是说的以前Unixpȝ的处理,现在可靠不可靠就是上面所说的实时与注册次数的区别?/p>


3.信号在进E中的注销?/strong>

在目标进E执行过E中Q会(x)(g)是否有信号{待处理Q每ơ从pȝI间q回到用L(fng)间时都做q样的检查)(j)。(“sigprocmaskq回前,也至会(x)其中一个未决且未阻塞的信号递送给q程”Q如果存在未决信L(fng)待处理且该信h有被q程dQ则在运行相应的信号处理函数前,q程?x)把信号在未决信号链中占有的l构卸掉。是否将信号从进E未决信号集中删除对于实时与非实时信h不同的。对于非实时信号来说Q由于在未决信号信息链中最多只占用一个sigqueuel构Q因此该l构被释攑֐Q应该把信号在进E未决信号集中删除(信号注销完毕Q;而对于实时信h_(d)可能在未决信号信息链中占用多个sigqueuel构Q因此应该针对占用gqueuel构的数目区别对待:(x)如果只占用一个sigqueuel构Q进E只收到该信号一ơ)(j)Q则应该把信号在q程的未决信号集中删除(信号注销完毕Q。否则,不在q程的未决信号集中删除该信号Q信h销完毕Q。进E在执行信号相应处理函数之前Q首先要把信号在q程中注销?/p>


4.信号生命l止?/strong>

q程注销信号后,立即执行相应的信号处理函敎ͼ执行完毕后,信号的本ơ发送对q程的媄(jing)响彻底结束?nbsp;


注:(x) 

1Q信h册与否,与发送信L(fng)函数Q如kill()或sigqueue(){)(j)以及(qing)信号安装函数Qsignal()?qing)sigaction()Q无养I只与信号值有养I信号值小于SIGRTMIN的信h多只注册一ơ,信号值在SIGRTMIN?qing)SIGRTMAX之间的信P只要被进E接收到p注册Q?/p>

2Q在信号被注销到相应的信号处理函数执行完毕q段旉内,如果q程又收到同一信号多次Q则对实时信h_(d)每一ơ都?x)在q程中注册;而对于非实时信号来说Q无论收到多次信号Q都?x)视为只收到一个信P只在q程中注册一ơ?/p>

 

Q三Q?nbsp; q程和信号两者的角度来看

实际执行信号的处理动作称Z号递达QDeliveryQ,信号从生到递达之间的状态,UCؓ(f)信号未决QPendingQ。进E可以选择dQBlockQ某个信受被d的信号生时保持在未决状态,直到q程解除Ҏ(gu)信号的阻塞,才执行递达的动作。注意,d和忽略是不同的,只要信号被阻塞就不会(x)递达Q而忽略是在递达之后可选的一U处理动作。信号在内核中的表示可以看作是这L(fng)Q?/p>

信号在内怸的表C示意图

 每个信号都有两个标志位分别表C阻塞和未决Q还有一个函数指针表C处理动作。信号生时Q内核在q程控制块中讄该信L(fng)未决标志Q直C号递达才清除该标志。在上图的例子中Q?/p>

1.      SIGHUP信号未阻塞也未生过Q当它递达时执行默认处理动作?/p>

2.      SIGINT信号产生q,但正在被dQ所以暂时不能递达。虽然它的处理动作是忽略Q但在没有解除阻塞之前不能忽略这个信P因ؓ(f)q程仍有Z(x)改变处理动作之后再解除阻塞?/p>

3.      SIGQUIT信号未生过Q一旦生SIGQUIT信号被dQ它的处理动作是用户自定义函数sighandler?/p>

 




李阳 2012-02-10 17:49 发表评论
]]>
【{】IO - 同步Q异步,dQ非d Q亡补牢篇Q?http://www.shnenglu.com/deane/articles/165219.html李阳李阳Thu, 09 Feb 2012 05:52:00 GMThttp://www.shnenglu.com/deane/articles/165219.htmlhttp://www.shnenglu.com/deane/comments/165219.htmlhttp://www.shnenglu.com/deane/articles/165219.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/165219.htmlhttp://www.shnenglu.com/deane/services/trackbacks/165219.html本文讨论的背景是Linux环境下的network IO?br />本文最重要的参考文献是Richard Stevens?#8220;UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”Q?.2?#8220;I/O Models ”QStevens在这节中详细说明了各UIO的特点和区别Q如果英文够好的话,推荐直接阅读。Stevens的文风是有名的深入浅出,所以不用担心看不懂。本文中的流E图也是截取自参考文献?

Stevens在文章中一共比较了五种IO ModelQ?br />    blocking IO
    nonblocking IO
    IO multiplexing
    signal driven IO
    asynchronous IO
׃signal driven IO在实际中q不常用Q所以我q只提及(qing)剩下的四UIO Model?br />
再说一下IO发生时涉?qing)的对象和步骤?br />对于一个network IO (q里我们以read举例)Q它?x)涉及(qing)到两个pȝ对象Q一个是调用q个IO的process (or thread)Q另一个就是系l内?kernel)。当一个read操作发生Ӟ它会(x)l历两个阶段Q?br />1 {待数据准备 (Waiting for the data to be ready)
2 数据从内核拯到进E中 (Copying the data from the kernel to the process)
Cq两点很重要Q因些IO Model的区别就是在两个阶段上各有不同的情况?/p>

blocking IO
在linux中,默认情况下所有的socket都是blockingQ一个典型的L作流E大概是q样Q?/p>


当用戯E调用了recvfromq个pȝ调用Qkernel开始了IO的第一个阶D:(x)准备数据。对于network io来说Q很多时候数据在一开始还没有到达Q比如,q没有收C个完整的UDP包)(j)Q这个时候kernelp{待_的数据到来。而在用户q程q边Q整个进E会(x)被阻塞。当kernel一直等到数据准备好了,它就?x)将数据从kernel中拷贝到用户内存Q然后kernelq回l果Q用戯E才解除block的状态,重新q行h?br />所以,blocking IO的特点就是在IO执行的两个阶D都被block了?/p>

non-blocking IO

linux下,可以通过讄socket使其变ؓ(f)non-blocking。当对一个non-blocking socket执行L作时Q流E是q个样子Q?/p>


从图中可以看出,当用戯E发出read操作Ӟ如果kernel中的数据q没有准备好Q那么它q不?x)block用户q程Q而是立刻q回一个error。从用户q程角度?Q它发v一个read操作后,q不需要等待,而是马上得C一个结果。用戯E判断结果是一个errorӞ它就知道数据q没有准备好Q于是它可以再次发送read操作。一旦kernel中的数据准备好了Qƈ且又再次收到了用戯E的system callQ那么它马上将数据拯C用户内存Q然后返回?br />所以,用户q程其实是需要不断的d询问kernel数据好了没有?/p>

IO multiplexing

IO multiplexingq个词可能有炚w生,但是如果我说selectQepollQ大概就都能明白了。有些地方也U这UIO方式为event driven IO。我们都知道Qselect/epoll的好处就在于单个process可以同时处理多个网l连接的IO。它的基本原理就是select/epollq个function?x)不断的轮询所负责的所有socketQ当某个socket有数据到达了Q就通知用户q程。它的流E如图:(x)


当用戯E调用了selectQ那么整个进E会(x)被blockQ而同Ӟkernel?#8220;监视”所有select负责的socketQ当M一个socket中的数据准备好了Qselect׃(x)q回。这个时候用戯E再调用read操作Q将数据从kernel拯到用戯E?br />q个囑֒blocking IO的图其实q没有太大的不同Q事实上Q还更差一些。因里需要用两个system call (select ?recvfrom)Q而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话Q用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好Q可能gq还更大。select/epoll的优势ƈ不是对于单个q接能处理得更快Q而是在于能处理更多的q接。)(j)
在IO multiplexing Model中,实际中,对于每一个socketQ一般都讄成ؓ(f)non-blockingQ但是,如上图所C,整个用户的process其实是一直被block的。只不过process是被selectq个函数blockQ而不是被socket IOlblock?/p>

Asynchronous I/O

linux下的asynchronous IO其实用得很少。先看一下它的流E:(x)


用户q程发vread操作之后Q立d可以开始去做其它的事。而另一斚wQ从kernel的角度,当它受到一个asynchronous read之后Q首先它?x)立刻返回,所以不?x)对用户q程产生Mblock。然后,kernel?x)等待数据准备完成,然后数据拷贝到用户内存Q当q一切都完成之后Qkernel?x)给用户q程发送一个signalQ告诉它r(ji)ead操作完成了?/p>

到目前ؓ(f)止,已经四个IO Model都介l完了。现在回q头来回{最初的那几个问题:(x)blocking和non-blocking的区别在哪,synchronous IO和asynchronous IO的区别在哪?br />先回{最单的q个Qblocking vs non-blocking。前面的介绍中其实已l很明确的说明了q两者的区别。调用blocking IO?x)一直block住对应的q程直到操作完成Q而non-blocking IO在kernelq准备数据的情况下会(x)立刻q回?/p>

在说明synchronous IO和asynchronous IO的区别之前,需要先l出两者的定义。Stevensl出的定义(其实是POSIX的定义)(j)是这样子的:(x)
    A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
    An asynchronous I/O operation does not cause the requesting process to be blocked;

两者的区别在于synchronous IO?#8221;IO operation”的时候会(x)processd。按照这个定义,之前所q的blocking IOQnon-blocking IOQIO multiplexing都属于synchronous IO。有人可能会(x)_(d)non-blocking IOq没有被block啊。这里有个非?#8220;狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作Q就是例子中的recvfromq个system call。non-blocking IO在执行recvfromq个system call的时候,如果kernel的数据没有准备好Q这时候不?x)blockq程。但是,当kernel中数据准备好的时候,recvfrom?x)将数据从kernel拯到用户内存中Q这个时候进E是被block了,在这D|间内Q进E是被block的。而asynchronous IO则不一P当进E发起IO 操作之后Q就直接q回再也不理睬了Q直到kernel发送一个信P告诉q程说IO完成。在q整个过E中Q进E完全没有被block?/p>

各个IO Model的比较如图所C:(x)


l过上面的介l,?x)发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然q程大部分时间都不会(x)被blockQ但是它仍然要求q程M动的checkQƈ且当数据准备完成以后Q也需要进E主动的再次调用recvfrom来将数据拯到用户内存。而asynchronous IO则完全不同。它?yu)像是用戯E将整个IO操作交给了他人(kernelQ完成,然后他h做完后发信号通知。在此期_(d)用户q程不需要去(g)查IO操作的状态,也不需要主动的L贝数据?br />
最后,再D几个不是很恰当的例子来说明这四个IO Model:
有AQBQCQD四个人在钓鱼Q?br />A用的是最老式的鱼竿,所以呢Q得一直守着Q等到鱼上钩了再拉杆Q?br />B的鱼竿有个功能,能够昄是否有鱼上钩Q所以呢QB和旁边的MM聊天Q隔?x)再看看有没有鱼上钩Q有的话p速拉杆;
C用的鱼竿和B差不多,但他想了一个好办法Q就是同时放好几栚w竿,然后守在旁边Q一旦有昄说鱼上钩了,它就对应的鱼竿拉v来;
D是个有钱人,q脆雇了一个h帮他钓鱼Q一旦那个h把鱼钓上来了Q就lD发个短信?/span>

原帖地址Q?a>http://blog.csdn.net/historyasamirror/archive/2010/07/31/5778378.aspx



李阳 2012-02-09 13:52 发表评论
]]>
深入了解epoll (?http://www.shnenglu.com/deane/articles/165218.html李阳李阳Thu, 09 Feb 2012 05:48:00 GMThttp://www.shnenglu.com/deane/articles/165218.htmlhttp://www.shnenglu.com/deane/comments/165218.htmlhttp://www.shnenglu.com/deane/articles/165218.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/165218.htmlhttp://www.shnenglu.com/deane/services/trackbacks/165218.html

一?介绍
Epoll 是一U高效的理socket的模型,相对于select和poll来说h更高的效率和易用性。传l的select以及(qing)poll的效率会(x)因ؓ(f) socket数量的线形递增而导致呈二次乃至三次方的下降Q而epoll的性能不会(x)随socket数量增加而下降。标准的linux-2.4.20内核不支持epollQ需要打patch。本文主要从linux-2.4.32和linux-2.6.10两个内核版本介绍epoll?br />二?Epoll的?br />epoll用到的所有函数都是在头文件sys/epoll.h中声明的Q下面简要说明所用到的数据结构和函数Q?br />所用到的数据结?br />typedef union epoll_data {
                void ptr;
                int fd;
                __uint32_t u32;
                __uint64_t u64;
      } epoll_data_t;

      struct epoll_event {
                __uint32_t events;    / Epoll events /
                epoll_data_t data;    / User data variable /
      };
l构体epoll_event 被用于注册所感兴的事g和回传所发生待处理的事gQ其中epoll_data 联合体用来保存触发事件的某个文g描述W相关的数据Q例如一个clientq接到服务器Q服务器通过调用accept函数可以得到于这个client对应的socket文g描述W,可以把这文g描述W赋lepoll_data的fd字段以便后面的读写操作在q个文g描述W上q行。epoll_event l构体的events字段是表C感兴趣的事件和被触发的事g可能的取gؓ(f)QEPOLLIN Q表C对应的文g描述W可以读Q?br />EPOLLOUTQ表C对应的文g描述W可以写Q?br />EPOLLPRIQ表C对应的文g描述W有紧急的数据可读
EPOLLERRQ表C对应的文g描述W发生错误;
EPOLLHUPQ表C对应的文g描述W被挂断Q?br />EPOLLETQ表C对应的文g描述W设定ؓ(f)edge模式Q?br />所用到的函敎ͼ(x)
1、epoll_create函数
    函数声明Qint epoll_create(int size)
    该函数生成一个epoll专用的文件描q符Q其中的参数是指定生成描q符的最大范围。在linux-2.4.32内核中根据size大小初始化哈希表的大,在linux2.6.10内核中该参数无用Q用红黑树(wi)理所有的文g描述W,而不是hash?br />2、epoll_ctl函数
    函数声明Qint epoll_ctl(int epfd, int op, int fd, struct epoll_event event)
    该函数用于控制某个文件描q符上的事gQ可以注册事Ӟ修改事gQ删除事件?br />    参数QepfdQ由 epoll_create 生成的epoll专用的文件描q符Q?br />                opQ要q行的操作例如注册事Ӟ可能的取?br />EPOLL_CTL_ADD 注册?br />EPOLL_CTL_MOD 修改?br />EPOLL_CTL_DEL 删除
fdQ关联的文g描述W;
eventQ指向epoll_event的指针;
如果调用成功q回0,不成功返?1
3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event   events,int maxevents,int timeout)
该函数用于轮询I/O事g的发生;
参数Q?br />epfd:由epoll_create 生成的epoll专用的文件描q符Q?br />epoll_event:用于回传代处理事件的数组Q?br />maxevents:每次能处理的事g敎ͼ
timeout:{待I/O事g发生的超时|msQ;-1怸时Q直到有事g产生才触发,0立即q回?br />q回发生事g数?1有错误?br />
举一个简单的例子Q?br />
C/C++ codeint main()
{
    //声明epoll_eventl构体的变量,ev用于注册事g,数组用于回传要处理的事g
    struct epoll_event ev,events[20];

    epfd=epoll_create(10000); //创徏epoll句柄
   
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    //把socket讄为非d方式
    setnonblocking(listenfd);
   
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = INADDR_ANY;
    serveraddr.sin_port=htons(SERV_PORT);
    bind(listenfd,(struct sockaddr )&serveraddr, sizeof(serveraddr));
    listen(listenfd, 255);

    //讄与要处理的事件相关的文g描述W?br />    ev.data.fd=listenfd;
    //讄要处理的事gcd
    ev.events=EPOLLIN;
    //注册epoll事g
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

    for ( ; ; )
    {
      //{待epoll事g的发?br />      nfds=epoll_wait(epfd,events,20,1000);
      //处理所发生的所有事?br />      for(i=0;i<nfds;++i)
      {
         if(events
.data.fd==listenfd)
         {
                connfd = accept(listenfd,(struct sockaddr )&clientaddr, &clilen);
                if(connfd<0)
                {
                  perror("connfd<0");
                }
                setnonblocking(connfd);
                //讄用于L作的文g描述W?br />                ev.data.fd=connfd;
                //讄用于注测的读操作事g
                ev.events=EPOLLIN|EPOLLET;
                //注册event
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
         }
         else if(events
.events&EPOLLIN)
         {
                read_socket(events
.data.fd);
                ev.data.fd=events
.data.fd;
                ev.events=EPOLLIN|EPOLLOUT|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
         }
         else if(events
.events&EPOLLOUT)
         {
                write_socket(events
.data.fd);
                ev.data.fd=events
.data.fd;
                ev.events=EPOLLIN|EPOLLET; //ET模式
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
         }
         else
         {
                perror("other event");
         }
      }
    }
}


Epoll的ET模式与LT模式
ETQEdge TriggeredQ与LTQLevel TriggeredQ的主要区别可以从下面的例子看出
egQ?br />1Q?标示道读者的文g句柄注册到epoll中;
2Q?道写者向道中写?KB的数据;
3Q?调用epoll_wait可以获得道读者ؓ(f)已就l的文g句柄Q?br />4Q?道读者读?KB的数?br />5Q?一ơepoll_wait调用完成
如果是ET模式Q管道中剩余?KB被挂P再次调用epoll_waitQ得不到道读者的文g句柄Q除非有新的数据写入道。如果是LT模式Q只要管道中有数据可读,每次调用epoll_wait都会(x)触发?br />
另一点区别就是设为ET模式的文件句柄必L非阻塞的?br />三?Epoll的实?br />Epoll 的源文g?usr/src/linux/fs/eventpoll.cQ在module_init时注册一个文件系l?eventpoll_fs_typeQ对该文件系l提供两U操作poll和releaseQ所以epoll_createq回的文件句柄可以被poll?select或者被其它epoll epoll_wait。对epoll的操作主要通过三个pȝ调用实现Q?br />1Q?sys_epoll_create
2Q?sys_epoll_ctl
3Q?sys_epoll_wait
下面l合源码讲述q三个系l调用?br />1.1 long sys_epoll_create (int size)
该系l调用主要分配文件句柄、inode以及(qing)filel构。在linux-2.4.32内核中,使用hash保存所有注册到该epoll的文件句柄,在该pȝ调用中根据size大小分配hash的大。具体ؓ(f)不小于sizeQ但于2size?的某ơ方。最ؓ(f)2?ơ方Q?12Q,最大ؓ(f)2?7ơ方Q?28 x 1024Q。在linux-2.6.10内核中,使用U黑?wi)保存所有注册到该epoll的文件句柄,size参数未用?br />1.2 long sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event event)
1Q?注册句柄 op = EPOLL_CTL_ADD
注册q程主要包括Q?br />AQ将fd插入到hashQ或rbtreeQ中Q如果原来已l存在返?EEXISTQ?br />BQ给fd注册一个回调函敎ͼ该函C(x)在fd有事件时调用Q在该函Cfd加入到epoll的就l队列中?br />CQ检查fd当前是否已经有期望的事g产生。如果有Q将其加入到epoll的就l队列中Q唤醒epoll_wait?br />
2Q?修改事g op = EPOLL_CTL_MOD
修改事g只是新的事件替换旧的事Ӟ然后(g)查fd是否有期望的事g。如果有Q将其加入到epoll的就l队列中Q唤醒epoll_wait?br />
3Q?删除句柄 op = EPOLL_CTL_DEL
fd从hashQrbtreeQ中清除?br />1.3 long sys_epoll_wait(int epfd, struct epoll_event events, int maxevents,int timeout)
如果epoll的就l队列ؓ(f)I,q且timeout?Q挂起当前进E,引vCPU调度?br />如果epoll的就l队列不I,遍历qA队列。对队列中的每一个节点,获取该文件已触发的事Ӟ判断其中是否有我们期待的事gQ如果有Q将其对应的epoll_eventl构copy到用户events?br />
revents = epi->file->f_op->poll(epi->file, NULL);
epi->revents = revents & epi->event.events;
if (epi->revents) {
……
copy_to_user;
……
}
需要注意的是,在LT模式下,把符合条件的事gcopy到用L(fng)间后Q还?x)把对应的文仉新挂接到qA队列。所以在LT模式下,如果一ơepoll_wait某个socket没有read/write完所有数据,下次epoll_waitq会(x)q回该socket句柄?br />四?使用epoll的注意事?br />1. ET模式比LT模式高效Q但比较难控制?br />2. 如果某个句柄期待的事件不变,不需要EPOLL_CTL_MODQ但每次d后将该句柄modify一ơ有助于提高E_性,特别在ET模式?br />3. socket关闭后最好将该句柄从epoll中deleteQEPOLL_CTL_DELQ,虽然epoll自n有处理,但会(x)使epoll的hash的节Ҏ(gu)增多Q媄(jing)响搜索hash的速度?br />  
QQ网l服务器的瓶颈在哪?
AQIO效率?br />
在大家苦苦的为在Uh数的增长而导致的pȝ资源吃紧上的问题正在发愁的时候,Linux 2.6内核中提供的System Epoll为我们提供了一套完的解决Ҏ(gu)。传l的select以及(qing)poll的效率会(x)因ؓ(f)在线人数的线形递增而导致呈二次乃至三次方的下降Q这些直接导致了|络服务器可以支持的人数有了个比较明昄限制?br />
自从Linux提供?dev/epoll的设备以?qing)后?.6内核中对/dev /epoll讑֤的访问的装QSystem EpollQ之后,q种现象得到了大大的~解Q如果说几个月前Q大家还对epoll不熟(zhn),那么现在来说的话Qepoll的应用已l得C大范围的普及(qing)?br />
那么I竟如何来用epoll呢?其实非常单?br />通过在包含一个头文g#include 以及(qing)几个单的API可以大大的提高你的|络服务器的支持人数?br />
首先通过create_epoll(int maxfds)来创Z个epoll的句柄,其中maxfdsZepoll所支持的最大句柄数。这个函C(x)q回一个新的epoll句柄Q之后的所有操作将通过q个句柄来进行操作。在用完之后Q记得用close()来关闭这个创建出来的epoll句柄?br />
之后在你的网l主循环里面Q每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的|络接口Q看哪一个可以读Q哪一个可以写了。基本的语法为:(x)
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创徏之后的句柄,events是一个epoll_event的指针,当epoll_waitq个函数操作成功之后Qepoll_events里面储存所有的d事g。max_events是当前需要监听的所有socket句柄数。最后一个timeout?epoll_wait的超Ӟ?的时候表C马上返回,?1的时候表CZ直等下去Q直到有事g范围Qؓ(f)L正整数的时候表C等q么长的旉Q如果一直没有事Ӟ则范围。一般如果网l主循环是单独的U程的话Q可以用-1来等Q这样可以保证一些效率,如果是和主逻辑在同一个线E的话,则可以用0来保证主循环的效率?br />
epoll_wait范围之后应该是一个@环,遍利所有的事gQ?br />
C/C++ codefor(n = 0; n < nfds; ++n) {
            if(events[n].data.fd == listener) { //如果是主socket的事件的话,则表C有新连接进入了Q进行新q接的处理?br />                   client = accept(listener, (struct sockaddr ) &local,
                                 &addrlen);
                   if(client < 0){
                     perror("accept");
                     continue;
                   }
                   setnonblocking(client); // 新q接|于非阻塞模?br />                   ev.events = EPOLLIN | EPOLLET; // q且新q接也加入EPOLL的监听队列?br />注意Q这里的参数EPOLLIN | EPOLLETq没有设|对写socket的监听,如果有写操作的话Q这个时候epoll是不?x)返回事件的Q如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET
                   ev.data.fd = client;
                   if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
//   讄好event之后Q将q个新的event通过epoll_ctl加入到epoll的监听队列里面,q里用EPOLL_CTL_ADD来加一个新?epoll事gQ通过EPOLL_CTL_DEL来减一个epoll事gQ通过EPOLL_CTL_MOD来改变一个事件的监听方式?br />                     fprintf(stderr, "epoll set insertion error: fd=d0,
                               client);
                     return -1;
                   }
            }
            else // 如果不是主socket的事件的话,则代表是一个用户socket的事Ӟ则来处理q个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理?br />                   do_use_fd(events[n].data.fd);
}



对,epoll的操作就q么单,d不过4个APIQepoll_create, epoll_ctl, epoll_wait和close?/font>

 

 

Linux 2.6内核中提高网lI/O性能的新Ҏ(gu)

1、ؓ(f)什么select是落后的Q?
首先Q在Linux内核中,select所用到的FD_SET是有限的Q即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个敎ͼ在我用的2.6.15-25-386内核中,该值是1024Q搜索内核源代码得到Q?
include/linux/posix_types.h:#define __FD_SETSIZE 1024
也就是说Q如果想要同时检?025个句柄的可读状态是不可能用select实现的。或者同时检?025个句柄的可写状态也是不可能的?
其次Q内怸实现select是用轮询Ҏ(gu)Q即每次(g)都?x)遍历所有FD_SET中的句柄Q显?dng)select函数执行旉与FD_SET中的句柄个数有一个比例关p,即select要检的句柄数越多就?x)越?gu)?
当然Q在前文中我q没有提?qing)pollҎ(gu)Q事实上用select的朋友一定也试过pollQ我个h觉得select和poll大同异Q个人偏好于用select而已?
2?.6内核中提高I(y)/O性能的新Ҏ(gu)epoll
epoll是什么?按照man手册的说法:(x)是ؓ(f)处理大批量句柄而作了改q的poll。要使用epoll只需要这三个pȝ调用Qepoll_create(2)Q?epoll_ctl(2)Q?epoll_wait(2)?
当然Q这不是2.6内核才有的,它是?.5.44内核中被引进?epoll(4) is a new API introduced in Linux kernel 2.5.44)

epoll的优?
<1>支持一个进E打开大数目的socket描述W?FD)
select 最不能忍受的是一个进E所打开的FD是有一定限制的Q由FD_SETSIZE讄Q默认值是2048。对于那些需要支持的上万q接数目的IM服务器来说显然太了。这时候你一是可以选择修改q个宏然后重新编译内核,不过资料也同时指样会(x)带来|络效率的下降,二是可以选择多进E的解决Ҏ(gu)(传统?ApacheҎ(gu))Q不q虽然linux上面创徏q程的代h较小Q但仍旧是不可忽视的Q加上进E间数据同步q比不上U程间同步的高效Q所以也不是一U完的Ҏ(gu)。不q?epoll则没有这个限Ӟ它所支持的FD上限是最大可以打开文g的数目,q个数字一般远大于2048,举个例子,?GB内存的机器上大约?0万左叻I具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和pȝ内存关系很大?

<2>IO效率不随FD数目增加而线性下?
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合Q不q由于网lgӞM旉只有部分的socket?z跃"的,但是select/poll每次调用都会(x)U性扫描全部的集合Q导致效率呈现线性下降。但是epoll不存在这个问题,它只?x)?z跃"的socketq行操作---q是因ؓ(f)在内核实Cepoll是根据每个fd上面的callback函数实现的。那么,只有"z跃"的socket才会(x)d的去调用 callback函数Q其他idle状态socket则不?x),在这点上Qepoll实现了一??AIOQ因时候推动力在os内核。在一?benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境Qepollq不比select/poll有什么效率,相反Q如果过多用epoll_ctl,效率相比q有E微的下降。但是一旦用idle connections模拟WAN环境,epoll的效率就q在select/poll之上了?
<3>使用mmap加速内怸用户I间的消息传递?br />q点实际上涉?qing)到epoll的具体实C。无论是select,pollq是epoll都需要内核把FD消息通知l用L(fng)_(d)如何避免不必要的内存拯很重要Q在q点上,epoll是通过内核于用L(fng)间mmap同一块内存实现的。而如果你x一样从2.5内核关注epoll的话Q一定不?x)忘记手?mmapq一步的?
<4>内核微调
q一点其实不epoll的优点了Q而是整个linuxq_的优炏V也怽可以怀疑linuxq_Q但是你无法回避linuxq_赋予你微调内核的能力。比如,内核TCP/IP协议栈用内存池理sk_buffl构Q那么可以在q行时期动态调整这个内存pool(skb_head_pool)的大?--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参?TCP完成3ơ握手的数据包队列长?Q也可以Ҏ(gu)你^台内存大动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本w大却很小的特D系l上试最新的NAPI|卡驱动架构?
epoll的?
令h高兴的是Q?.6内核的epoll比其2.5开发版本的/dev/epollz了许多Q所以,大部分情况下Q强大的东西往往是简单的。唯一有点ȝ(ch)是epoll?U工作方?LT和ET?br />LT(level triggered)是缺省的工作方式Qƈ且同时支持block和no-block socket.在这U做法中Q内核告诉你一个文件描q符是否qA了,然后你可以对q个qA的fdq行IO操作。如果你不作M操作Q内核还是会(x)l箋通知你的Q所以,q种模式~程出错误可能性要一炏V传l的select/poll都是q种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在q种模式下,当描q符从未qA变ؓ(f)qAӞ内核通过epoll告诉你。然后它?x)假设你知道文g描述W已l就l,q且不会(x)再ؓ(f)那个文g描述W发送更多的qA通知Q直C做了某些操作D那个文g描述W不再ؓ(f)qA状态了(比如Q你在发送,接收或者接收请求,或者发送接收的数据于一定量时导致了一个EWOULDBLOCK 错误Q。但是请注意Q如果一直不对这个fd作IO操作(从而导致它再次变成未就l?Q内怸?x)发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark认?
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系l调用,具体用法请参考http://www.xmailserver.org/linux-patches/nio-improve.html Q?
在http://www.kegel.com/rn/也有一个完整的例子Q大家一看就知道如何使用?
Leader/follower模式U程pool实现Q以?qing)和epoll的配?/p>

在Linux上开发网l服务器的一些相关细?poll与epoll
  随着2.6内核对epoll的完全支持,|络上很多的文章和示例代码都提供了这样一个信息:(x)使用epoll代替传统?poll能给|络服务应用带来性能上的提升。但大多文章里关于性能提升的原因解释的较少Q这里我试分析一下内核(2.6.21.1Q代码中poll?epoll的工作原理,然后再通过一些测试数据来Ҏ(gu)具体效果?POLLQ?

先说pollQpoll或select为大部分Unix/LinuxE序员所熟?zhn)Q这俩个东西原理cMQ性能上也不存在明昑ַ异,但selectҎ(gu)监控的文件描q符数量有限Ӟ所以这里选用poll做说明?br />poll是一个系l调用,其内核入口函Cؓ(f)sys_pollQsys_poll几乎不做M处理直接调用do_sys_pollQdo_sys_poll的执行过E可以分Z个部分:(x)
1Q将用户传入的pollfd数组拯到内核空_(d)因ؓ(f)拯操作和数l长度相养I旉上这是一个OQnQ操作,q一步的代码在do_sys_poll中包括从函数开始到调用do_poll前的部分?
2Q查询每个文件描q符对应讑֤的状态,如果该设备尚未就l,则在该设备的{待队列中加入一ƈl箋查询下一讑֤的状态。查询完所有设备后如果没有一个设备就l,q时则需要挂起当前进E等待,直到讑֤qA或者超Ӟ挂v操作是通过调用schedule_timeout执行的。设备就l后q程被通知l箋q行Q这时再ơ遍历所有设备,以查扑ְl设备。这一步因Zơ遍历所有设备,旉复杂度也是OQnQ,q里面不包括{待旉。相关代码在do_poll函数中?
3Q将获得的数据传送到用户I间q执行释攑ֆ存和剥离{待队列{善后工作,向用L(fng)间拷贝数据与剥离{待队列{操作的的时间复杂度同样是OQnQ,具体代码包括do_sys_poll函数中调用do_poll后到l束的部分?
EPOLLQ?
接下来分析epollQ与poll/select不同Qepoll不再是一个单独的pȝ调用Q而是由epoll_create/epoll_ctl/epoll_wait三个pȝ调用l成Q后面将?x)看到这样做的好处?br />先来看sys_epoll_create(epoll_create对应的内核函敎ͼ(j)Q这个函C要是做一些准备工作,比如创徏数据l构Q初始化数据q最l返回一个文件描q符Q表C新创徏的虚拟epoll文gQ,q个操作可以认ؓ(f)是一个固定时间的操作?
epoll是做Z个虚拟文件系l来实现的,q样做至有以下两个好处Q?br />1Q可以在内核里维护一些信息,q些信息在多ơepoll_wait间是保持的,比如所有受监控的文件描q符?
2Q?epoll本n也可以被poll/epoll;
具体epoll的虚拟文件系l的实现和性能分析无关Q不再赘q?
在sys_epoll_create中还能看C个细节,是epoll_create的参数size在现阶段是没有意义的Q只要大于零p?

接着是sys_epoll_ctl(epoll_ctl对应的内核函敎ͼ(j)Q需要明的是每ơ调用sys_epoll_ctl只处理一个文件描q符Q这里主要描q当op为EPOLL_CTL_ADD时的执行q程Qsys_epoll_ctl做一些安全性检查后q入ep_insertQep_insert里将 ep_poll_callback做ؓ(f)回掉函数加入讑֤的等待队列(假定q时讑֤未qAQ,׃每次poll_ctl只操作一个文件描q符Q因此也可以认ؓ(f)q是一个O(1)操作

ep_poll_callback函数很关键,它在所{待的设备就l后被系l回掉,执行两个操作Q?

1Q将qA讑֤加入qA队列Q这一步避免了像poll那样在设备就l后再次轮询所有设备找qA者,降低了时间复杂度Q由O(jin)QnQ到OQ?Q?
2Q唤醒虚拟的epoll文g;
最后是sys_epoll_waitQ这里实际执行操作的是ep_poll函数。该函数{待进E自w插入虚拟epoll文g的等待队列,直到被唤醒(见上面ep_poll_callback函数描述Q,最后执行ep_events_transfer结果拷贝到用户I间。由于只拯qA讑֤信息Q所以这里的拯是一个O(1Q操作?br />q有一个让人关心的问题是epoll对EPOLLET的处理,卌沿触发的处理Q粗略看代码是把一部分水^触发模式下内核做的工作交l用h处理Q直觉上不会(x)Ҏ(gu)能有太大媄(jing)响,感兴的朋友Ƣ迎讨论?
POLL/EPOLLҎ(gu)Q?
表面上poll的过E可以看作是׃ơepoll_create/若干ơepoll_ctl/一ơepoll_wait/一ơclose{系l调用构成,实际上epollpoll分成若干部分实现的原因正是因为服务器软g中用poll的特点(比如Web服务器)(j)Q?
1Q需要同时poll大量文g描述W?
2Q每ơpoll完成后就l的文g描述W只占所有被poll的描q符的很一部分?
3Q前后多ơpoll调用Ҏ(gu)件描q符数组QufdsQ的修改只是很小;
传统的poll函数相当于每ơ调用都重v炉灶Q从用户I间完整dufdsQ完成后再次完全拯到用L(fng)_(d)另外每次poll都需要对所有设备做臛_做一ơ加入和删除{待队列操作Q这些都是低效的原因?

epoll以上情况都l化考虑Q不需要每ơ都完整d输出ufdsQ只需使用epoll_ctl调整其中一部分,不需要每ơepoll_wait都执行一ơ加入删除等待队列操作,另外改进后的机制使的不必在某个设备就l后搜烦(ch)整个讑֤数组q行查找Q这些都能提高效率。另外最明显的一点,从用L(fng)使用来说Q用epoll不必每次都轮询所有返回结果已扑և其中的就l部分,OQnQ变OQ?Q,Ҏ(gu)能也提高不?

此外q里q发C点,是不是将epoll_ctlҎ(gu)一ơ可以处理多个fdQ像semctl那样Q会(x)提高些许性能呢?特别是在假设pȝ调用比较耗时的基上。不q关于系l调用的耗时问题q会(x)在以后分析?/p>

POLL/EPOLL试数据Ҏ(gu)Q?
试的环境:(x)我写了三D代码来分别模拟服务器,zd的客L(fng)Q僵ȝ客户端,服务器运行于一个自~译的标?.6.11内核pȝ上,g?PIII933Q两个客L(fng)各自q行在另外的PC上,q两台PC比服务器的硬件性能要好Q主要是保证能轻易让服务器满载,三台机器间用一?00M交换接?
服务器接受ƈpoll所有连接,如果有request到达则回复一个responseQ然后lpoll?br />zd的客L(fng)QActive ClientQ模拟若qƈ发的zdq接Q这些连接不间断的发送请求接受回复?br />僉|的客L(fng)QzombieQ模拟一些只q接但不发送请求的客户端,其目的只是占用服务器的poll描述W资源?
试q程Q保?0个ƈ发活动连接,不断的调整僵q发q接敎ͼ记录在不同比例下使用poll与epoll的性能差别。僵dƈ发连接数Ҏ(gu)比例分别是:(x)0Q?0Q?0Q?0Q?0Q?60Q?20Q?40Q?280Q?560Q?120Q?0240?
下图中横轴表C僵dƈ发连接与zdq发q接之比Q纵轴表C完?0000ơ请求回复所p的时_(d)以秒为单位。红色线条表Cpoll数据Q绿色表C?epoll数据。可以看出,poll在所监控的文件描q符数量增加Ӟ其耗时呈线性增长,而epoll则维持了一个^E的状态,几乎不受描述W个数媄(jing)响?
在监控的所有客L(fng)都是zdӞpoll的效率会(x)略高于epollQ主要在原点附近Q即僉|q发q接?Ӟ图上不易看出来)(j)Q究竟epoll实现比poll复杂Q监控少量描q符q它的长处?/p>



李阳 2012-02-09 13:48 发表评论
]]>
加速LinuxE序~译http://www.shnenglu.com/deane/articles/165217.html李阳李阳Thu, 09 Feb 2012 05:45:00 GMThttp://www.shnenglu.com/deane/articles/165217.htmlhttp://www.shnenglu.com/deane/comments/165217.htmlhttp://www.shnenglu.com/deane/articles/165217.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/165217.htmlhttp://www.shnenglu.com/deane/services/trackbacks/165217.html目来大Q每ơ需要重?a onclick="javascript:tagshow(event, '%E7%BC%96%E8%AF%91');" href="javascript:;" target="_self">~译整个目都是一件很费旉的事情。Research了一下,扑ֈ以下可以帮助提高速度的方法,ȝ一下?

  tmpfs

  有h说在Windows下用了RAMDisk把一个项目编译时间从4.5时减少C5分钟Q也许这个数字是有点夸张了,不过_想惻I?a onclick="javascript:tagshow(event, '%E6%96%87%E4%BB%B6');" href="javascript:;" target="_self">文g攑ֈ内存上做~译应该是比在磁盘上快多了吧Q尤其如果编译器需要生成很多(f)时文件的话?/p>

  q个做法的实现成本最低,?a onclick="javascript:tagshow(event, 'Linux');" href="javascript:;" target="_self">Linux中,直接mount一个tmpfs可以了。而且Ҏ(gu)~译的工E没有Q何要求,也不用改动编译环境?/p>

  mount -t tmpfs tmpfs ~/build -o size=1G

  ?.6.32.2的Linux Kernel?span class="channel_keylink">试一下编译速度Q?/p>

  用物理磁盘:(x)40?6U?/p>

  用tmpfsQ?9?6U?/p>

  ?#8230;…没什么变化。看来编译慢很大E度上瓶颈ƈ不在IO上面。但对于一个实际项目来_(d)~译q程中可能还?x)有打包{IO密集的操作,所以只要可能,用tmpfs是有益无害的。当然对于大目来说Q你需要有_的内存才能负担得赯个tmpfs的开销?/p>

  make -j

  既然IO不是瓉Q那CPU应该是一个媄(jing)响编译速度的重要因素了?/p>

  用make -j带一?a onclick="javascript:tagshow(event, '%E5%8F%82%E6%95%B0');" href="javascript:;" target="_self">参数Q可以把目在进行ƈ行编译,比如在一台双核的机器上,完全可以用make -j4Q让make最多允?个编?a onclick="javascript:tagshow(event, '%E5%91%BD%E4%BB%A4');" href="javascript:;" target="_self">命o(h)同时执行Q这样可以更有效的利用CPU资源?/p>

  q是用Kernel?span class="channel_keylink">试Q?/p>

  用makeQ?40?6U?/p>

  用make -j4Q?3?6U?/p>

  用make -j8Q?2?9U?/p>

  由此看来Q在多核CPU上,适当的进行ƈ行编译还是可以明显提高编译速度的。但q行的Q务不宜太多,一般是以CPU的核心数目的两倍ؓ(f)宜?/p>

  不过q个Ҏ(gu)不是完全没有cost的,如果目的Makefile不规范,没有正确?a onclick="javascript:tagshow(event, '%E8%AE%BE%E7%BD%AE');" href="javascript:;" target="_self">好依赖关p,q行~译的结果就是编译不能正常进行。如果依赖关p设|过于保守,则可能本w编译的可ƈ行度׃降了Q也不能取得最佳的效果?/p>

  ccache

  ccache用于把编译的中间l果q行~存Q以便在再次~译的时候可以节省时间。这对于玩Kernel来说实在是再好不q了Q因为经帔R要修改一些Kernel的代码,然后再重新编译,而这两次~译大部分东西可能都没有发生变化。对于^?a onclick="javascript:tagshow(event, '%E5%BC%80%E5%8F%91');" href="javascript:;" target="_self">开?/strong>目来说Q也是一栗ؓ(f)什么不是直接用make所支持的增量编译呢Q还是因为现实中Q因为Makefile的不规范Q很可能q种“聪明”的方案根本不能正常工作,只有每次make clean再make才行?/p>

  安装完ccache后,可以?usr/local/bin下徏立gccQg++Qc++Qcc的symbolic linkQ链?usr/bin/ccache上。M认在调用gcc{命令时?x)调用到ccache可以了Q通常情况?usr/local /bin?x)在PATH中排?usr/bin前面Q?/p>

  l箋试Q?/p>

  用ccache的第一ơ编?make -j4)Q?3?8U?/p>

  用ccache的第二次~译(make -j4)Q??8U?/p>

  用ccache的第三次~译(修改若干配置Qmake -j4)Q?3?8U?/p>

  看来修改配置Q我改了CPUcd...Q对ccache的媄(jing)响是很大的,因ؓ(f)基本头文件发生变化后Q就D所有缓?a onclick="javascript:tagshow(event, '%E6%95%B0%E6%8D%AE');" href="javascript:;" target="_self">数据都无效了Q必重头来做。但如果只是修改一?c文g的代码,ccache的效果还是相当明昄。而且使用ccache寚w目没有特别的依赖Q布|成本很低,q在日常工作中很实用?/p>

  可以用ccache -s来查看cache的用和命中情况Q?/p>

  cache directory                     /home/lifanxi/.ccachecache hit                           7165cache miss                         14283called for link                       71not a C/C++file                     120no input file                       3045files in cache                     28566cache size                          81.7 Mbytesmax cache size                     976.6 Mbytes

  可以看到Q显然只有第二编ơ译时cache命中了,cache miss是第一ơ和W三ơ编译带来的。两ơcache占用?1.7M的磁盘,q是完全可以接受的?/p>

  distcc

  一台机器的能力有限Q可以联合多台电(sh)脑一h~译。这在公司的日常开发中也是可行的,因ؓ(f)可能每个开发h员都有自q开发编译环境,它们的编译器版本一般是一致的Q公司的|络也通常h较好?a onclick="javascript:tagshow(event, '%E6%80%A7%E8%83%BD');" href="javascript:;" target="_self">性能。这时就是distcc大显w手的时候了?/p>

  使用distccQƈ不像惌中那栯求每台电(sh)脑都h完全一致的环境Q它只要求源代码可以用make -jq行~译Qƈ且参与分布式~译的电(sh)脑系l中h相同的编译器。因为它的原理只是把预处理好的源文g分发到多台计机上,预处理、编译后的目标文件的链接和其它除~译以外的工作仍然是在发L(fng)译的L?sh)脑上完成,所以只要求发v~译的那台机器具备一套完整的~译环境?yu)可以了?/p>

  distcc安装后,可以启动一下它的服务:(x)

  /usr/bin/distccd  --daemon --allow 10.64.0.0/16

  默认?632端口允许来自同一个网l的distccq接?/p>

  然后讄一下DISTCC_HOSTS环境变量Q设|可以参与编译的机器列表。通常localhost也参与编译,但如果可以参与编译的机器很多Q则可以把localhost从这个列表中LQ这h机就完全只是q行预处理、分发和链接了,~译都在别的机器上完成。因为机器很多时Qlocalhost的处理负担很重,所以它?yu)׃?#8220;D”~译了?/p>

  export DISTCC_HOSTS="localhost 10.64.25.1 10.64.25.2 10.64.25.3"

  然后与ccachecM把g++Qgcc{常用的命o(h)链接?usr/bin/distcc上就可以了?/p>

  在make的时候,也必ȝ-j参数Q一般是参数可以用所有参用编译的计算机CPU内核L的两倍做为ƈ行的d数?/p>

  同样试一下:(x)

  一台双核计机Qmake -j4Q?3?6U?/p>

  两台双核计算机,make -j4Q?6?0U?/p>

  两台双核计算机,make -j8Q?5?9U?/p>

  跟最开始用一台双核时?3分钟相比Q还是快了不的。如果有更多的计机加入Q也可以得到更好的效果?/p>

  在编译过E中可以用distccmon-text来查看编译Q务的分配情况。distcc也可以与ccache同时使用Q通过讄一个环境变量就可以做到Q非常方ѝ?/p>

  ȝ一下:(x)

  tmpfsQ?解决IO瓉Q充分利用本机内存资?/p>

  make -jQ?充分利用本机计算资源

  distccQ?利用多台计算?/p>

  ccacheQ?减少重复~译相同代码的时?/p>

  q些工具的好处都在于布v的成本相对较低,l合利用q些工具Q就可以轻轻松松的节省相当可观的旉。上面介l的都是q些工具最基本?a onclick="javascript:tagshow(event, '%E7%94%A8%E6%B3%95');" href="javascript:;" target="_self">用法Q更多的用法可以参考它们各自的man page?/p>

李阳 2012-02-09 13:45 发表评论
]]>
Linux下Gcc生成和用静态库和动态库详解Q{Q?/title><link>http://www.shnenglu.com/deane/articles/165216.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 09 Feb 2012 05:43:00 GMT</pubDate><guid>http://www.shnenglu.com/deane/articles/165216.html</guid><wfw:comment>http://www.shnenglu.com/deane/comments/165216.html</wfw:comment><comments>http://www.shnenglu.com/deane/articles/165216.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/deane/comments/commentRss/165216.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/deane/services/trackbacks/165216.html</trackback:ping><description><![CDATA[<span id="z1djztn" class="Apple-style-span" style="word-spacing: 0px; font: medium Simsun; text-transform: none; color: rgb(0,0,0); text-indent: 0px; white-space: normal; letter-spacing: normal; border-collapse: separate; orphans: 2; widows: 2; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px"><span id="htlzn73" class="Apple-style-span" style="font-size: 14px; line-height: 26px; font-family: Arial; text-align: left"> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-size: 18px; color: rgb(0,0,0)"><strong><br />一、基本概?/strong></span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.1什么是?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">在windowsq_和linuxq_下都大量存在着库?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">本质上来说库是一U可执行代码的二q制形式Q可以被操作pȝ载入内存执行?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">׃windows和linux的^C同(主要是编译器、汇~器和连接器的不同)(j)Q因此二者库的二q制是不兼容的?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">本文仅限于介llinux下的库?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.2库的U类</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">linux下的库有两种Q静态库和共享库Q动态库Q?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">二者的不同点在于代码被载入的时M同?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库的代码在~译q程中已l被载入可执行程序,因此体积较大?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">׃n库的代码是在可执行程序运行时才蝲入内存的Q在~译q程中仅单的引用Q因此代码体U较?yu)?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.3库存在的意义</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">库是别h写好的现有的Q成熟的Q可以复用的代码Q你可以使用但要记得遵守许可协议?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">现实中每个程序都要依赖很多基的底层库Q不可能每个人的代码都从零开始,因此库的存在意义非同d?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">׃n库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该׃n库的实例?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.4库文件是如何产生的在linux?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库的后~?aQ它的生分两步</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">Step 1.由源文g~译生成一?oQ每?o里都包含q个~译单元的符可</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">Step 2.ar命o(h)很?o转换?aQ成为静态库</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">动态库的后~?soQ它由gcc加特定参数编译生?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">具体Ҏ(gu)参见后文实例?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.5库文件是如何命名的,有没有什么规?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">在linux下,库文件一般放?usr/lib?lib下,</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库的名字一般ؓ(f)libxxxx.aQ其中xxxx是该lib的名U?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">动态库的名字一般ؓ(f)libxxxx.so.major.minorQxxxx是该lib的名Uͼmajor是主版本P minor是副版本?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.6如何知道一个可执行E序依赖哪些?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">ldd命o(h)可以查看一个可执行E序依赖的共享库Q?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">例如# ldd /bin/lnlibc.so.6</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">=> /lib/ld- linux.so.2 (0×40000000)</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">可以看到l(f)n命o(h)依赖于libc库和ld-linux?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.7可执行程序在执行的时候如何定位共享库文g</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">当系l加载可执行代码时候,能够知道其所依赖的库的名字,但是q需要知道绝对\径?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">此时需要系l动态蝲入器(dynamic linker/loader)</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">对于elf格式的可执行E序Q是由ld-linux.so*来完成的Q它先后搜烦(ch)elf文g?nbsp;DT_RPATHD?#8212;环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文g列表—/lib/,/usr/lib目录扑ֈ库文件后其载入内存</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">如:(x)export LD_LIBRARY_PATH=’pwd’</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">当前文件目录添加ؓ(f)׃n目录</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.8在新安装一个库之后如何让系l能够找C</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">如果安装?lib或?usr/lib下,那么ld默认能够扑ֈQ无需其他操作?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">如果安装在其他目录,需要将其添加到/etc/ld.so.cache文g中,步骤如下</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.~辑/etc/ld.so.conf文gQ加入库文g所在目录的路径</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">2.q行ldconfigQ该命o(h)?x)重?etc/ld.so.cache文g</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">二、用gcc生成静态和动态链接库的示?/span></p><span style="font-family: 宋体"><strong></strong></span> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">我们通常把一些公用函数制作成函数库,供其它程序用?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">函数库分为静态库和动态库两种?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库在程序编译时?x)被q接到目标代码中Q程序运行时不再需要该静态库?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">动态库在程序编译时q不?x)被q接到目标代码中Q而是在程序运行是才被载入Q因此在E序q行时还需要动态库存在?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">本文主要通过举例来说明在Linux中如何创建静态库和动态库Q以?qing)用它们?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">Z便于阐述Q我们先做一部分准备工作?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.1准备好测试代码hello.h、hello.c和main.cQ?/strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">hello.h(见程?)函数库的头文件?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">hello.c(见程?)是函数库的源E序Q其中包含公用函数helloQ该函数在屏幕上输?Hello XXX!"?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">main.c(见程?)为测试库文g的主E序Q在ȝ序中调用了公用函数hello?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> E序1: hello.h</p> <table cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1"> <tbody> <tr> <td> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">#ifndef HELLO_H<span id="ffvn7vb" class="Apple-converted-space"> </span><br />#define HELLO_H<span id="zxpxh1f" class="Apple-converted-space"> </span><br />  <br />void hello(const char *name);<span id="fhxfx59" class="Apple-converted-space"> </span><br />  <br />#endif</p></td></tr></tbody></table><br /> <div>E序2Qhello.c <table cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1"> <tbody> <tr> <td> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">#include <stdio.h><span id="rh1plln" class="Apple-converted-space"> </span><br />void hello(const char *name) { </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">        printf("Hello %s!\n", name);<span id="l99f99j" class="Apple-converted-space"> </span><br />}</p></td></tr></tbody></table><br /></div> <div><span style="font-family: 宋体">E序3Qmain.c</span> <table cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1"> <tbody> <tr> <td> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">#include "hello.h"<span id="fnpfnf7" class="Apple-converted-space"> </span><br /> int main()<span id="hrxxv3p" class="Apple-converted-space"> </span><br /> {<span id="bddbjdv" class="Apple-converted-space"> </span><br />     hello("everyone");<span id="tdvvtfv" class="Apple-converted-space"> </span><br />     return 0;<span id="ptj3lvv" class="Apple-converted-space"> </span><br /> }</p></td></tr></tbody></table> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.2问题的提?/strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">注意Q这个时候,我们~译好的hello.o是无法通过gcc –o ~译的,q个道理非常单,</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">hello.c是一个没有main函数?cE序Q因此不够成一个完整的E序Q如果用gcc –o ~译q连接它QGCC报错?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">无论静态库Q还是动态库Q都是由.o文g创徏的。因此,我们必须源E序hello.c通过gcc先编译成.o文g?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">q个时候我们有三种思\Q?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1Q?nbsp; 通过~译多个源文Ӟ直接目标代码合成一?o文g?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">2Q?nbsp; 通过创徏静态链接库libmyhello.aQ得main函数调用hello函数时可调用静态链接库?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">3Q?nbsp; 通过创徏动态链接库libmyhello.soQ得main函数调用hello函数时可调用静态链接库?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.3思\一Q编译多个源文g</strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在系l提C符下键入以下命令得?/span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">文g?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -c hello.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Z么不使用</span><span style="font-family: 宋体">gcc–o hello hello.cpp </span><span style="font-family: 宋体">q个道理我们之前已经说了Q?/span><span style="font-family: 宋体">-c</span><span style="font-family: 宋体">是什么意思呢Q这涉及(qing)?/span><span style="font-family: 宋体">gcc </span><span style="font-family: 宋体">~译选项的常识?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们通常使用?/span><span style="font-family: 宋体">gcc –o </span><span style="font-family: 宋体">是将</span><span style="font-family: 宋体">.c</span><span style="font-family: 宋体">源文件编译成Z个可执行的二q制代码<span style="color: rgb(240,0,0)">(-o选项其实是制定输出文件文件名Q如果不?c选项Qgcc默认?x)编译连接生成可执行文gQ文件的名称?o选项指定</span>)Q这包括调用作ؓ(f)</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">内的一部分真正?/span><span style="font-family: 宋体">C</span><span style="font-family: 宋体">~译器(</span><span style="font-family: 宋体">ccl</span><span style="font-family: 宋体">Q,以及(qing)调用</span><span style="font-family: 宋体">GNU C</span><span style="font-family: 宋体">~译器的输出中实际可执行代码的外?/span><span style="font-family: 宋体">GNU</span><span style="font-family: 宋体">汇编器(asQ和q接器工PldQ?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">?/span><span style="font-family: 宋体">gcc –c</span><span style="font-family: 宋体">是?/span><span style="font-family: 宋体">GNU</span><span style="font-family: 宋体">汇编器将源文件{化ؓ(f)目标代码之后q束,在这U情况下,只调用了C~译器(cclQ和汇编器(asQ,而连接器(ld)q没有被执行Q所以输出的目标文g不会(x)包含作ؓ(f)</span><span style="font-family: 宋体">Linux</span><span style="font-family: 宋体">E序在被装蝲和执行时所必须的包含信息,但它可以在以后被q接C个程序?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们q行</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命o(h)看看是否生存?/span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">文g?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c hello.h hello.o main.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">?/span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命o(h)l果中,我们看到?/span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">文gQ本步操作完成?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">同理~译</span><span style="font-family: 宋体">main</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#gcc –c main.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">两个文仉接成一?/span><span style="font-family: 宋体">.o</span><span style="font-family: 宋体">文g?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#gcc –o hello hello.o main.o</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">q行</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Hello everyone!</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">完成</span><span style="font-family: 宋体">^ ^!</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.4思\二:(x)静态链接库</strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">下面我们先来看看如何创徏静态库Q以?qing)用它?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">静态库文g名的命名规范是以</span><span style="font-family: 宋体">lib</span><span style="font-family: 宋体">为前~Q紧接着跟静态库?/span><span style="font-family: 宋体">Q扩展名?/span><span style="font-family: 宋体">.a</span><span style="font-family: 宋体">。例如:(x)我们创建的静态库名ؓ(f)</span><span style="font-family: 宋体">myhello</span><span style="font-family: 宋体">Q则静态库文g名就?/span><span style="font-family: 宋体">libmyhello.a</span><span style="font-family: 宋体">。在创徏和用静态库Ӟ需要注意这炏V?/span><span style="font-family: 宋体">创徏静态库?/span><span style="font-family: 宋体">ar</span><span style="font-family: 宋体">命o(h)</span><span style="font-family: 宋体">?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在系l提C符下键入以下命令将创徏静态库文g</span><span style="font-family: 宋体">libmyhello.a</span><span style="font-family: 宋体">?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ar rcs libmyhello.a hello.o</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们同样q行</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命o(h)查看l果Q?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c  hello.h hello.o libmyhello.a main.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命o(h)l果中有</span><span style="font-family: 宋体">libmyhello.a</span><span style="font-family: 宋体">?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">静态库制作完了Q如何用它内部的函数呢Q只需要在使用到这些公用函数的源程序中包含q些公用函数的原型声明,然后在用</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">命o(h)生成目标文g时指明静态库名,</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">会(x)从静态库中将公用函数q接到目标文件中。注意,</span><span style="color: rgb(240,0,0)"><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">?x)在静态库名前加上前缀</span><span style="font-family: 宋体">lib</span><span style="font-family: 宋体">Q然后追加扩展名</span><span style="font-family: 宋体">.a</span><span style="font-family: 宋体">得到的静态库文g名来查找静态库文g,因此Q我们在写需要连接的库时Q只写名字就可以Q如lib<span style="color: rgb(0,0,0)">myhello</span>.a的库Q只?-l<span style="color: rgb(0,0,0)">myhello</span></span></span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在程?/span><span style="font-family: 宋体">3:main.c</span><span style="font-family: 宋体">中,我们包含了静态库的头文g</span><span style="font-family: 宋体">hello.h</span><span style="font-family: 宋体">Q然后在ȝ?/span><span style="font-family: 宋体">main</span><span style="font-family: 宋体">中直接调用公用函?/span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">。下面先生成目标E序</span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">Q然后运?/span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">E序看看l果如何?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -o hello main.c -static -L. -lmyhello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Hello everyone!</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们删除静态库文g试试公用函数</span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">是否真的q接到目标文?/span><span style="font-family: 宋体"> hello</span><span style="font-family: 宋体">中了?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># rm libmyhello.a</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">rm: remove regular file `libmyhello.a'? y</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Hello everyone!</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">E序照常q行Q静态库中的公用函数已经q接到目标文件中了?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"><span style="font-family: 宋体">静态链接库的一个缺Ҏ(gu)Q如果我们同时运行了许多E序Qƈ且它们用了同一个库函数Q这P在内存中?x)大量拷贝同一库函数。这P׃(x)费很多珍贵的内存和存储I间。用了׃n链接库的Linux可以避免这个问题?/span></span></p><span style="font-family: 宋体"></span> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">׃n函数库和静态函数在同一个地方,只是后缀有所不同。比如,在一个典型的LinuxpȝQ标准的׃n数序函数库是/usr/lib/libm.so?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="color: rgb(240,0,0); font-family: 宋体">当一个程序用共享函数库Ӟ在连接阶Dƈ不把函数代码q接q来Q而只是链接函数的一个引用。当最l的函数导入内存开始真正执行时Q函数引用被解析Q共享函数库的代码才真正导入到内存中。这P׃n链接库的函数可以被许多E序同时׃nQƈ且只需存储一ơ就可以了。共享函数库的另一个优Ҏ(gu)Q它可以独立更新Q与调用它的函数毫不影响?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.5思\三、动态链接库Q共享函数库Q?/strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们l箋看看如何?/span><span style="font-family: 宋体">Linux</span><span style="font-family: 宋体">中创建动态库。我们还是从</span><span style="font-family: 宋体">.o</span><span style="font-family: 宋体">文g开始?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">动态库文g名命名规范和静态库文g名命名规范类|也是在动态库名增加前~</span><span style="font-family: 宋体">lib</span><span style="font-family: 宋体">Q但其文件扩展名?/span><span style="font-family: 宋体">.so</span><span style="font-family: 宋体">。例如:(x)我们创建的动态库名ؓ(f)</span><span style="font-family: 宋体">myhello</span><span style="font-family: 宋体">Q则动态库文g名就?/span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">。用</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">来创建动态库?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在系l提C符下键入以下命令得到动态库文g</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -shared -fPIC -o libmyhello.so hello.o</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"> </span><span style="font-family: 宋体">“PIC”</span><span style="font-family: 宋体">命o(h)行标记告?/span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">产生的代码不要包含对函数和变量具体内存位|的引用Q这是因为现在还无法知道使用该消息代码的应用E序?x)将它连接到哪一D内存地址I间。这L(fng)译出?/span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">可以被用于徏立共享链接库。徏立共享链接库只需要用</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">?/span><span style="font-family: 宋体">”-shared”</span><span style="font-family: 宋体">标记卛_?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们照样使用</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命o(h)看看动态库文g是否生成?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.cpp hello.h hello.o libmyhello.so main.cpp</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">调用动态链接库~译目标文g?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在程序中使用动态库和用静态库完全一P也是在用到q些公用函数的源E序中包含这些公用函数的原型声明Q然后在?/span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">命o(h)生成目标文g时指明动态库名进行编译。我们先q行</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">命o(h)生成目标文gQ再q行它看看结果?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">如果直接用如下方法进行编译,q连接:(x)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -o hello main.c -L. -lmyhello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Q?/span><span style="font-family: 宋体">”-lmyhello”</span><span style="font-family: 宋体">标记来告?/span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">驱动E序在连接阶D引用共享函数库</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">?/span><span style="font-family: 宋体">”-L.”</span><span style="font-family: 宋体">标记告诉</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">函数库可能位于当前目录。否?/span><span style="font-family: 宋体">GNU</span><span style="font-family: 宋体">q接器会(x)查找标准pȝ函数目录:<span style="font-size: 16px">它先后搜?.elf文g?nbsp;DT_RPATHD?#8212;2.环境变量LD_LIBRARY_PATH—3./etc/ld.so.cache文g列表—4./lib/,/usr/lib目录扑ֈ库文件后其载入内存,但是我们生成的共享库在当前文件夹下,q没有加Cq的4个\径的M一个中Q因此,执行后会(x)出现错误Q?/span></span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">错误提示Q找不到动态库文g</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">。程序在q行Ӟ?x)?/span><span style="font-family: 宋体">/usr/lib</span><span style="font-family: 宋体">?/span><span style="font-family: 宋体">/lib</span><span style="font-family: 宋体">{目录中查找需要的动态库文g。若扑ֈQ则载入动态库Q否则将提示cM上述错误而终止程序运行。有多种Ҏ(gu)可以解决Q?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Q?Q我们将文g</span><span style="font-family: 宋体"> libmyhello.so</span><span style="font-family: 宋体">复制到目?/span><span style="font-family: 宋体">/usr/lib</span><span style="font-family: 宋体">中,再试试?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># mv libmyhello.so /usr/lib</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">成功Q?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Q?Q既然连接器?x)搜寻LD_LIBRARY_PATH所指定的目录,那么我们可以这个环境变量设|成当前目录Q?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">先执行:(x)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">export LD_LIBRARY_PATH=$(pwd)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">再执行:(x)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">成功Q?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Q?Q?/span></p><span style="font-family: 宋体"></span> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">执行Q?nbsp; </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">ldconfig   /usr/zhsoft/lib     <br />       <br />      <br />  ?   当用户在某个目录下面创徏或拷贝了一个动态链接库,若想使其被系l共?可以执行一?<span style="color: rgb(240,0,0)">ldconfig   目录?/span>"q个命o(h).此命令的功能在于让ldconfig指定目录下的动态链接库被系l共享v?意即:在缓存文?etc/ld.so.cache中追加进指定目录下的׃n?本例让系l共享了/usr/zhsoft/lib目录下的动态链接库.该命令会(x)重徏/etc/ld.so.cache文g</p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">成功Q?/p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">KEKEKEKEKEKEKEKEKEKEKEKEKEKEKEKE</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">下面的这个错误我没有遇到Q不q也记录下,l遇到的人:(x)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> <span style="font-family: 宋体">{  </span><span style="font-family: 宋体">q步后我没有成功,报错内容如下Q?/span><span style="font-family: 宋体">/hello: error while loading shared libraries: /usr/lib/libmyhello.so: cannot restore segment prot after reloc: Permission denied</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">google了一下,发现?/span><span style="font-family: 宋体">SELinux搞的|解决办法有两个:(x)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">1.<br />    chcon -t texrel_shlib_t   </span><span style="font-family: 宋体">/usr/lib/libmyhello.so</span><span style="font-family: 宋体"><br />    (chcon -t texrel_shlib_t "你不能share的库的绝对\?)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">2.<br />    #vi /etc/sysconfig/selinux file<br />    或者用<br />    #gedit /etc/sysconfig/selinux file<br />    修改SELINUX=disabled<br />    重启</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">}</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">KEKEKEKEKEKEKEKEKEKEKEKEKEKEKEKE</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">q也q一步说明了动态库在程序运行时是需要的?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"> </p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">可以查看E序执行时调用动态库的过E:(x)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ldd hello<br />执行 test,可以看到它是如何调用动态库中的函数的?br />[pin@localhost 20090505]$ ldd hello<br />        linux-gate.so.1 => (0x00110000)<br />        libmyhello.so => /usr/lib/libmyhello.so (0x00111000)<br />        libc.so.6 => /lib/libc.so.6 (0x00859000)<br />        /lib/ld-linux.so.2 (0x0083a000)</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们回过头看看,发现使用静态库和用动态库~译成目标程序用的gcc命o(h)完全一P<br />那当静态库和动态库同名Ӟgcc命o(h)?x)用哪个库文g呢?q寚w题必I到底的心情Q?br />来试试看?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">先删除除.c?h外的所有文Ӟ恢复成我们刚刚编辑完举例E序状态?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># rm -f hello hello.o /usr/lib/libmyhello.so</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c hello.h main.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在来创徏静态库文glibmyhello.a和动态库文glibmyhello.so?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -c hello.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ar rcs libmyhello.a hello.o</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -shared -fPCI -o libmyhello.so hello.o</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c hello.h hello.o libmyhello.a libmyhello.so main.c</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">通过上述最后一条ls命o(h)Q可以发现静态库文glibmyhello.a和动态库文glibmyhello.so都已l生成,q在当前目录中。然后,我们q行gcc命o(h)来用函数库myhello生成目标文ghelloQƈq行E序 hello?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -o hello main.c -L. -lmyhello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">./hello: error while loading shared libraries: libmyhello.so: cannot open shar<br />ed object file: No such file or directory</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="color: rgb(240,0,0); font-family: 宋体">从程序helloq行的结果中很容易知道,当静态库和动态库同名Ӟ gcc命o(h)优先用动态库?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="color: rgb(0,0,255)"><strong><span style="font-family: 宋体">Note:</span></strong></span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong><span style="font-family: 宋体">~译参数解析</span></strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 黑体"><br /></span><span style="font-family: 宋体">最主要的是GCC命o(h)行的一个选项:<br />-shared 该选项指定生成动态连接库Q让q接器生成Tcd的导出符可Q有时候也生成p接Wcd的导出符P(j)Q不用该标志外部E序无法q接。相当于一个可执行文g<br />l -fPICQ表C编译ؓ(f)位置独立的代码,不用此选项的话~译后的代码是位|相关的所以动态蝲入时是通过代码拯的方式来满不同q程的需要,而不能达到真正代码段׃n的目的?br />l -L.Q表Cq接的库在当前目录中<br />l -ltestQ编译器查找动态连接库时有隐含的命名规则,卛_l出的名字前面加上libQ后面加?so来确定库的名U?br />l LD_LIBRARY_PATHQ这个环境变量指C动态连接器可以装蝲动态库的\径?br />l 当然如果有root权限的话Q可以修?etc/ld.so.conf文gQ然后调?/sbin/ldconfig来达到同L(fng)目的Q不q如果没有root权限Q那么只能采用输出LD_LIBRARY_PATH的方法了?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">调用动态库的时候有几个问题?x)经常碰刎ͼ有时Q明明已l将库的头文件所在目?通过 “-I” includeq来了,库所在文仉过 “-L”参数引导Qƈ指定?#8220;-l”的库名,但通过ldd命o(h)察看Ӟ是L找不C指定链接的so文gQ这时你要作的就是通过修改 LD_LIBRARY_PATH或?etc/ld.so.conf文g来指定动态库的目录。通常q样做就可以解决库无法链接的问题了?br /></span></p><br /> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"><strong>静态库链接时搜索\径顺序:(x)</strong></span><br /><br /><span style="font-family: 宋体">1. ld?x)去找GCC命o(h)中的参数-L<br /><br />2. 再找gcc的环境变量LIBRARY_PATH<br /><br />3. 再找内定目录 /lib /usr/lib /usr/local/lib q是当初compile gcc时写在程序内?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong><span style="font-family: 宋体">动态链接时、执行时搜烦(ch)路径序:</span></strong></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /><span style="font-family: 宋体">1.  ~译目标代码时指定的动态库搜烦(ch)路径Q?br /><br />2.  环境变量LD_LIBRARY_PATH指定的动态库搜烦(ch)路径Q?br /><br />3.  配置文g/etc/ld.so.conf中指定的动态库搜烦(ch)路径Q?br /><br />4. 默认的动态库搜烦(ch)路径/libQ?br /><br />5. 默认的动态库搜烦(ch)路径/usr/lib?/span></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /></p> <p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 黑体"><strong>有关环境变量Q?/strong></span><br /><br /><span style="font-family: 宋体">LIBRARY_PATH环境变量Q指定程序静态链接库文g搜烦(ch)路径<br /><br />LD_LIBRARY_PATH环境变量Q指定程序动态链接库文g搜烦(ch)路径</span></p></div></span></span><br /><img src ="http://www.shnenglu.com/deane/aggbug/165216.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/deane/" target="_blank">李阳</a> 2012-02-09 13:43 <a href="http://www.shnenglu.com/deane/articles/165216.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境q程间通信 ׃n内存Q下Q?/title><link>http://www.shnenglu.com/deane/articles/139057.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 21 Jan 2011 12:59:00 GMT</pubDate><guid>http://www.shnenglu.com/deane/articles/139057.html</guid><wfw:comment>http://www.shnenglu.com/deane/comments/139057.html</wfw:comment><comments>http://www.shnenglu.com/deane/articles/139057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/deane/comments/commentRss/139057.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/deane/services/trackbacks/139057.html</trackback:ping><description><![CDATA[<p><br>pȝ调用mmap()通过映射一个普通文件实现共享内存。系lV则是通过映射Ҏ(gu)文gpȝshm中的文g实现q程间的׃n内存通信。也是_(d)每个׃n内存区域对应Ҏ(gu)文gpȝshm中的一个文Ӟq是通过shmid_kernell构联系h的)(j)Q后面还阐q?/p> <p><a name=1><span id="p7h7p9l" class=atitle><strong><font size=5>1、系lV׃n内存原理</font></strong></span></a></p> <p>q程间需要共享的数据被放在一个叫做IPC׃n内存区域的地方,所有需要访问该׃n区域的进E都要把该共享区域映到本进E的地址I间中去。系lV׃n内存通过shmget获得或创Z个IPC׃n内存区域Qƈq回相应的标识符。内核在保证shmget获得或创Z个共享内存区Q初始化该共享内存区相应的shmid_kernell构注同Ӟq将在特D文件系lshm中,创徏q打开一个同名文Ӟq在内存中徏立v该文件的相应dentry?qing)inodel构Q新打开的文件不属于M一个进E(Mq程都可以访问该׃n内存区)(j)。所有这一切都是系l调用shmget完成的?/p> <p>注:(x)每一个共享内存区都有一个控制结构struct shmid_kernelQshmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储理和文件系l结合v来的桥梁Q定义如下:(x)</p> <table cellSpacing=0 cellPadding=0 width="70%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>struct shmid_kernel /* private to the kernel */ { struct kern_ipc_perm shm_perm; struct file * shm_file; int id; unsigned long shm_nattch; unsigned long shm_segsz; time_t shm_atim; time_t shm_dtim; time_t shm_ctim; pid_t shm_cprid; pid_t shm_lprid; }; </pre> </td> </tr> </tbody> </table> <br> <p>该结构中最重要的一个域应该是shm_fileQ它存储了将被映文件的地址。每个共享内存区对象都对应特D文件系lshm中的一个文Ӟ一般情况下Q特D文件系lshm中的文g是不能用read()、write(){方法访问的Q当采取׃n内存的方式把其中的文件映到q程地址I间后,可直接采用访问内存的方式对其讉K?/p> <p>q里我们采用[1]中的图表l出与系lV׃n内存相关数据l构Q?/p> <br><img height=409 alt="" src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/image001.jpg" width=580 border=0> <br> <p>正如消息队列和信L(fng)一P内核通过数据l构struct ipc_ids shm_idsl护pȝ中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_idl构数组Q而每个ipc_idl构数组中有个指向kern_ipc_perml构的指针。到q里读者应该很熟?zhn)了,对于pȝV׃n内存区来_(d)kern_ipc_perm的宿Lshmid_kernell构Qshmid_kernel是用来描qC个共享内存区域的Q这样内核就能够控制pȝ中所有的׃n区域。同Ӟ在shmid_kernell构的filecd指针shm_file指向文gpȝshm中相应的文gQ这P׃n内存区域׃shm文gpȝ中的文g对应h?/p> <p>在创Z一个共享内存区域后Q还要将它映到q程地址I间Q系l调用shmat()完成此项功能。由于在调用shmget()Ӟ已经创徏了文件系lshm中的一个同名文件与׃n内存区域相对应,因此Q调用shmat()的过E相当于映射文gpȝshm中的同名文gq程Q原理与mmap()大同异?/p> <div id="dddfxhx" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><font color=#4c6e94><strong>回页?/strong></font></a></p> <p><a name=2><span id="5ftldbd" class=atitle><strong><font size=5>2、系lV׃n内存API</font></strong></span></a></p> <p>对于pȝV׃n内存Q主要有以下几个APIQshmget()、shmat()、shmdt()?qing)shmctl()?/p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>#include <sys/ipc.h> #include <sys/shm.h> </pre> </td> </tr> </tbody> </table> <br> <p>shmgetQ)(j)用来获得׃n内存区域的IDQ如果不存在指定的共享区域就创徏相应的区域。shmat()把共享内存区域映到调用q程的地址I间中去Q这Pq程可以方便地对共享区域进行访问操作。shmdt()调用用来解除q程对共享内存区域的映射。shmctl实现对共享内存区域的控制操作。这里我们不对这些系l调用作具体的介l,读者可参考相应的手册面Q后面的范例中将l出它们的调用方法?/p> <p>注:(x)shmget的内部实现包含了许多重要的系lV׃n内存机制Qshmat在把׃n内存区域映射到进E空间时Qƈ不真正改变进E的表。当q程W一ơ访问内存映区域访问时Q会(x)因ؓ(f)没有物理表的分配而导致一个缺异常,然后内核再根据相应的存储理机制为共享内存映区域分配相应的表?/p> <div id="bvfnnbv" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><font color=#4c6e94><strong>回页?/strong></font></a></p> <p><a name=3><span id="l1l7btb" class=atitle><strong><font size=5>3、系lV׃n内存限制</font></strong></span></a></p> <p>?proc/sys/kernel/目录下,记录着pȝV׃n内存的一下限Ӟ如一个共享内存区的最大字节数shmmaxQ系l范围内最大共享内存区标识W数shmmni{,可以手工对其调整Q但不推荐这样做?/p> <p>在[2]中,l出了这些限制的试Ҏ(gu)Q不再赘q?/p> <div id="blrzprz" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><font color=#4c6e94><strong>回页?/strong></font></a></p> <p><a name=4><span id="1zhfdn1" class=atitle><strong><font size=5>4、系lV׃n内存范例</font></strong></span></a></p> <p>本部分将l出pȝV׃n内存API的用方法,q对比分析系lV׃n内存机制与mmap()映射普通文件实现共享内存之间的差异Q首先给Z个进E通过pȝV׃n内存通信的范例:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>/***** testwrite.c *******/ #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> typedef struct{ char name[4]; int age; } people; main(int argc, char** argv) { int shm_id,i; key_t key; char temp; people *p_map; char* name = "/dev/shm/myshm2"; key = ftok(name,0); if(key==-1) perror("ftok error"); shm_id=shmget(key,4096,IPC_CREAT); if(shm_id==-1) { perror("shmget error"); return; } p_map=(people*)shmat(shm_id,NULL,0); temp='a'; for(i = 0;i<10;i++) { temp+=1; memcpy((*(p_map+i)).name,&temp,1); (*(p_map+i)).age=20+i; } if(shmdt(p_map)==-1) perror(" detach error "); } /********** testread.c ************/ #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> typedef struct{ char name[4]; int age; } people; main(int argc, char** argv) { int shm_id,i; key_t key; people *p_map; char* name = "/dev/shm/myshm2"; key = ftok(name,0); if(key == -1) perror("ftok error"); shm_id = shmget(key,4096,IPC_CREAT); if(shm_id == -1) { perror("shmget error"); return; } p_map = (people*)shmat(shm_id,NULL,0); for(i = 0;i<10;i++) { printf( "name:%s\n",(*(p_map+i)).name ); printf( "age %d\n",(*(p_map+i)).age ); } if(shmdt(p_map) == -1) perror(" detach error "); } </pre> </td> </tr> </tbody> </table> <br> <p>testwrite.c创徏一个系lV׃n内存区,q在其中写入格式化数据;testread.c讉K同一个系lV׃n内存区,d其中的格式化数据。分别把两个E序~译为testwrite?qing)testreadQ先后执?/testwrite?/testread ?/testread输出l果如下Q?/p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24; name: g age 25; name: h age 26; name: I age 27; name: j age 28; name: k age 29; </pre> </td> </tr> </tbody> </table> <br> <p>通过对试验结果分析,Ҏ(gu)pȝV与mmap()映射普通文件实现共享内存通信Q可以得出如下结论:(x)</p> <p>1?pȝV׃n内存中的数据Q从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的׃n内存通信可以指定何时数据写入磁盘文件中。注Q前面讲刎ͼpȝV׃n内存机制实际是通过映射Ҏ(gu)文gpȝshm中的文g实现的,文gpȝshm的安装点在交换分ZQ系l重新引导后Q所有的内容都丢失?/p> <p>2?pȝV׃n内存是随内核持箋的,即所有访问共享内存的q程都已l正常终止,׃n内存Z然存在(除非昑ּ删除׃n内存Q,在内栔R新引g前,对该׃n内存区域的Q何改写操作都一直保留?/p> <p>3?通过调用mmap()映射普通文件进行进E间通信Ӟ一定要注意考虑q程何时l止寚w信的媄(jing)响。而通过pȝV׃n内存实现通信的进E则不然。注Q这里没有给出shmctl的用范例,原理与消息队列大同小异?/p> <div id="ftvlj79" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><font color=#4c6e94><strong>回页?/strong></font></a></p> <p><a name=5><span id="p3rtbhr" class=atitle><strong><font size=5>l论Q?/font></strong></span></a></p> <p>׃n内存允许两个或多个进E共享一l定的存储区Q因为数据不需要来回复Ӟ所以是最快的一U进E间通信机制。共享内存可以通过mmap()映射普通文ӞҎ(gu)情况下还可以采用匿名映射Q机制实玎ͼ也可以通过pȝV׃n内存机制实现。应用接口和原理很简单,内部机制复杂。ؓ(f)了实现更安全通信Q往往q与信号灯等同步机制共同使用?/p> <p>׃n内存涉及(qing)C存储理以及(qing)文gpȝ{方面的知识Q深入理解其内部机制有一定的隑ֺQ关键还要紧紧抓住内怋用的重要数据l构。系lV׃n内存是以文g的Ş式组l在Ҏ(gu)文gpȝshm中的。通过shmget可以创徏或获得共享内存的标识W。取得共享内存标识符后,要通过shmat这个内存区映射到本q程的虚拟地址I间?/p> <!-- CMA ID: 21240 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl --><br> <p><a name=resources><span id="fr7hjhp" class=atitle><strong><font size=5>参考资?</font></strong></span></a></p> <p>[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出,脉络清晰?/p> <p>[2] UNIX|络~程W二P(x)q程间通信Q作者:(x)W.Richard StevensQ译者:(x)杨张,清华大学出版C。对mmap()有详l阐q?/p> <p>[3] Linux内核源代码情景分析(上)(j)Q毛h、胡希明著,江大学出版C,l出了mmap()相关的源代码分析?/p> <p>[4]shmget、shmat、shmctl、shmdt手册</p> <br> <img src ="http://www.shnenglu.com/deane/aggbug/139057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/deane/" target="_blank">李阳</a> 2011-01-21 20:59 <a href="http://www.shnenglu.com/deane/articles/139057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境q程间通信 ׃n内存Q上Q?/title><link>http://www.shnenglu.com/deane/articles/139056.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 21 Jan 2011 12:58:00 GMT</pubDate><guid>http://www.shnenglu.com/deane/articles/139056.html</guid><wfw:comment>http://www.shnenglu.com/deane/comments/139056.html</wfw:comment><comments>http://www.shnenglu.com/deane/articles/139056.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/deane/comments/commentRss/139056.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/deane/services/trackbacks/139056.html</trackback:ping><description><![CDATA[<p><br>采用׃n内存通信的一个显而易见的好处是效率高Q因E可以直接读写内存,而不需要Q何数据的拯。对于像道和消息队列等通信方式Q则需要在内核和用L(fng)间进行四ơ的数据拯Q而共享内存则只拷贝两ơ数据[1]Q一ơ从输入文g到共享内存区Q另一ơ从׃n内存区到输出文g。实际上Q进E之间在׃n内存Ӟq不Ld量数据后就解除映射Q有新的通信Ӟ再重新徏立共享内存区域。而是保持׃n区域Q直到通信完毕为止Q这P数据内容一直保存在׃n内存中,q没有写回文件。共享内存中的内容往往是在解除映射时才写回文g的。因此,采用׃n内存的通信方式效率是非帔R的?/p> <p>Linux?.2.x内核支持多种׃n内存方式Q如mmap()pȝ调用QPosix׃n内存Q以?qing)系lV׃n内存。linux发行版本如Redhat 8.0支持mmap()pȝ调用?qing)系lV׃n内存Q但q没实现Posix׃n内存Q本文将主要介绍mmap()pȝ调用?qing)系lV׃n内存API的原理及(qing)应用?/p> <p><a name=1><span id="n7pj1pp" class=atitle>一、内核怎样保证各个q程d到同一个共享内存区域的内存面</span></a></p> <p>1、page cache?qing)swap cache中页面的区分Q一个被讉K文g的物理页面都ȝ在page cache或swap cache中,一个页面的所有信息由struct page来描q。struct page中有一个域为指针mapping Q它指向一个struct address_spacecdl构。page cache或swap cache中的所有页面就是根据a(chn)ddress_spacel构以及(qing)一个偏U量来区分的?/p> <p>2、文件与address_spacel构的对应:(x)一个具体的文g在打开后,内核?x)在内存中?f)之徏立一个struct inodel构Q其中的i_mapping域指向一个address_spacel构。这P一个文件就对应一个address_spacel构Q一个address_space与一个偏U量能够定一个page cache 或swap cache中的一个页面。因此,当要d某个数据Ӟ很容易根据给定的文g?qing)数据在文g内的偏移量而找到相应的面?/p> <p>3、进E调用mmap()Ӟ只是在进E空间内新增了一块相应大的~冲区,q设|了相应的访问标识,但ƈ没有建立q程I间到物理页面的映射。因此,W一ơ访问该I间Ӟ?x)引发一个缺异常?/p> <p>4、对于共享内存映情况,~页异常处理E序首先在swap cache中寻扄标页Q符合address_space以及(qing)偏移量的物理)(j)Q如果找刎ͼ则直接返回地址Q如果没有找刎ͼ则判断该|否在交换?swap area)Q如果在Q则执行一个换入操作;如果上述两种情况都不满Q处理程序将分配新的物理面Qƈ把它插入到page cache中。进E最l将更新q程表?<br>注:(x)对于映射普通文件情况(非共享映)(j)Q缺异常处理程序首先会(x)在page cache中根据a(chn)ddress_space以及(qing)数据偏移量寻扄应的面。如果没有找刎ͼ则说明文件数据还没有d内存Q处理程序会(x)从磁盘读入相应的面Qƈq回相应地址Q同Ӟq程表也会(x)更新?</p> <p>5、所有进E在映射同一个共享内存区域时Q情况都一P在徏立线性地址与物理地址之间的映之后,不论q程各自的返回地址如何Q实际访问的必然是同一个共享内存区域对应的物理面?<br>注:(x)一个共享内存区域可以看作是Ҏ(gu)文gpȝshm中的一个文Ӟshm的安装点在交换区上?</p> <p>上面涉及(qing)C一些数据结构,围绕数据l构理解问题?x)容易一些?/p> <div id="tnnnppf" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><u><font color=#0000ff>回页?/font></u></a></p> <p><a name=2><span id="1p7jhzp" class=atitle>二、mmap()?qing)其相关pȝ调用</span></a></p> <p>mmap()pȝ调用使得q程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进E地址I间后,q程可以向访问普通内存一样对文gq行讉KQ不必再调用read()QwriteQ)(j){操作?/p> <p>注:(x)实际上,mmap()pȝ调用q不是完全ؓ(f)了用于共享内存而设计的。它本n提供了不同于一般对普通文件的讉K方式Q进E可以像d内存一样对普通文件的操作。而Posix或系lV的共享内存I(y)PC则纯_用于共享目的,当然mmap()实现׃n内存也是其主要应用之一?/p> <p><a name=N1006A><span id="rbjr1jr" class=smalltitle>1、mmap()pȝ调用形式如下Q?/span></a></p> <p>void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) <br>参数fd为即映到q程I间的文件描q字Q一般由open()q回Q同Ӟfd可以指定?1Q此旉指定flags参数中的MAP_ANONQ表明进行的是匿名映(不涉?qing)具体的文g名,避免了文件的创徏?qing)打开Q很昄只能用于h亲缘关系的进E间通信Q。len是映到调用q程地址I间的字节数Q它从被映射文g开头offset个字节开始算赗prot 参数指定׃n内存的访问权限。可取如下几个值的或:(x)PROT_READQ可读)(j) , PROT_WRITE Q可写)(j), PROT_EXEC Q可执行Q? PROT_NONEQ不可访问)(j)。flags׃下几个常值指定:(x)MAP_SHARED , MAP_PRIVATE , MAP_FIXEDQ其中,MAP_SHARED , MAP_PRIVATE必选其一Q而MAP_FIXED则不推荐使用。offset参数一般设?Q表CZ文g头开始映。参数addr指定文g应被映射到进E空间的起始地址Q一般被指定一个空指针Q此旉择起始地址的Q务留l内核来完成。函数的q回gؓ(f)最后文件映到q程I间的地址Q进E可直接操作起始地址值的有效地址。这里不再详l介lmmap()的参敎ͼ读者可参考mmap()手册获得进一步的信息?</p> <p><a name=N10074><span id="jdlvpfx" class=smalltitle>2、系l调用mmap()用于׃n内存的两U方式:(x)</span></a></p> <p>Q?Q用普通文件提供的内存映射Q适用于Q何进E之_(d)此时Q需要打开或创Z个文Ӟ然后再调用mmap()Q典型调用代码如下:(x) </p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode> fd=open(name, flag, mode); if(fd<0) ... </pre> </td> </tr> </tbody> </table> <br> <p>ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现׃n内存的通信方式有许多特点和要注意的地方Q我们将在范例中q行具体说明?</p> <p>Q?Q用特D文件提供匿名内存映:(x)适用于具有亲~关pȝq程之间Q由于父子进E特D的亲缘关系Q在父进E中先调用mmap()Q然后调用fork()。那么在调用fork()之后Q子q程l承父进E匿名映后的地址I间Q同样也l承mmap()q回的地址Q这P父子q程可以通过映射区域q行通信了。注意,q里不是一般的l承关系。一般来_(d)子进E单独维护从父进E承下来的一些变量。而mmap()q回的地址Q却q子进E共同维护?<br>对于h亲缘关系的进E实现共享内存最好的方式应该是采用匿名内存映的方式。此Ӟ不必指定具体的文Ӟ只要讄相应的标志即可,参见范例2?</p> <p><a name=N10089><span id="xrrphrh" class=smalltitle>3、系l调用munmap()</span></a></p> <p>int munmap( void * addr, size_t len ) <br>该调用在q程地址I间中解除一个映关p,addr是调用mmap()时返回的地址Qlen是映区的大。当映射关系解除后,对原来映地址的访问将DD错误发生?</p> <p><a name=N10093><span id="v7zpnnl" class=smalltitle>4、系l调用msync()</span></a></p> <p>int msync ( void * addr , size_t len, int flags) <br>一般说来,q程在映空间的对共享内容的改变q不直接写回到磁盘文件中Q往往在调用munmapQ)(j)后才执行该操作。可以通过调用msync()实现盘上文件内容与׃n内存区的内容一致?</p> <div id="bxfddlt" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><u><font color=#0000ff>回页?/font></u></a></p> <p><a name=3><span id="r7rpxfv" class=atitle>三、mmap()范例</span></a></p> <p>下面给Z用mmap()的两个范例:(x)范例1l出两个q程通过映射普通文件实现共享内存通信Q范?l出父子q程通过匿名映射实现׃n内存。系l调用mmap()有许多有的地方Q下面是通过mmapQ)(j)映射普通文件实现进E间的通信的范例,我们通过该范例来说明mmap()实现׃n内存的特点及(qing)注意事项?/p> <p><a name=N100A5><span id="vddl7hh" class=smalltitle>范例1Q两个进E通过映射普通文件实现共享内存通信</span></a></p> <p>范例1包含两个子程序:(x)map_normalfile1.c?qing)map_normalfile2.c。编译两个程序,可执行文件分别ؓ(f)map_normalfile1?qing)map_normalfile2。两个程序通过命o(h)行参数指定同一个文件来实现׃n内存方式的进E间通信。map_normalfile2试图打开命o(h)行参数指定的一个普通文Ӟ把该文g映射到进E的地址I间QƈҎ(gu)后的地址I间q行写操作。map_normalfile1把命令行参数指定的文件映到q程地址I间Q然后对映射后的地址I间执行L作。这P两个q程通过命o(h)行参数指定同一个文件来实现׃n内存方式的进E间通信?/p> <p>下面是两个程序代码:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>/*-------------map_normalfile1.c-----------*/ #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> typedef struct{ char name[4]; int age; }people; main(int argc, char** argv) // map a normal file as shared mem: { int fd,i; people *p_map; char temp; fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777); lseek(fd,sizeof(people)*5-1,SEEK_SET); write(fd,"",1); p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE, MAP_SHARED,fd,0 ); close( fd ); temp = 'a'; for(i=0; i<10; i++) { temp += 1; memcpy( ( *(p_map+i) ).name, &temp,2 ); ( *(p_map+i) ).age = 20+i; } printf(" initialize over \n ")Q? sleep(10); munmap( p_map, sizeof(people)*10 ); printf( "umap ok \n" ); } /*-------------map_normalfile2.c-----------*/ #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> typedef struct{ char name[4]; int age; }people; main(int argc, char** argv) // map a normal file as shared mem: { int fd,i; people *p_map; fd=open( argv[1],O_CREAT|O_RDWR,00777 ); p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE, MAP_SHARED,fd,0); for(i = 0;i<10;i++) { printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age ); } munmap( p_map,sizeof(people)*10 ); } </pre> </td> </tr> </tbody> </table> <br> <p>map_normalfile1.c首先定义了一个people数据l构Q(在这里采用数据结构的方式是因为,׃n内存区的数据往往是有固定格式的,q由通信的各个进E决定,采用l构的方式有普遍代表性)(j)。map_normfile1首先打开或创Z个文Ӟq把文g的长度设|ؓ(f)5个peoplel构大小。然后从mmap()的返回地址开始,讄?0个peoplel构。然后,q程睡眠10U钟Q等待其他进E映同一个文Ӟ最后解除映?/p> <p>map_normfile2.c只是单的映射一个文Ӟq以people数据l构的格式从mmap()q回的地址处读?0个peoplel构Qƈ输出d的|然后解除映射?/p> <p>分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运?/map_normalfile2 /tmp/test_shmQ程序输出结果如下:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>initialize over umap ok </pre> </td> </tr> </tbody> </table> <br> <p>在map_normalfile1输出initialize over 之后Q输出umap ok之前Q在另一个终端上q行map_normalfile2 /tmp/test_shmQ将?x)生如下输?Z节省I间Q输出结果ؓ(f)E作整理后的l果)Q?/p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24; name: g age 25; name: h age 26; name: I age 27; name: j age 28; name: k age 29; </pre> </td> </tr> </tbody> </table> <br> <p>在map_normalfile1 输出umap ok后,q行map_normalfile2则输出如下结果:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24; name: age 0; name: age 0; name: age 0; name: age 0; name: age 0; </pre> </td> </tr> </tbody> </table> <br> <p><strong>从程序的q行l果中可以得出的l论</strong> </p> <p>1?最l被映射文g的内容的长度不会(x)过文g本n的初始大,x不能改变文件的大小Q?/p> <p>2?可以用于q程通信的有效地址I间大小大体上受限于被映文件的大小Q但不完全受限于文g大小。打开文g被截短ؓ(f)5个peoplel构大小Q而在map_normalfile1中初始化?0个people数据l构Q在恰当时候(map_normalfile1输出initialize over 之后Q输出umap ok之前Q调用map_normalfile2?x)发现map_normalfile2输出全?0个peoplel构的|后面给l讨论?<br>注:(x)在linux中,内存的保护是以页为基本单位的Q即使被映射文g只有一个字节大,内核也会(x)为映分配一个页面大的内存。当被映文件小于一个页面大时Q进E可以对从mmap()q回地址开始的一个页面大进行访问,而不?x)出错;但是Q如果对一个页面以外的地址I间q行讉KQ则D错误发生Q后面将q一步描q。因此,可用于进E间通信的有效地址I间大小不会(x)过文g大小?qing)一个页面大的和?</p> <p>3?文g一旦被映射后,调用mmap()的进E对q回地址的访问是Ҏ(gu)一内存区域的访问,暂时q了磁盘上文g的媄(jing)响。所有对mmap()q回地址I间的操作只在内存中有意义,只有在调用了munmap()后或者msync()Ӟ才把内存中的相应内容写回盘文gQ所写内容仍然不能超q文件的大小?/p> <p><a name=N100E0><span id="x7b1nvf" class=smalltitle>范例2Q父子进E通过匿名映射实现׃n内存</span></a></p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>#include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> typedef struct{ char name[4]; int age; }people; main(int argc, char** argv) { int i; people *p_map; char temp; p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS,-1,0); if(fork() == 0) { sleep(2); for(i = 0;i<5;i++) printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age); (*p_map).age = 100; munmap(p_map,sizeof(people)*10); //实际上,q程l止Ӟ?x)自动解除映? exit(); } temp = 'a'; for(i = 0;i<5;i++) { temp += 1; memcpy((*(p_map+i)).name, &temp,2); (*(p_map+i)).age=20+i; } sleep(5); printf( "parent read: the first people,s age is %d\n",(*p_map).age ); printf("umap\n"); munmap( p_map,sizeof(people)*10 ); printf( "umap ok\n" ); } </pre> </td> </tr> </tbody> </table> <br> <p>考察E序的输出结果,体会(x)父子q程匿名׃n内存Q?/p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>child read: the 1 people's age is 20 child read: the 2 people's age is 21 child read: the 3 people's age is 22 child read: the 4 people's age is 23 child read: the 5 people's age is 24 parent read: the first people,s age is 100 umap umap ok </pre> </td> </tr> </tbody> </table> <br> <div id="b9vnx7n" class=ibm-alternate-rule> <hr> </div> <p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link ><u><font color=#0000ff>回页?/font></u></a></p> <p><a name=4><span id="hdldlll" class=atitle>四、对mmap()q回地址的访?/span></a></p> <p>前面对范例运行结构的讨论中已l提刎ͼlinux采用的是式理机制。对于用mmap()映射普通文件来_(d)q程?x)在自己的地址I间新增一块空_(d)I间大小由mmap()的len参数指定Q注意,q程q不一定能够对全部新增I间都能q行有效讉K。进E能够访问的有效地址大小取决于文件被映射部分的大。简单的_(d)能够容纳文g被映部分大的最页面个数决定了q程从mmap()q回的地址开始,能够有效讉K的地址I间大小。超q这个空间大,内核?x)根据超q的严重E度q回发送不同的信号l进E。可用如下图C明:(x)</p> <br><img height=169 alt="?1" src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/image001.gif" width=580 border=0> <br> <p>注意Q文件被映射部分而不是整个文件决定了q程能够讉K的空间大,另外Q如果指定文件的偏移部分Q一定要注意为页面大的整数倍。下面是对进E映地址I间的访问范例:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>#include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> typedef struct{ char name[4]; int age; }people; main(int argc, char** argv) { int fd,i; int pagesize,offset; people *p_map; pagesize = sysconf(_SC_PAGESIZE); printf("pagesize is %d\n",pagesize); fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777); lseek(fd,pagesize*2-100,SEEK_SET); write(fd,"",1); offset = 0; //此处offset = 0~译成版?Qoffset = pagesize~译成版? p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset); close(fd); for(i = 1; i<10; i++) { (*(p_map+pagesize/sizeof(people)*i-2)).age = 100; printf("access page %d over\n",i); (*(p_map+pagesize/sizeof(people)*i-1)).age = 100; printf("access page %d edge over, now begin to access page %d\n",i, i+1); (*(p_map+pagesize/sizeof(people)*i)).age = 100; printf("access page %d over\n",i+1); } munmap(p_map,sizeof(people)*10); } </pre> </td> </tr> </tbody> </table> <br> <p>如程序中所注释的那P把程序编译成两个版本Q两个版本主要体现在文g被映部分的大小不同。文件的大小介于一个页面与两个面之间Q大ؓ(f)Qpagesize*2-99Q,版本1的被映射部分是整个文Ӟ版本2的文件被映射部分是文件大减M个页面后的剩余部分,不到一个页面大?大小为:(x)pagesize-99)。程序中试图讉K每一个页面边界,两个版本都试囑֜q程I间中映pagesize*3的字节数?/p> <p>版本1的输出结果如下:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>pagesize is 4096 access page 1 over access page 1 edge over, now begin to access page 2 access page 2 over access page 2 over access page 2 edge over, now begin to access page 3 Bus error //被映文件在q程I间中覆盖了两个面Q此Ӟq程试图讉KW三个页? </pre> </td> </tr> </tbody> </table> <br> <p>版本2的输出结果如下:(x)</p> <table cellSpacing=0 cellPadding=0 width="100%" border=0> <tbody> <tr> <td class=code-outline> <pre class=displaycode>pagesize is 4096 access page 1 over access page 1 edge over, now begin to access page 2 Bus error //被映文件在q程I间中覆盖了一个页面,此时Q进E试图访问第二个面 </pre> </td> </tr> </tbody> </table> <br> <p>l论Q采用系l调用mmap()实现q程间通信是很方便的,在应用层上接口非常简z。内部实现机制区涉及(qing)Clinux存储理以及(qing)文gpȝ{方面的内容Q可以参考一下相关重要数据结构来加深理解。在本专题的后面部分Q将介绍pȝv׃n内存的实现?/p> <!-- CMA ID: 22175 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl --><br> <p><a name=resources><span id="bbj79nf" class=atitle>参考资?</span></a></p> <p>[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出,脉络清晰?/p> <p>[2] UNIX|络~程W二P(x)q程间通信Q作者:(x)W.Richard StevensQ译者:(x)杨张,清华大学出版C。对mmap()有详l阐q?/p> <p>[3] Linux内核源代码情景分析(上)(j)Q毛h、胡希明著,江大学出版C,l出了mmap()相关的源代码分析?/p> <p>[4]mmap()手册<br></p> <img src ="http://www.shnenglu.com/deane/aggbug/139056.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/deane/" target="_blank">李阳</a> 2011-01-21 20:58 <a href="http://www.shnenglu.com/deane/articles/139056.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shmathttp://www.shnenglu.com/deane/articles/139048.html李阳李阳Fri, 21 Jan 2011 09:31:00 GMThttp://www.shnenglu.com/deane/articles/139048.htmlhttp://www.shnenglu.com/deane/comments/139048.htmlhttp://www.shnenglu.com/deane/articles/139048.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/139048.htmlhttp://www.shnenglu.com/deane/services/trackbacks/139048.html

  作用:׃n内存区对象映到调用q程的地址I间

  核心处理函数Q?void *shmat( int shmid , char *shmaddr , int shmflag );shmat()是用来允许本q程讉K一块共享内存的函数?
  int shmid是那块共享内存的ID?
  char *shmaddr是共享内存的起始地址
  int shmflag是本q程对该内存的操作模式。如果是SHM_RDONLY的话Q就是只L式。其它的是读写模?
  成功Ӟq个函数q回׃n内存的v始地址。失败时q回-1
  最q用到内存共享,攉整理了些资料Q做了个单的Ҏ(gu)
  mmappȝ调用 pȝV׃n内存
获取׃n
  内存I(y)D
#include <sys/mman.h>
  fd=open(name ,flag,mode);
  if(fd<0)
  ….
#include <sys/ipc.h>
  #include <sys/shm.h>
  int shmget(key_t key, size_t size, int shmflg);
映射内存 ptr=mmap(NULL,len, PROT_READ|PROT_WRITE, 
  MAP_SHARED , fd , 0); 
void *shmat( int shmid , char *shmaddr , int shmflag );
解除映射 int munmap( void * addr, size_t len ) ; int shmdt( char *shmaddr );
  使进E中的映内存无效化Q不可以使用。但是保留空?/td>
其它 同步Q?
  int msync ( void * addr , size_t len, int flags);
控制Q?
  shmctl( shmid , IPC_STAT , &buf ); 
  // 取得׃n内存的状?
  shmctl( shmid , IPC_RMID , &buf ); 
  // 删除׃n内存–删除׃n内存Q彻底不可用Q释攄?/td>


李阳 2011-01-21 17:31 发表评论
]]>
linux下获取时间的若干函数http://www.shnenglu.com/deane/articles/118718.html李阳李阳Fri, 25 Jun 2010 08:00:00 GMThttp://www.shnenglu.com/deane/articles/118718.htmlhttp://www.shnenglu.com/deane/comments/118718.htmlhttp://www.shnenglu.com/deane/articles/118718.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/118718.htmlhttp://www.shnenglu.com/deane/services/trackbacks/118718.html
asctimeQ将旉和日期以字符串格式表C)(j)
 
 
相关函数
 timeQctimeQgmtimeQlocaltime
 
表头文g
 #include<time.h>
 
定义函数
 char * asctime(const struct tm * timeptr);
 
函数说明
 asctime()参数timeptr所指的tml构中的信息转换成真实世界所使用的时间日期表C方法,然后结果以字符串Ş态返回。此函数已经由时{换成当地旉Q字W串格式?“Wed Jun 30 21:49:08 1993\n”
 
q回?br> 若再调用相关的时间日期函敎ͼ此字W串可能?x)被破坏。此函数与ctime不同处在于传入的参数是不同的l构?br> 
附加说明
 q回一字符串表C目前当地的旉日期?br> 
范例
 #include <time.h>
main()
{
time_t timep;
time (&timep);
printf(“%s”,asctime(gmtime(&timep)));
}
 
执行
 Sat Oct 28 02:10:06 2000
 
ctimeQ将旉和日期以字符串格式表C)(j)
 
相关函数
 timeQasctimeQgmtimeQlocaltime
 
表头文g
 #include<time.h>
 
定义函数
 char *ctime(const time_t *timep);
 
函数说明
 ctime()参数timep所指的time_tl构中的信息转换成真实世界所使用的时间日期表C方法,然后结果以字符串Ş态返回。此函数已经由时{换成当地旉Q字W串格式?#8220;Wed Jun 30 21 :49 :08 1993\n”。若再调用相关的旉日期函数Q此字符串可能会(x)被破坏?br> 
q回?br> q回一字符串表C目前当地的旉日期?br> 
范例
 #include<time.h>
main()
{
time_t timep;
time (&timep);
printf(“%s”,ctime(&timep));
}
 
执行
 Sat Oct 28 10 : 12 : 05 2000
 
gettimeofdayQ取得目前的旉Q?br> 
相关函数
 timeQctimeQftimeQsettimeofday
 
表头文g
 #include <sys/time.h>
#include <unistd.h>
 
定义函数
 int gettimeofday ( struct timeval * tv , struct timezone * tz )
 
函数说明
 gettimeofday()?x)把目前的时间有tv所指的l构q回Q当地时区的信息则放到tz所指的l构中?br>timevall构定义?
struct timeval{
long tv_sec; /*U?/
long tv_usec; /*微秒*/
};
timezone l构定义?
struct timezone{
int tz_minuteswest; /*和Greenwich 旉差了多少分钟*/
int tz_dsttime; /*日光节约旉的状?/
};
上述两个l构都定义在/usr/include/sys/time.h。tz_dsttime 所代表的状态如?br>DST_NONE /*不?/
DST_USA /*国*/
DST_AUST /*x*/
DST_WET /*西欧*/
DST_MET /*中欧*/
DST_EET /*东欧*/
DST_CAN /*加拿?/
DST_GB /*大不列颠*/
DST_RUM /*|马g*/
DST_TUR /*土耛_*/
DST_AUSTALT /*xQ?986q以后)(j)*/
 
q回?br> 成功则返?Q失败返回-1Q错误代码存于errno。附加说明EFAULT指针tv和tz所指的内存I间出存取权限?br> 
范例
 #include<sys/time.h>
#include<unistd.h>
main(){
struct timeval tv;
struct timezone tz;
gettimeofday (&tv , &tz);
printf(“tv_sec; %d\n”, tv,.tv_sec) ;
printf(“tv_usec; %d\n”,tv.tv_usec);
printf(“tz_minuteswest; %d\n”, tz.tz_minuteswest);
printf(“tz_dsttime, %d\n”,tz.tz_dsttime);
}
 
执行
 tv_sec: 974857339
tv_usec:136996
tz_minuteswest:-540
tz_dsttime:0
 
gmtimeQ取得目前时间和日期Q?br> 
相关函数
 time,asctime,ctime,localtime
 
表头文g
 #include<time.h>
 
定义函数
 struct tm*gmtime(const time_t*timep);
 
函数说明
 gmtime()参数timep 所指的time_t l构中的信息转换成真实世界所使用的时间日期表C方法,然后结果由l构tmq回?br>l构tm的定义ؓ(f)
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
int tm_sec 代表目前U数Q正常范围ؓ(f)0-59Q但允许?1U?br>int tm_min 代表目前分数Q范?-59
int tm_hour 从午夜算L(fng)时数Q范围ؓ(f)0-23
int tm_mday 目前月䆾的日敎ͼ范围01-31
int tm_mon 代表目前月䆾Q从一月算P范围?-11
int tm_year ?900 q算赯今的q数
int tm_wday 一星期的日敎ͼ从星期一vQ范围ؓ(f)0-6
int tm_yday 从今q??日算赯今的天数Q范围ؓ(f)0-365
int tm_isdst 日光节约旉的旗?br>此函数返回的旉日期未经时区转换Q而是UTC旉?br> 
q回?br> q回l构tm代表目前UTC 旉
 
范例
 #include <time.h>
main(){
char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
time_t timep;
struct tm *p;
time(&timep);
p=gmtime(&timep);
printf(“%d%d%d”,(1900+p->tm_year), (1+p->tm_mon),p->tm_mday);
printf(“%s%d;%d;%d\n”, wday[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec);
}
 
执行
 2000/10/28 Sat 8:15:38
 
localtimeQ取得当地目前时间和日期Q?br> 
相关函数
 time, asctime, ctime, gmtime
 
表头文g
 #include<time.h>
 
定义函数
 struct tm *localtime(const time_t * timep);
 
函数说明
 localtime()参数timep所指的time_tl构中的信息转换成真实世界所使用的时间日期表C方法,然后结果由l构tmq回。结构tm的定义请参考gmtime()。此函数q回的时间日期已l{换成当地时区?br> 
q回?br> q回l构tm代表目前的当地时间?br> 
范例
 #include<time.h>
main(){
char *wday[]={“Sun”,”Mon”,”Tue”,”Wed”,”Thu”,”Fri”,”Sat”};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得当地旉*/
printf (“%d%d%d ”, (1900+p->tm_year),( l+p->tm_mon), p->tm_mday);
printf(“%s%d:%d:%d\n”, wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec);
}
 
执行
 2000/10/28 Sat 11:12:22
 
mktimeQ将旉l构数据转换成经q的U数Q?br> 
相关函数
 timeQasctimeQgmtimeQlocaltime
 
表头文g
 #include<time.h>
 
定义函数
 time_t mktime(strcut tm * timeptr);
 
函数说明
 mktime()用来参数timeptr所指的tml构数据转换成从公元1970q????? U算赯今的UTC旉所l过的秒数?br> 
q回?br> q回l过的秒数?br> 
范例
 /* 用time()取得旉Q秒敎ͼ(j)Q利用localtime()
转换成struct tm 再利用mktineQ)(j)struct tm转换成原来的U数*/
#include<time.h>
main()
{
time_t timep;
strcut tm *p;
time(&timep);
printf(“time() : %d \n”,timep);
p=localtime(&timep);
timep = mktime(p);
printf(“time()->localtime()->mktime():%d\n”,timep);
}
 
执行
 time():974943297
time()->localtime()->mktime():974943297
 
settimeofdayQ设|目前时_(d)(j)
 
相关函数
 timeQctimeQftimeQgettimeofday
 
表头文g
 #include<sys/time.h>
#include<unistd.h>
 
定义函数
 int settimeofday ( const struct timeval *tv,const struct timezone *tz);
 
函数说明
 settimeofday()?x)把目前旉设成由tv所指的l构信息Q当地时Z息则设成tz所指的l构。详l的说明请参考gettimeofday()。注意,只有root权限才能使用此函CҎ(gu)间?br> 
q回?br> 成功则返?Q失败返回-1Q错误代码存于errno?br> 
错误代码
 EPERM q由root权限调用settimeofdayQ)(j)Q权限不够?br>EINVAL 时区或某个数据是不正的Q无法正设|时间?br> 
timeQ取得目前的旉Q?br> 
相关函数
 ctimeQftimeQgettimeofday
 
表头文g
 #include<time.h>
 
定义函数
 time_t time(time_t *t);
 
函数说明
 此函C(x)q回从公?970q??日的UTC旉???U算起到现在所l过的秒数。如果t qI指针的话,此函C?x)将q回值存到t指针所指的内存?br> 
q回?br> 成功则返回秒敎ͼp|则返?(time_t)-1)|错误原因存于errno中?br> 
范例
 #include<time.h>
mian()
{
int seconds= time((time_t*)NULL);
printf(“%d\n”,seconds);
}
 



李阳 2010-06-25 16:00 发表评论
]]>
vim目录?wi)插件NERD tree的安装方?http://www.shnenglu.com/deane/articles/109379.html李阳李阳Wed, 10 Mar 2010 10:33:00 GMThttp://www.shnenglu.com/deane/articles/109379.htmlhttp://www.shnenglu.com/deane/comments/109379.htmlhttp://www.shnenglu.com/deane/articles/109379.html#Feedback1http://www.shnenglu.com/deane/comments/commentRss/109379.htmlhttp://www.shnenglu.com/deane/services/trackbacks/109379.htmlpȝ Debian 4.0
[1]下蝲NERD tree
wget http://www.vim.org/scripts/download_script.php?src_id=9870 -O NERDTree.zip

[2]d.vim配置
查看~/.vim目录Q如果不存在Q创建此目录

[3]copy NERDTree.zip to ~/.vim

[4]unzip NERDTree.zip

ok .

使用Ҏ(gu)和效果如图:(x)
nerd_tree.png





李阳 2010-03-10 18:33 发表评论
]]>
Vim之Nerd Tree杂草帮助http://www.shnenglu.com/deane/articles/109378.html李阳李阳Wed, 10 Mar 2010 10:32:00 GMThttp://www.shnenglu.com/deane/articles/109378.htmlhttp://www.shnenglu.com/deane/comments/109378.htmlhttp://www.shnenglu.com/deane/articles/109378.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/109378.htmlhttp://www.shnenglu.com/deane/services/trackbacks/109378.html 

  一直苦于没有好的文件浏览器Q别人所U道的WinManager我也不习(fn)惯不喜欢Q虽然听说过NerdTree却也因ؓ(f)觉得不会(x)怎么栯(g)没有试用。这ơ实在忍不过了,一试之下才发现Q原来还是很手滴——到底还是树(wi)状显C方式好?#8230;…比netrw方便……
  仔细看了一遍帮助,好多快捷键!没用熟了q真C住。ؓ(f)了哀(zhn)我那已成天外飞仙的pQ将nerdtree的帮助录一部分如下Q以供参考用:(x)

  1 目录
  2 ================================================================================
  3 1.?br>  4 2.功能
  5   2.1.全局命o(h)
  6   2.2.书签
  7     2.2.1.书签?br>  8     2.2.2.书签命o(h)
  9     2.2.3.无效书签
 10   2.3.Nerdtree映射
 11   2.4.文gpȝ菜单
 12 3.选项
 13   3.1.选项ȝ
 14   3.2.选项明细
 15
 16
 17 1.?/strong>
 18 --------------------------------------------------------------------------------
 19 Nerd tree可以让你览文gpȝq打开文g或目录?br> 20 你可以通过键盘或鼠标控制它以树(wi)状图昄文gpȝQ也可以在其中进行一些简单的文gpȝ操作?br> 21
 22 nerdtree提供如下功能?qing)特性:(x)
 23 *以承树(wi)的Ş式显C文件和目录
 24 *对如下类型的文gq行不同的高亮显C?br> 25  *文g
 26  *目录
 27  *sym-links
 28  *快捷方式
 29  *只读文g
 30  *可执行文?br> 31 *提供许多映射来控制树(wi)状结?br> 32  *对展开/收拢/览目录l点的映?br> 33  *对在新的或已存在的窗口或Tab中打开文g的映?br> 34  *Ҏ(gu)变根l点的映?br> 35  *Mappings to navigate around the tree
 36 *可以文件和目录d到收藏夹
 37 *可以用鼠标进行大部分的树(wi)状结构导?br> 38 *Ҏ(gu)(wi)状结构内容的qo(h)Q可在运行时切换Q?br> 39  *自定义文件过滤器可以L某些文gQ比如vim备䆾文g{)(j)的显C?br> 40  *可选是否显C隐藏文?br> 41  *可选不昄文g只显C目?br> 42 *提供文本文gpȝ菜单来创?删除/Ud/复制目录或文?br> 43 *可以自定义NerdH口的位|和大小
 44 *可以自定义结Ҏ(gu)序方?br> 45 *当你览文gpȝ的时候就?x)有一个文件系l的模型被创建或l护。这样做有几个优点:(x)
 46  *所有文件系l信息都被缓存了Q有需要的时候只要重新读入缓?br> 47  *如果重新览之后讉Kq的tree的一部分Q结点就?x)以上次保持的展开或合拢的样子昄
 48 *该脚本能C光标位置和窗口位|,所以可以用NERDTreeToggle来切换tree的显CZ隐藏
 49 *对于多TabQ可以共享一个TreeQ也可以各自拥有各自的treeQ还可以混合以上两种方式
 50 *默认情况下,该脚本覆盖vim的默认文件浏览器(netrw)Q所以如果直接输?edit命o(h)也会(x)用nerd?wi)打开
 51
 52
 53
 54 2.功能
 55 --------------------------------------------------------------------------------
 56   2.1.全局命o(h)
 57   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 58   :NERDTree [<start-directory> | <bookmark>]
 59       打开一个NerdtreeQ根l点由参数指定,不指定参数就是以当前目录为根l点
 60   :NERDTreeFromBookmark <bookmark>
 61       打开一个NerdtreeQ根l点由参数所指定的书{?br> 62   :NERDTreeToggle [<start-directory> | <bookmark>]
 63       在当前Tab中如果Nerdtree已经存在Q就切换昄与隐藏;
 64       如果不存在,q当于执行:NERDTree命o(h)
 65   :NERDTreeMirror
 66       从另一个Tab中共享一个NerdTreeq来Q在当前Tab的Tree所作的改变也反应到原Tab中)(j)
 67       如果d只有一个TreeQ就直接׃nQ如果不止一个,׃(x)询问׃n哪个
 68   :NERDTreeClose
 69       在当前Tab中关闭Tree
 70
 71   2.2.书签
 72   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 73   在NerdTree中,书签用于标记某个感兴的文g或目录,比如可以用书{标记所有Project目录
 74
 75     2.2.1.书签?/strong>
 76     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 77     如果书签被激z,则显CZ?wi)状囄上?br> 78     可以双击或用NERDTree-o来激z选中文g
 79     可以用NERDTree-t映射佉K中文g用新Tab打开Qƈ跛_新tab?br> 80     可以用NERDTree-T映射佉K中文g用新Tab打开Q但不蟩到新Tab?br> 81
 82     2.2.2.书签命o(h)
 83     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 84     以下命o(h)只在在Nerdtree的buffer中有?br> 85     :Bookmark <name>
 86         选中l点dC{ֈ表中Qƈ命名为nameQ书{֐不可包含I格Q;
 87         如与现有书签重名Q则覆盖现有书签?br> 88     :BookmarkToRoot <bookmark>
 89         以指定目录书{或文g书签的父目录作ؓ(f)根结Ҏ(gu)CNerdTree
 90     :RevealBookmark <bookmark>
 91         如果指定书签已经存在于当前目录树(wi)下,打开它的上层l点q中该书{?
 92     :OpenBookmark <bookmark>
 93         打开指定的文件。(参数必须是文件书{)(j)
 94         如果该文件在当前的目录树(wi)下,则打开它的上层l点q中该书{?br> 95     :ClearBookmarks [<bookmarks>]
 96         清除指定书签Q如未指定参敎ͼ则清除所有书{?br> 97     :ClearAllBookmarks
 98         清除所有书{?br> 99     :ReadBookmarks
100         重新d'NERDTreeBookmarksFile'中的所有书{?br>101
102     2.2.3.无效书签
103     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
104     如果监测到无效书{,脚本׃(x)发布一个错误消息ƈ无效书{Z可用Q?br>105     无效书签被Ud书签文g的最后,在有效书{֒无效书签之间有一个空行?br>106     书签文g中的每一行代表一个书{,格式?lt;bookmark name><space><full path to the bookmark location>
107     如果修正了某个无效书{,则可以重启vim或?ReadBookmarks命o(h)重新d书签信息
108
109   2.3.Nerdtree映射
110   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
111   o.......在已有窗口中打开文g、目录或书签Qƈ跛_该窗?nbsp;  .....|NERDTree-o|  
112   go......在已有窗口中打开文g、目录或书签Q但不蟩到该H口 .....|NERDTree-go|
113   t.......在新Tab中打开选中文g/书签Qƈ跛_新Tab          .....|NERDTree-t|  
114   T.......在新Tab中打开选中文g/书签Q但不蟩到新Tab        .....|NERDTree-T|  
115   i.......split一个新H口打开选中文gQƈ跛_该窗?nbsp;       .....|NERDTree-i|  
116   gi......split一个新H口打开选中文gQ但不蟩到该H口      .....|NERDTree-gi|
117   s.......vsp一个新H口打开选中文gQƈ跛_该窗?nbsp;         .....|NERDTree-s|  
118   gs......vsp一个新H口打开选中文gQ但不蟩到该H口        .....|NERDTree-gs|
119   !.......执行当前文g                                     .....|NERDTree-!|  
120   O.......递归打开选中l点下的所有目?nbsp;                    .....|NERDTree-O|  
121   x.......合拢选中l点的父目录                             .....|NERDTree-x|  
122   X.......递归合拢选中l点下的所有目?nbsp;                    .....|NERDTree-X|  
123   e.......Edit the current dif                             .....|NERDTree-e|  
124
125   双击......相当于NERDTree-o
126   中键......Ҏ(gu)件相当于NERDTree-iQ对目录相当于NERDTree-e
127
128   D.......删除当前书签
129           
130   P.......跛_根结?br>131   p.......跛_父结?br>132   K.......跛_当前目录下同U的W一个结?br>133   J.......跛_当前目录下同U的最后一个结?br>134   <C-j>...跛_当前目录下同U的前一个结?br>135   <C-k>...跛_当前目录下同U的后一个结?br>136           
137   C.......选中目录或选中文g的父目录设ؓ(f)根结?br>138   u.......当前根l点的父目录设ؓ(f)根目录,q变成合拢原根结?br>139   U.......当前根l点的父目录设ؓ(f)根目录,但保持展开原根l点
140   r.......递归h选中目录
141   R.......递归h根结?br>142   m.......昄文gpȝ菜单
143   cd......CWD设ؓ(f)选中目录
144           
145   I.......切换是否昄隐藏文g
146   f.......切换是否使用文gqo(h)?br>147   F.......切换是否昄文g
148   B.......切换是否昄书签
149           
150   q.......关闭NerdTreeH口
151   ?.......切换是否昄Quick Help
152         
153   
154
155   2.4.文gpȝ菜单
156   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
157   帮助说中包含新徏、复制、移动、删除四U命令,但copy只支?nixpȝ
158
159 3.自定义选项
160 --------------------------------------------------------------------------------
161 loaded_nerd_tree            不用NerdTree脚本
162 NERDChristmasTree           让Tree把自q装饰得多姿多彩漂亮点
163 NERDTreeAutoCenter          控制当光标移动超q一定距LQ是否自动将焦点调整到屏中心
164 NERDTreeAutoCenterThreshold 与NERDTreeAutoCenter配合使用
165 NERDTreeCaseSensitiveSort   排序时是否大写敏感
166 NERDTreeChDirMode           定是否改变Vim的CWD
167 NERDTreeHighlightCursorline 是否高亮昄光标所在行
168 NERDTreeHijackNetrw         是否使用:edit命o(h)时打开W二NerdTree
169 NERDTreeIgnore              默认?#8220;无视”文g
170 NERDTreeBookmarksFile       指定书签文g
171 NERDTreeMouseMode           指定鼠标模式Q?.双击打开Q?.单目录双文gQ?.单击打开Q?br>172 NERDTreeQuitOnOpen          打开文g后是否关闭NerdTreeH口
173 NERDTreeShowBookmarks       是否默认昄书签列表
174 NERDTreeShowFiles           是否默认昄文g
175 NERDTreeShowHidden          是否默认昄隐藏文g
176 NERDTreeShowLineNumbers     是否默认昄行号
177 NERDTreeSortOrder           排序规则
178 NERDTreeStatusline          H口状态栏
179 NERDTreeWinPos              H口位置Q?left' or 'right'Q?br>180 NERDTreeWinSize             H口?br>
我的配置Q?br> 1 "NERD Tree
 2 let NERDChristmasTree=1
 3 let NERDTreeAutoCenter=1
 4 let NERDTreeBookmarksFile=$VIM.'\Data\NerdBookmarks.txt'
 5 let NERDTreeMouseMode=2
 6 let NERDTreeShowBookmarks=1
 7 let NERDTreeShowFiles=1
 8 let NERDTreeShowHidden=1
 9 let NERDTreeShowLineNumbers=1
10 let NERDTreeWinPos='left'
11 let NERDTreeWinSize=31
12 nnoremap <silent> <leader>f :NERDTreeToggle<CR>

 原文地址 http://mt-zj.blogspot.com/2009/02/vimnerd-tree.html


李阳 2010-03-10 18:32 发表评论
]]>
vi/vim使用q阶: vimgdb调试时的常见问题?qing)解?/title><link>http://www.shnenglu.com/deane/articles/109366.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 10 Mar 2010 08:59:00 GMT</pubDate><guid>http://www.shnenglu.com/deane/articles/109366.html</guid><wfw:comment>http://www.shnenglu.com/deane/comments/109366.html</wfw:comment><comments>http://www.shnenglu.com/deane/articles/109366.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/deane/comments/commentRss/109366.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/deane/services/trackbacks/109366.html</trackback:ping><description><![CDATA[<br>转:(x)<br><br>  <p align=left><strong><span>[ </span></strong><strong><span>问题一</span></strong><strong><span>: </span></strong><strong><span>q行</span></strong><strong><span>GDB</span></strong><strong><span>命o(h)时提C?/span></strong><strong><span>"unable to read from GDB pseudo tty"? ]</span></strong></p> <p align=left><span>有读者在试图执行</span><span>GDB</span><span>命o(h)Ӟ出现上面的提C?/span><span> </span></p> <p align=left><span>出现q个问题是由于没有正设|?/span><span>gdbprg</span><span>变量。用下面的命o(h)讄一?/span><span>GDB</span><span>E序的位|即可解x问题Q?/span><span> </span></p> <div> <p align=left><span>:set gdbprg=/path/to/gdb </span></p> </div> <p align=left><span>h上面?/span><em><span>/path/to/gdb</span></em><span>替换成你计算Z</span><span>GDB</span><span>E序所在的路径。你可以把这句话加到你的</span><span><a target=_top><span>vimrc</span></a></span><span>中,q样每次启动</span><span>vi</span><span>时会(x)自动讄此变量?/span><span> </span></p> <p align=left><strong><span>[ </span></strong><strong><span>问题二:(x)</span></strong><strong><span> </span></strong><strong><span>提示</span></strong><strong><span>"GDB busy: command discarded, please try again"? ]</span></strong></p> <p align=left><span>当你的程序需要用</span><em><span>scanf()</span></em><span>或?/span><em><span>getchar()</span></em><span>q类函数d用户输入Ӟ你可能会(x)看到q这L(fng)提示Q?/span><span> </span></p> <p align=left><span>GDB busy: command discarded, please try again </span></p> <p align=left><span>出现q个提示Q说明你?/span><span>GDB</span><span>正在{待用户输入Q所以无法响应你所输入的其?/span><span>GDB</span><span>命o(h)?/span><span> </span></p> <p align=left><span>在?/span><span>vimgdb</span><span>Ӟ如果你的E序需要读取用戯入,你必通过</span><span>GDB</span><span>?/span><strong><span>tty</span></strong><span>命o(h)?/span><strong><span>run</span></strong><span>来重定向E序的标准输入?/span><span> </span></p> <p align=left><span>首先Q你打开一个终?/span><span>(</span><span>可以?/span><span>xterm/rxvt/urxvt/putty/…)</span><span>Q在此终端内输入命o(h)</span><strong><span>tty</span></strong><span>Q?/span><span> </span></p> <div> <p align=left> </p> <p align=left><span>$ tty</span></p> <p align=left><span>/dev/pts/17 </span></p> </div> <p align=left><span>q条命o(h)用来昄此终端所使用的设备文件名。获得了q个讑֤文g名后Q你可以重定向E序的标准输入输出到q个l端Q在</span><span>GDB</span><span>中输入下面的命o(h)Q?/span><span> </span></p> <div> <p align=left><span>tty /dev/pts/17 </span></p> </div> <p align=left><span>执行完这条命令后Q程序的标准输入</span><span>/</span><span>输出p重定向到</span><em><span>/dev/pts/17</span></em><span>了,q样Q当执行?/span><em><span>scanf()</span></em><span>?/span><em><span>getchar()</span></em><span>函数Ӟ切换到那个终端输入指定参敎ͼ然后回RQ程序就?x)l向下执行?/span><span> </span></p> <p align=left><strong><span>[ </span></strong><strong><span>问题三:(x)</span></strong><strong><span> vimgdb</span></strong><strong><span>可以?/span></strong><strong><span>windows</span></strong><strong><span>下用吗</span></strong><strong><span>? ]</span></strong></p> <p align=left><span>vimgdb</span><span>不能?/span><span>windows</span><span>下用。不q你可以用作者提供的</span><span>Clewn</span><span>?/span><span>Pyclewn</span><span>。这两个E序可以?/span><span>Windows</span><span>?/span><span>Vim/Gvim</span><span>中用?/span><span> </span></p> <p align=left><span>q里有我写的一关?/span><span>pyclewn</span><span>的教E?/span><span>:<a target=_top><span><span>?/span></span><span>VIM</span><span><span>中?/span></span><span>GDB</span><span><span>调试</span></span><span> – pyclewn</span></a></span><span>Q?/span><span>clewn</span><span>的用法与之类伹{?/span><span> </span></p> <p align=left><strong><span>[ </span></strong><strong><span>问题四:(x)</span></strong><strong><span> </span></strong><strong><span>按空格后</span></strong><strong><span>vimgdb</span></strong><strong><span>的命令窗口没有弹出来</span></strong><strong><span>? ]</span></strong></p> <p align=left><span>首先保</span><span>vimgdb</span><span>的按键定义文件存在。执行下面的命o(h)Q?/span><span> </span></p> <div> <p align=left><span>:set runtimepath? </span></p> </div> <p align=left><span>(g)查这条命令所列出的所有目录,如果M目录中包?/span><em><span>macros/gdb_mappings.vim</span></em><span>文gQ说明你的按键映文件已l存在。如果你没有扑ֈ该文Ӟ?/span><span>vimgdb</span><span>目录中找到这个命令,拯CqCQ意一个目录中?/span><span> </span></p> <p align=left><span>接下来用下面的命令加?/span><span>vimgdb</span><span>的键l定Q?/span><span> </span></p> <div> <p align=left><span>:run macros/gdb_mappings.vim </span></p> </div> <p align=left><span>现在Q你应该可以使用</span><span>vimgdb</span><span>所定义的快捷键了?/span><span> <br><br><br><br><br></span></p> <img src ="http://www.shnenglu.com/deane/aggbug/109366.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/deane/" target="_blank">李阳</a> 2010-03-10 16:59 <a href="http://www.shnenglu.com/deane/articles/109366.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Vim使用囄http://www.shnenglu.com/deane/articles/108817.html李阳李阳Wed, 03 Mar 2010 10:16:00 GMThttp://www.shnenglu.com/deane/articles/108817.htmlhttp://www.shnenglu.com/deane/comments/108817.htmlhttp://www.shnenglu.com/deane/articles/108817.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/108817.htmlhttp://www.shnenglu.com/deane/services/trackbacks/108817.html
下面两张Vim使用图是从网上收集的Q供大家参考:(x)




















李阳 2010-03-03 18:16 发表评论
]]>
个h常用的VIM命o(h)列表http://www.shnenglu.com/deane/articles/108815.html李阳李阳Wed, 03 Mar 2010 10:01:00 GMThttp://www.shnenglu.com/deane/articles/108815.htmlhttp://www.shnenglu.com/deane/comments/108815.htmlhttp://www.shnenglu.com/deane/articles/108815.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/108815.htmlhttp://www.shnenglu.com/deane/services/trackbacks/108815.html 

vim是一个多模式的编辑器。就目前来看Q主要有以下几个主要模式?br>
  1.通常模式(n) 在其它Q何一个模式下,用ESC或?ctrl+c 键可以退到通常模式

  2.插入模式(i) 在这个模式下,vim像一个常见的~辑?在通常模式?用i或者a可以q行本模?当然,q有一些其它命令也可以.

  3.可视模式(v) 在这个模式下,可以使用hjklq行选择.然后q行copy,paste或者其它操? 在通常模式?用v命o(h)q行可视模式.

  4. 块操作模?V) q是块操作模? 在通常模式?用ctrl+vq入本模?

  5. 修改模式(R) q是改写的模?很多软g法用insert键来完成q个切换.在vim?从通常模式用R卛_q入改写模式.

  6. 扩展命o(h)模式(ex) q是命o(h)执行模式 在通常模式下用:切换到此模式.



个h常用的VIM命o(h)列表:

Ud光标
?k nk:向上Udn?9999k或gg可以UdW一?GUd最后一?br>?j nj:向下Udn?br>?h nh:向左Udn?br>?l nl:向右Udn?

wQ光标以单词向前Ud nwQ光标向前移动n个单?光标到单词的W一个字母上
bQ与w相反
e: 光标以单词向前移?neQ光标向前移动n个单?光标到单词的最后一个字母上
ge:与e相反

$:Ud光标到行?n$:Ud到第n行的行尾
0QNumQ:(x)Ud光标到行?br>^:Ud光标到行首第一个非I字W上?/p>

f<a>:Ud光标到当前行的字Wa上,nf<a>Ud光标到当前行的第n个a字符?br>F:相反

%:UdC制匹配的括号上去Q)(j)Q{}Q[]Q?lt;>{?/p>

nG:Ud到第n行上 G:到最后一?/p>

CTRLQG 得到当前光标在文件中的位|?/p>

向前页QCTRL+F
向下Ud半屏QCTRLQG
向后页QCTRL+B

存盘Q?br>:q! :不存盘退?br>:e! :攑ּ修改文g内容Q重新蝲入该文g~辑
:wq Q存盘退?/p>

dwQ删除一个单?需光标移到单词的W一个字母上Q按dwQ如果光标在单词L位置Q用daw
dnw:删除n个单?br>dne:也可Q只是删除到单词?br>dnl:向右删除n个字?br>dnh:向左删除n个字?br>dnj:向下删除n?br>dnk:向上删除n?br>d$Q删除当前光标到改行的行字母
ddQ删除一?br>cnw[word]:n个word改变为word
cc:改变整行
C$:改变到行?/p>

J: 删除换行W,光标移到改行,按shift+j删除行尾的换行符Q下一行接上来?
u: 撤销前一ơ的操作
shif+u(U):撤销对该行的所有操作?br>
:set showmode :讄昄工作模式

oQ在当前行的下面另v一?br>OQshift+o)Q在当前行的上面另v一?/p>

nk或njQ光标向上或向下Un行,n为数?br>an!【ESC】:(x)在行后面加n个感叹号(!)
nx:执行nơx(删除)操作

ZZQ保存当前文档ƈ退出VIM

:help Q查看帮助文档,在这之中Q按CTRL+] q入连接,按CTRLQO q回?br>:help subject :看某一主题的帮助,ZZ 退出帮?/p>

:set number / set nonumber :昄/不显C?br>:set ruler /set noruler:昄/不显C标?/p>

/pattern 正方向搜索一个字W模?br>?pattern 反方向搜索一个字W模?br>然后按n l箋向下?/p>

把光标放到某个单词上面,然后?#215;号键Q表C查找这个单?br>查找整个单词Q?\<word\>

:set hlsearch 高亮昄查找到的单词
:set nohlsearch 关闭改功?br>
m[a-z]:在文中做标记Q标记号可ؓ(f)a-z?6个字母,用`a可以Ud到标记a?br>
r:替换当前字符
nr字符Q替换当前n个字W?/p>

查找替换Q?br>way1:
/【word?:查找某个word
cw【newword?替换为新word
n: l箋查找
.: 执行替换

way2:
:s/string1/string2/g:在一行中string1替换为string2,g表示执行 用c表示需要确?br>:num1,num2 s/string1/string2/g:在行num1至num2中间string1替换为string2
:1,$ s/string1/string2/g:在全文中string1替换为string2

 


v:q入visual 模式
【ESC】退?br>V:shift+v q入行的visual 模式
CTRL+V:q如块操作模式用o和O改变选择的边的大?/p>

_脓(chung)QpQ这是粘贴用x或d删除的文?br>复制Q?br>ynwQ复制n个单?br>yyQ复制一?br>ynl:复制n个字W?br>y$:复制当前光标臌֤
nyy:拯n?br>完了用p_脓(chung)

:split:分割一个窗?br>:split file.c Qؓ(f)另一个文件file.c分隔H口
:nsplit file.c: 为另一个文件file.c分隔H口Qƈ指定其行?br>CTRLQW在窗口中切换
:closeQ关闭当前窗?/p>

在所有行插入相同的内容如include<Q操作方法如下:(x)
光标移到开始插入的位置Q按CTRL+Vq入VISUAL模式Q选择好模块后
按IQshift+i)Q后插入要插入的文本Q按[ESC]完成?/p>

:read file.c 文件file.c的内Ҏ(gu)入到当前光标所在的下面
:0read file.c 文件file.c的内Ҏ(gu)入到当前文g的开始处(W?行)(j)
:nread file.c 文件file.c的内Ҏ(gu)入到当前文g的第n行后?br>:read !cmd :外部命令cmd的输出插如到当前光标所在的下面

:n1,n2 write temp.c 本文g中的n1,到n2行写入temp.cq个文g中去

CTRLQLh屏幕
shift + < 左移一?br>shift + > 右移一?/p>

u: undo
CTRL+R: re-do
J: 合ƈ一?br>CTRL+p 自动完成功能
CTRL+g 查看当前文g全\?br>

q[a-z] 开始记录但前开始的操作为宏Q名U可为【a-z】,然后用ql止录制宏?br>用reg昄当前定义的所有的宏,用@[a-z]来在当前光标处执行宏[a-z].





李阳 2010-03-03 18:01 发表评论
]]>
vim命o(h)列表http://www.shnenglu.com/deane/articles/108813.html李阳李阳Wed, 03 Mar 2010 09:57:00 GMThttp://www.shnenglu.com/deane/articles/108813.htmlhttp://www.shnenglu.com/deane/comments/108813.htmlhttp://www.shnenglu.com/deane/articles/108813.html#Feedback0http://www.shnenglu.com/deane/comments/commentRss/108813.htmlhttp://www.shnenglu.com/deane/services/trackbacks/108813.html

linux下不熟?zhn)vim命o(h)实在是说不过MQ下面是常用的vim命o(h)列表。vim是linux下命令行一Ƅ典编辑器操作单功能强大,q是很多unix上元老E序员的挚爱。我本地机器上一直用的是vim+ctagsl合Q很受用?/p>

q入vi的命?br>vi filename :打开或新建文Ӟq将光标|于W一行首
vi +n filename Q打开文gQƈ光标置于第n行首
vi + filename Q打开文gQƈ光标置于最后一行首
vi +/pattern filenameQ打开文gQƈ光标置于第一个与pattern匚w的串?br>vi -r filename Q在上次正用vi~辑时发生系l崩溃,恢复filename
vi filename….filename Q打开多个文gQ依ơ编?/p>

Ud光标cd?br>h Q光标左UM个字W?br>l Q光标右UM个字W?br>spaceQ光标右UM个字W?br>BackspaceQ光标左UM个字W?br>k或Ctrl+pQ光标上UM?br>j或Ctrl+n Q光标下UM?br>Enter Q光标下UM?br>w或W Q光标右UM个字臛_?br>b或B Q光标左UM个字臛_?br>e或E Q光标右UM个字j臛_?br>) Q光标移臛_?br>( Q光标移臛_?br>}Q光标移x落开?br>{Q光标移x落结?br>nGQ光标移至第n行首
n+Q光标下Un?br>n-Q光标上Un?br>n$Q光标移至第n行尾
H Q光标移臛_q顶?br>M Q光标移臛_q中间行
L Q光标移臛_q最后行
0Q(注意是数字零Q光标移臛_前行?br>$Q光标移臛_前行?/p>

屏幕Lcd?br>Ctrl+uQ向文g首翻半屏
Ctrl+dQ向文g半屏
Ctrl+fQ向文g一?br>CtrlQbQ向文g首翻一?br>nzQ将Wn行滚臛_q顶部,不指定n时将当前行滚臛_q顶部?/p>

插入文本cd?br>i Q在光标?br>I Q在当前行首
aQ光标后
AQ在当前行尾
oQ在当前行之下新开一?br>OQ在当前行之上新开一?br>rQ替换当前字W?br>RQ替换当前字W及(qing)其后的字W,直至按ESC?br>sQ从当前光标位置处开始,以输入的文本替代指定数目的字W?br>SQ删除指定数目的行,q以所输入文本代替?br>ncw或nCWQ修Ҏ(gu)定数目的?br>nCCQ修Ҏ(gu)定数目的?/p>

删除命o(h)
ndw或ndWQ删除光标处开始及(qing)其后的n-1个字
doQ删臌?br>d$Q删臌?br>nddQ删除当前行?qing)其后n-1?br>x或XQ删除一个字W,x删除光标后的Q而X删除光标前的
Ctrl+uQ删除输入方式下所输入的文?/p>

搜烦(ch)?qing)替换命?:
/patternQ从光标开始处向文件尾搜烦(ch)pattern
?patternQ从光标开始处向文仉搜烦(ch)pattern
nQ在同一方向重复上一ơ搜索命?br>NQ在反方向上重复上一ơ搜索命?br>Qs/p1/p2/gQ将当前行中所有p1均用p2替代
Qn1,n2s/p1/p2/gQ将Wn1至n2行中所有p1均用p2替代
Qg/p1/s//p2/gQ将文g中所有p1均用p2替换

选项讄
allQ列出所有选项讄情况
termQ设|终端类?br>ignoranceQ在搜烦(ch)中忽略大写
listQ显C制表位(Ctrl+I)和行标志($)
numberQ显C?br>reportQ显C由面向行的命o(h)修改q的数目
terseQ显C简短的警告信息
warnQ在转到别的文g时若没保存当前文件则昄NO write信息
nomagicQ允许在搜烦(ch)模式中,使用前面不带“\”的特D字W?br>nowrapscanQ禁止vi在搜索到达文件两端时Q又从另一端开?br>mesgQ允许vi昄其他用户用write写到自己l端上的信息

最后行方式命o(h)
Qn1,n2 co n3Q将n1行到n2行之间的内容拯到第n3行下
Qn1,n2 m n3Q将n1行到n2行之间的内容U至到第n3行下
Qn1,n2 d Q将n1行到n2行之间的内容删除
Qw Q保存当前文?br>Qe filenameQ打开文gfilenameq行~辑
QxQ保存当前文件ƈ退?br>QqQ退出vi
Qq!Q不保存文gq出vi
Q?commandQ执行shell命o(h)command
Qn1,n2 w!commandQ将文g中n1行至n2行的内容作ؓ(f)command的输入ƈ执行之,若不?br>定n1Qn2Q则表示整个文件内容作为command的输?br>Qr!commandQ将命o(h)command的输出结果放到当前行 ?/p>



李阳 2010-03-03 17:57 发表评论
]]>
þþþùƷ۲ӰԺ| ŷƷһƷþ| Ⱦþۺ| þˬˬƬav| þ¶Ʒ| Ůaaaþþü| ƷŮþþ| ۲˾þþƷٸAV| ˾Ʒþ| ޹þþþƷ| ŷձþùʵҶ԰ | 66ƷۺϾþþþþþ| 97þ㽶߿ۿ| þþþavר| 츾þþ| ݺɫþþһ| þþƷAVɫ | þ99Ƶ| þþƷֻоƷ2020| ɫۺϾþۺ| þùƷþþ| Ʒ˾þþþþþ| þùһ| þþþþྫƷֱ| ĻþӰԺ| þۺ༤| þۺ| þþùҺ| ƷþþþaӰԺ| ޾ƷþþþþðĦ| þþþƷһ | vaþþþ| ŷ˾þô߽ۺ| þۺ97ɫֱ| þer99ȾƷһ| ˾þ111վ| þҹӰ| ɫۺϾþþþ| ˾þþƷ| Ļ뾫ƷԴþ| þۺϸþúݺ97ɫ|