﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-hex108-随笔分类-Kernel</title><link>http://www.cppblog.com/hex108/category/15506.html</link><description>懂历史 ==&gt; 知未来
</description><language>zh-cn</language><lastBuildDate>Tue, 26 Jul 2011 14:41:10 GMT</lastBuildDate><pubDate>Tue, 26 Jul 2011 14:41:10 GMT</pubDate><ttl>60</ttl><item><title>linux信号机制 － 用户堆栈和内核堆栈的变化</title><link>http://www.cppblog.com/hex108/archive/2011/07/26/151886.html</link><dc:creator>hex108</dc:creator><author>hex108</author><pubDate>Tue, 26 Jul 2011 10:27:00 GMT</pubDate><guid>http://www.cppblog.com/hex108/archive/2011/07/26/151886.html</guid><wfw:comment>http://www.cppblog.com/hex108/comments/151886.html</wfw:comment><comments>http://www.cppblog.com/hex108/archive/2011/07/26/151886.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hex108/comments/commentRss/151886.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hex108/services/trackbacks/151886.html</trackback:ping><description><![CDATA[<div>此文只简单分析发送信号给用户程序后，用户堆栈和内核堆栈的变化。没有分析实时信号，当然整个过程基本一致。很多参考了＜情景分析＞，所以有些代码和现在的内核可能不同，比如RESTORE_ALL，但大体的机制是类似的。<br /><br />1. 一个信号小例子<br /><br />hex@Gentoo ~/signal $ cat sigint.c <br />#include &lt;stdio.h&gt;<br />#include &lt;stdlib.h&gt;<br />#include &lt;signal.h&gt;<br /><br />void sig_int(int signo)<br />{<br />&nbsp;&nbsp;&nbsp; printf("hello\n");<br />}<br /><br />int main()<br />{<br />&nbsp;&nbsp;&nbsp; if(signal(SIGINT, sig_int) == SIG_ERR){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("can't catch SIGINT\n");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(-1);<br />&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp; for(;;)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;<br /><br />&nbsp;&nbsp; &nbsp;return 0;<br />}<br /><br />2. 用户堆栈里发生的故事<br /><br />2.1 编译运行该程序,并设置断点在sig_int函数开头(0x80482e8)，并设置SIGINT信号的处理方式<br />hex@Gentoo ~/signal $ gdb ./sigint<br />(gdb) b *0x80482e8<br />Breakpoint 1 at 0x80482e8: file sigint.c, line 6.<br />(gdb) handle SIGINT noprint pass<br />SIGINT is used by the debugger.<br />Are you sure you want to change it? (y or n) y<br />Signal&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Stop&nbsp;&nbsp; &nbsp;Print&nbsp;&nbsp; &nbsp;Pass to program&nbsp;&nbsp; &nbsp;Description<br />SIGINT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; No&nbsp;&nbsp; &nbsp;No&nbsp;&nbsp; &nbsp;Yes&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Interrupt<br />(gdb) r<br />Starting program: /home/gj/signal/sigint <br /><br />2.2 向该程序发送信号: kill -INT 此程序的pid号<br />hex@Gentoo ~/signal $ kill -INT 4639<br /><br />2.3 该程序收到信号后停在断点处<br />Breakpoint 1, sig_int (signo=2) at sigint.c:6<br />6&nbsp;&nbsp; &nbsp;{<br />(gdb) i r esp<br />esp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xbfffe7ec&nbsp;&nbsp; &nbsp;0xbfffe7ec<br />(gdb) x/40a 0xbfffe7ec<br />0xbfffe7ec:&nbsp;&nbsp; &nbsp;0xb7fff400&nbsp;&nbsp; &nbsp;0x2&nbsp;&nbsp; &nbsp;0x33&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe7fc:&nbsp;&nbsp; &nbsp;0x7b&nbsp;&nbsp; &nbsp;0x7b&nbsp;&nbsp; &nbsp;0x8048930 &lt;__libc_csu_init&gt;&nbsp;&nbsp; &nbsp;0x80488f0 &lt;__libc_csu_fini&gt;<br />0xbfffe80c:&nbsp;&nbsp; &nbsp;0xbfffed58&nbsp;&nbsp; &nbsp;0xbfffed40&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe81c:&nbsp;&nbsp; &nbsp;0xbfffec18&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe82c:&nbsp;&nbsp; &nbsp;0x8048336 &lt;main+58&gt;&nbsp;&nbsp; &nbsp;0x73&nbsp;&nbsp; &nbsp;0x213&nbsp;&nbsp; &nbsp;0xbfffed40<br />0xbfffe83c:&nbsp;&nbsp; &nbsp;0x7b&nbsp;&nbsp; &nbsp;0xbfffead0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe84c:&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe85c:&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe86c:&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />0xbfffe87c:&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0&nbsp;&nbsp; &nbsp;0x0<br />栈上的内容为信号栈sigframe：<br />根据此结构可以知道：<br />1). 返回地址0xb7fff400，它指向<a title="vdso" href="http://www.cppblog.com/hex108/archive/2010/11/22/134313.html">vdso</a>里的sigreturn<br />(gdb) x/10i 0xb7fff400<br />&nbsp;&nbsp; 0xb7fff400 &lt;__kernel_sigreturn&gt;:&nbsp;&nbsp; &nbsp;pop&nbsp;&nbsp;&nbsp; %eax<br />&nbsp;&nbsp; 0xb7fff401 &lt;__kernel_sigreturn+1&gt;:&nbsp;&nbsp; &nbsp;mov&nbsp;&nbsp;&nbsp; $0x77,%eax<br />&nbsp;&nbsp; 0xb7fff406 &lt;__kernel_sigreturn+6&gt;:&nbsp;&nbsp; &nbsp;int&nbsp;&nbsp;&nbsp; $0x80<br />这个地址根据内核的不同而不同，我的内核版本是2.6.38。<br />2). 信号处理程序完成后，会回到 eip = 0x8048336 的地址继续执行。<br /><br /><br />2.4 执行完sig_int函数后，进入了__kernel_sigreturn，接着回到了代码0x8048336处，一切恢复了正常。<br />(gdb) x/5i $pc<br />=&gt; 0x8048336 &lt;main+58&gt;:&nbsp;&nbsp; &nbsp;jmp&nbsp;&nbsp;&nbsp; 0x8048336 &lt;main+58&gt;<br />(gdb) i r esp<br />esp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xbfffed40&nbsp;&nbsp; &nbsp;0xbfffed40<br /><br />在用户层我们能看到的只有上面这么多信息了，可能有一个地方不能理解：在上面过程c中 从0xbfffe7ec起那一块栈上的内容从哪来的？（正常情况下堆栈esp应该一直指向在过程d中显示的esp值0xbfffed40）<br /><br />现在来看看在上面这些现象之下，内核的堆栈发生了怎样的变化。<br /><br />3. 内核堆栈里发生的故事<br />3.1 发信号时 <br />在 2.2 里当执行kill -INT 4639后，pid为4639的程序（也就是我们运行的 ./sigint）会收到一个信号，但是信号实际都是在内核里实现的。每个进程（这里只讲进程的情况，线程类似，线程有一个tid）都有一个pid，与此pid对应有一个结构 task_struct ，在task_struct里有一个变量 struct sigpending pending，当该进程收到信号时，并不会立即作出反应，只是让内核把这个信号记在了此变量里（它里面是一个链表结构）。当然，此时与内核堆栈还没有多大关系。<br /><br />3.2 检测信号<br />&nbsp; 如果只记录了信号，但没有相应反应，那有什么用啊。一个进程在什么 情况下会检测信号的存在呢？在&lt;情景分析&gt;里说到了：&#8220;在中断机制中，处理器的硬件在每条指令结束时都要检测是否有中断请求的存在。信号机制是纯软件的，当然不能依靠硬件来检测信号的到来。同时，要在每条指令结束时都来检测显然是不现实的，甚至是不可能的。所以对信号的检测机制是：每当从系统调用，中断处理或异常处理返回到用户空间的前夕；还有就是当进程被从睡眠中唤醒（必定是在系统调用中）的时候，此时若发现有信号在等待就要提前从系统调用返回。总而言之，不管是正常返回还是提前返回，在返回到用户空间的前夕总是要检测信号的存在并作出反应。&#8221;<br /><br />&nbsp; 因此，对收到的信号做出反应的时间是 从内核返回用户空间的前夕，那么有那些情况会让程序进入内核呢？答案是中断，异常和系统调用。简单了解一下它们发生时内核堆栈的变化。<br /><blockquote>&nbsp; //-----中断，异常，系统调用 : 开始 <br />&nbsp;&nbsp; 1)在用户空间发生中断时，CPU会自动在内核空间保存用户堆栈的SS， 用户堆栈的ESP， EFLAGS, 用户空间的CS, EIP, 中断号 - 256<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 － 256 <br />&nbsp;&nbsp; 进入内核后，会进行一个SAVE_ALL，这样内核栈上的内容为：<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 － 256 | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX<br /><br />&nbsp;&nbsp; 好了，一切都处理完时，内核jmp到RESTORE_ALL（它是一个宏，例：在x86_32体系结构下，/usr/src/kernel/arch/286/kernel/entry_32.S文件里包含该宏的定义）<br /><br />&nbsp;&nbsp; RESTORE做的工作，从它的代码里就可以看出来了：&nbsp; &nbsp;<br />&nbsp;&nbsp; 首先把栈上的 ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX pop到对应的寄存器里<br />&nbsp;&nbsp; 然后将esp ＋ 4 把 &#8220;中断号 － 256&#8221; pop掉 <br />&nbsp;&nbsp; 此时内核栈上的内容为：<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP <br />&nbsp;&nbsp; 最后执行iret指令，此时CPU会从内核栈上取出SS, ESP, ELFGAS, CS, EIP，然后接着运行。<br /><br />&nbsp;&nbsp; 2) 在用户空间发生异常时，CPU自动保存在内核栈的内容为：<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 出错代码 error_code<br />&nbsp;&nbsp; （注：CPU只是在进入异常时才知道是否应该把出错代码压入堆栈（为什么?），而从异常处理通过iret指令返回时已经时过境迁，CPU已经无从知当初发生异常的原因，因此不会自动跳过这一项，而要靠相应的异常处程序对堆栈加以调整，使得在CPU开始执行iret指令时堆栈顶部是返回地址）<br /><br />&nbsp;&nbsp; 进入内核后，没有进行SAVE_ALL，而是进入相应的异常处理函数（这个函数是包装后的，真正的处理函数在后面）（在此函数里会把真正的处理函数的地址push到栈上），然后jmp到各种异常处理所共用的程序入口error_code，它会像SAVE_ALL那样保存相应的寄存器（没有保存ES），此时内核空间上的内容为：<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 出错代码 error_code | 相应异常处理函数入口 | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX<br />&nbsp;&nbsp; （注：如果没有出错代码，则此值为0）<br /><br />&nbsp;&nbsp; 最后结束时与中断类似（RESTORE_ALL）。<br /><br />&nbsp;&nbsp; 3) 发生系统调用时，CPU自动保存在内核栈的内容为:<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP <br />&nbsp;&nbsp; 为了与中断和异常的栈一致，在进入系统调用入口（ENTRY(system_call)）后会首先push %eax，然后进行SAVE_ALL，此时内核栈上的内容为<br />&nbsp;&nbsp; | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | EAX | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX<br />&nbsp; <br />&nbsp;&nbsp; 最后结束时与中断类似（RESTORE_ALL）。<br />&nbsp;&nbsp; //-----中断，异常，系统调用 : 结束<br /></blockquote><br />&nbsp;&nbsp; 中断，异常，系统调用这部分有一点遗漏的地方：检测信号的时机就是紧挨着RESTORE_ALL之前发生的。<br /><br />3.3 对检测到的信号做出反应<br />&nbsp; 如果检测到有要处理的信号时，就要开始做一些准备工作了，此时内核里的内容为（进入内核现场时的内容）<br />&nbsp; | 用户堆栈的SS1 | 用户堆栈的ESP1 | EFLAGS1 | 用户空间的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1<br />&nbsp; （注：？的值有三个选择：中断号 － 256／出错代码 error_code／出错代码 error_code） <br />&nbsp; 假设将要处理的信号对应的信号处理程序是用户自己设置的，即本文中SIGINT对应的信号处理程序sig_int。<br />&nbsp; 现在要做的事情是让cpu去执行信号处理程序sig_int，但是执行前需要做好准备工作：<br />&nbsp; 3.3.1&nbsp; setup_frame<br />&nbsp; 在用户空间设置好信号栈(struct sigframe)(假设设置好栈后esp的值为sigframe_esp，在本文中其值为0xbfffe7ec)，即在2.3里看到的栈内容。<br />&nbsp; 注：struct sigframe里至少包含以下内容：<br />&nbsp; 用户堆栈的SS1， 用户堆栈的ESP1， EFLAGS1， 用户空间的CS1， EIP1， ES1， DS1， EAX1， EBP1， EDI1， ESI1， EDX1， ECX1， EBX1 <br /><br />&nbsp; 3.3.2 设置即将运行的eip的值为信号处理函数sig_int的地址（为0x80482e8），并设置用户ESP的值为sigframe_esp(为0xbfffe7ec)，这是通过修改内核栈里的EIP和ESP的值实现的，因为在从系统调用里iret时，会从内核栈里取EIP，ESP。<br />&nbsp; 这时内核栈的内核为:<br />&nbsp; | 用户堆栈的SS1 | <span style="color: red;">0xbfffe7ec</span> | EFLAGS1 | 用户空间的CS1 | <span style="color: red;">0x80482e8</span> | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1<br />&nbsp; <br />&nbsp; 最后，进行RESTORE_ALL，内核栈上的内容为：<br />&nbsp; | 用户堆栈的SS1 | 0xbfffe7ec | EFLAGS1 | 用户空间的CS1 | 0x80482e8<br />&nbsp; <br />&nbsp; RESTORE_ALL里执行完iret后，寄存器内容为： EIP为0x80482e8(即sig_int),esp为0xbfffe7ec 。 于是用户空间到了步骤 2.3<br /><br />3.4 信号处理程序完成以后<br />&nbsp; 2.3 -&gt; 2.4，进入了sig_return系统调用，在sig_return里，内核栈的内容为（每个名字后面加一个2以便与前面的1区分）<br />&nbsp; | 用户堆栈的SS2 | 用户堆栈的ESP2 | EFLAGS2 | 用户空间的CS2 | EIP2 | ? | ES2 | DS2 | EAX2 | EBP2 | EDI2 | ESI2 | EDX2 | ECX2 | EBX2<br />&nbsp; sig_return要做的主要工作就是根据用户栈里sigframe的值修改内核栈里的内容，使内核栈变为:<br />&nbsp; | 用户堆栈的SS1 | 用户堆栈的ESP1 | EFLAGS1 | 用户空间的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br />&nbsp; 至此内核栈里的内容和进行信号处理前一样了。经过RESTORE_ALL后，用户堆栈里的内容也和以前一样（主要指ESP的值一样）。 <br /><br />&nbsp; "kill -INT 4639" 只是一段小插曲。程序从原处开始运行。</div><img src ="http://www.cppblog.com/hex108/aggbug/151886.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hex108/" target="_blank">hex108</a> 2011-07-26 18:27 <a href="http://www.cppblog.com/hex108/archive/2011/07/26/151886.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下的vdso与vsyscall</title><link>http://www.cppblog.com/hex108/archive/2010/11/22/134313.html</link><dc:creator>hex108</dc:creator><author>hex108</author><pubDate>Mon, 22 Nov 2010 13:19:00 GMT</pubDate><guid>http://www.cppblog.com/hex108/archive/2010/11/22/134313.html</guid><wfw:comment>http://www.cppblog.com/hex108/comments/134313.html</wfw:comment><comments>http://www.cppblog.com/hex108/archive/2010/11/22/134313.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hex108/comments/commentRss/134313.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hex108/services/trackbacks/134313.html</trackback:ping><description><![CDATA[
<p>&nbsp;&nbsp;&nbsp;&nbsp; 传统的系统调用是怎样的？&nbsp;&nbsp;&nbsp; —— <strong>int 0x80的时代</strong></p> <blockquote> <div><pre>....             ；通过寄存器传参
mov $n ,eax      ；将系统调用号放到eax中
<span style="color: #0000ff">int</span> 0x80</pre></div>
<p><br><strong>sysenter/sysexit的出场</strong></p></blockquote>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在一个Kernel.org的邮件列表中，有一封邮件讨论了&#8220;"Intel P6 vs P7 system call performance&#8221;，最后得出的结论是采用传统的int 0x80的系统调用浪费了很多时间（具体原因可以看参考资料1），而sysenter/sysexit可以弥补这个缺点，所以决定在linux内核中用后都替换前者（最终在2.6版本的内核中才加入了此功能，即采用sysenter/sysexit）。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在替换之前首先需要知道满足如下条件的ntel机器才会有sysenter/sysexit指令对：Family &gt;= 6，Model &gt;= 3，Stepping &gt;= 3</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如何用替换sysenter/sysexit替换以前的int 0x80呢？linux kenerl 需要考虑到这点：有的机器并不支持sysenter/sysexit&nbsp; ， 于是它跟glibc说好了，&#8220;你以后调用系统调用的时候就从我给你的这个地址调用，这个地址指向的内容要么是int 0x80调用方式，要么是sysenter/sysexit调用方式，我会根据机器来选择其中一个&#8221;（kernel与glibc的配合是如此的默契），这个地址便是vsyscall的首地址。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以将vdso看成一个shared objdect file（这个文件实际上不存在）,内核将其映射到某个地址空间，被所有程序所共享。（我觉得这里用到了一个技术：多个虚拟页面映射到同一个物理页面。即内核把vdso映射到某个物理页面上，然后所有程序都会有一个页表项指向它，以此来共享，这样每个程序的vdso地址就可以不相同了）</p>
<blockquote>
<div><pre>hex108@ubuntu:~/program$ uname -a
Linux ubuntu 2.6.35-22-generic #33-Ubuntu SMP Sun Sep 19 20:34:50 UTC 2010 i686 GNU/Linux<br>hex108@ubuntu:~/program$ sudo sysctl -w kernel.randomize_va_space=0     <font color="#ff0000">//这个是必须的，否则vdso的地址是随机的(vsyscall的地址也会相应</font></pre><pre><font color="#ff0000">                                                                        // </font><span style="color: rgb(255,0,0)">地发生</span><font color="#ff0000">变化 )，</font><span style="color: rgb(255,0,0)">在下面dd的时候就会出现错误</span></pre><pre><font color="#ff0000"></font><span style="color: rgb(255,0,0)">                                                                        //dd: reading `/proc/self/mem': Input/output error</span></pre></div></blockquote>
<blockquote><pre><font color="#ff0000">                                                                        </font>
kernel.randomize_va_space = 0
hex108@ubuntu:~/program$ cat /proc/self/maps 
00110000-0012c000 r-xp 00000000 08:01 260639     /lib/ld-2.12.1.so
0012c000-0012d000 r--p 0001b000 08:01 260639     /lib/ld-2.12.1.so
0012d000-0012e000 rw-p 0001c000 08:01 260639     /lib/ld-2.12.1.so
0012e000-0012f000 r-xp 00000000 00:00 0          [vdso]
0012f000-00286000 r-xp 00000000 08:01 260663     /lib/libc-2.12.1.so
00286000-00287000 ---p 00157000 08:01 260663     /lib/libc-2.12.1.so
00287000-00289000 r--p 00157000 08:01 260663     /lib/libc-2.12.1.so
00289000-0028a000 rw-p 00159000 08:01 260663     /lib/libc-2.12.1.so
0028a000-0028d000 rw-p 00000000 00:00 0 
08048000-08051000 r-xp 00000000 08:01 130326     /bin/cat
08051000-08052000 r--p 00008000 08:01 130326     /bin/cat
08052000-08053000 rw-p 00009000 08:01 130326     /bin/cat
08053000-08074000 rw-p 00000000 00:00 0          [heap]
b7df0000-b7ff0000 r--p 00000000 08:01 660864     /usr/lib/locale/locale-archive
b7ff0000-b7ff1000 rw-p 00000000 00:00 0 
b7ffd000-b7ffe000 r--p 002a1000 08:01 660864     /usr/lib/locale/locale-archive
b7ffe000-b8000000 rw-p 00000000 00:00 0 
bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]</pre>
<div><pre>hex108@ubuntu:~/program$ dd <span style="color: #0000ff">if</span>=/proc/self/mem of=gate.so bs=4096 skip=$[0x12e] count=1
dd: `/proc/self/mem': cannot skip to specified offset
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.00176447 s, 2.3 MB/s
hex108@ubuntu:~/program$ file gate.so 
gate.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped
hex108@ubuntu:~/program$ objdump -d gate.so 

gate.so:     file format elf32-i386


Disassembly of section .text:

ffffe400 &lt;__kernel_sigreturn&gt;:
ffffe400:	58                   	pop    %eax
ffffe401:	b8 77 00 00 00       	mov    $0x77,%eax
ffffe406:	cd 80                	<span style="color: #0000ff">int</span>    $0x80
ffffe408:	90                   	nop
ffffe409:	8d 76 00             	lea    0x0(%esi),%esi

ffffe40c &lt;__kernel_rt_sigreturn&gt;:
ffffe40c:	b8 ad 00 00 00       	mov    $0xad,%eax
ffffe411:	cd 80                	<span style="color: #0000ff">int</span>    $0x80
ffffe413:	90                   	nop

ffffe414 &lt;__kernel_vsyscall&gt;:
ffffe414:	cd 80                	<span style="color: #0000ff">int</span>    $0x80
ffffe416:	c3                   	ret    </pre></div>
<div>&nbsp;</div>
<p><strong>syscall 才是最后的赢家？</strong></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x86 64位从AMD引进了syscall指令（我在x86 64的机器上，看到的结果是syscall取代了sysenter/sysexit（所有的系统调用用的都是syscall）），但是vdso,vsyscall的机制依旧未变，只是kernel决定只在遇到以下几个系统调用gettimeofday,time和getcpu（通过内核里vsyscall.h中enum vsyscall_num的声明看出来，或者在glibc源代码中搜索&#8220;VSYSCALL_ADDR_&#8221;(</p>
<p>#define VSYSCALL_ADDR_vgettimeofday&nbsp;&nbsp;&nbsp; 0xffffffffff600000</p>
<p>#define VSYSCALL_ADDR_vtime &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0xffffffffff600400</p>
<p>#define VSYSCALL_ADDR_vgetcpu &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0xffffffffff600800</p>
<p>)）时才采用vdso机制（间接调用syscall，具体可以参看资料2），其他系统调用直接用指令syscall，原因是：</p>

<hr>

<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "快速系统调用指令"比起中断指令来说，其消耗时间必然会少一些，但是随着 CPU 设计的发展，将来应该不会再出现类似 Intel Pentium4 这样悬殊的差距。而"快速系统调用指令"比起中断方式的系统调用方式，还存在一定局限，例如无法在一个系统调用处理过程中再通过"快速系统调用指令"调用别的系统调用。因此，并不一定每个系统调用都需要通过"快速系统调用指令"来实现。比如，对于复杂的系统调用例如 fork，两种系统调用方式的时间差和系统调用本身运行消耗的时间来比，可以忽略不计，此处采取"快速系统调用指令"方式没有什么必要。而真正应该使用"快速系统调用指令"方式的，是那些本身运行时间很短，对时间精确性要求高的系统调用，例如 getuid、gettimeofday 等等。因此，采取灵活的手段，针对不同的系统调用采取不同的方式，才能得到最优化的性能和实现最完美的功能。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ----引自参考资料1</p>

<hr>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <p>&nbsp;</p>
<p>ps:文中的内核版本为2.6.36，glibc版本为2.11<br></p>
<p>参考资料:</p>
<p>1.&nbsp; <a href="http://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html">Linux 2.6 对新型 CPU 快速系统调用的支持</a>: <a href="http://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html">http://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html</a>&nbsp; (这篇我觉得最好)</p>
<p>2. <a href="http://www.win.tue.nl/~aeb/linux/lk/lk-4.html">System Calls</a> ： <a href="http://www.win.tue.nl/~aeb/linux/lk/lk-4.html">http://www.win.tue.nl/~aeb/linux/lk/lk-4.html</a>（里面有程序可以用来搜索vsyscall等的地址，很直接）</p>
<p>3. <a href="http://www.trilithium.com/johan/2005/08/linux-gate/">What is linux-gate.so.1</a> ： <a href="http://www.trilithium.com/johan/2005/08/linux-gate/">http://www.trilithium.com/johan/2005/08/linux-gate/</a></p>
<p>4. Intel手册，里面有各种资料，手册还是很重要的，也是最基本的</p></blockquote><img src ="http://www.cppblog.com/hex108/aggbug/134313.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hex108/" target="_blank">hex108</a> 2010-11-22 21:19 <a href="http://www.cppblog.com/hex108/archive/2010/11/22/134313.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>