[
  {
    "path": "README.md",
    "content": "﻿<a name=\"top\"></a>\n\n<h1 align=\"center\"><b>朴素linux</b></h1>\n\n　　大学里我坚持的最久的一项任务就是自学 linux 内核，\n虽然以后可能也没机会从事 linux 内核方面的工作，\n但是至少提升了自己的编程水平。\n\n　　linux 最新内核的源代码已经没有进行全面研究的可能了，\n我看的是 linux0.01 的内核源码。\n没有指导直接看源代码是不太容易看懂的，\n因为其中涉及到不少硬件操作的规范，\n《linux内核完全注释2.01》——赵炯 著 \n对早期linux内核的分析是最详细的，真的达到了完全注释的地步，\n虽然书里分析的是 linux0.11 的源代码，\n但相对于 0.01 的改动不多。\n\n　　而最新的内核由于巨大的代码量，\n要达到源代码的完全注释应该是不可能的了，\n但是从大粒度上进行的分析也是很有价值的。\n《Linux内核设计与实现》英文名为 \n*Linux kernerl Development* ，\n是 *Robert Love* 所著，陈莉君、康华、张波 翻译的，\n我从这本书中了解了最新内核的进程调度思想。\n\n　　还有一本书，书名是《LINUX内核源代码情景分析》 \n毛德操 胡希明 著，这本书对于 PCI \n总线操作规范的介绍可谓完全注释。为什么我会看 PCI \n总线的操作？因为现在的电脑都是用 PCI 而非早期的 ISA \n总线了，linux0.01 对硬盘的操作使用 ISA 规定的固定端口，\n而 PCI 总线中的硬盘的端口是动态设定的，\n与 ISA 时的端口不一致了，所以如果想用 linux0.01 \n读写我本机的硬盘的话就得加入 PCI 的功能，\n所以我才看关于最新内核的书，我是被逼的。\n\n　　就快要毕业去工作了，想着写几篇文章同大家分享一下 \nlinux 和 C 语言方面的底层知识。那些既想了解底层\n又不愿意系统地看源代码或操作系统方面书籍的同学可以来看看，\n就当是看一部小说吧。\n这个系列的名字叫<b>朴素linux</b>。\n\n　　以下是目录，目录随着进度变更，\n还有可能被重新分类整理，请见谅。\n\n<a name=\"content\"></a>\n\n* 解剖C语言\n\t1. [照妖镜和火眼金睛](https://github.com/1184893257/simplelinux/blob/master/gcc.md#top) \\[2012/11/8更新](3)  \n\t怎么获得C语言翻译后的汇编代码，怎么获得消除宏的C源程序\n\t2. [局部变量](https://github.com/1184893257/simplelinux/blob/master/localvar.md#top) \\[2012/11/12更新](4)  \n\ti=3; (++i)+(++i)+(++i) 不同编译器结果不同，怎么看它们的运算过程。\n\t3. [全局变量](https://github.com/1184893257/simplelinux/blob/master/globalvar.md#top) \\[2012/11/8上线](5)  \n\t全局变量与局部变量在访问方式上有什么不同\n\t4. [函数调用](https://github.com/1184893257/simplelinux/blob/master/call.md#top) \\[2012/11/9上线](6)  \n\t调用一个函数的前前后后\n\t5. [值传递](https://github.com/1184893257/simplelinux/blob/master/byval.md#top) \\[2012/11/11上线](7)  \n\tC语言只有值传递，怎么修改外部变量\n\t6. [数组与指针](https://github.com/1184893257/simplelinux/blob/master/array.md#top) \\[2012/12/23更新](8)  \n\t数组的起始地址存在哪儿？\n\t7. [字符串](https://github.com/1184893257/simplelinux/blob/master/string.md#top) \\[2012/11/15上线](9)  \n\t为什么有的字符串不能修改\n\t8. [结构体](https://github.com/1184893257/simplelinux/blob/master/struct.md#top) \\[2012/11/17上线](10)  \n\t结构体与子元素什么关系，数组不能复制？\n\t9. [奇怪的宏](https://github.com/1184893257/simplelinux/blob/master/macro.md#top) \\[2012/11/19上线](11)  \n\tdo{...} while(0)是何用意\n\t10. [内存对齐](https://github.com/1184893257/simplelinux/blob/master/align.md#top) \\[2012/11/28更新](12)  \n\t为什么要进行内存对齐，怎么关闭内存对齐\n\t11. [函数帧](https://github.com/1184893257/simplelinux/blob/master/frame.md#top) \\[2012/11/24上线](13)  \n\t函数的局部环境：函数帧\n\t12. [函数帧应用一：谁调用了main？](https://github.com/1184893257/simplelinux/blob/master/main.md#top) \\[2012/11/27上线](14)  \n\t不复杂\n\t13. [函数帧应用二：所有递归都可以变循环](https://github.com/1184893257/simplelinux/blob/master/recur.md#top) \\[2012/11/30上线](15)  \n\t真的可以\n\t14. [未初始化全局变量](https://github.com/1184893257/simplelinux/blob/master/bss.md#top) \\[2012/12/3上线](16)  \n\t未初始化全局变量 不跟 初始化全局变量 存一块儿\n\t15. [进程内存分布](https://github.com/1184893257/simplelinux/blob/master/mem.md#top) \\[2012/12/6上线](17)  \n\t全局变量、堆、栈 在哪儿？访问它们的特点\n\t16. [编译优化](https://github.com/1184893257/simplelinux/blob/master/optimize.md#top) \\[2012/12/9上线](18)  \n\tC语言比汇编慢，怎么优化编译过程\n\t17. [static变量 及 作用域控制](https://github.com/1184893257/simplelinux/blob/master/static.md#top) \\[2012/12/12上线](19)  \n\t压缩变量的作用域，提高源代码的可读性\n\t18. [变量名、函数名](https://github.com/1184893257/simplelinux/blob/master/name.md#top) \\[2012/12/15上线](20)  \n\t变量名、函数名在哪里终结，有什么用？\n\t19. [函数指针](https://github.com/1184893257/simplelinux/blob/master/pfunc.md#top) \\[2012/12/18上线](21)  \n\t函数指针跟普通指针有什么区别\n\t20. [可变参数](https://github.com/1184893257/simplelinux/blob/master/varargs.md#top) \\[2012/12/21上线](22)  \n\t可变参数怎么实现的？变参函数的可行性？\n\t21. [C语言的栈是静态的](https://github.com/1184893257/simplelinux/blob/master/staticstack.md#top) \\[2012/12/23上线](23)  \n\t变参函数力不能及的地方\n\t22. [内联汇编](https://github.com/1184893257/simplelinux/blob/master/inlineasm.md#top) \\[2012/12/24上线](24)  \n\tgcc 以及 VC 的内联汇编\n\t23. [汇编实现的动态栈](https://github.com/1184893257/simplelinux/blob/master/dynamicstack.md#top) \\[2012/12/25上线](25)  \n\t实现一个运行时的接受可变参数的printf\n* 内核小知识\n\t1. [linux0.01进程时间片的消耗和再生](https://github.com/1184893257/simplelinux/blob/master/process0.01.md#top) \\[2012/11/7更新](1)\n\t2. [linux2.6.XX进程切换和时间片再生](https://github.com/1184893257/simplelinux/blob/master/process2.6.md#top) \\[2012/11/7上线](2)\n"
  },
  {
    "path": "align.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">内存对齐\n</h1>\n\n## 为什么要进行内存对齐\n\n　　在计算机组成原理中我们学到：\n一块内存芯片一般只提供 8 位数据线，要进行 16 位数据的读写\n可采用<a target=\"_blank\" href=\"http://baike.baidu.com/view/1881700.htm\">奇偶分体</a>来组织管理多个芯片，\n32 位也类似：\n\n![align](http://fmn.rrimg.com/fmn056/20121121/1905/original_zCiC_1caa000049ee125c.jpg)\n\n　　这样，连续的四个字节会分布在不同的芯片上，\n送入地址 0，我们可将第 0、1、2、3 四个字节一次性读出组成\n一个 32 位数，送入地址 4（每个芯片接收到的地址是1），\n可一次性读出 4、5、6、7 四个字节。\n\n　　但是如果要读 1、2、3、4 四个字节，就麻烦了，\n有的 CPU 直接歇菜了：我处理不了！\n但 Intel 的 CPU 走的是复杂指令集路线，\n岂能就此认输，它通过两次内存读，\n然后进行拼接合成我们想要的那个 32 位数，\n而这一切是在比机器码更低级的微指令执行阶段完成的，\n所以 movl 1, %eax 会不出意外地读出 1、2、3、4 四个字节\n到 eax，证据如下（mem.c）：\n\n\t#include <stdio.h>\n\t\n\tchar a[]={0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};\n\t\n\tint main()\n\t{\n\t\tint *p = (int*)(a + 1);\n\t\tint ans = *p;\n\t\t\n\t\tprintf(\"*p:\\t%p\\n\", ans);\n\t\tprintf(\"a:\\t%p\\n\", a);\n\t\tprintf(\"p:\\t%p\\n\", p);\n\t\treturn 0;\n\t}\n\n`　　`该程序的运行结果如下：\n\n\t[lqy@localhost temp]$ gcc -o mem mem.c\n\t[lqy@localhost temp]$ ./mem\n\t*p:\t0x55443322\n\ta:\t0x80496a8\n\tp:\t0x80496a9\n\t[lqy@localhost temp]$ \n\n`　　`可看出程序确实从一个未对齐到 4 字节的地址（0x80496a9）\n后读出了 4 个字节，从汇编可看出确实是 1 条 mov 指令读出来的：\n\n\tmovl\t$a, %eax\n\taddl\t$1, %eax\n\tmovl\t%eax, 28(%esp)\t# 初始化指针 p\n\tmovl\t28(%esp), %eax\n\tmovl\t(%eax), %eax\t# 这里读出了 0x55443322\n\tmovl\t%eax, 24(%esp)\t# 初始化 ans\n\n`　　`虽然 Intel 的 CPU 能这样处理，但还是要浪费点时间不是，\n所以 C 程序还是要采取措施避免这种情况的发生，\n那就是内存对齐。\n\n## 内存对齐的结果\n\n　　内存对齐的完整描述你还是去百度吧，\n这里我只是含糊地介绍一下：\n\n1. 保证最大类型对齐到它的 size\n2. 尽量不浪费空间\n\n比如：\n\n\tstruct A{\n\t\tchar a;\n\t\tint c;\n\t};\n\n它的大小为 8，c 的内部偏移为 4，\n这样就可以一次性读出 c 了。\n\n再如：\n\n\tstruct B{\n\t\tchar a;\n\t\tchar b;\n\t\tint c;\n\t};\n\n它的大小还是 8，第 2 条起作用了！\n\n## 关闭内存对齐\n\n　　讲到内存对齐，估计大家最期待的一大快事就是怎么关闭它\n（默认是开启的），毕竟 Intel CPU 如此强大，\n关闭了也没事。\n\n　　关闭它也甚是简单，添加预处理指令 #pragma pack(1) \n就行，windows linux 都管用：\n\n\t#include <stdio.h>\n\t\n\t#pragma pack(1)\n\t\n\tstruct _A{\n\t    char c;\n\t    int i;\n\t};\n\t//__attribute__((packed));\n\t\n\ttypedef struct _A A;\n\t\n\tint main()\n\t{\n\t\tprintf(\"%d\\n\", sizeof(A));\n\t\treturn 0;\n\t}\n\n`　　`linux gcc 中更常见的是使用 `__attribute__((packed))`，\n这个属性只解除对一个结构体的内存对齐，而 #pragma pack(1) \n解除了整个 C源文件 的内存对齐，\n所以有时候 `__attribute__((packed))` 显得更为合理。\n\n　　什么时候可能需要注意或者关闭内存对齐呢？\n我想大概是这两种情况：\n\n* 结构化文件的读写\n* 网络数据传输\n\n## 另一个浪费内存的家伙\n\n　　说到内存对齐，我想起了另一个喜欢浪费内存的家伙：\n参数对齐（我瞎编的名字，C 标准中或许有明确规定）。\n看下面这个程序：\n\n\t#include <stdio.h>\n\t\n\ttypedef unsigned char u_char;\n\t\n\tu_char add(u_char a, u_char b)\n\t{\n\t\treturn (u_char)(a+b);\n\t}\n\t\n\tint main()\n\t{\n\t\tu_char a=1, b=2;\n\t\t\n\t\tprintf(\"ans:%d\\n\", add(a, b));\n\t\treturn 0;\n\t}\n\n`　　`你说 add 函数的参数会占几个字节呢？2个？4个？\n结果是 8 个……\n\n　　“可恨”的是，这个家伙浪费内存的行为却被所有编译器纵容，\n我们无法追究它的责任。\n（应该是为了方便计算参数位置而规定的）\n\n[回目录][content]\n"
  },
  {
    "path": "array.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">ָ\n</h1>\n\nָʲô\n\nCarray.c\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint date[3] = {2012,11,11};\n\t\t\n\t\tint *p = date;\n\t\t\n\t\tint a = date[1];\n\t\tint b = p[1];\n\t\t\n\t\tprintf(\"a:%d b:%d\\n\", a, b);\n\t\tprintf(\"date:%p\\np   :%p\\n\", date, p);\n\t\treturn 0;\n\t}\n\n``༰עͣ\n\n<table>\n<tr><td>\n<pre><code>    .file\t\"array.c\"\n\t.section\t.rodata\n.LC0:\n\t.string\t\"a:%d b:%d\\n\"\n.LC1:\n\t.string\t\"date:%p\\np   :%p\\n\"\n\t.text\n.globl main\n\t.type\tmain, @function\nmain:\n\tpushl\t%ebp\t\t\t#-ָ֡л\n\tmovl\t%esp, %ebp\t\t#/\n\tandl\t$-16, %esp\t\t#-ջ뵽16ֽ\n\tsubl\t$48, %esp\t\t#-ؾֲռ\n\tmovl\t$2012, 24(%esp)\t#\\\n\tmovl\t$11, 28(%esp)\t#-dateʼ\n\tmovl\t$11, 32(%esp)\t#/\n\tleal\t24(%esp), %eax\t#\\\n\tmovl\t%eax, 44(%esp)\t#-ʼָp\n\tmovl\t28(%esp), %eax\t#\\\n\tmovl\t%eax, 40(%esp)\t#-ʼa\n\tmovl\t44(%esp), %eax\t#\\\n\taddl\t$4, %eax\t\t#-\\\n\tmovl\t(%eax), %eax\t#-ʼb\n\tmovl\t%eax, 36(%esp)\t#/\n</code></pre></td>\n<td valign=\"bottom\"><img src=\"http://fmn.rrfmn.com/fmn059/20121113/1850/original_fmSH_48030000e812125c.jpg\" /></td>\n</tr>\n\n<tr><td>\n<pre><code>    movl    $.LC0, %eax\n\tmovl\t36(%esp), %edx\n\tmovl\t%edx, 8(%esp)\n\tmovl\t40(%esp), %edx\n\tmovl\t%edx, 4(%esp)\n\tmovl\t%eax, (%esp)\n\tcall\tprintf\t\t#һε printf \n</code></pre></td>\n<td valign=\"bottom\"><img src=\"http://fmn.rrimg.com/fmn062/20121113/1850/original_6dpg_28e80000e7fc1191.jpg\" /></td>\n</tr>\n\n<tr><td>\n<pre><code>    movl    $.LC1, %eax\n\tmovl\t44(%esp), %edx\n\tmovl\t%edx, 8(%esp)\n\tleal\t24(%esp), %edx\n\tmovl\t%edx, 4(%esp)\n\tmovl\t%eax, (%esp)\n\tcall\tprintf\t\t#ڶε printf \n</code></pre></td>\n<td valign=\"bottom\"><img src=\"http://fmn.rrimg.com/fmn056/20121113/1850/original_ECol_44540000c23f1190.jpg\" /></td>\n</tr>\n\n<tr><td colspan=\"2\">\n<pre><code>    movl    $0, %eax    #return 0\n\tleave\n\tret\n\t.size\tmain, .-main\n\t.ident\t\"GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)\"\n\t.section\t.note.GNU-stack,\"\",@progbits\n</code></pre></td>\n</tr>\n</table>\n\nҶ lea ָܱȽİ mov ָ\n lea ָݵڴĵַ mov ָݵĵ\nڴֵ磺\n\n\tleal 24(%esp), %eax\t\t#  24+esp Ľ eax\n\tmovl 24(%esp), %eax\t\t#  2012  eax\n\n## ܽ\n\nȣǿ main ҲһĻ\n<em></em>з Double һĽṹ\n\nΣָд洢ռģ32λ4ֽڵĴС\nд洢һڴַֻеԪд洢ռ䣬\n C оõ׵ַ棩\n˵ûд洢ռġ轫׵ֵַһָ룬\n׵ַĴʽǣ\n\n* ȫ飬 mov ָеһ\n* Ǿֲ飬 lea ָ esp  ebp + \nĽ\n\n``<b>׵ַһָĲ\nھֲռУҲȫֱռУ\nǱ̻ڴ</b>\nˣ׵ַܱġ\n\nprintf ĵøĺһȴ\nȻ call printfӡָ붼ʹ %p ʽ\nн£\n\n\t[lqy@localhost temp]$ gcc -o array array.c\n\t[lqy@localhost temp]$ ./array\n\ta:11 b:11\n\tdate:0xbf9c3b68\n\tp   :0xbf9c3b68\n\t[lqy@localhost temp]$ \n\n``date  p ֵ date ׵ַ\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "bss.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">未初始化全局变量\n</h1>\n\n　　为下一篇介绍进程内存分布做准备，\n这一篇先来介绍一下未初始化全局变量：\n\n　　未初始化全局变量，这名字就很直白，就是 C 程序中定义成\n全局作用域而又没有初始化的变量，我们知道这种变量在程序运行\n后是被自动初始化为 全0 的。编译器编译的时候会将这类变量\n收集起来集中放置到 .bss 段中，<b>这个段只记录了段长，\n没有实际上的内容（全是0，没必要存储），\n在程序被装载时操作系统会\n为它分配等于段长的内存，并全部初始化为0</b>。\n\n　　这有两个 C程序，都定义了全局数组 data（长度为1M，\n占用内存4MB），一个部分初始化（bss\\_init1.c），\n一个未初始化（bss\\_uninit1.c）：\n\nbss_init1.c：\n\n\t#include <stdio.h>\n\t#include <windows.h>\n\t\n\t#define MAXLEN 1024*1024\n\t\n\tint data[MAXLEN]={1,};\n\t\n\tint main()\n\t{\n\t\tSleep(-1);\n\t\treturn 0;\n\t}\n\nbss_uninit1.c：\n\n\t#include <stdio.h>\n\t#include <windows.h>\n\t\n\t#define MAXLEN 1024*1024\n\t\n\tint data[MAXLEN];\n\t\n\tint main()\n\t{\n\t\tSleep(-1);\n\t\treturn 0;\n\t}\n\n`　　`编译以上两个程序后：\n\n![bss1](http://fmn.rrfmn.com/fmn059/20121203/1935/original_4q5M_35d80000b351118d.jpg)\n\n　　可以看到有初始化的可执行文件的大小差不多是4MB，\n而未初始化的只有47KB！这就是 .bss 段有段长，\n而没有实际内容的表现。用 UltraEdit 打开 bss_init1.exe \n可看到文件中大部分是全0（data数组的内容）：\n\n![bss5](http://fmn.rrimg.com/fmn065/20121203/1935/original_RbRN_5afd0000b341125d.jpg)\n\n　　但是接下来运行（return 0 之前的 Sleep(-1) 保证了\n程序暂时不会退出）的时候，却发现 bss_init1.exe \n占用的空间明显少于 4MB，这是怎么回事呢？\n\n![bss2](http://fmn.rrimg.com/fmn065/20121203/1935/original_ejt4_363e0000b309118d.jpg)\n\n　　这就涉及程序装载的策略了。早期的操作系统（如：linux 0.01）\n采用的是一次装载：将可执行文件一次性完整装入内存后再执行程序。\n不管程序是 1KB 还是 60MB，都要等全部装入内存后才能执行，\n这显然是不太合理的。\n\n　　而现在的操作系统都是采用延迟装载：\n<b>将进程空间映射到可执行文件之后就开始执行了</b>，\n执行的时候如果发现要读/写的页不在内存中，\n就根据映射关系去读取进来，然后继续执行应用程序\n（应该是在页保护异常的处理中实现的）。\n\n　　bss_init1.exe 肯定是被映射了，<b>而程序中又没有对 data \n数组进行读/写操作</b>，所以操作系统也就懒得去装入这片内存了。\n下面修改一下这两个程序：在 Sleep(-1) 前将 data 数组\n的每个元素赋值为 -1：\n\n\tint i;\n\tfor(i=0; i<MAXLEN; ++i)\n\t\tdata[i] = -1;\n\n`　　`再运行，它们占用的内存都是 4M 了：\n\n![bss4](http://fmn.rrimg.com/fmn061/20121203/1935/original_OHR0_75660000b3e5118c.jpg)\n\n[回目录][content]\n"
  },
  {
    "path": "byval.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">ֵ\n</h1>\n\nںһƪѾҿֵݵࣺ\n<b>ʵΡβиԵĴ洢ռ䣨ʵҲֻһֵ\nûд洢ռ䣩ʵ -> βǸֵḶ́\nںǰ̣\n˺жβν޸ģʵεֵű䡣</b>\n\nǾû취ں޸ⲿˣ\nָͺˣǲҪ޸ֵָ\nֻ޸ָָڴ飺\n\n## 0ָ루͡ṹ壩\n\n\tint add(int a, int b)\n\t{\n\t\treturn a + b;\n\t}\n\n``ֲֻĺֻҪֵм󷵻ؽ\nҪ޸ⲿ\n\n## 1ָ\n\n\tvoid swap(int *a, int *b)\n\t{\n\t\tint t = *a;\n\t\t*a = *b;\n\t\t*b = t;\n\t}\n\n``ʹó\n\n\tint a=1, b=2;\n\tswap(&a, &b);\n\n``ʹ 1 ָһĿģ\n\n1. Ҫ޸ⲿ   ṹ ֵ\n2. <b>Ϊ˽ʡռ</b>躯Ҫһⲿ\nռÿռܴĽṹ 4KB С\nȻûָĵַ\nֻռ 4 ֽڣýṹΪ\nҪռ 4KB ڴ棬һҪֵ  \n<b> char* ַҲԿǳڽʡռĿġ</b>\n\n## 2ָ\n\nC ׼ͷļ string.h ṩһ strdup \n\n\tchar *strdup(char *s);\n\n``úǸַ\nصַĴ洢ռǶ̬ģԭַĿռ䡣\nǵ 1 ˵ҪԷƱ\nҲǴ²֪O(_)O~\n\nڲ÷ֵ 2 ָʵ\n\n\tvoid my_strdup(char **p, char *s)\n\t{\n\t\tunsigned int len = strlen(s) + 1;\n\t\n\t\t*p = (char *)malloc(len);\n\t\tmemcpy(*p, s, len);\n\t}\n\n``ʹó\n\n\tchar *s = \"abc\";\n\tchar *d = NULL;\n\t\n\tmy_strdup(&d, s);\n\n``пԿ <b>2 ָҪ޸ָʱ</b>\nһҪںΪָ붯̬ռ䡣\nһ÷ָʵ֣жָҪһ޸ģ\n 2 ָҪܶ࣬Ϊֵֻ 1 \nĸ﷨ûƵġ\n\nstrdup صַҪ free Ŷ\n\n⣬һûʹ 3 ָıҪ\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "call.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">\n</h1>\n\n## ǰ\n\nд 顢ṹ塢ָ ֮дõģ\nΪûǣЩ\nҾĻܻ¶δţ\nԻȼ򵥵طһºðɣ\n֮ٲϵƺһ\n\n## CԴdouble.c\n\n\t#include <stdio.h>\n\t\n\tint Double(int b)\n\t{\n\t\tint c;\n\t\t\n\t\tc = b + b;\n\t\t++b;\t// Ӱ쵽 a \n\t\t\n\t\treturn c;\n\t}\n\t\n\tint main()\n\t{\n\t\tint a = 1;\n\t\tint d = Double(a);\n\t\n\t    printf(\"a:%d d:%d\\n\", a, d);\n\t    return 0;\n\t}\n\n## \n\n\tgcc -S double.c\n\n``gcc -S double.c ĬϾǰѻԴ double.s У\n֮ǰһֱ -o ѡΪ˱ĽO(_)O~\ndouble.s еݼ򻯺\n\n<pre><code>Double:<b>\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\n\tsubl\t$16, %esp\n\tmovl\t8(%ebp), %eax\n\taddl\t%eax, %eax\n\tmovl\t%eax, -4(%ebp)\n\taddl\t$1, 8(%ebp)\n\tmovl\t-4(%ebp), %eax\n\tleave\n\tret</b>\n\nmain:\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\n\tandl\t$-16, %esp\n\tsubl\t$32, %esp<b>\n\tmovl\t$1, 28(%esp)\n\tmovl\t28(%esp), %eax\n\tmovl\t%eax, (%esp)\n\tcall\tDouble</b>\n\tmovl\t%eax, 24(%esp)\n\tmovl\t$.LC0, %eax\n\tmovl\t24(%esp), %edx\n\tmovl\t%edx, 8(%esp)\n\tmovl\t28(%esp), %edx\n\tmovl\t%edx, 4(%esp)\n\tmovl\t%eax, (%esp)\n\tcall\tprintf\n\tmovl\t$0, %eax\n\tleave\n\tret\n</code></pre>\n\n## \n\n<b></b>Ƕ Double һá\n\nڴ main е 1 <b></b>ָʼ\n\n<table border=\"1\">\n\n<tr><td><pre><code>\n\tmovl\t$1, 28(%esp)\t# a=1\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrimg.com/fmn056/20121109/1855/original_1Ykq_291900004dfe1191.jpg\" />\n</td></tr>\n<tr><td>\nǸ ֲ a ֵ<br>\nջָĴ esp ڵֵ 8000\nִָջͼ\n</td></tr>\n\n<tr><td><pre><code>\n\tmovl\t28(%esp), %eax\n\tmovl\t%eax, (%esp)\t# (%esp) = a\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrimg.com/fmn063/20121109/1855/original_6CEt_3c0400004d8a125d.jpg\" />\n</td></tr>\n<tr><td>\nָ a ֵ 1 д˵ַΪ 8000 ڴ飨4ֽڣ\n b ɣ<br>ڻҪ½ۡ\n</td></tr>\n\n<tr><td><pre><code>\n\tcall\tDouble\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrfmn.com/fmn058/20121109/1855/original_cK4p_448e000041101190.jpg\" />\n</td></tr>\n<tr><td>\ncall ִָУɷΪ裺<br>\n<ol>\n\t<li> call ָһָĵַѹջ\n\t<li> eip ޸Ϊ Double ĵַ\n</ol>\nȻת Double ȡִָˡ<br>\n call ָ֮ movl\t%eax, 24(%esp) \nĵַ 1000ô call ִк\nջΪͼ\n</td></tr>\n\n<tr><td><pre><code>\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrfmn.com/fmn058/20121109/1855/original_5Rl9_5de000004db9118f.jpg\" />\n</td></tr>\n<tr><td>\nȽɵ ebp ѹջȻʱ esp ֵ ebp\nῴĿ\n</td></tr>\n\n<tr><td><pre><code>\n\tsubl\t$16, %esp\t# esp -= 16\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrimg.com/fmn056/20121109/1855/original_7ikk_0fad00004dc5118e.jpg\" />\n</td></tr>\n<tr><td>\nDouble ľֲռôˣȻе˷ѣ\n</td></tr>\n\n<tr><td><pre><code>\n\tmovl\t8(%ebp), %eax\n\taddl\t%eax, %eax\n\tmovl\t%eax, -4(%ebp)\t# c = b + b\n\taddl\t$1, 8(%ebp)\t\t# ++b\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrimg.com/fmn065/20121109/1855/original_VXpq_05be00004de5118d.jpg\" />\n</td></tr>\n<tr><td>\n8(%ebp) ǰպñʾ 8000 ڴ飬\n 4 ָ 8000 ڴֵеĴ\nǿȷ 8000 ǲ b\n -4(%ebp)  c\n</td></tr>\n\n<tr><td><pre><code>\n\tmovl\t-4(%ebp), %eax\t# eax = c\n\tleave\n\tret\n</code></pre></td>\n<td rowspan=\"2\"><img src=\"http://fmn.rrimg.com/fmn056/20121109/1850/original_zq1t_433a00004e92125b.jpg\" />\n</td></tr>\n<tr><td>\nβˣ<b>ķֵҪ洢ۼӼĴ eax </b>\nleave ָͬ<br>\nmovl %ebp, %esp<br>\npopl %ebp<br>\nս Double ʱָβӦ<br>\n<b>Ǿֲռ䱻ˣebp Ҳԭˡ</b>\n<p> ret ൱ popl %eip\nͼִ call ָָ֮ˡ</p>\nȻ Double ľֲռ䱻ˣ\nеֵǱֲģһֱ֮ printf ʱ\nDouble ľֲԼݲűǡ\n</td></tr>\n\n</table>\n\n## \n\nһķٴӴϻعһ Double \n\n<pre><code>Double:\n\tpushl\t%ebp\t\t\t\t#----------ָ֡ ebp л\n\tmovl\t%esp, %ebp\t\t\t#---------/\n\tsubl\t$16, %esp\t\t\t#----------ؾֲռ\n\tmovl\t8(%ebp), %eax\t\t#\\\n\taddl\t%eax, %eax\t\t\t#-\\\n\tmovl\t%eax, -4(%ebp)\t\t#-C Ĳ\n\taddl\t$1, 8(%ebp)\t\t\t#/\n\tmovl\t-4(%ebp), %eax\t\t#-----ֵ浽 eax Ĵ\n\tleave\t\t\t\t\t\t#--------\\\n\tret\t\t\t\t\t\t\t#---------ջָָ֡롢\n</code></pre>\n\nΪָҲл֡\n\n<b> Double ̫򵥣ûгֱĴָ\neax ĴҪÿ֪淵ֵģ\nں󲿷ֿ϶ᱻ޸ģӵĺںǰ\npushl ޸ĵļĴ ret ֮ǰ popl ĴԻָԭֵ</b>\n\n## С\n\nͨ Double ķ\nע⵽ ebp  esp ƣ\nоҲͨ ebp + ƫ ķʽ   ֲ\nڿҳŵˣ<b>esp  ebp ƫƾ\nֲıʽ</b>\n ָ֡Ĵ ebp ٳһƪз\n\nͬʱҲԵؿֵݵḶ́\nǰ a Ƶ bֵǷֱǲͬڴ飩\n b ֱӱˣҲٿ a\nȻ Double  ++b ˣ a ֵȻΪ 1\nн£\n\n\t[lqy@localhost temp]$ gcc -o double double.c\n\t[lqy@localhost temp]$ ./double\n\ta:1 d:2\n\t[lqy@localhost temp]$ \n\n``һƪټֵݡ\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "dynamicstack.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">汇编实现的动态栈\n</h1>\n\n　　这一篇就是实现 d_printf，废话不多说，直接上代码。\n由于 VC 的内联汇编还是比较清晰，那就先贴 VC 版的。\n\n## 一、d_printf VC版\n\n\t#include <stdio.h>\n\t\n\tvoid d_printf(const char *fmt, int n, int a[])\n\t{\n\t\tstatic int size1, size2;\n\t\tstatic const char *fmt_copy;\n\t\n\t\tsize1 = 4*n;\t\t// 可变参数的空间大小\n\t\tsize2 = size1 + 4;\t// 还有 fmt 指针4字节, 恢复 esp 时用\n\t\tfmt_copy = fmt;\n\t\t\n\t\t__asm{\n\t\t\t// 保护要修改的 ecx/esi/edi 寄存器\n\t\t\tpush ecx\n\t\t\tpush esi\n\t\t\tpush edi\n\t\n\t\t\t// 给 ecx/esi/edi 赋值\n\t\t\tmov ecx, n\t// movsd 的执行次数\n\t\t\tmov esi, a\t// a -> esi\n\t\t\tsub esp, size1\n\t\t\tmov edi, esp\t// esp - size1 -> edi\n\t\n\t\t\trep\tmovsd\t\t// n 次4字节拷贝\n\t\t\tpush fmt_copy\t// 压栈格式串(字符串指针)\n\t\t\tcall printf\n\t\n\t\t\tadd esp, size2\t// 恢复栈\n\t\t\t// 恢复各个寄存器\n\t\t\tpop edi\n\t\t\tpop esi\n\t\t\tpop ecx\n\t\t}\n\t}\n\t\n\tint main()\n\t{\n\t\tchar fmt[1024];\t// 格式串\n\t\tchar c;\t\t\t// 额外读取一个字符\n\t\tint a[1024];\t// 存读到的整数\n\t\tint i;\n\t\t\n\t\twhile(EOF != scanf(\"%s%c\", fmt, &c)) // 读到 EOF 就结束\n\t\t{\n\t\t\tif(c == '\\n') // 格式串后没有数字\n\t\t\t{\n\t\t\t\tprintf(\"%s\\n\\n\", fmt); // 直接打印, 不用 d_printf\n\t\t\t\tcontinue;\n\t\t\t}\n\t\n\t\t\t// 循环读取各个整数\n\t\t\ti = 0;\n\t\t\tdo\n\t\t\t{\n\t\t\t\tscanf(\"%d%c\", &a[i++], &c);\n\t\t\t}while(c != '\\n');\n\t\n\t\t\t// 调用 d_printf, i 刚好是输入的整数的个数\n\t\t\td_printf(fmt, i, a);\n\t\t\tprintf(\"\\n\\n\"); // 补个换行比较好看O(∩_∩)O~\n\t\t}\n\t}\n\n## 二、d_printf gcc 版（main 函数跟 VC 版的一样）\n\n\t#include <stdio.h>\n\t\n\tvoid d_printf(const char *fmt, int n, int a[])\n\t{\n\t\tint d0, d1, d2;\n\t\t\n\t\tstatic int size1, size2;\n\t\tstatic const char *fmt_copy;\n\t\n\t\tsize1 = 4*n;\t\t// 可变参数的空间大小\n\t\tsize2 = size1 + 4;\t// 还有 fmt 指针4字节, 恢复 esp 时用\n\t\tfmt_copy = fmt;\n\t\t\n\t\tasm volatile(\n\t\t\t\"subl %6, %%esp\\n\\t\"\n\t\t\t\"movl %%esp, %%edi\\n\\t\"\n\t\t\t\"rep ; movsl\\n\\t\"\n\t\t\t\"pushl %3\\n\\t\"\n\t\t\t\"call printf\\n\\t\"\n\t\t\t\"addl %7, %%esp\"\n\t\t\t: \"=&S\"(d0), \"=&D\"(d1), \"=&c\"(d2)\n\t\t\t: \"m\"(fmt_copy), \"0\"(a), \"2\"(n), \"m\"(size1), \"m\"(size2));\n\t}\n\t\n\tint main()\n\t{\n\t\tchar fmt[1024];\t// 格式串\n\t\tchar c;\t\t\t// 额外读取一个字符\n\t\tint a[1024];\t// 存读到的整数\n\t\tint i;\n\t\t\n\t\twhile(EOF != scanf(\"%s%c\", fmt, &c)) // 读到 EOF 就结束\n\t\t{\n\t\t\tif(c == '\\n') // 格式串后没有数字\n\t\t\t{\n\t\t\t\tprintf(\"%s\\n\\n\", fmt); // 直接打印, 不用 d_printf\n\t\t\t\tcontinue;\n\t\t\t}\n\t\n\t\t\t// 循环读取各个整数\n\t\t\ti = 0;\n\t\t\tdo\n\t\t\t{\n\t\t\t\tscanf(\"%d%c\", &a[i++], &c);\n\t\t\t}while(c != '\\n');\n\t\n\t\t\t// 调用 d_printf, i 刚好是输入的整数的个数\n\t\t\td_printf(fmt, i, a);\n\t\t\tprintf(\"\\n\\n\"); // 补个换行比较好看O(∩_∩)O~\n\t\t}\n\t}\n\n## 三、运行效果\n\n　　linux 中的运行效果如下：\n\n\t[lqy@localhost temp]$ ./d_printf \n\tnospaceword\n\tnospaceword\n\t\n\t%X 256\n\t100\n\t\n\t%d+%d=%d 1 2 3\n\t1+2=3\n\t\n\t>%3d>%03d> 3 3\n\t>  3>003>\n\t\n\t>%3d>%-3d> 3 3\n\t>  3>3  >\n\t\n\t[lqy@localhost temp]$ \n\n`　　`最后输入 EOF 结束：Ctrl + D（linux）、Ctrl + Z\n（windows）。<b>由于转义符是编译时处理的，printf 是不管的，\n所以\\n什么的在这里不管用^_^</b>。\n\n## 四、为什么用 static\n\n　　d\\_printf 中为什么将 size1、size2、fmt_copy 声明为\nstatic 变量呢？\n\n1. 不能用寄存器。size2 是在 call printf 之后使用的，\n<b>但是后来我发现 printf 执行完后改动了好几个寄存器的值</b>，\n所以如果将 size2 保存到某个寄存器中是不行的\n（难怪C语言喜欢内存，寄存器太不可靠了o(╯□╰)o）。\n2. 不能用局部变量。不让用寄存器就用内存呗！\n<b>但是我们要自主修改 esp，\n而局部变量有可能是通过 esp+常量偏移 定位的</b>\n（如果是用 ebp+常量偏移（VC一般这么用）定位的就没问题），\n所以 subl %6, %%esp 之后 到 addl %7, %%esp 之前\n都不能使用局部变量，否则会定位错误。\n所以才使用 static 变量，<b>\n因为 static 变量是用绝对地址定位的，跟 esp 毫无关系</b>。\n\n## 五、使用内联汇编的建议\n\n1. 不要用。\n2. 如果非得用的话，尽量用 C 语言实现+-*/，\n如 d_printf 中给 size1、size2 赋值；\n内联汇编只实现不得不用的部分（内联汇编一般都短小精悍）。\n\n<h2 align=\"center\">《解剖C语言》就此完结。</h2>\n\n[回目录][content]\n"
  },
  {
    "path": "frame.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">函数帧\n</h1>\n\n　　这标题一念出来我立刻想到了一个名人：白素贞……当然，\n此女与本文无关，下面进入正题：\n\n<pre>其实程序运行就好比一帧一帧地放电影，每一帧是一次函数调用，电影放完了，我们就看到结局了。</pre>\n\n　　我们用一个递归求解阶乘的程序来看看这个放映过程（fac.c）：\n\n\t#include <stdio.h>\n\t\n\tint fac(int n)\n\t{\n\t\tif(n <= 1)\n\t\t\treturn 1;\n\t\treturn n * fac(n-1);\n\t}\n\t\n\tint main()\n\t{\n\t\tint n = 3;\n\t\tint ans = fac(n);\n\t\t\n\t\tprintf(\"%d! = %d\\n\", n, ans);\n\t\treturn 0;\n\t}\n\n## main 帧\n\n　　首先 main 函数被调用（程序可不是从 main 开始执行的）：\n\n<table>\n<tr><td>\n<pre><code>main:\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\n\tandl\t$-16, %esp\n\tsubl\t$32, %esp\n\tmovl\t$3, 28(%esp)\t# n = 3\n\tmovl\t28(%esp), %eax\n\tmovl\t%eax, (%esp)\n\tcall\tfac\n\tmovl\t%eax, 24(%esp)\t# 返回值存入 ans\n\tmovl\t$.LC0, %eax\n\tmovl\t24(%esp), %edx\n\tmovl\t%edx, 8(%esp)\n\tmovl\t28(%esp), %edx\n\tmovl\t%edx, 4(%esp)\n\tmovl\t%eax, (%esp)\n\tcall\tprintf\n\tmovl\t$0, %eax\n\tleave\n\tret\n</code></pre></td>\n<td><img src=\"http://fmn.rrimg.com/fmn056/20121124/1940/original_D0zG_726b00003e04118c.jpg\" /></td>\n</tr></table>\n\n`　　`main 函数创建了一帧：\n\n* 从 esp 到 ebp + 4\n* 上边是本次调用的返回地址、旧的 ebp 指针\n* 然后是 main 的局部变量 n、ans\n* 最下边是参数的空间，右上图显示的是 main 中调用 printf \n前的栈的使用情况\n\n`　　`进入 main 函数，前 4 条指令开辟了这片空间，\n在退出 main 函数之前的 leave ret 回收了这片空间\n（<b>C++ 在回收这片空间之前要析构此函数中的所有局部对象</b>）。\n<b>在 main 函数执行期间 ebp 一直指向 帧顶 - 4 的位置，\nebp 被称为帧指针也就是这个原因</b>。\n\n## 调用惯例\n\n　　调用函数的时候，先传参数，然后 call，\n具体这个过程怎么实现有相关规定，这样的规定被称为<b>调用惯例</b>，\nC语言中有多种调用惯例，它们的不同之处在于：\n\n1. 参数是压栈还是存入寄存器\n2. 参数压栈的次序（从右至左 | 从左至右）\n3. 调用完成后是调用者还是被调用者来恢复栈\n\n`　　`各种调用惯例<em>《程序员的自我修养》——链接、装载与库</em>\n这本书中有简要介绍，我照抄后在本文后面列出。C语言默认的\n调用惯例是 cdecl：\n\n1. 参数从右至左压栈\n2. 调用完成后调用者负责恢复栈\n\n`　　`可以从 printf(\"%d! = %d\\n\", n, ans); 的调用过程\n中看出。\n\n　　虽然 VC、gcc 都默认使用 cdecl 调用惯例，\n但它们的实现却各有风格：\n\n* VC 一般是从右至左 push 参数，call，add esp, XXX\n* 而 gcc 在给局部变量分配空间的时候也给参数分配了足够的空间，\n所以只要从右至左 mov 参数, XXX(%esp)，call 就可以了，\n调用者根本不用去恢复栈，因为传参数的时候并没有修改栈指针 esp。\n\n## fac 帧\n\n　　说完调用惯例我们接着来看第一次调用 fac：\n\n<table>\n<tr><td>\n<pre><code>fac:\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\n\tsubl\t$24, %esp\n\tcmpl\t$1, 8(%ebp)\n\tjg\t.L2\t\t\t# n > 1 就跳到 .L2\n\tmovl\t$1, %eax\n\tjmp\t.L3\t\t\t# 无条件跳到 .L3\n.L2:\n\tmovl\t8(%ebp), %eax\n\tsubl\t$1, %eax\n\tmovl\t%eax, (%esp)\n\tcall\tfac\t\t#  fac(n-1)\n\timull\t8(%ebp), %eax\t# eax = n * eax\n.L3:\n\tleave\n\tret\n</code></pre></td>\n<td><img src=\"http://fmn.rrfmn.com/fmn058/20121124/1940/original_XcDN_08c400005ab9125d.jpg\" /></td>\n</tr></table>\n\n　　fac(3) 开辟了第一个 fac 帧：\n\n* 从 esp 到 ebp + 4（fac 还能\"越界\"地读到参数 n）\n* 上边是 返回地址、旧的 ebp 指针（指向 main 帧）\n* fac 没有局部变量，又浪费了很多字节\n* 参数占了最下边的 4 字节（需要递归时使用）\n\n`　　`这时还不满足递归终止条件，于是fac(3)又递归地调用了fac(2)，\nfac(2)又递归的调用了fac(1)，到这个时候栈变成了如下情况：\n\n![total](http://fmn.rrimg.com/fmn062/20121124/1940/original_y9zg_1a3800005b5a118e.jpg)\n\n　　上图的箭头的含义很明显：\n<b>从 ebp 可回溯到所有的函数帧</b>，\n这是由于每个函数开头都来两条 pushl %ebp、movl %esp, %ebp造成的。\n\n　　参数总是调用者写入，被调用者来读取（被调用者修改参数毫无意义），\n这是一种默契^_^。\n\n程序继续运行：\n\n1. fac(1) 满足了递归终止条件，fac(1) 返回 1，fac(1)#3 帧消亡\n2. 继续执行 fac(2)，fac(2) 返回 1\\*2，fac(2)#2 帧消亡\n3. 继续执行 fac(3)，fac(3) 返回 2\\*3，fac(1)#1 帧消亡\n4. 继续执行 main，printf 结果，返回 0，main 帧消亡\n5. 继续执行 ？？？（且听下回分解）\n\n最终程序结束（进程僵死，一会儿后操作系统会来收尸\n（回收内存及其他资源））。\n\n## 小结\n\n　　函数帧保存的是函数的一个完整的局部环境，\n保证了函数调用的正确返回（函数帧中有返回地址）、\n返回后继续正确地执行，因此函数帧是 C语言 能调来调去的保障。\n\n<h2 align=\"center\">主要的调用惯例</h2>\n<table border=\"1\">\n <tr>\n  <th>调用惯例</th>\n  <th>出栈方</th>\n  <th>参数传递</th>\n  <th>名字修饰</th>\n </tr>\n <tr>\n  <td>cdecl</td>\n  <td>函数调用方</td>\n  <td>从右至左的顺序压参数入栈</td>\n  <td>下划线+函数名</td>\n </tr>\n <tr>\n  <td>stdcall</td>\n  <td>函数本身</td>\n  <td>从右至左的顺序压参数入栈</td>\n  <td>下划线+函数名+@+参数的字节数，\n  如函数 int func(int a, double b)的修饰名是\n  _func@12</td>\n </tr>\n  <tr>\n  <td>fastcall</td>\n  <td>函数本身</td>\n  <td>头两个 DWORD(4字节)类型或者更少字节的参数\n  被放入寄存器，其他剩下的参数按从右至左的顺序入栈</td>\n  <td>@+函数名+@+参数的字节数</td>\n </tr>\n <tr>\n  <td>pascal</td>\n  <td>函数本身</td>\n  <td>从左至右的顺序入栈</td>\n  <td>较为复杂，参见pascal文档</td>\n </tr>\n </table>\n\n[回目录][content]\n"
  },
  {
    "path": "gcc.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">ͻ۽\n</h1>\n\n linux ±д C \nô㽫Ϭķ\n\n---\n\n## \n\nһCmax.c\n\n\t#define MAX(a,b) ((a)>=(b)?(a):(b))\n\t\n\tint main(){\n\t\tint c=MAX(1,2); // עעעע\n\t\treturn 0;\n\t}\n\n``ܼ򵥣ǶʹһMAX꣬\nʽǰǻᱻ滻ΪĿģ\nڿĲգ\n\n\tgcc -E -o max2.c max.c\n\n``<em> -o max2.c  gcc \nҪ max2.c ļ</em>\n\n֣ΰɣ\n\n\t# 1 \"max.c\"\n\t# 1 \"<built-in>\"\n\t# 1 \"<>\"\n\t# 1 \"max.c\"\n\t\n\t\n\tint main(){\n\t int c=((1)>=(2)?(1):(2));\n\t return 0;\n\t}\n\n``max2.cеݣMAX(1,2) 滻 \n((1)>=(2)?(1):(2))ֻˣ\n\nþ滻꣬ǺҶ̫á\n ִ linux ںԴмֱõ˼£\n˵ linux ں Cꡢ дġ\nǿǶ׵ģҲ˵   Ҳ \nлԳܹ滻ĺ꣬\n൱ˡʮַļ򵥵һ䣬\nԭΪĿʱܾͱ߰ʮַˣ\nҪ䣬ʹˡ\n\nں꣬һƪܡ\n\n---\n\n## ۽\n\nӦǲ۽𾦵ģ\n۽𾦿Կ΢Сϸڡд˸Hello World\nhello.c\n\n\t#include <stdio.h>\n\n\tint main(){\n\t\tprintf(\"Hello, World!\\n\");\n\t\treturn 0;\n\t}\n\n``Hello World Ͳý˰ɣO(_)O~\nȻû۽һ£\n\n\tgcc -S -o hello.s hello.c\n\n``Hello World Ļͳ(hello.s)\n\n\t\t.file\t\"hello.c\"\n\t\t.section\t.rodata\n\t.LC0:\n\t\t.string\t\"Hello, World!\"\n\t\t.text\n\t.globl main\n\t\t.type\tmain, @function\n\tmain:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tandl\t$-16, %esp\n\t\tsubl\t$16, %esp\n\t\tmovl\t$.LC0, (%esp)\n\t\tcall\tputs\n\t\tmovl\t$0, %eax\n\t\tleave\n\t\tret\n\t\t.size\tmain, .-main\n\t\t.ident\t\"GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)\"\n\t\t.section\t.note.GNU-stack,\"\",@progbits\n\n``Ǻ\nֻҪ֪ôá۽𾦡ˣ\nļƪÿˡ\n\n---\n\nͻ۽ʵǿضϱ\nõмģgccǣ\n\n\tԤ->->->\n\n``ʹòͬıѡԵóͬм\n\n<table border=\"1\">\n <tr>\n  <th>׶</th>\n  <th></th>\n  <th>ضϺĲ</th>\n </tr>\n <tr>\n  <td></td>\n  <td></td>\n  <td>CԴ</td>\n </tr>\n <tr>\n  <td>Ԥ</td>\n  <td>gcc -E</td>\n  <td>滻˺CԴ(û#define,#include),\n\tɾע</td>\n </tr>\n <tr>\n  <td></td>\n  <td>gcc -S</td>\n  <td>Դ</td>\n </tr>\n <tr>\n  <td></td>\n  <td>gcc -c</td>\n  <td>Ŀļļ\n  вڴļеⲿ</td>\n </tr>\n <tr>\n  <td></td>\n  <td>gcc</td>\n  <td>ִгһɶĿļӶɣ\n\tļбҵõ</td>\n </tr>\n</table>\n\nҲͬѧ -c һûأ\nļķҲõǺ٣õʱ˵ɡ\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "globalvar.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">ȫֱ\n</h1>\n\n## ʵƷ\n\nСglobal.c\n\n\t#include <stdio.h>\n\t\n\tint i = 1;\n\t\n\tint main()\n\t{\n\t    ++i;\n\t\n\t    printf(\"%d\\n\",i);\n\t    return 0;\n\t}\n\n``i Ķ屻˺ߣ\ni ͳΪȫֱеĽҲģ\nҹĵ i ʲô\n\n## \n\nյĻ۽Ҳˣǻῴ\nǵ÷ִļܿս\n\n\t[lqy@localhost temp]$ gcc -o global global.c\n\t[lqy@localhost temp]$ objdump -s -d global > global.txt\n\t[lqy@localhost temp]$ \n\n* *gcc -o global global.c* Ǳ global.c \nɿִļ global  \n* *objdump -s -d global > global.txt* Ƿ global\n\t* -s ԽжεʮƵķʽӡ\n\t* -d ԽаָĶη\n\t* > global.txt ǽ׼ global.txt ļ\nרҵĻ\"ض\"\n\n<em>objdump  linux һ๤ߣ\nܹĿļִļ</em>\n\nglobal ļķ global.txt \nļУ global.txtļȽϳҵ 357 У\nλ main \n\n<pre><code>080483c4 &lt;main>:\n 80483c4:\t55                   \tpush   %ebp\n 80483c5:\t89 e5                \tmov    %esp,%ebp\n 80483c7:\t83 e4 f0             \tand    $0xfffffff0,%esp\n 80483ca:\t83 ec 10             \tsub    $0x10,%esp<b>\n 80483cd:\ta1 64 96 04 08       \tmov    0x8049664,%eax\n 80483d2:\t83 c0 01             \tadd    $0x1,%eax\n 80483d5:\ta3 64 96 04 08       \tmov    %eax,0x8049664</b>\n 80483da:\t8b 15 64 96 04 08    \tmov    0x8049664,%edx\n 80483e0:\tb8 c4 84 04 08       \tmov    $0x80484c4,%eax\n 80483e5:\t89 54 24 04          \tmov    %edx,0x4(%esp)\n 80483e9:\t89 04 24             \tmov    %eax,(%esp)\n 80483ec:\te8 03 ff ff ff       \tcall   80482f4 <printf@plt>\n 80483f1:\tb8 00 00 00 00       \tmov    $0x0,%eax\n 80483f6:\tc9                   \tleave\n 80483f7:\tc3                   \tret\n</code></pre>\n\n<b></b>ֱ ++i ķ\n<b>ȫֱ i ˾ԵַΪ 0x8049664 \nڴ飨СΪ4ֽڣ</b>\nע 0x8049664 ָ 0x8049664\nҪʾֵΪ 0x8049664 ӦдΪ $0x8049664\n\n## յ\n\nͨ۽𾦣\n\n\tgcc -S -o global.s global.c\n\n``ǿ ++i Ļʽǣ\n\n\tmovl\ti, %eax\n\taddl\t$1, %eax\n\tmovl\t%eax, i\n\n``գ̫ʧˣ\n\n## Ŀļеȫֱ\n\nĿļҲ objdump ࣺ\n\n\t[lqy@localhost temp]$ gcc -c -o global.o global.c\n\t[lqy@localhost temp]$ objdump -s -d global.o > global.txt \n\t[lqy@localhost temp]$ \n\n``global.o  global.txt ûôˣ\nֻ 35 У ++i ֵĽǣ\n\n\t   9:\ta1 00 00 00 00       \tmov    0x0,%eax\n\t   e:\t83 c0 01             \tadd    $0x1,%eax\n\t  11:\ta3 00 00 00 00       \tmov    %eax,0x0\n\n``ôô 0x8049664 أ\nĿļ  ִļˣ\n<b>ȫֱĿļֻһðƵַ\nӺ󣨸ĿļЭ̣ΪԼȫֱһ̣\nϣյľԵַ</b>\n\n## ĿļͿִļ\n\n<b>ĿļǱĲ\nѾ C   ת</b>\nǲֻĲҪӹ\n\nִļɶĿļ C пӶɵģ\nC пṩ printf Ⱥʵ֡\n\nlinux ĿļĬչ.oͿִļ\nELF ʽļݰһʽ֯Ķļ\nƵģWindows  VISUAL C++ Ŀļ\nչ.obj COFF ʽִļ\nչ.exe PE ʽ\nELF  PE Ǵ COFF չġ\n\nΪ linux ĿļͿִļݸʽһģ\n objdump ȿԷִļҲԷĿļ\n\n## С\n\nȫֱҲյڴַˣ\nһǳ\n\nֽһߣobjdump\nкҪõĹߺʹ÷ˣgccobjdump\nš񻹲ҪĶO(_)O~\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "inlineasm.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">内联汇编\n</h1>\n\n　　内联汇编是指在 C/C++ 代码中嵌入的汇编代码，\n与全部是汇编的汇编源文件不同，它们被嵌入到 C/C++ 的大环境中。\n\n## 一、gcc 内联汇编\n\n　　gcc 内联汇编的格式如下：\n\n    asm ( 汇编语句\n        : 输出操作数\t\t// 非必需\n        : 输入操作数\t\t// 非必需\n        : 其他被污染的寄存器\t// 非必需\n        );\n\n`　　`我们通过一个简单的例子来了解一下它的格式（gcc_add.c）：\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint a=1, b=2, c=0;\n\t\n\t\t// 蛋疼的 add 操作\n\t\tasm(\n\t\t\t\"addl %2, %0\"\t\t// 1\n\t\t\t: \"=g\"(c)\t\t\t// 2\n\t\t\t: \"0\"(a), \"g\"(b)\t// 3\n\t\t\t: \"memory\");\t\t// 4\n\t\n\t\tprintf(\"现在c是:%d\\n\", c);\n\t\treturn 0;\n\t}\n\n`　　`内联汇编中：\n\n1. 第1行是汇编语句，用双引号引起来，\n<b>多条语句用 ; 或者 \\n\\t 来分隔。</b>\n2. 第2行是输出操作数，都是 \"=?\"(var) 的形式，\nvar 可以是任意内存变量（输出结果会存到这个变量中），\n? 一般是下面这些标识符\n（表示内联汇编中用什么来代理这个操作数）：\n\t* a,b,c,d,S,D 分别代表 eax,ebx,ecx,edx,esi,edi 寄存器\n\t* r 上面的寄存器的任意一个（谁闲着就用谁）\n\t* m 内存\n\t* i 立即数（常量，只用于输入操作数）\n\t* g 寄存器、内存、立即数 都行（gcc你看着办）\n\n\t<b>在汇编中用 %序号 来代表这些输入/输出操作数，\n序号从 0 开始。为了与操作数区分开来，\n寄存器用两个%引出，如：%%eax</b>\n3. 第3行是输入操作数，都是 \"?\"(var) 的形式，\n<b>? 除了可以是上面的那些标识符，还可以是输出操作数的序号，\n表示用 var 来初始化该输出操作数</b>，\n上面的程序中 %0 和 %1 就是一个东西，初始化为 1（a的值）。\n4. 第4行标出那些在汇编代码中修改了的、\n又没有在输入/输出列表中列出的寄存器，\n这样 gcc 就不会擅自使用这些\"危险的\"寄存器。\n还可以用 \"memory\" 表示在内联汇编中修改了内存，\n之前缓存在寄存器中的内存变量需要重新读取。\n\n`　　`上面这一段内联汇编的效果就是，\n把a与b的和存入了c。当然这只是一个示例程序，\n谁要真这么用就蛋疼了，\n<b>内联汇编一般在不得不用的情况下才使用</b>。\n\n## 二、VC 内联汇编\n\n　　gcc 内联汇编被设计得很复杂，初学者看了往往头大，\n而 VC 的内联汇编就简单多了：\n\n\t__asm{\n\t\t汇编语句\n\t}\n\n`　　`一个例子程序如下（vc_add.c）：\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint a=1, b=2, c=0;\n\t\n\t\t// 蛋疼的 add 操作\n\t\t__asm{\n\t\t\tpush eax\t// 保护 eax\n\t\n\t\t\tmov eax, a\t// eax = a;\n\t\t\tadd eax, b\t// eax = eax + b;\n\t\t\tmov c, eax\t// c = eax;\n\t\n\t\t\tpop eax\t\t// 恢复 eax\n\t\t}\n\t\n\t\tprintf(\"现在c是:%d\\n\", c);\n\t\treturn 0;\n\t}\n\n`　　`VC 的内联汇编中可以直接以变量名的形式使用局部变量，\n这就方便多了。<b>但是，\nVC 内联汇编中有些变量名是保留的，比如：size，\n使用这些变量名就会报错（把b改成size，\n上面的程序就编译不通过了）。所以，起名字一定要小心！</b>\n\n　　因为 VC 没有输入/输出操作数列表，\n它也不看你的汇编代码（直接拿去用），\n所以它不知道你修改了哪些寄存器，\n这些要修改的寄存器可能保存着重要数据，\n所以用 push/pop 来 保护/恢复 要修改的寄存器。\n而 gcc 就不需要，它能从输入/输出列表中获得丰富的信息\n来调剂各个寄存器的使用，\n并进行优化，所以从效率上说 VC 完败！\n\n## 三、为什么用内联汇编\n\n　　用内联汇编的主要目的是为了提高效率：\n假设有一个比较文本差异的程序 diff，\n它花了 99% 的时间在 strcmp 这个函数上，\n如果用内联汇编实现的一个高效的 strcmp 比用 C 语言实现的快\n 1 倍，那么专家花在这个小小函数上的心思就能够将整个程序的效率\n提高差不多 1 倍，这是很值得去做的\"斤斤计较\"。\n\n　　还有一个目的就是为了实现 C 语言无法实现的部分，\n比如说 IO 操作，还有我们上一篇中提到的自主修改 esp 寄存器\n也是必须用汇编才能实现的。\n\n## 四、memcpy\n\n　　学 gcc 内联汇编最好的导师莫过于 linux 内核，\n有很多常用的小函数如 memcpy、strlen、strcpy、……\n其中都有短小精悍的内联汇编版本，\n如在 linux 2.6.37 中的 memcpy 函数：\n\n\t// 位于 /arch/x86/boot/compressed/misc.c\n\tvoid *memcpy(void *dest, const void *src, size_t n)\n\t{\n\t\tint d0, d1, d2;\n\t\tasm volatile(\n\t\t\t\"rep ; movsl\\n\\t\"\n\t\t\t\"movl %4,%%ecx\\n\\t\"\n\t\t\t\"rep ; movsb\\n\\t\"\n\t\t\t: \"=&c\" (d0), \"=&D\" (d1), \"=&S\" (d2)\n\t\t\t: \"0\" (n >> 2), \"g\" (n & 3), \"1\" (dest), \"2\" (src)\n\t\t\t: \"memory\");\n\t\n\t\treturn dest;\n\t}\n\n`　　`与 gcc_add.c 相比，这个函数要复杂不少：\n\n* 关键字 volatile 是告诉 gcc 不要尝试去移动、\n删除这段内联汇编。\n* rep ; movsl 的工作流程如下：\n\n\t\twhile(ecx) {\n\t\t\tmovl (%esi), (%edi);\n\t\t\tesi += 4;\n\t\t\tedi += 4;\n\t\t\tecx--;\n\t\t}\n\n\trep ; movsb 与此类似，只是每次拷贝的不是双字（4字节），\n而是字节。\n* <b>\"=&D\" (d1) 不是想将 edi 的最终值输出到 d1 中，\n而是想告诉 gcc edi的值早就改了，\n不要认为它的值还是初始化时的 dest，\n避免\"吝啬的\" gcc 把修改了的 edi 还当做 dest 来用。</b>\n而 d0、d1、d2 在开启优化后会被 gcc 无视掉\n（输出到它们的值没有被用过）。\n\n`　　`memcpy 先复制一个一个的双字，\n到最后如果还有没复制完的（少于4个字节），\n再一个一个字节地复制。\n我最终实现的 d_printf 就模仿了这个函数。\n\n<b>深入研究：</b><br>\n<a target=\"_blank\" href=\"http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html\">gcc 内联汇编 HOWTO 文档</a><br>\n<a target=\"_blank\" href=\"http://lxr.free-electrons.com/ident\">Linux Cross Reference——各版本 linux 内核函数检索</a>\n\n[回目录][content]\n"
  },
  {
    "path": "localvar.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">ֲ\n</h1>\n\nڽļƪУ\nҽ\"۽\"һC\nΪҽҿ C Եİء\n\n## \n\nһ죬һѷһֵ C \n\n\tint i = 3;\n\tint ans = (++i)+(++i)+(++i);\n\n``˵ 18һΪ 4+5+6=15 ء\n\n## ֤\n\nȻҾ֤һ½ģ\nдµĲԳinc.c\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint i = 3;\n\t\tint ans = (++i)+(++i)+(++i);\n\t\n\t\tprintf(\"%d\\n\",ans);\n\t\treturn 0;\n\t}\n\n`` linux б롢У£\n\n\t[lqy@localhost temp]$ gcc -o inc inc.c\n\t[lqy@localhost temp]$ ./inc\n\t16\n\t[lqy@localhost temp]$ \n\n``Ȼֳһ˷˼ 16\n\n## \n\nðɣȲ 18 ˣ 16 ôģ\n\n\tgcc -S -o inc.s inc.c\n\n``˻Դļ inc.s\nֻе<b></b>֣\n\n<pre><code>    .file\t\"inc.c\"\n\t.section\t.rodata\n.LC0:\n\t.string\t\"%d\\n\"\n\t.text\n.globl main\n\t.type\tmain, @function\nmain:\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\n\tandl\t$-16, %esp\n\tsubl\t$32, %esp<b>\n\tmovl\t$3, 28(%esp)\n\taddl\t$1, 28(%esp)\n\taddl\t$1, 28(%esp)\n\tmovl\t28(%esp), %eax\n\taddl\t%eax, %eax\n\taddl\t$1, 28(%esp)\n\taddl\t28(%esp), %eax\n\tmovl\t%eax, 24(%esp)</b>\n\tmovl\t$.LC0, %eax\n\tmovl\t24(%esp), %edx\n\tmovl\t%edx, 4(%esp)\n\tmovl\t%eax, (%esp)\n\tcall\tprintf\n\tmovl\t$0, %eax\n\tleave\n\tret\n\t.size\tmain, .-main\n\t.ident\t\"GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)\"\n\t.section\t.note.GNU-stack,\"\",@progbits\n</code></pre>\n\nҲòһֻĸʽˣ\n AT&T ʽ x86 ࣬\n windows ϼһ Intel ʽĻ࣬\nAT&T  Intel ʽĻЩ죬\nǺܺġ\n\nǼĴʽͬ\n Intel ʽļĴǰ˸ %eax  %eax  \nȻ˫ԪָĲĴݷ Intel ĸպ෴\nmov eaxebx൱ eax=ebx;  movl %ebx%eax\nҴֵ  \nһЩ𣬲׿׵ġ\n\n岿ּӵעͰɣ\n\n\tmovl\t$3, 28(%esp)\t# i = 3;\n\taddl\t$1, 28(%esp)\t# ++i; // 4\n\taddl\t$1, 28(%esp)\t# ++i; // 5\n\tmovl\t28(%esp), %eax\t# eax = i;\n\taddl\t%eax, %eax\t\t# eax = eax + eax; // 10\n\taddl\t$1, 28(%esp)\t# ++i; // 6\n\taddl\t28(%esp), %eax\t# eax = eax + i; // 16\n\tmovl\t%eax, 24(%esp)\t# ans = eax;\n\n``ȻǾ֪ 16 ôˡ\n\nΪʲôҾͿ϶ 28(%esp) Ǳ i أ\nΪֻд 3ûбڴ汻д 3\nҴ֮ĸָҲȷ C \nеı iñ߰\nһֲյԼĴѰַһڴ棡\nƵģֲ ans  24(%esp)\nóֲ󶼱 esp Ѱַڴ\n֮ƪ»ῴۻǺȷ\n\n## \n\nͬĳ VC ϱУ\nDebug ģʽн 16Release ģʽн 18\n Visual Studio 2010  Debug  \nRelease ģʽ¶ 18\n\nVC  VS ҲԿ룬\nڵԹϵжϵʱ\nVC ʹ Alt + 8 򿪷ര壬\nVS һ C Դ༭ѡ\"ת\"򿪷ര塣\n\nΪʲô Intel Լ CPUAT&T һ Intel \nͬʽĻأûӣAT&T ҲǺǵģ\n˼Ӱȫ磺UnixCԡ\n\n## С\n\nӦȡ飺\n<b>ʵʩݼıӦڱʽжγ</b>\nͲǿˣǱɷӣ\n\nC ׼涨[е](http://blog.csdn.net/huiguixian/article/details/6438613)֮䣬\nִе˳ġ\n˱ŻĿռ䡣[[1]](#tip1)\n\nõ 15 ĻԸĳ\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t    int i = 3;\n\t    int ans = (i+2)*3;\n\t\ti += 3;\n\t\n\t    printf(\"%d\\n\",ans);\n\t    return 0;\n\t}\n\n`` windows » linux £\n Release  Debugһ 15\n\n  C ԣųƪѾܹΪǽˣ\nպǱԪô\n\n[Ŀ¼][content]\n\n<a name=\"tip1\"></a>\n[1]  ohyeah ָ[http://rs.xidian.edu.cn/forum.php?mod=redirect&goto=findpost&ptid=412474&pid=8298351](http://rs.xidian.edu.cn/forum.php?mod=redirect&goto=findpost&ptid=412474&pid=8298351)\n"
  },
  {
    "path": "macro.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">奇怪的宏\n</h1>\n\n　　这一篇介绍这些奇怪的宏：\n\n## 一、do while(0)\n\n　　为了交换两个整型变量的值，前面<em>值传递</em>中已经用\n包含指针参数的 swap 函数做到了，这次用<b>宏</b>来实现（swap.c）：\n\n\t#include <stdio.h>\n\t\n\t#define SWAP(a,b)\t\t\\\n\t\tdo{\t\t\t\t\t\\\n\t\t\tint t = a;\t\t\\\n\t\t\ta = b;\t\t\t\\\n\t\t\tb = t;\t\t\t\\\n\t\t}while(0)\n\t\n\tint main()\n\t{\n\t\tint c=1, d=2;\n\t\tint t;\t// 测试 SWAP 与环境的兼容性\n\t\t\n\t\tSWAP(c,d);\n\t\t\n\t\tprintf(\"c:%d d:%d\\n\", c, d);\n\t\treturn 0;\n\t}\n\n`　　`这个宏看起来就有点怪了：do while(0) 是写了个循环\n又不让它循环，蛋疼啊！其实不然，这样写是有妙用的：\n\n　　<b>首先</b>，SWAP 有多条语句，如果这样写：\n\n\t#define SWAP(a,b)\t\t\\\n\t\t\tint t = a;\t\t\\\n\t\t\ta = b;\t\t\t\\\n\t\t\tb = t;\n\n`　　`那么用的时候就得这么用：\n\n\tSWAP(c,d)\n\n`　　`<b>不能加分号</b>！不习惯吧？\n\n　　<b>其次</b>，使用 do{...}while(0)，\n中间的语句用大括号括起来了，所以是另一个命名空间，\n<b>其中的新变量 t 不会发生命名冲突</b>。\n\n　　SWAP 宏要比之前那个函数的效率要高，\n因为没有发生函数调用，没有参数传递，\n宏会在编译前被替换，所以只是嵌入了一小段代码。\n\n## 二、&#35;\n\n　　标题我没打错，这里要说的就是井号，#的功能是将其后面的\n宏参数进行字符串化操作。比如下面代码中的宏： \n\n\t#define WARN_IF(EXP) \\\n\tdo{ if (EXP) \\\n\t\tfprintf(stderr, \"Warning: \" #EXP \"\\n\"); } \\\n\twhile(0) \n\n`　　`那么实际使用中会出现下面所示的替换过程： \n\n\tWARN_IF (divider == 0); \n\n\n`　　`被替换为 \n\n\tdo { if (divider == 0) \n\t\tfprintf(stderr, \"Warning: \" \"divider == 0\" \"\\n\"); \n\t} while(0); \n\n`　　`需要注意的是<b>C语言中多个双引号字符串放在一起\n会自动连接起来</b>，所以如果 divider 为 0 的话，就会打印出：\n\n\tWarning: divider == 0\n\n## 三、&#35;&#35;\n\n　　# 还是比较少用的，## 却比较流行，\n在 linux0.01 中就用到过。## 被称为连接符，\n用来将两个 记号（编译原理中的词汇） 连接为一个 记号。\n看下面的例子吧（add.c）：\n\n\t#include <stdio.h>\n\t\n\t#define add(Type)\t\t\t\t\\\n\tType add##Type(Type a, Type b){\t\\\n\t\treturn a+b;\t\t\t\t\t\\\n\t}\n\t\n\t// 下面两条是奇迹发生的地方\n\tadd(int)\n\tadd(double)\n\t\n\tint main()\n\t{\n\t\tint a = addint(1, 2);\n\t\tdouble d = adddouble(1.5, 1.5);\n\t\t\n\t\tprintf(\"a:%d d:%lf\\n\", a, d);\n\t\treturn 0;\n\t}\n\n`　　`那两行被替换后是这个样子的：\n\n\tint addint(int a, int b){ return a+b; }\n\tdouble adddouble(double a, double b){ return a+b; }\n\n<b>以上内容都可以使用<em>照妖镜</em>看到宏被替换后的情形</b>。\n\n[回目录][content]\n"
  },
  {
    "path": "main.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">谁调用了main？\n</h1>\n\n　　这是函数帧的应用之一。\n\n## 操作可行性\n\n　　从上一篇中可以发现：用帧指针 ebp 可以回溯到所有的函数帧，\n那么 main 函数帧之上的函数帧自然也是可以的；\n而帧中 旧ebp 的上一个四字节存的是函数的返回地址，\n由这个地址我们可以判断出谁调用了这个函数。\n\n## 准备活动\n\n　　下面就是这次黑客行动的主角（up.c）：\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint *p;\n\t\t\n\t\t// 以下这行内联汇编将 ebp 寄存器的值存到指针 p 中\n\t\t__asm__(\"movl %%ebp, %0\"\n\t\t\t\t:\"=m\"(p));\n\t\t\n\t\twhile(p != NULL){\n\t\t\tprintf(\"%p\\n\", p[1]);\n\t\t\tp = (int*)(p[0]);\n\t\t}\n\t\t\n\t\treturn 0;\n\t}\n\n`　　`首先，请允许我使用一下 gcc 内联汇编，\n这里简单的解释一下：\n\n1. \"=m\"(p) 表示将内存变量 p 作为一个输出操作数\n2. %0 代表的是第一个操作数，那就是 p 了\n3. 为了与操作数区别开来，寄存器要多加个 %，\n%%ebp 表示的就是 ebp 寄存器\n\n`　　`总之，这块内联汇编将 ebp 寄存器的值赋给了指针 p。\n\n　　然后解释一下while循环：循环中，首先打印 p[1]，\np[1]就是该帧所存的返回地址；然后将指针 p 改为 p[0]，\np[0]是 旧ebp（上一帧的帧指针）；\n这样，程序将按照<b>调用顺序的逆序</b>打印出各个返回地址。\n\n　　为什么终止条件是 p==NULL 呢？这是 gcc 为了支援我们的\n黑客行动特意在开始执行程序的时候将 ebp 清零了，\n所以第一次执行某个函数的时候压栈的 旧ebp 是 NULL。\n\n## 开始行动\n\n　　我们使用静态链接的方式编译 up.c\n（静态链接的可执行文件中包含所有用户态下执行的代码），\n然后执行它：\n\n\t[lqy@localhost temp]$ gcc -static -o up up.c\n\t[lqy@localhost temp]$ ./up\n\t0x8048464\n\t0x80481e1\n\t[lqy@localhost temp]$ \n\n## 分析结果\n\n　　up 打印了了两个指向代码区的地址，\n接着就看它们是属于哪两个函数了：\n\n\tnm up | sort > up.txt\n\n* nm up 可列出各个全局函数的地址\n* | sort > up.txt 通过<b>管道</b>将 nm up 的输出作为 sort 的输入，\nsort 排序后<b>输出重定向</b>到 up.txt 文件中（输出有1910行，\n不得不这么做o(╯□╰)o）\n\n`　　`然后发现两个地址分别位于 `__libc_start_main`、_start 中：\n\n\t...\n\t08048140 T _init\n\t080481c0 T _start\n\t080481f0 t __do_global_dtors_aux\n\t08048260 t frame_dummy\n\t080482bc T main\n\t08048300 T __libc_start_main\n\t080484d0 T __libc_check_standard_fds\n\t...\n\n`　　`实际上程序正好是从 _start 开始执行的，\n而且从 up 的反汇编结果中可看出 _start 的第一条指令\n xor %ebp,%ebp 就是那条传说中的将 ebp 清零的指令\n（两个一样的数相异或的结果一定是0）。\n\n　　那么调用 main 函数之前程序都干了些啥事呢？\n比如说<b>堆的初始化</b>，如果是 C++ 程序的话，\n全局对象的构造也是在 main 之前完成的\n（不能让 main 中使用全局对象的时候竟然还没构造吧！），\n而全局对象的析构也相当有趣地在 main 执行完了之后才执行。\n\n　　main 在你心目中的地位是不是一落千丈了？\n\n[回目录][content]\n"
  },
  {
    "path": "mem.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">进程内存分布\n</h1>\n\n　　之前一直在分析栈，栈这个东西的作用也介绍得差不多了，\n但是栈在哪儿还没有搞清楚，以及堆、代码、全局变量它们在哪儿，\n这都牵涉到进程的内存分布。\n\n## linux 0.01 的进程内存分布\n\n　　内存分布随着操作系统的更新换代，越来越科学合理，\n也越来越复杂，所以我们还是先了解一下早期操作系统的典型\nlinux 0.01 的进程的内存分布：\n\n　　linux 0.01 的一个进程固定拥有64MB的线性内存空间\n（ACM竞赛中单个程序的最大内存占用限制为64MB，\n这肯定有猫腻O(∩_∩)O~），各个进程挨个放置在一张页目录表中，\n一个页目录表可管理4G的线性空间，因此 linux0.01 最多有\n64个进程。每个进程的内存分布如下：\n\n![mem1](http://fmn.rrimg.com/fmn061/20121206/1925/original_tXyg_61c80000059b118d.jpg)\n\n* .text 里存的是机器码序列\n* .rodata 里存的是源字符串等只读内容\n* .data 里存的是初始化的全局变量\n* .bss 上一篇介绍过了，存的是未初始化的全局变量\n* 堆、栈就不用介绍了吧！\n\n`　　`.text .rodata .data .bss 是常驻内存的，\n<b>也就是说进程从开始运行到进程僵死它们一直蹲在那里，\n所以访问它们用的是常量地址</b>；而栈是不断的加帧（函数调用）\n、减帧（函数返回）的，帧内的局部变量只能用相对于当前\nesp（指向栈顶）或 ebp（指向当前帧）的相对地址来访问。\n\n　　栈被放置在高地址也是有原因的：\n<b>调用函数（加帧）是减 esp 的，函数返回（减帧）是加 esp 的，\n调用在前，所以栈是向低地址扩展的，放在高地址再合适不过了</b>。\n\n## 现代操作系统的进程内存分布\n\n　　认识了 linux 0.01 的内存分布后，\n再看看现代操作系统的内存分布发生了什么变化：\n\n　　<b>首先</b>，linux 0.01 进程的64MB内存限制太过时了，\n现在的程序都有潜力使用到 2GB、3GB 的内存空间\n（每个进程一张页目录表），当然，机器有硬伤的话也没办法，\n我的电脑就只有 2GB 的内存，想用 3GB 的内存是没指望了。\n但也不是有4GB内存就可以用4GB（32位），\n因为操作系统还要占个坑呢！\n现代 linux 中 0xC0000000 以上的 1GB 空间是操作系统专用的，\n而 linux 0.01 中第1个 64MB 是操作系统的坑，\n所以别的进程完全占有它们的 64MB，\n也不用跟操作系统客气。\n\n　　<b>其次</b>，linux 0.01只有进程没有线程，\n但是现代 linux 有多线程了\n（linux 的线程其实是个轻量级的进程），\n<b>一个进程的多个线程之间共享全局变量、堆、打开的文件……\n但栈是不能共享的</b>：栈中各层函数帧代表着一条执行线索，\n一个线程是一条执行线索，所以每个线程独占一个栈，\n而这些栈又都必须在所属进程的内存空间中。\n\n　　根据以上两点，进程的内存分布就变成了下面这个样子：\n\n![mem2](http://fmn.xnpic.com/fmn056/20121206/1925/original_22bc_2556000005a8118c.jpg)\n\n　　再者，如果把动态装载的动态链接库也考虑进去的话，\n上面的分布图将会更加\"破碎\"。\n\n　　<b>如果我们的程序没有采用多线程的话，\n一般可以简单地认为它的内存分布模型是 linux 0.01 的那种</b>。\n\n[回目录][content]\n"
  },
  {
    "path": "name.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">变量名、函数名\n</h1>\n\n　　C程序在执行的时候直接用内存地址去定位变量、函数，\n而不是根据名字去搜索，所以C程序执行的速度比脚本语言要快不少。\n\n　　对于函数中的局部变量来说，编译为汇编的时候，\n名字就已经被彻彻底底地忘记了，\n因为局部变量在函数帧中，这一帧要占多少字节，\n各局部变量在帧中的相对位置，\n都在编译成汇编的时候就可以确定下来，\n生成目标文件、可执行文件的时候也不需要再更改。\n\n　　而 全局变量、static变量、函数 由于要将所有目标文件、\n库链接到一起之后才能最终确定它们的绝对地址，\n所以在链接前名字还是标志着它们的存在。\n它们的信息存储在符号表（符号数组）中，\n其中每一项除了有符号名，还有符号地址（链接后填入），\n所以 nm 命令可得到 地址-符号名 映射。\n虽然程序运行时用不到符号表，\n但是默认情况下可执行文件中还是存着符号表，\n看下面这个程序（name.c）：\n\n\t#include <stdio.h>\n\t\n\tint globalvar;\n\t\n\tint main()\n\t{\n\t\tstatic int staticval;\n\t\treturn 0;\n\t}\n\n`　　`name.c 中有全局变量、static变量、函数(main)，\n查看它编译后的目标文件、可执行文件的 地址-符号 映射：\n\n\t[lqy@localhost notlong]$ gcc -c name.c\n\t[lqy@localhost notlong]$ nm name.o\n\t00000004 C globalvar\n\t00000000 T main\n\t00000000 b staticval.1672\n\t[lqy@localhost notlong]$ gcc -o name name.c\n\t[lqy@localhost notlong]$ nm name | sort\n\t08048274 T _init\n\t080482e0 T _start\n\t08048310 t __do_global_dtors_aux\n\t08048370 t frame_dummy\n\t08048394 T main\n\t...\n\t此处省略X行\n\t...\n\t08049604 b staticval.1672\n\t08049608 B globalvar\n\t0804960c A _end\n\t         U __libc_start_main@@GLIBC_2.0\n\t         w __gmon_start__\n\t         w _Jv_RegisterClasses\n\t[lqy@localhost notlong]$ \n\n`　　`可执行文件中的 地址-符号 映射还有什么存在的意义呢？\n它可用于汇编级调试的时候设置断点，\n比如linux内核编译后就生成了 System.map 文件，\n便于进行内核调试：\n\n\t00000000 A VDSO32_PRELINK\n\t00000040 A VDSO32_vsyscall_eh_frame_size\n\t000001d3 A kexec_control_code_size\n\t00000400 A VDSO32_sigreturn\n\t0000040c A VDSO32_rt_sigreturn\n\t00000414 A VDSO32_vsyscall\n\t00000424 A VDSO32_SYSENTER_RETURN\n\t01000000 A phys_startup_32\n\tc1000000 T _text\n\tc1000000 T startup_32\n\tc1000054 t default_entry\n\tc1001000 T wakeup_pmode_return\n\tc100104c t bogus_magic\n\tc100104e t save_registers\n\tc100109d t restore_registers\n\tc10010c0 T do_suspend_lowlevel\n\tc10010d6 t ret_point\n\tc10010e8 T _stext\n\tc10010e8 t cpumask_weight\n\tc10010f9 t run_init_process\n\tc1001112 t init_post\n\tc10011b0 T do_one_initcall\n\t...\n\n[回目录][content]\n"
  },
  {
    "path": "optimize.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">编译优化\n</h1>\n\n　　C语言没有汇编快，因为C语言要由编译器翻译为汇编，\n编译器毕竟是人造的，翻译出来的汇编源代码总有那么N条\n指令在更智能、更有创造性的我们看来是多余的。\n\n　　C语言翻译后的汇编有如下恶劣行径：\n\n1. <b>C语言偏爱内存</b>。我们写的汇编一般偏爱寄存器，\n寄存器比内存要快很多倍。当然，寄存器的数量屈指可数，\n数据多了的话也必须用内存。\n2. <b>内存多余读</b>。假如在一个 for 循环中经常要执行\n++i 操作，编译后的汇编可能是这样的情形：\n\n\t\tmovl i, %eax\n\t\taddl $1, %eax\n\t\tmovl %eax, i\n\n\t即使 eax 寄存器一直存着 i 的值，\nC语言也喜欢操作它前先读一下，以上3条指令浓缩为一条\nincl %eax 速度就快上好几倍了。\n\n`　　`尽管C语言\"如此不堪\"，但是考虑到高级语言带来的\n源码可读性和开发效率在数量级上的提高，我们还是原谅了它。\n而且很多编译器都有提供优化的选项，\n开启优化选项后C语言翻译出来的汇编代码几近无可挑剔。\n\n　　VC、VS有 Debug、Release 编译模式，\nRelease 下编译后，程序的大小、执行效率都有显著的改善。\ngcc 也有优化选项，我们来看看 gcc 优化的神奇效果：\n\n　　我故意写了一个垃圾程序（math.c）：\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint a=1, b=2;\n\t\tint c;\n\t\t\n\t\tc = a + a*b + b;\n\t\t\n\t\tprintf(\"%d\\n\", c);\n\t\treturn 0;\n\t}\n\n且看看不优化的情况下，汇编代码有多么糟糕：\n\n编译命令：gcc -S math.c  \nmain部分的汇编代码：\n\n\tmain:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tandl\t$-16, %esp\n\t\tsubl\t$32, %esp\n\t\tmovl\t$1, 28(%esp)\t# 28(%esp) 是 a\n\t\tmovl\t$2, 24(%esp)\t# 24(%esp) 是 b\n\t\tmovl\t24(%esp), %eax\t#\\\n\t\taddl\t$1, %eax\t\t#-\\\n\t\timull\t28(%esp), %eax\t#-eax=(b+1)*a\n\t\taddl\t24(%esp), %eax\t#\\\n\t\tmovl\t%eax, 20(%esp)\t#-c=(b+1)*a+b\n\t\tmovl\t$.LC0, %eax\n\t\tmovl\t20(%esp), %edx\n\t\tmovl\t%edx, 4(%esp)\n\t\tmovl\t%eax, (%esp)\n\t\tcall\tprintf\n\t\tmovl\t$0, %eax\n\t\tleave\n\t\tret\n\n汇编代码规模庞大，翻译水平中规中矩。\n现在开启优化选项：\n\n编译命令：gcc -O2 -S math.c\n\n\tmain:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tandl\t$-16, %esp\n\t\tsubl\t$16, %esp\n\t\tmovl\t$5, 4(%esp)\n\t\tmovl\t$.LC0, (%esp)\n\t\tcall\tprintf\n\t\txorl\t%eax, %eax\n\t\tleave\n\t\tret\n\n`　　`规模变为原来的一半，而且 gcc 发现了 a、b、c \n变量是多余的，直接将结果 5 传给 printf 打印了出来\n——计算器是编译器必备的一大技能。\n初中那时候苦逼地做计算题，怎么就不学学C语言呢O(∩_∩)O~\n\n[回目录][content]\n"
  },
  {
    "path": "pfunc.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">函数指针\n</h1>\n\n## 一、函数指针的值\n\n　　函数指针跟普通指针一样，存的也是一个内存地址，\n只是这个地址是一个函数的起始地址，\n下面这个程序打印出一个函数指针的值（func1.c）：\n\n\t#include <stdio.h>\n\t\n\ttypedef int (*Func)(int);\n\t\n\tint Double(int a)\n\t{\n\t\treturn (a + a);\n\t}\n\t\n\tint main()\n\t{\n\t\tFunc p = Double;\n\t\tprintf(\"%p\\n\", p);\n\t\treturn 0;\n\t}\n\n`　　`编译、运行程序：\n\n\t[lqy@localhost notlong]$ gcc -O2 -o func1 func1.c\n\t[lqy@localhost notlong]$ ./func1\n\t0x80483d0\n\t[lqy@localhost notlong]$ \n\n`　　`然后我们用 nm 工具查看一下 Double 的地址，\n看是不是正好是 0x80483d0：\n\n\t[lqy@localhost notlong]$ nm func1 | sort\n\t08048294 T _init\n\t08048310 T _start\n\t08048340 t __do_global_dtors_aux\n\t080483a0 t frame_dummy\n\t080483d0 T Double\n\t080483e0 T main\n\t...\n\n`　　`不出意料，Double 的起始地址果然是 0x080483d0。\n\n## 二、调用函数指针指向的函数\n\n　　直接调用一个函数是 call 一个常量，\n而通过函数指针调用一个函数显然不能这么做，\n因为函数地址是可变的了，指向谁就得 call 谁。\n下面比较一下直接调用和通过函数指针间接调用同一个函数的\n汇编代码（func2.c）：\n\n\t#include <stdio.h>\n\t\n\ttypedef int (*Func)(int);\n\t\n\tint Double(int a)\n\t{\n\t\treturn (a + a);\n\t}\n\t\n\tint main()\n\t{\n\t\tFunc p = Double;\n\t\tDouble(2);\t// 直接调用\n\t\tp(2);\t\t// 间接调用\n\t\treturn 0;\n\t}\n\n`　　`部分汇编代码如下：\n\n\t\tmovl\t$2, (%esp)\n\t\tcall\tDouble\n\t\tmovl\t$2, (%esp)\n\t\tmovl\t28(%esp), %eax\t# 28(%esp) 是 p\n\t\tcall\t*%eax\n\n`　　`可见通过函数指针间接调用一个函数，\ncall 指令的操作数不再是一个常量，\n而是寄存器 eax（其它寄存器应该也行），\n此时 eax 寄存器的值正好是 Double 函数的起始地址，\n所以接着就会去执行 Double 函数的指令。\n\n## 三、参数弱匹配\n\n　　从上面的例子中我们也看到了函数指针也没什么特别的，\n也就存了个地址，但是调用一个函数不仅需要知道它的起始地址，\n还得根据它的参数列表来压栈传递参数。\n\n　　参数列表在定义函数指针类型的时候就约定好了，\n凡是具有相同参数列表的函数都可以赋值给该类型的函数指针，\n而参数列表不同的函数也可以通过强制类型转换后赋值给它\n（C语言的指针类型可以任意转换⊙﹏⊙），\n下面这个程序就大胆的强制转换了一下（func3.c）：\n\n\t#include <stdio.h>\n\t\n\ttypedef int (*Func)(int);\n\t\n\tint Double2(int a, int b)\n\t{\n\t\treturn (a + a);\n\t}\n\t\n\tint main()\n\t{\n\t\tFunc p = (Func)Double2;\n\t\tprintf(\"%d\\n\", p(2));\n\t\treturn 0;\n\t}\n\n`　　`不强制转换的话，编译的时候会报告一个 warring\n（居然不是 error ⊙﹏⊙），\n上面这个程序编译的时候 0 error 0 warring，\n执行也没有出错：\n\n\t[lqy@localhost notlong]$ gcc -o func3 func3.c\n\t[lqy@localhost notlong]$ ./func3\n\t4\n\t[lqy@localhost notlong]$ \n\n`　　`真算是朵奇葩了！\n\n　　没有出错的原因是：参数 a 对应的刚好是压栈的 2，\n而 b 对应的是一个危险地带，还好没用到 b，\n所以这个程序依然顺利地执行完了。\n\n　　<b>综上所述，函数指针真没什么特别的。</b>\n\n[回目录][content]\n"
  },
  {
    "path": "process0.01.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">linux0.01ʱƬĺ\n</h1>\n\nǡlinuxϵеĵһƪΪʲôҪӽ̿ʼأ\nΪϵͳš͵ľǽ̣\nԽ̵ĹҪĲ־ǶʱƬĴ\n<b>ⲿֵ㷨Ӱ쵽лٶȣ\nǲϵͳһҪָ꣩</b>\nͬʱֳ<b>ϵͳ</b>\nǡϵͳἰ\n\n---\n\nȣвϵͳӦеԣ\n\n1. ÿ̶ᱻɸʱƬʱƬ꣬\n<b>ʱ</b>ͲͶ\n2. ÿ̶ȼȼߵĽ̷϶ʱƬ\n3. ʱƬ󣬽ûսᣬӦٷʱƬ\nܲһ̱ʱɰ^_^\n\n``linux0.01<b>һʱƬ10</b>0.01룩\n̵ȼ1~15<b>ȼ·\nʱƬʱ̻õʱƬ</b>\n\n<b>ôʱƬôĵأ</b>\n\nﲻòǵĴ󹦳8253\nû΢ԭϽĿɱ̶ʱ/\n<b>ÿ10msһʱжϣ\nжϳаѵǰ̵ʱƬ1</b>\nʱƬĵġ\n\n˵жϣһҲϤģǾ8259жϿ\nоƬͲ˵ˣ߾Ϳʼᷳƪ\nO(_)O~\n\n---\n\nǰ̵ʱƬ<b>0</b>֮ͻschedule\nлһеʣʱƬḶ̌\nҪн̣ǸO(n)ʱ临ӶȵĹ\nn̵ĸ\n\n*ע⣺һ£ǰֻԼеʱƬ֮\nscheduleÿһʱƬ͵һschedule\nʹˡ*\n\nģһ½̵ĵ\n\n1. 3ֱ̣ʣʱƬ543ȼ15\nʱ schedule\nô1ᱻѡΪҪִеḶ̌\n\n\t![1](http://fmn.rrimg.com/fmn063/20121022/1950/original_9rlY_112f0000631a1191.jpg)\n\n2. ִеһ50msĹ1ʱƬ\n\n\t![2](http://fmn.rrimg.com/fmn063/20121022/1950/original_zHfv_346c000051721190.jpg)\n\n3. ʱʱжϳлscheduleл2ִУ\nǽ2ִ20msڵȴIOˣ\n\n\t![3](http://fmn.rrimg.com/fmn060/20121022/1950/original_eMlh_2ddb000063e4118f.jpg)\n\n4. ߵĺлscheduleл3ִУ\nһֱ3ʱƬҲȫ꣺\n\n\t![4](http://fmn.rrimg.com/fmn065/20121022/1950/original_jC84_7acf000062c5118e.jpg)\n\n5. ڽ1ͽ3ûʱƬ˲ͶУ\n2˲ͶУ\nscheduleϣͨʱƬΣ\n\n\tÿµʱƬļ㹫ʽǣ\n\n\t\tµʱƬ = ɵʱƬ / 2 + ȼ\n\n\tʽӿе֣ɵʱƬӦö0\nģпԿߵĽܻ̿ʱƬ\nߵĽ̾ͱرˣ\n\n\t![5](http://fmn.rrimg.com/fmn063/20121022/1950/original_Gfvs_7179000063f0118d.jpg)\n\n\tߵḺ̌رյԭIOƵ\nı༭׽״̬\nرյĻѱscheduleѡУ\n\n\tʱƬʱ2ԭ2ʱƬ䣬\nʱƬպñѣʱ1ͽ3ʱƬ15\n࣬Ҳ˵ҪȽ1ͽ3̱֮\n2ܱͶС⻹ֻ2CPUḶ̌\n300ms2ˣĽ20أ\nǾ͵3sǴֵλҪðˣ\n\nĹ̻һʱϣģ\n\n![timeline](http://fmn.rrimg.com/fmn063/20121022/1950/original_lAYT_11df0000635e118c.jpg)\n\n---\n\n## ܽ\n\n``linux0.01л(schedule)ĿУ\n\n1. ѡʣʱƬĿеĽ(O(n))\nУл\n2. ûпͶеḶ̌·ʱƬ(O(n))\nִ1\n\n``linux0.01ĽлO(n)Ӷȵģ\n O(nlog(n))  O(n) 漣ǽлĿܴ\nO(n)  O(1) \n\n֪Σ»طֽ⡣\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "process2.6.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">linux2.6.XXлʱƬ\n</h1>\n\nһƪнlinux0.01Ľлʱ临ӶO(n)ģ\nlinux0.01˵ʲô⣬\nΪlinux0.01ֻ64̡\nڵlinuxϵͳмǧִ̲У\nΪ˸ܵصȽ̣ʱƬ㷨ҲԽԽӣ\nȻO(n)л㷨ǾеΣˡ\n\nһƪпԿʱҪط\n\n1. ѡʵĽ̽лO(n)\n2. ʱƬO(n)\n\n``<b>linux2.6.XXгɹİǽO(1)</b>\n\n---\n\n# ѡ̽л\n\nlinux2.6.XX иװ˽ϢĽṹ壺\nȼ(struct prio_array)ṹ3Ա\n\n1. ̸\n2. ȼλͼ\n3. ȼе\n\n``<b>λͼÿһλӦһȼУ\nзǿգǶӦһλ1</b>\n\n![bits](http://fmn.rrimg.com/fmn062/20120928/2320/original_boj5_0d35000017351190.jpg)\n\nlinux2.6.XX Ľ140ȼ\nԶӦλͼӦ140أʵ5unsigned long\nɣ160ءlinux0.01ͬ2.6ȼ\nֵԽСȼԽߡ2.6ִʣʱƬḶ̌\nִȼߵĽ̡\n<b>ԵȵʱֻҪҳ1Ϊ1λ\nȻӸλӦĶгһ̾Ϳˡ</b>\nѰҵһΪ1λbsflָ\nһο4ֽڣ۴ڶٽָ౻ִ5Σ\n㿪˵΢\n\nǰλͼôڿеĽ\nȼ7ֻҪ7гһ̣лˡ\n\n---\n\n# ʱƬ\n\n<b>ǰ̵ʱƬĹ֮·ʱƬ</b>\nʱƬúٷõԭȼ\nȼĽ̾͵õȸȼĽ˲ͶУ\nͲǶˣ\nʵһȼ飨Ϊexpired\n֮ǰ̸۵һֱactiveȼ飩\nٷʱƬĽ̶õexpired֮С\n\nһʱ\nactiveеĽһתƵexpired֮У\nʱscheduleύһactiveexpired\n򵥵ؽһָͺˣ\nȻֿԴactiveѡˡ\n\nͼĲïɣ\n\n1. ԭ3̣ABCǵȼֱ141616\n\n\t![1](http://fmn.rrimg.com/fmn057/20120928/2320/original_ZYS3_1591000055d5125e.jpg)\n\n2. ȼAᱻִУAʱƬˣ\nAᱻ·ʱƬõexpiredУ\n\n\t![2](http://fmn.rrfmn.com/fmn059/20120928/2320/original_UTKq_2df900008cff125d.jpg)\n\n3. BCȼߵģB迿λͼǶͷ\nʵ˫ҾͲˣBִ\n\n\t![3](http://fmn.rrimg.com/fmn062/20120928/2320/original_AOhZ_310800008d9c125c.jpg)\n\n4. C\n\n\t![4](http://fmn.rrfmn.com/fmn058/20120928/2315/original_V9Ta_1eec00008e33125b.jpg)\n\n5. activeѾûнˣָ룺\n\n\t![5](http://fmn.rrimg.com/fmn056/20120928/2320/original_cNKM_5ebe00008c481191.jpg)\n\n``<b>ÿһôļ򵥣\n̵лʱ俪Ѿ̵û̫Ĺϵˡ</b>\n\n---\n\nʵʱƬٷO(1)Ǹ\nΪnٷʱƬܿO(n)ġ\nʵǽʱƬĹ̯ÿˣ\nÿ̶мʮmsʱȥУ\nҲںѼusʱԼһʱƬ\nlinux0.01ƾͲˣ\nٸ̶ﵽһл̵ļȡʱƬ\nлеٶˡ\n\nѡбO(1)һ׳ˣ\nһƪᵽO(nlog(n))O(n)\nԭһģ\n\n* \n\t* 鲢򡢿() -> \n\t* O(nlog(n)) -> O(n)\n\t* Ƚ -> ǱȽ\n* л\n\t* 0.01 -> 2.6.XX\n\t* O(n) -> O(1)\n\t* Ƚ -> ǱȽ\n\nǴnѡһ̣  \n0.01ʹñȽʣʱƬҳģ  \n2.6.XXȻȼλͼҳһΪ1λ\nûбȽϡ\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "recur.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">所有递归都可以变循环\n</h1>\n\n　　这是函数帧的应用之二。\n\n　　还记得大一的C程序设计课上讲到汉诺塔的时候老师说：\n<b>所有递归都可以用循环实现</b>。这听起来好像可行，\n然后我就开始想怎么用循环来解决汉诺塔问题，\n我大概想了一个星期，最后终于选择了……放弃……\n当然，我不是来推翻标题的，\n随着学习的深入，以及\"自觉修炼\"，现在我可以肯定地告诉大家：\n所有递归都可以用循环实现，更确切地说：\n<b>所有递归都可以用循环+栈实现</b>\n（就多个数据结构，还不算违规吧O(∩_∩)O~）。\n\n　　<b>通过在我们自定义的栈中自建函数帧，\n我们可以达到和函数调用一样的效果</b>。\n但是因为这样做还是比较麻烦，所以就不转换汉诺塔问题了，\n而是转换之前的那个递归求解阶乘的程序（fac.c）：\n\n\t#include <stdio.h>\n\t\n\tint fac(int n)\n\t{\n\t\tif(n <= 1)\n\t\t\treturn 1;\n\t\treturn n * fac(n-1);\n\t}\n\t\n\tint main()\n\t{\n\t\tint n = 3;\n\t\tint ans = fac(n);\n\t\t\n\t\tprintf(\"%d! = %d\\n\", n, ans);\n\t\treturn 0;\n\t}\n\n## 技术难点\n\n　　我们可以在自建的函数帧中存储局部变量、存储参数，\n<b>但是我们不能存返回地址，因为我们得不到机器指令的地址！</b>\n不过，C语言有一个类似于指令地址的东西：switch case\n中的 case子句，我们可以用一个case代表一个地址，\n技术难点就此突破了。\n\n## 源程序\n\n　　虽然我简化了很多步骤，但源程序还是比较长（fac2.c）：\n\n\t#include <stdio.h>\n\t\n\t// 栈的设置\n\t#define STACKDEEPTH\t1024\n\tint stack[STACKDEEPTH];\n\tint *esp = &stack[STACKDEEPTH];\n\t#define PUSH(a)\t*(--esp) = a\n\t#define POP(b)\tb = *(esp++)\n\t\n\t// 其它模拟寄存器\n\tint eax;// 存返回值\n\tint eip;// 用于分支选择\n\t\n\tint main()\n\t{\n\t\tint n = 3;\n\t\t\n\t\t// 模仿 main 调用 fac(n)\n\t\tPUSH(n);\n\t\tPUSH(10002);// 模仿返回 main 的地址\n\t\teip = 10000;\n\t\t\n\t\tdo{\n\t\t\tswitch(eip){\n\t\t\tcase 10000:\n\t\t\t\t--esp;// 为帧分配空间\n\t\t\t\tif(esp[2] <= 1){// 模仿递归终止条件\n\t\t\t\t\teax = 1;\n\t\t\t\t\t++esp;// 回收帧空间\n\t\t\t\t\tPOP(eip);\n\t\t\t\t}else{// 模仿递归计算 fac(n-1)\n\t\t\t\t\tesp[0] = esp[2] - 1;\n\t\t\t\t\tPUSH(10001);\n\t\t\t\t\teip = 10000;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t\t\n\t\t\tcase 10001:// 返回 n * (fac(n-1)的结果)\n\t\t\t\teax = esp[2] * eax;\n\t\t\t\t++esp;// 回收帧空间\n\t\t\t\tPOP(eip);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}while(eip != 10002);\n\t\t\n\t\tprintf(\"%d! = %d\\n\", n, eax);\n\t\treturn 0;\n\t}\n\n## 自建的函数帧\n\n　　为了简化程序，ebp我们就不用了，\n完全用esp来操作栈，一个函数帧只占用 8 个字节：\n\n![recur1](http://fmn.rrimg.com/fmn063/20121130/1830/original_OJ3e_0ad000003200125b.jpg)\n\n　　在计算到 fac(1) 的时候，栈中内容如下：\n\n![recur2](http://fmn.rrimg.com/fmn056/20121130/1830/original_LBeS_30f500003160118f.jpg)\n\n　　比起肆意挥霍栈空间的 gcc（fac帧用了32字节，\n浪费了20字节，实际使用了12字节），\n我们的程序真的是太节省了（一帧只用8字节）。\n\n## 小结\n\n　　当然，本文的方法只用于学术讨论，\n说明所有递归都可以变循环，编程的时候还是不要这么用。\n因为代码复杂、容易出错、难以理解，\n唯一的优点是能省空间省到极限。\n\n　　这种递归变循环的方式并没有降低时间复杂度，\n但却是通用的（所有递归都可以这么变循环）；\n而有一部分递归可以基于巧妙的算法变成循环，\n并且大大降低时间复杂度，如：动态规划、贪心算法\n（详见《算法导论》）。\n\n[回目录][content]\n"
  },
  {
    "path": "static.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">static变量 及 作用域控制\n</h1>\n\n## 一、static变量\n\n　　<b>static变量放在函数中，就只有这个函数能访问它；\n放在函数外就只有这个文件能访问它。</b>\n下面我们看看两个函数中重名的static变量是怎么区别开来的\n（static.c）：\n\n\t#include <stdio.h>\n\t\n\tvoid func1()\n\t{\n\t\tstatic int n = 1;\n\t\tn++;\n\t}\n\t\n\tvoid func2()\n\t{\n\t\tstatic int n = 2;\n\t\tn++;\n\t}\n\t\n\tint main()\n\t{\n\t\treturn 0;\n\t}\n\n`　　`下面是编译后的部分汇编：\n\n\tfunc1:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tmovl\tn.1671, %eax\n\t\taddl\t$1, %eax\n\t\tmovl\t%eax, n.1671\n\t\tpopl\t%ebp\n\t\tret\n\t\n\tfunc2:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tmovl\tn.1674, %eax\n\t\taddl\t$1, %eax\n\t\tmovl\t%eax, n.1674\n\t\tpopl\t%ebp\n\t\tret\n\n`　　`好家伙！编译器居然\"偷偷\"地改了变量名，\n这样两个static变量就容易区分了。\n\n　　<b>其实static变量跟全局变量一样被放置在 .data段 或 \n.bss段 中，所以它们也是程序运行期间一直存在的，\n最终也是通过绝对地址来访问</b>。\n但是它们的作用域还是比全局变量低了一级：\nstatic变量被标识为LOCAL符号，全局变量被标识为GLOBAL符号，\n在链接过程中，目标文件寻找外部变量时只在GLOBAL符号中找，\n所以static变量别的源文件是\"看不见\"的。\n\n## 二、作用域控制\n\n　　作用域控制为的是提高源代码的可读性，\n一个变量的作用域越小，它可能出没的范围就越小。\n\n　　C语言中的变量按作用域从大到小可分为四种：\n全局变量、函数外static变量、函数内static变量、局部变量：\n\n1. 全局变量是杀伤半径最大的：<b>不仅在定义该变量的源文件中可用，\n而且在任一别的源文件中只要用 extern 声明它后也可以使用</b>，\n因此，当你看到一个全局变量的时候应该心生敬畏！\n2. 函数外的static变量处于文件域中，\n只有定义它的源文件中可以使用。如果你看到一个static变量，\n那是作者在安慰你：哥们（妹子），这个变量不会在别的文件中出现。\n3. 函数内static变量在函数的<b>每次调用</b>中可用（只初始化一次），\n<b>它同以上两种变量一样在程序运行期间一直存在</b>，\n所以它的功能是局部变量无法实现的。\n4. 局部变量在函数的<b>一次调用</b>中使用，\n调用结束后就消失了。\n\n`　　`显然，作用域越小越省心，\n该是局部变量的就不要定义成全局变量，\n如果\"全局变量\"只在本源文件中使用那就加个static。\n\n　　即便是局部变量也还可以压缩其作用域：\n\n　　有的同学写的函数一开头就声明了函数中要用到的所有局部变量，\n一开始我也这么做，因为我担心：<b>如果把变量定义在循环体内，\n是不是每一次循环都会给它们分配空间、回收空间，从而降低效率？</b>\n但事实是它们的空间在函数的开头就一次性分配好了（scope.c）：\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tint a = 1;\n\t\t{\n\t\t\tint a = 2;\n\t\t\t{\n\t\t\t\tint a = 3;\n\t\t\t}\n\t\t\t{\n\t\t\t\tint a = 4;\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\n编译后的汇编代码如下：\n\n\tmain:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tsubl\t$16, %esp\n\t\tmovl\t$1, -4(%ebp)\n\t\tmovl\t$2, -8(%ebp)\n\t\tmovl\t$3, -12(%ebp)\n\t\tmovl\t$4, -16(%ebp)\n\t\tmovl\t$0, %eax\n\t\tleave\n\t\tret\n\n`　　`各层局部环境中的变量a是subl $16, %esp一次性分配好的。\n<b>由此可见不是每个{}都要分配回收局部变量，\n一个函数只分配回收一次</b>。因此，\n如果某个变量只在某个条件、循环中用到的话，\n还是在条件、循环中定义吧，这样，\n规模比较大的函数的可读性将提高不少，而效率丝毫没有下降，\n可谓是百利而无一害！\n\n[回目录][content]\n"
  },
  {
    "path": "staticstack.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">C语言的栈是静态的\n</h1>\n\n　　C语言有了可变参数之后，我们可以传任意个数的参数了，\n似乎挺动态的了，但是可变参数函数还是不够动态。\n\n## 一、鞭长莫及\n\n　　我们可以在 main 中写出好几条参数个数不同的调用 sum 的语句，\n但是具体到某一条语句，sum 的参数个数是一定的，\n比如上一篇中的 sum(2, 3, 4) 的参数个数是 3。\n<b>如果程序运行中调用 sum 函数的时候，\n参数个数根据用户输入而定，那就不能用可变参数来实现了。</b>\n也就是说不能用 sum 来实现以下这个函数的功能：\n\n\t// 将数组 a 的所有元素（个数为 n）求和后返回\n\tint d_sum(int n, int a[]);\n\n`　　`当然，这个函数不用 sum 来做是很好实现的。\n我再换一个问题，下面这个函数怎么用 printf 来实现：\n\n\t// fmt 存的是格式串，它描述了 n 个整数（数组 a 中）\n\t// 的格式，某次调用如下：\n\t//   int a[] = {1, 2, 3};\n\t//   d_printf(\"%d+%d=%d\", 3, a);\n\tvoid d_printf(const char *fmt, int n, int a[]);\n\n`　　`这就没法做了吧！\n\n## 二、寻根究底\n\n　　d_printf 没法实现的原因是这样的代码真没法写：\n传给 printf 的参数的个数到运行的时候才知道，\n而调用 printf 的语句又必须明确的列出所有参数。\n\n　　其根本原因是<b>C语言的栈是静态的</b>，\n上一篇的 va.c 编译后的汇编代码如下：\n\n\tmain:\n\t\tpushl\t%ebp\n\t\tmovl\t%esp, %ebp\n\t\tandl\t$-16, %esp\n\t\tsubl\t$16, %esp\t# 给main帧分配栈空间\n\t\tmovl\t$4, 8(%esp)\n\t\tmovl\t$3, 4(%esp)\n\t\tmovl\t$2, (%esp)\n\t\tcall\tsum\t\t\t# 调用变参函数 sum\n\t\tmovl\t$.LC0, (%esp)\n\t\tmovl\t%eax, 4(%esp)\n\t\tcall\tprintf\t\t# 调用变参函数 printf\n\t\txorl\t%eax, %eax\n\t\tleave\n\t\tret\n\n`　　`可以看到虽然 main 函数中调用了两个变参函数，\n但是栈却没有一点动态可变的意思，居然是用一条 subl $16, %esp \n分配了固定的 16 字节的栈空间\n（编译的时候计算得出需要12字节，取整吧，16字节！）。\n\n　　而在 d_printf 的实现中需要分配 4+4*n 字节的栈空间，\n用于存传给 printf 的 格式串指针 和 n个整数，\n用C语言是没法实现啰。\n\n## 三、另辟蹊径\n\n　　C语言不能直接使用寄存器，但是汇编可以，\n如果我们在 d_printf 中嵌入一段汇编来修改 esp 寄存器，\n达到动态分配栈空间的效果，然后存入参数，call printf，\n就可以完成任务了。\n\n　　接下来的两篇就来实现 d_printf 啰！\n\n[回目录][content]\n"
  },
  {
    "path": "string.md",
    "content": "[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[Ŀ¼][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">ַ\n</h1>\n\nһƪַַʹã\nҲ٣\n\n## һַĴ洢λ\n\nCԴstring1.c\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tputs(\"Hello, World!\");\n\t\treturn 0;\n\t}\n\n``ֱӿִļķ\n\n\t[lqy@localhost temp]$ gcc -o string1 string1.c\n\t[lqy@localhost temp]$ ./string1 \n\tHello, World!\n\t[lqy@localhost temp]$ objdump -s -d string1 > string1.txt\n\t[lqy@localhost temp]$ \n\n``string1.txt еĲ£\n\n<pre><code>Contents of section .rodata:\n 8048488 03000000 01000200 00000000 48656c6c  ............Hell\n 8048498 6f2c2057 6f726c64 2100               o, World!.      \n\n...\n\n080483b4 &lt;main>:\n 80483b4:\t55                   \tpush   %ebp\n 80483b5:\t89 e5                \tmov    %esp,%ebp\n 80483b7:\t83 e4 f0             \tand    $0xfffffff0,%esp\n 80483ba:\t83 ec 10             \tsub    $0x10,%esp<b>\n 80483bd:\tc7 04 24 94 84 04 08 \tmovl   $0x8048494,(%esp)\n 80483c4:\te8 27 ff ff ff       \tcall   80482f0 &lt;puts@plt></b>\n 80483c9:\tb8 00 00 00 00       \tmov    $0x0,%eax\n 80483ce:\tc9                   \tleave  \n 80483cf:\tc3                   \tret    \n</code></pre>\n\nɼ 0x8048494 Ӧ \"Hello, World!\" ĵַ\nȻ .rodata  0x8048488 + 12 ô洢\n \"Hello, World!\" ĸַ\n<a target=\"_blank\" href=\"http://www.asciitable.com/\">ASCII</a>\n'H'  <a target=\"_blank\" href=\"http://www.asciitable.com/\">ASCII</a> 0x48\n̾ '!'  <a target=\"_blank\" href=\"http://www.asciitable.com/\">ASCII</a>  0x21\nԶΪ˸ַ 0x00\n<b>ֻҪ˫ַԶΪǼӽ</b>\n\nɴǷ֣\n<b>ַȫֱһΪ̬ݴ洢ڿִļУ\nʹõʱóַʡ</b>\n\nַ .rodata У\nе .text ΣΣеһ\nڿִļڴʱ\nֻģֻͨҳʵֵģ\nҳһλΪ 1 ʾҳд\nΪ 0 ʾҳֻͼֻҳдݣ\nCPU ͻᴥҳ쳣\n<b>Դַеκַڳʱ</b>\n\n## ַָ  ַ\n\nCԴstring2.c\n\n\t#include <stdio.h>\n\t\n\tint main()\n\t{\n\t\tchar *s1 = \"1234567\";\n\t\tchar s2[]= \"1234567\";\n\t\t\n\t\tputs(s1);\n\t\tputs(s2);\n\t\treturn 0;\n\t}\n\n``ִļķĸֵ£\n\n\t 80483bd:\tc7 44 24 1c c4 84 04 \tmovl   $0x80484c4,0x1c(%esp)\n\t 80483c4:\t08 \n\t 80483c5:\ta1 c4 84 04 08       \tmov    0x80484c4,%eax\n\t 80483ca:\t8b 15 c8 84 04 08    \tmov    0x80484c8,%edx\n\t 80483d0:\t89 44 24 14          \tmov    %eax,0x14(%esp)\n\t 80483d4:\t89 54 24 18          \tmov    %edx,0x18(%esp)\n\n``0x80484c4 ַ\"1234567\"ĵַ\nԣ<b>ֵַָʱݵԴַĵַ\nֲֵַʱҪһֲռ</b>\n˵2ַʽ˷ѿռ䣨4ֽ vs 8ֽڣ\n˷ʱ䣨1mov vs 4movǵ2ַʽҲ\nһǴ<b>1ַʽݵԴַĵַ\nԴַֻҳУ޷޸ģַȴ޸</b>\n\n飺бûĶַ\nǾָɣ ַ  ̬Ŀռ 档\n\n## ʽ  ת\n\nַ֣иʽתȥ\nCԴstring3.c\n\n\t#include <stdio.h>\n\t\t\n\tint main()\n\t{\n\t\tprintf(\"--------\\n%d\\n\", 123);\n\t\treturn 0;\n\t}\n\n### Դļ\n\n\tgcc -S string3.c\n\n``£\n\n\t.LC0:\n\t\t.string\t\"--------\\n%d\\n\"\n\n### Ŀļ\n\n\tgcc -c string3.c\n\tobjdump -s -d string3.o > string3.txt\n\n``-c Ĭ string3.o ļУ\nstring3.txt еַ\n\n\tContents of section .rodata:\n\t 0000 2d2d2d2d 2d2d2d2d 0a25640a 00        --------.%d..   \n\n``'\\n'滻 0x0a%d û\n\n### ִļ\n\n\tgcc -o string3 string3.c\n\tobjdump -s -d string3 > string3.txt\n\n``ִļĿļһ\n\n\tContents of section .rodata:\n\t 80484a8 03000000 01000200 00000000 2d2d2d2d  ............----\n\t 80484b8 2d2d2d2d 0a25640a 00                 ----.%d..       \n\n``֪ˣ<b>תڱɶļ\nͱתΪǸַ</b>\nʽȻҪ printf ʱõġ\n֪ʲôã printf \nôתַǾͲòˣ\nתȥġ\n\n뿴 printf ŵʵ֣\nlinux 0.01  kernel/vsprintf.c һ򻯵\nûʵָĴʵ֣תַǲĵġ\n\nlinux 汾ںԴ룺<a target=\"_blank\" href=\"http://www.kernel.org/pub/linux/kernel/\">http://www.kernel.org/pub/linux/kernel/</a>  \nlinux 0.01<a target=\"_blank\" href=\"http://www.kernel.org/pub/linux/kernel/Historic/\">http://www.kernel.org/pub/linux/kernel/Historic/</a>\n\n[Ŀ¼][content]\n"
  },
  {
    "path": "struct.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">结构体\n</h1>\n\n　　结构体是 C 语言主要的自定义类型方案，\n这篇就来认识一下结构体。\n\n## 一、结构体的形态\n\n　　C源程序（struct.c）：\n\n\t#include <stdio.h>\n\t\n\ttypedef struct{\n\t\tunsigned short int a;\n\t\tunsigned short int b;\n\t}Data;\n\t\n\tint main()\n\t{\n\t\tData c, d;\n\t\t\n\t\tc.a = 1;\n\t\tc.b = 2;\n\t\td = c;\n\t\t\n\t\tprintf(\"d.a:%d\\nd.b:%d\\n\", d.a, d.b);\n\t\treturn 0;\n\t}\n\n`　　`赋值部分翻译后：\n\n\t\tmovw\t$1, 28(%esp)\t# c.a = 1\n\t\tmovw\t$2, 30(%esp)\t# c.b = 2\n\t\tmovl\t28(%esp), %eax\t#\n\t\tmovl\t%eax, 24(%esp)\t# d = c\n\n`　　`可以看出：\n\n* c.a 是在 28(%esp) 之后的2个字节\n* c.b 是在 30(%esp) 之后的2个字节\n* c 是 28(%esp) 之后的4个字节\n* d 是 24(%esp) 之后的4个字节\n\n`　　`不得不感叹名字（结构体名字、子元素名字）再一次被抛弃了，\n子元素名代表的是相对于结构体的偏移。\n\n## 二、结构体的复制\n\n　　大一的时候，老师千叮咛万嘱咐：<b>数组不能复制！</b>，\n但是当发现下面这个程序正常运行后，我困惑了（block.c）：\n\n\t#include <stdio.h>\n\t\n\ttypedef struct{\n\t\tchar data[1000];\n\t}Block;\n\t\n\tBlock a={{'a','b','c',}};\n\t\n\tint main()\n\t{\n\t\tBlock b;\n\n\t\tb=a;\n\t\t\n\t\tputs(b.data);\n\t\treturn 0;\n\t}\n\n`　　`Block a={{'a','b','c',}} 是对 a 的部分初始化，\n'c' 后面自动填 0，写成 Block a={{\"abc\"}} 也一样，\nC 语言对初始化还是很宽容的。\n\n　　上面这个程序居然正常的编译、运行了，这究竟是怎样的逆天？\n看看汇编部分：\n\n\tleal\t24(%esp), %edx\n\tmovl\t$a, %ebx\n\tmovl\t$250, %eax\n\tmovl\t%edx, %edi\t# edi = &b\n\tmovl\t%ebx, %esi\t# esi = &a\n\tmovl\t%eax, %ecx\t# ecx = 250\n\trep movsl\n\n`　　`我们发现程序确实通过 250 次 movsl 复制了一个\"数组\"。\n其原因是：结构体是可以复制的，\n结构体又可以包括任意类型的子元素，数组也行，\n所以\"数组\"也被复制了。\n\n　　那为什么纯粹的数组就不能复制呢？\n我们可以这样去理解：<b>一个变量能被复制的必要条件是\n我们知道它的大小</b>。结构体做为自定义类型，\n在编译的时候编译器必然存储了它的子元素类型、个数等相关信息，\n结构体的大小也就知道了；而数组一般只在乎它的类型和\n起始地址，元素个数总是被忽视的（例如：\nvoid func(char s[]) 可接受任何长度的字符数组做参数），\n而且元素个数也没有被当做数组的一部分存入内存，\n所以数组的复制是不好实现的。\n\n## 小结\n\n　　如果给结构体下一个实在点的定义话，那就是：\n<b>有格式的字节数组</b>。有了结构体后 C 语言的\n变量类型就丰富多了，但是同时也要注意：\n\n1. 超过 4 字节的结构体不宜做参数（参数传递浪费时间、空间），\n换做指针更好。\n2. 超过 4 字节的结构体不宜做返回值类型\n（话说一般返回值都用 eax 来存，\n那么超过 4 字节的时候怎么存呢？自己去探索吧！）。\n\n[回目录][content]\n"
  },
  {
    "path": "varargs.md",
    "content": "﻿[content]: https://github.com/1184893257/simplelinux/blob/master/README.md#content\n\n[回目录][content]\n\n<a name=\"top\"></a>\n\n<h1 align=\"center\">可变参数\n</h1>\n\n　　\tC语言的可变参数的实现非常巧妙：\n大师只用了 3 个宏就解决了这个难题。\n\n## 一、可变参数的应用\n\n　　这里实现一个简单的可变参数函数 sum：\n它将个数不定的多个整型参数求和后返回，\n其第 1 个参数指明了要相加的数的个数（va.c）：\n\n\t#include <stdio.h>\n\t#include <stdarg.h>\n\t\n\t// 要相加的整数的个数为 n\n\tint sum(int n, ...)\n\t{\n\t    va_list ap;\n\t    va_start(ap, n);\n\t\n\t    int ans = 0;\n\t    while(n--)\n\t        ans += va_arg(ap, int);\n\t\n\t    va_end(ap);\n\t    return ans;\n\t}\n\t\n\tint main()\n\t{\n\t    int ans = sum(2, 3, 4);\n\t    printf(\"%d\\n\", ans);\n\t\n\t    return 0;\n\t}\n\n`　　`sum 函数的第一个参数是 int n，\n逗号后面是连续的 3 个英文句点，\n表示参数 n 之后可以跟 0、1、2…… 个任意类型的参数。\nsum 可以这么用：\n\n\tsum(0);\n\tsum(1, 2);\n\tsum(3, 1, 1, 1);\n\n## 二、可变参数的实现\n\n　　可以看到在 sum 函数中用到了 3 个函数一样的东西：\nva\\_start、va\\_arg、va\\_end，\n它们是标准库（意味着各种平台都有）头文件 stdarg.h 中\n定义的宏，这 3 个宏经过清理后是下面这个样子：\n\n\ttypedef char* va_list;\n\t#define va_start(ap,v)  ( ap = (va_list)(&v) + sizeof(v) )\n\t#define va_arg(ap,t)    ( *(t *)((ap += sizeof(t)) - sizeof(t)) )\n\t#define va_end(ap)      ( ap = NULL )\n\n* va\\_start 将 ap 定位到可变参数列表的起始地址\n* va\\_arg 每次返回一个参数，并后移 ap 指针\n* va\\_end 将 ap 置 NULL（避免非法使用）\n\n`　　`这 3 个宏的实现就是基于 C语言默认调用惯例是从右至左\n将参数压栈的事实，比如说 va.c 中调用 sum 函数，\n参数压栈的顺序为：4->3->2，\n又因为 x86 CPU 的栈是向低地址增长的，\n所以参数的排列顺序如下：\n\n![args](http://fmn.rrimg.com/fmn062/20121221/1930/original_qweH_1b90000008bb125c.jpg)\n\n　　va\\_start(n, ap)\n就是 ( ap = (char*)(&n) + 4 )\n因此 ap 被赋值为 ebp+12 也就是变参列表的起始地址。\n\n　　之后 va\\_arg  取出每一个参数：\n( *(int *)((ap += 4) - 4) )\n它首先将变参指针 ap 右移到下一个参数的起始地址，\n再将加赋操作的返回值减到之前的位置取出一个参数。\n这样，<b>用一条语句既取出了当前参数，又后移了指针 ap</b>，\n真是神了！\n\n　　sum 中循环使用 va\\_arg 就取出了 n 个要相加的整数。\n\n## 三、变参函数的可行性\n\n　　一个变参函数能接受个数、类型可变的参数，\n需要满足以下两个条件：\n\n1. 能定位到可变参数列表的起始地址\n2. 能获知可变参数的个数、每个参数的大小（类型）\n\n`　　`条件 1 只要有个前置参数就能满足，\n而对于这样的变参函数：void func(...);\n编译能通过，但是不能用 va_start 取到变参列表的起始地址，\n所以基本不可行。\n\n<hr width=\"50%\">\n\n　　sum 函数中参数 n 被用来定位可变参数列表的起始地址\n（满足条件1）；n 的值是可变参数的个数，\n类型默认全部是 int 型（满足条件2），\n因此 sum 能正常工作。\n\n<hr width=\"50%\">\n\n　　再看看 printf 函数是如何满足以上两个条件的，\nprintf 函数的原型是：\n\n\tint printf(const char *fmt, ...);\n\n`　　`printf 的第1个参数 fmt（格式串）被用来定位其后\n的可变参数的起始地址（满足条件1）；\nfmt 指向的字符串中的各个格式描述符如：%d、%lf、%s 等\n告诉了 printf fmt 之后参数的个数、各个参数的类型\n（满足条件2），因此 printf 能正常工作。\n\n<hr width=\"50%\">\n\n　　当然，sum、printf 能正常工作是设计者一厢情愿的期望，\n如果使用者不按规矩传入参数、格式串，函数能正常工作才怪！\n比如：\n\n\tsum(2, \"111\", \"222\");\n\tprintf(\"%s\", 0);\n\n`　　`编译器可不会进行可变参数的类型检查、格式串-参数匹配，\n后果将会在运行的时候出现……\n\n[回目录][content]\n"
  }
]