forked from GHScan/TechNotes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path12.html
60 lines (59 loc) · 48.1 KB
/
12.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
</head>
<ul><li>4 - 程序员修炼之道, 36, 需求之坑
<ul><li>完美,不是在没什么可以增加,而是在没什么需要去掉时达到的</li><li>需求很少存在于表面上,通常,他们深深的埋藏在层层假定、误解和政治手段下面</li><li>Don't gather requirements, dig for them</li><li>把政策文档和需求文档分开,用超链接关联起来,使需求成为一般陈述,并把政策信息作为例子发给开发者,最后,政策成为应用中的元数据</li><li>找出用户要做特定事情的原因,而不只是他们目前做这件事情的方式。最后,你需要解决的是他们的商业问题,而不只是满足他们的陈述</li><li>用文档记载需求背后的原因将在每天进行决策时给你的团队带来无价的信息</li><li>有一种深入了解用户需求、却未得到足够利用的技术:成为用户。Work with a user to think like a user</li><li>成功的工具会适应使用它们的双手,你为他人构建的工具必须具有可适应性</li><li>需求不是架构,需求不是设计,需求不是用户界面。需求是需要</li><li>千年虫问题的根本原因:没有超出当时的商业实践往前看。(用2个数字表示年份,是早在计算机之前就在使用的惯例)</li><li>Abstractions live longer than details,抽象比细节活得更长久</li><li>许多项目的失败都归咎于项目范围的增大,即特性膨胀(feature bloat);通过追踪需求,你可以看到每项新特性对项目进度的影响</li><li>Use a project glossary, 使用项目词汇表。如果用户和开发者用不同的名称指代同一事物,甚至,用同一个名称指代不同的事物,这样的项目很难取得成功</li><li>使用Web页分发需求文档,将需求树的不同细节层次通过超链接连接起来,这样,项目出资人可以在根上巡视进度,而程序员也可以通过超链接深入细节。这避免了典型的两寸厚的名为“需求分析”的文件夹,因为它永远没人阅读,墨水刚沾上纸面,就过时了。</li></ul></li><li>10 - 程序员修炼之道,37,解开不可能解开的谜题
<ul><li>故事:弗里吉亚国王戈尔迪斯曾经系过一个没有人能解开的节,据说能解开的人将会统治整个亚洲。亚历山大大帝来了,用剑劈开了结。对要求做了不同解释,就这样,最后亚历山大统治了亚洲</li><li>解决方法可能在另外的地方。秘诀是确定真正的约束,并找出解决方法,有些约束是绝对的,有些是先入之见</li><li>解决问题的关键在于确定加给你的各种约束,并确定你确实拥有的自由度,不要太快排除潜在的解决方案,你必须挑战先入之见,并评估他们是否是真实的、必需遵守的约束</li><li>Don't think outside the box, find the box。在起跳出盒子思考之前,先找到盒子</li><li>找更容易的方法:(很多时候,对需求的重新诠释能让整个问题全部消失)
<ul><li>有更容易的方法吗</li><li>你在设法解决真正的问题,还是被外围的技术问题转移了注意力</li><li>这件事情为什么是一个问题</li><li>是什么使他如此难以解决</li><li>他必须以这种方式完成吗</li><li>他真的必须完成吗</li></ul></li><li>你需要的是真正的约束、令人误解的约束,以及区分它们的智慧</li></ul></li><li>10 - 程序员修炼之道,38,等你准备好
<ul><li>有时犹豫的人能得以保全。了不起的表演者有一个共同的特征:他们知道何时开始,何时等待</li><li>Listen to nagging doubts, start when you're ready。倾听反复出现的疑虑,等你准备好再开始</li><li>怎样区分是良好的判断,还是在拖延?进行“概念验证”(proof of concept),如果开始不久后,你就觉得你在浪费时间,这厌烦可能表明你最初的勉强只是希望推迟启动;或者,随着原型的进展,你得到启示,并且想明白如何纠正错误,你会愉快的放弃原型并投入项目,你为自己和团队节省了可观、本会浪费的努力</li><li>从“政治策略”角度说,去构建原型比简单宣布“我觉得不该启动”、并开始玩单人指派游戏更容易接受</li></ul></li><li>10 - 程序员修炼之道,39,规范陷阱
<ul><li>编写程序规范就是把需求规约到程序员能够接管的程度的过程。这是一个交流过程,旨在解释并澄清系统需求、消除歧义</li><li>规范留给未来维护代码的程序员记录,规范也是和用户的约定,是隐含合约:最终系统会符合合约要求</li><li>语言自身的表达力存在问题,所有图形技术和形式方法仍然依赖于自然语言表达要进行的操作,所以,Some things are better done than described(对有些事情,做胜于描述)</li><li>怎样用文字描述系鞋带?(所谓语言自身的表达力限制…)用图呢?用照片?用拓扑学的形式方法?用金属丝模型?</li><li>需求不要规定太多实现细节,如果没给编码者留下任何解释余地,该设计实质上剥夺了他们发挥技巧和艺术才能的权利。随着需求越来越详细,你得到的回报会递减,甚至是负回报</li><li>不要把规范当做安乐毯,去编码!去构建原型、或者用曳光弹开发</li></ul></li><li>10 - 程序员修炼之道,40,圆圈与箭头
<ul><li>盲目的采用任何技术,而不把它放进你的开发实践和能力的语境中,这样的处方肯定会让你失望</li><li>Don't be a slave to formal methods,不要做形式方法的奴隶
<ul><li>大多形式化方法的图和文字是设计者对需求的理解,没有经过用户的形式检查,因此,向用户展示原型并让他们使用可能更好</li><li>形式化方法鼓励专门化,如一组人构建数据模型,一组人考察架构,一组人搜集需求用例,但这些都引入了额外的沟通成本</li><li>大多数形式化方法都把静态的对象或数据模型与某种事件或活动图表机制结合在一起,而我们需要元数据来让我们的系统具备运行时改变特征的能力,很少有方法能够阐述这种系统本该具备的动态性,事实上,大多形式化方法会让你误入歧途,鼓励你在对象之间建立静态关系,而这些对象本该动态的编制在一起</li></ul></li><li>绝不要低估新工具和新方法的代价,做好准备,把使用这些技术的第一个项目当做一种学习经验</li><li>形式开发方法是工具箱里的又一种工具,如果仔细分析后仍然觉得有需要,则使用它,但记得谁是主人,不要变成方法学的奴隶</li><li>注重实效的程序员批判的看待方法学,并从各种方法学中提取精华,融合成每个月都在变得更好的一套工作习惯,你应该不断努力提炼和改善你的开发过程,决不把方法学的呆板限制当做你世界的边界</li><li>方法学肯定有其位置,但是,如果一个项目的哲学是“类图就是应用,其余的只是机械的编码”,这就是一个浸满水的项目和一个路途遥远的家</li></ul></li><li>12 - 程序员修炼之道,41,注重实效的团队
<ul><li>监督6名一流程序员,其管理上的挑战可与放牧猫群相比</li><li>注重实效的个体有好处,而如果个体是在注重实效的团队中,则好处倍涨</li><li>不要留破窗:质量是一个团队问题,最勤勉的开发者如果被派到不在乎质量的团队里,会发现很难保持修正琐碎问题的热情。如果团队主动鼓励开发者不要把时间花费在这样的修正上,问题会进一步恶化。团队是一个整体,不应该容忍破窗。所谓团队中质量官负责产品质量的做法,是荒谬的,因为质量只能源于全体团队成员的贡献</li><li>煮青蛙:作为整体的团队更容易被煮熟。因此要确保每个人都主动监视环境变化,你必须注意到他们正在发生,否则会置身于热水中</li><li>交流:团队中的开发者必须互相交谈,而团队作为实体也需要同外界明晰的交流。与优秀的团队开会,他们准备良好的演出,制作新鲜、一致的文档,用同样的声音说话(在团队内进行激励、活跃的辩论),甚至还有幽默感。一个简单的营销诀窍,能帮助团队与外界交流:创立品牌。想一个不寻常的名字,logo,然后在与人交谈、报告中使用。</li><li>DRY:团队成员间的重复,需要靠交流来避免,可以就每项任务,单独指定责任人,所有团队成员都咨询他</li><li>正交性:Organize around functionality, not job functions,围绕功能、而不是工作职务组织。按照功能划分团队,各个团队分别负责最终系统的特定方面内容,有助于使团队作为整体与变化的各种效应隔离开来,这种分组能够极大的减少各个开发者工作之间的相互影响、缩短时间标志、提高质量、并减少缺陷数目。这种途径还能带来更愿意付出的开发者,每个开发者知道他们要对特定的功能负责,所以他们会觉得自己是工作成果的主人。这种方式需要负责任的开发者,以及强力的项目管理,项目可以设立两个头,一个主管技术,一个主管行政。</li><li>自动化:确保一致和准确的一种很好的方式是使团队的每件事情都自动化。比如输入的时候靠编辑器安排代码布局,夜间构建的同时进行自动测试。</li><li>知道何时停止绘画:每个成员都能以他们的方式闪亮,给他们足够的空间以支持他们,并确保项目的交付能够符合需要,但要抵抗这些画家不断画下去的诱惑。</li></ul></li><li>13 - 程序员修炼之道,42,无处不在的自动化
<ul><li>“文明通过增加我们不加思索就能完成的重要操作的数目而取得进步”——阿尔弗雷德</li><li>汽车时代破晓时分,T型福特车的操作说明有两页不止,而现代汽车你只需要转动车钥匙,前者可能会撑掉引擎,而自动启动器不会</li><li>与自动化相比,人工流程不能保持一致性,也无法保证可重复性,特别是在不同的人对流程的各个方面有不同解释时</li><li>Don't use manual procedures,不用使用手工流程</li><li>用shell、脚本不止能保持一致性、可重复性,而且由于源码控制,它的历史还可追朔</li><li>特别受欢迎的自动化工具cron(或windows下的at)</li><li>自动化-项目编译:项目编译是一件可靠、可重复进行的琐碎工作,相比makefile,IDE很难获得我们寻求的自动化程度,我们想要一条命令就完成签出、构建、测试和发布</li><li>自动化-生成代码:以公共来源派生知识,自动生成源代码、头文件或是文档</li><li>自动化-回归测试:从源码树顶部发出一条命令,测试整个项目或者单个模块</li><li>自动化-构建自动化:夜间进行daily build
<ul><li>从仓库签出源码</li><li>从头开始构建项目,标注发布或者版本号,或者时间戳</li><li>创建可发布映像,确定文件所有权和权限,同时生成伴随发布的所有例子、文档、README文件以及其他随同发布的任何东西</li><li>运行测试</li></ul></li><li>自动化-最终构建:最终构建需要锁住仓库或是指定版本号,设置不同的优化和调试标志。注意当编译标志不同时,需要再针对这个版本进行所有测试</li><li>自动化-自动化管理:程序员无法把所有时间都投入到实际编程,需要回复e-mail、完成书面工作、发布文档到web上等,可以让这些事情都脚本化</li><li>自动化-网页生成:从代码、需求分析、设计文档中提取文档、图片、图标或图形,最终发布到网站上,随着daily build自动发布出去,无需人的干预。这是DRY的一种应用</li><li>自动化-批准流程:在源码里放上含need_review语义的注释,利用脚本从中提取信息并通过网页、邮件发布出去,申请review或者会议</li><li>要避免“鞋匠的孩子没有鞋”的情况,软件开发者自己的日常不要疏忽自动化</li></ul></li><li>16 - 关于反射
<ul><li>Reflection
<ul><li>Generally
<ul><li>reflection is a mechanism, which makes possible for an entity to investigate and change itself.</li></ul></li><li>In computer science
<ul><li>reflection refers to the ability of a computer program to examine and modify its own structure and/or its behavior.</li><li>The programming paradigm driven by reflection is called reflective programming . It is a particular kind of metaprogramming</li></ul></li></ul></li><li>?Reflection capabilities
<ul><li>Self-examination (introspection)
<ul><li>Allows to examine the structure or the behavior of a program by the program itself</li></ul></li><li>Self-modification
<ul><li>Allows the program to modify itself
<ul><li>Generate and add new code</li><li>Modify existing code</li><li>Remove existing code</li><li>Change the way the existing source or binary code is interpreted (intercession)</li></ul></li></ul></li></ul></li><li>Reflection categories
<ul><li>Structural (a.k.a. linguistic)
<ul><li>Allows the program directly to inspect and/or change its structure at various stages
<ul><li>the source code</li><li>the intermediate representation (parse tree, etc.)</li><li>the executable native code (or byte code)</li></ul></li></ul></li><li>Behavioral
<ul><li>Allows the program to alter its own behavior or meaning by
<ul><li>altering the evaluation rules for the particular language expressions (function call, class member access, return statement, etc.)</li><li>altering the evaluator of the program (i.e. change of compiler or interpreter)</li></ul></li></ul></li><li>Combined approach</li></ul></li><li>?Reflection use-cases
<ul><li>Generic examination and manipulation of program’s data-structures and code
<ul><li>Traversals of
<ul><li>Members of namespaces</li><li>Member variables and member functions of classes</li><li>Base classes and inheritance hierarchy</li></ul></li><li>Generic (class) member manipulation (getters / setters)</li><li>Genericobjectconstruction
<ul><li>Picking one of the constructors</li><li>Supplying the parameters if necessary</li></ul></li><li>Generic function invocation
<ul><li>Remote Procedure Calls / Remote Method Invocation</li><li>Native scripting and scripting language bindings</li></ul></li></ul></li><li>Serialization-like and persistence-related operations</li><li>Object-Relational Mapping</li><li>Base-level object unique identification and comparison</li><li>Code generation</li><li>Design pattern implementation</li><li>Logging and debugging</li></ul></li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,0节
<ul><li>程序计数器的变化过程叫做控制转移(control transfer),而控制转移序列叫控制流(control flow)</li><li>典型的控制流是连续指令的地址相邻,程序也能够对内部状态的变化做出反应,从而产生跳转、调用、返回这些非平滑突变</li><li>程序也应该对系统状态的变化做出反应,这些系统状态不由程序变量捕获,比如,IO中断、时钟中断、硬件异常、系统调用等,这些突变被叫做ECF(exceptional control flow,异常控制流)</li><li>ECF被计算机系统的各个层次使用:在硬件层,被用于相应时钟中断、处理处理器异常;在操作系统层,被用于实现系统调用、进程调度、虚拟存储器、IO;在应用层,被用于实现信号等软中断。同时,了解ECF也有利于理解setjmp等非本地调用(non local jmp)以及语言层面的异常机制(如C++、Java中的try、throw)</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,1节,异常
<ul><li>这里的异常是广义的,包括通常意义上的中断(异步)和异常(同步)</li><li>异常(exception):处理器状态的一个变化(事件)触发了从应用程序到异常处理程序的一个控制转移,异常处理程序执行完毕过后,会将控制返回给被中断程序或终止</li><li>处理器检测到事件时,会通过异常表(exception table),进行一个间接过程调用,跳转到专门的操作系统子程序——异常处理程序(exception handler)</li><li>系统中每种类型的异常都有一个非负编号(exception number),其中,除零、缺页、存储器访问违例、断点、算数溢出、内存不对齐等ISA级硬件异常(包括时钟中断?),其编号由处理器设计者分配,而系统调用、IO中断(因为需要协调CPU和其他设备),其编号由操作系统指定</li><li>处理器检测到事件后,从特殊的CPU寄存器(exception table base register)中取得异常表地址,进而得到handler</li><li>exception handler运行在内核态,这意味着他们拥有所有系统资源的访问权限</li><li>异常分为四类:中断(interrupt)、陷阱(trap)、故障(fault)和终止(abort)。其中中断是异步的,其他都是同步的,必然是由特殊指令引发</li><li>中断:异步发生,是来自处理器外部的IO设备的信号的结果,它不是由任意一条专门的指令造成的,故是异步的,处理程序被称作interrupt handler。IO设备,如网络适配器、磁盘控制器、定时器芯片,通过向处理器的一个管脚发信号,并将异常号放到系统总线上,来触发中断,这个异常号标志了中断设备。处理器执行完当前指令之前,发现中断管脚的电压变高,就从系统总线读取异常号,进而调用handler,handler返回后,将控制返回给下调指令,就像什么都没发生过一样</li><li>陷阱:陷阱是一条指令的结果,最重要的用途是在用户程序和操作系统之间提供一个像过程一样的接口,叫做系统调用。为了允许用户程序对内核服务的受控访问,处理器提供了一条特殊的“syscall n”指令,来让用户请求read、fork、execve、exit等服务。普通程序运行在用户模式(user mode),系统调用运行在内核模式(kernel mode),用户模式限制了可执行指令的类型,并且只能访问用户栈,内核模式允许访问受限指令(privilege instruction)和内核栈</li><li>故障:故障由错误引起,它可能被故障处理程序修正,如果handler能够修正这个错误,它就将控制返回故障指令,从而重新执行。典型的如page fault,而floating exception fault、segmentation fault就是不能修复的典型。</li><li>终止:不可恢复的致命错误,典型的如硬件错误,比如DRAM/SRAM位被损坏后发生的奇偶错误,handler会直接返回到abort从而结束程序</li><li>pentium的异常号例子:0~31,包括除法错误、一般保护故障、缺页、机器检查等硬件异常;32~127,操作系统定义的异常(中断或陷阱);128,系统调用(陷阱);129~255,操作系统定义的异常(中断或陷阱)</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,2节,进程
<ul><li>进程的经典定义是一个执行中的程序实例,进程包括一个上下文(context),它是程序正常运行所需的状态,包括程序中的代码、数据、栈、通用寄存器、程序计数器、环境变量以及文件描述符。进程给程序提供了两个关键抽象:
<ul><li>一个独立的逻辑控制流,提供一个假象,让我们觉得我们的程序独占CPU</li><li>一个私有的地址空间,提供一个假象,让我们觉得我们的程序独占存储器空间(n位地址机器上的2^n空间)</li></ul></li><li>PC值的序列叫做逻辑控制流</li><li>进程是轮流使用处理器的,每个进程执行它的流的一部分,然后被抢占式的挂起(preempted),从我们进程的角度看,似乎CPU或周期性的停顿(stall)</li><li>逻辑流在时间上重叠的多个进程被称为并发进程(concurrent process)</li><li>进程和其他进程轮换运行的概念叫做多任务(multitasking)。一个进程执行它控制流的一部分的那段时间叫做时间片(time slice),多任务也叫时间分片(time slicing)</li><li>常见的linux进程地址空间,从低地址到高地址:前3/4是给用户程序的,文本段、数据段、堆、共享库、栈;后1/4是留给内核的,包括内核在该进程中使用(比如执行系统调用)的代码、数据和栈</li><li>为了能让操作系统提供一个无懈可击的进程抽象,处理器提供一种机制,限制一个应用可以执行的指令和它可以访问的地址空间,于是有了内核态和用户态。典型的,处理器的某个控制寄存器中的一个模式位提供了这种功能,描述了当前进程享有的权利,当模式置位时,进程就运行在内核模式中,可以执行任意指令,访问任意空间;而用户态的程序不能访问特权指令(privilege instruction),比如不能停止处理器、改变模式位、发起IO操作,也不能访问内核代码和栈,任何这样的尝试都会导致保护故障,用户程序只能通过系统调用接口间接访问内核代码和数据。进程初始时是用户模式的,切换到内核态的唯一方法是通过诸如中断、故障或陷入系统调用(trapping system call)这样的异常</li><li>上下文(context)是进程的状态,包括一系列对象,比如通用寄存器、浮点寄存器、程序计数器、状态寄存器、用户栈、内核栈和各种内核数据结构,比如描绘地址空间的页表(page table)、包含当前进程信息的进程表(process table)以及当前进程打开文件信息的文件表(file table)</li><li>程序执行的某些时刻,内核可以决定抢占当前进程,并重新开始先前一个被抢占的进程,这种决定叫调度(scheduling),由内核中的scheduler的代码处理。内核调度一个新进程,需要上下文切换(context switch):(1)保存当前进程的上下文(2)恢复某个先前被抢占的进程的上下文(3)将控制传递给这个新恢复的进程</li><li>read调用请求一个磁盘访问,内核可以选择进行上下文切换,执行另外一个进程,而不是等待数据从磁盘到达;sleep更是显示的请求让调用进程休眠;即使系统调用没有阻塞,内核也可以决定执行上下文切换,而不是直接返回;另外,周期性的定时器中断发生时,内核也可能判断当前进程已经运行了足够长的时间,从而切换到其他进程</li><li>高速缓存污染(cache pollution):一般而言,高速缓存不能和中断、上下文切换这样的ECF很好交互,exception handler或者被调度程序都可能在cache中是冷的(cold),需要再次warmup,这种情况称作interrupt、scheduling污染(pollute)了cache</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,3节,系统调用和错误处理
<ul><li>标准C库提供了一组针对最常用系统调用的方便封装函数(wrapper)</li><li>unix系统调用往往会通过errno报告错误,请检查它。strerror返回描述文本</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,4节,进程控制
<ul><li>getpid, getppid</li><li>从程序员角度看,进程总处在三种状态之一:
<ul><li>运行。要么在CPU上执行,要么等待执行且终将被调度</li><li>停止。进程的执行被挂起,suspended</li><li>终止。永远停止了,可能是因为收到信号、return或者exit()。等待被回收(waitpid)</li></ul></li><li>fork创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的一份拷贝,包括文本、数据、bss段、堆和用户栈,同时还得到文件描述符的拷贝,因此父子进程可以共享文件。</li><li>画进程图理解fork比较有用,每个水平箭头对应从左到右执行指令的进程,每个垂直箭头对应fork调用</li><li>终止但还未回收的进程称为僵死进程(zombie),父进程可以通过waitpid来回收,从而彻底销毁zombie进程的内核资源,如果父进程没能回收就终止了,内核会安排init进程回收它们。长时间运行的程序如shell或服务器,总应该回收它们的子进程,否则zombie进程消耗的系统存储器会累积</li><li>sleep挂起当前进程直到超时或收到信号</li><li>pause挂起当前进程直到收到信号</li><li>execve如果没有找到filename,则返回;否则会加载目标程序;getenv、setenv、unsetenv可以访问环境变量。利用fork和execve可以实现shell</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,5节,信号
<ul><li>硬件错误(以及硬件中断时?)系统会向程序发送信号,比如除0会发送SIGFPE、非法指令发送SIGILL、非法存储器访问SIGSEGV;按ctrl-c,发送SIGINT;一个进程可以给另一个进程发送SIGKILL来终止它;进程终止或暂停时,会发送SIGCHILD给父进程</li><li>系统检测系统事件、kill调用、子进程事件时,会向目标进程发送信号,该信号k会被记录到pending signal的位向量中,当控制从内核态切换回用户态时(系统调用或时钟中断等),系统会挑选一个非blocked的pending signal(即block向量对应位为0),执行其handler,并清除位向量的标记。显然,相同signal无法排队,所以不能被计数。在早期unix上,slow system call(如read、connect)等会被信号打断,而今天的部分unix系统扔保持这一性质,故避开signal函数,改用能明确要求重启(slow system call被信号打断后不返回而是restart)的sigaction函数</li><li>为方便操作一组进程,有了进程组的概念。getpgrp、setpgid</li><li>kill命令可以给任意进程发送任意信号,kill函数类似。shell将每行命令产生的进程叫做一个作业(job),他们有相同的进程组,进程组号往往是根进程的pid。shell中至多有一个前台作业和0~n个后台作业。ctrl-c、ctlr-z都将操作前台作业中的进程组。&命令创建job并挂起到后台运行;cltr-z将前台进程转入后台,但停止,用bg来继续后台job,用fg来将后台job拉回前台</li><li>alarm会在n秒后给当前进程抛一次SIGALRM信号</li><li>信号的handler可以是:SIG_IGN无视信号;SIG_DEF默认;user defined</li><li>sigprocmask可以用来操作信号的block向量,常用于暂时的屏蔽某信号处理,避免被软中断。sigemptyset、sigfillset、sigaddset、sigdelset用来操作临时的block数据。考虑sigprocmask的一种用法:svn,平时block掉SIGINT,只在connect、receive的前后临时开启handler,于是,长时间无响应被ctrl-c时,也能优雅的在signal handler中cleanup环境,无需担心handler会操作全局数据而带来的信号不可重入问题。</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,6节,非本地跳转
<ul><li>setjmp:保存当前调用栈的上下文(ebp、eip等)。longjmp,跳转到env记录的setjmp位置,要求目标栈帧是活跃的(即setjmp函数还未退出)。sigsetjmp、siglongjmp用于想在signal handler中调用longjmp用。在C中用setjmp、longjmp,小心资源泄露;在C++中,由于是库层的方案,编译器不知道,无法析构栈上对象,所以请用try、throw</li></ul></li><li>16 - 深入理解计算机系统,8章,异常控制流,7节,操作进程的工具
<ul><li>ps、pgrep、pkill、kill、top</li><li>strace打印系统调用,最好以-static方式编译目标程序,否则,由于以动态库方式引用各种库,会看到大量动态库相关的syscall干扰分析。-c可以查看调用时间和次数</li><li>ltrace,library trace,打印动态库的调用,用法同strafe</li></ul></li><li>17 - 程序员修炼之道,43,无情的测试
<ul><li>大多数开发者讨厌测试,温和的测试,避开最脆弱的地方;注重实效的程序员不同,无情的测试,避免bug带来的羞辱</li><li>找bug像捕鱼,用细网捕小鱼,用粗网捕大鱼;小鱼苗会飞快的变成大鱼,所以一有了代码就尽快测试</li><li>Test earlier, test often, test automatically</li><li>Coding ain't done, 'til all the tests run,要通过全部测试,编码才算完成</li><li>测试什么:
<ul><li>单元测试,对模块单独测试</li><li>集成测试,保证项目的子系统能够工作。是单元测试的一种扩展,只不过测试的是整个子系统遵循合约的情况</li><li>资源耗尽、错误和恢复:包括内存空间、磁盘空间、CPU带宽、磁盘带宽、网络带宽、调色板、视频分辨率等的测试,当系统失败时,会graceful的失败吗</li><li>性能测试</li><li>可用性测试:软件对于用户,是双手的延伸吗</li><li>验证和校验:做的东西是用户需要的吗?没有bug,但回答的问题本身是错误的话…</li></ul></li><li>怎样测试:
<ul><li>测试代码自身的设计和方法学:扇入扇出比、响应集、类耦合比</li><li>回归测试:前面的测试方法都支持回归测试</li><li>测试数据:真实世界的数据+合成数据(大量、边界条件、统计规律的数据)</li><li>测试GUI系统:事件捕捉、释放,脚本驱动。某些图形系统无法自动化测试,只能依赖于对测试结果的人工解释。对有GUI前段的数据处理应用,你的设计应该足够解耦,使得无需GUI,都能进行逻辑测试</li><li>对测试进行测试:故意造成bug,确保测试程序能发现</li><li>彻底测试:只能通过覆盖分析(coverage analysis)。及时代码覆盖率100%,也不一定测试了所有情况,test state coverage, not code coverage</li></ul></li><li>何时测试:
<ul><li>很多项目dead line之前才测试;我们应该在代码一存在就开始测试</li></ul></li><li>把网收紧
<ul><li>find bugs once,一个bug只抓一次;一旦出现,就应该追加测试来覆盖</li></ul></li><li>独立于GUI测试逻辑太困难的话,耦合太高</li></ul></li><li>19 - 程序员修炼之道,44,全部都是写
<ul><li>典型情况下,开发者不会太关注文档;最好情况下,它是一件倒霉的差事;最坏情况下,它被当做优先级最低的任务,希望管理部门会在项目结束时忘掉它</li><li>Treat english as just another programing language,把我们的注重实效的编码原则应用于文档</li><li>代码已经说明了它是怎样完成的(how),为此加上注释是多余的,违反DRY</li><li>不该放到注释中的:文件中的代码导出的函数列表;修订历史;该文件使用的其他文件列表;文件名</li><li>注释给了你完美的机会,记录难以描述、容易忘记,又不能记载在别的地方的东西:工程上的权衡、为何要做出某些决策、放弃了哪些替代方案</li><li>源文件中应该出现的:文件所有者,即责任者;版权提示和法律样本</li><li>文档和源码都是模型的视图,确保修改总是在模型上,然后通过工具生成多个视图</li><li>书面文档一印出来就过时了,任何形式的文档都只是snapshot;所以你应该通过标记语言,生成各种形式的文档比如web</li></ul></li><li>19 - 程序员修炼之道,45,极大的期望
<ul><li>一个小孩打开圣诞礼物,却大哭起来——这不是他想要的廉价娃娃;某个项目团队奇迹般的实现了一个极其复杂的应用,却遭到用户抵制,因为该应用没有帮助系统。现实中,项目的成功是由它在多大程度上满足了用户的期望来衡量的</li><li>Gently exceed your users' expectations,温和的超出用户的期望</li><li>与用户交流,你会发现他们有的期望无法满足,有的期望过于保守。与用户一同工作,以使他们正确的理解你要交付的产品,并且在整个开发过程中保持这样的交流,决不要忘了你的应用要解决的商业问题。每个用户都应该理解所期望的是什么以及它是怎么被构建出来的,如果团队能够与外界流畅的交流,这个过程几乎是自动的</li><li>“曳光弹”和“原型与便签”是促成这个过程的重要技术</li><li>交流固然重要,但也要给用户惊讶(注意,不是惊吓),给他们的东西应该比他们期望的多一点点,额外提供一些相对表面的特性,不会因为特性膨胀给系统带来过度负担的东西。记住,不要因为这些特性破坏了系统</li><li>交付软件时,用户怎样评论?他们对应用各方面的关注,和你投入的努力成比例吗?</li></ul></li><li>19 - 程序员修炼之道,46,傲慢与偏见
<ul><li>注重实效的程序员不会逃避责任,他们像过去的手艺人一样,以将自己的名字刻到作品上为傲</li><li>署名可能带来麻烦,人们可能变得有地盘意识,不要怀有偏见,以猜忌之心阻止他人查看你的代码,你同样也应该尊重他人的代码。你要别人怎样对你,你就怎样对人。开发者之间应该相互尊重</li><li>匿名(尤其是在大型项目中)可能会为邋遢、错误、懒惰和糟糕的代码提供繁殖地。只把自己看做齿轮上的一个齿、在无休止的状况报告中制造撇脚的接口、而不去编写优良的代码,那太容易了</li><li>你的签名应该被视为质量的保证,人们看到你的名字时,应该期望它是可靠、用心编写、测试充分的专业作品</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,0节
<ul><li>人们经常问“X程序在Y机器上运行得有多快?”。这里其实有两个变量,X和Y,分别是考察程序和机器。考察程序,是developer的工作,可能是性能调优。考察机器,可能是人们购买新机器时用来衡量机器性能的问题</li><li>对于特定程序和数据的组合,机器会执行固定的指令序列,因此你可能会以为在计算机系统上获得完美的计时测量会很容易。但实际很难,因为:
<ul><li>计算机并不止执行一个程序,他会在多个线程之间切换,实际调度情况依赖于定时器频率、共享系统的用户数、网络流量和磁盘操作</li><li>高速缓存的命中情况不仅依赖于本程序,还受其他程序影响。分支预测依赖于历史记录,也受其他程序影响</li></ul></li><li>计算机用来记录时间流逝的两种基本机制
<ul><li>低频率计时器,它周期性的中断处理器(timer interrupt),然后在被中断程序的用户态/内核态字段里增加计数,从而标志被中断程序又执行了一个time slice。这是interval counter</li><li>通过库函数或者特定的汇编指令(如rdtsc)访问处理器的周期计数器,从而精确计时。这是cycle counter</li></ul></li><li>特殊小组和专业性能测试公司会建立特殊配置的机器,使造成计时不规则的来源最少,比如限制访问、关掉OS网络服务</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,1节,计算机系统上的时间流
<ul><li>计算机在两个完全不同的时间尺度(time scale)上工作
<ul><li>微观上,每个cycle处理一条或多条指令,这里的cycle大概是10^-9秒(纳秒,ns)</li><li>宏观上,处理器必须响应外部事件,如各种io中断和定时器中断,其中timer interrupt一般是10ms级别(linux可能是250HZ~1000HZ,windows-nt的interval则可能是20~48ms)。在早期的计算机上,鼠标输入中断或者按键中断的handler频率之高、处理之复杂,都有可能由其handler导致cpu高load average</li></ul></li><li>如果只有io和系统调用导致中断,则操作系统无法及时的调度线程,因此引入定制器中断,其interval往往是1~10ms</li><li>系统和应用对时间的看法
<ul><li>系统角度,一个cpu的processor时间会被用于轮流的执行线程A,B,C....,而每个线程代码又分为用户态和内核态,因此,cpu processor的时间会被划分为As, Au, Bs, Bu, Cs Cu...(这里的s、u分别是system和user)。当processor的定时器中断发生时,其handler会依据当时的执行状态给Xs或者Xu增加计数,表示其执行时间又增加了一个time slice</li><li>应用A的角度,时间被分为活跃和非活跃的;其中活跃部分,A的用户态代码或者内核态代码正被某个processor执行,而非活跃部分,A被挂起或者等待被调度。导致A被调度让出processor可能是因为A主动发起syscall,也可能是被timer interrupt给preempt掉</li></ul></li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,2节,通过间隔计数(interval counting)来测量时间
<ul><li>time命令,分别会打印被测量程序的用户态耗时、内核态耗时、总时间跨度、进程代码的执行时间占总时间跨度的百分比。其中用户态、内核态耗时是通过timer interrupt的次数来衡量的,因此单位是timer interval</li><li>库函数times,输出参数包括user time、system time,以及已回收(waitpid)的子进程的总user time、system time。返回值是进程启动以来的总时间跨度。单位是CLK_TCK或者sysconfig(_SC_CLK_TCK)</li><li>clock返回当前进程的user、system总时间。单位是CLOCKS_PER_SEC,注意这个单位不一定和times的单位宏相等</li><li>当待测程序的执行时间相对interval time的乘数不大时,比如不超过1秒时,则误差较大,而超过1秒后,误差越来越小。比如,待测程序时间少于10ms,误差可能超过50%。主要的误差问题:
<ul><li>单位是interval time,精度太低</li><li>用户程序时间和syscall时间被计入,这问题不大,但中断handler也被记进去了,尤其像linux下handler较复杂耗时达10^5个cycle等,因此该误差对小程序影响大</li><li>多任务时,由于cache pollution和miss predict造成的耗时增长也被计入</li></ul></li><li>interval counting的缺点是粒度太大,不能用于小于100ms的计时,优点是,对于大于1s的计时,能够区分活跃和非活跃态,只记录本程序的system、user时间,不受系统高负载影响</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,3节,周期计数器
<ul><li>ia32有个指令rdtsc(ReaD Time Stamp Counter),将cycle counter寄存器的值放入edx:eax。对于1Ghz的处理器,int64要570年才溢出</li><li>cycle counter是cpu的每个processor都有的,因此如果没有进行processor affinity的绑定,那两次rdtsc之差可能没有参考意义:两次rdtsc可能分别属于不同的processor,而且不同processor的频率也可能临时性的不同(降频、睿频)</li><li>rdtsc之差表示程序经过的cycle数,但由于现代cpu有自动降频(节能设置)、睿频(intel core)的功能,因此,可能不能简单的除以cycles per second来将cycles转化为秒数,尤其对于非cpu ciritical的程序更是如此</li><li>由于乱序的影响,rdtsc不应该被用于测量很少的几个指令的耗时</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,4节,用周期计数器来测量程序执行时间</li><li style="list-style: none; display: inline"><ul><li>一个检测processor频率的方法(前提是单processor或者进行了processor affinity):x = rdtsc; sleep(1); rdtsc - x。因为sleep参数为1,这个粒度较大,基本不受schedule时间误差影响</li><li>简单的 x = rdtsc; ops; rdtsc - x 用于计时,在被测程序时间较短,少于1个time slice时,很准确,哪怕系统负载很高;当被测试间大于1个time slice,由于进程调度,当前进程会进入非活跃期,所以测得的时间可能有严重误差,即系统负载敏感</li><li>假设op()耗时为c,小于一个time slice,而"for (int i = 0; i < N; ++i) o();"的耗时为C,那么C的准确估计值就是N x c,C的估计值和实测值之差可以用于衡量各种测试方案的准确性(比如K次最优,中断补偿,cache冷却)</li><li>为了尽量减少cache miss对测量的影响,可能会有这样的调用序列“op(); x = rdtsc; o(); rdtsc - x”,其中第一个op()是为了warm up the cache。但考虑到warm up instruction cache比较合理,而warm data cache可能不合适,因此可能会这么调用“op(); clear_level_1_data_cache(); x = rdtsc; op(); rdtsc - x”,其中clear_level_1_data_cache是访问一个size等于level 1 data cache的全局数组,使L1 dcache冷下来。当然,具体实施上,clear_level_1_data_cache的动作也可能会影响到共享的L2上的instruction部分,产生一定的干扰...</li><li>鉴于context switch引入的miss predict、cache pollution、inactive state都是造成测试结果偏大,因此可以采用K次最优测量法:连续测试,保存最快的K次测试结果,当最快的K次中较慢的几次结果和最快的结果误差不超过常数c%,则完成收敛,测试结束,最多测试M次。显然K越大越精确,比如3、5</li><li>由于部分系统中中断处理程序较复杂,引入的误差较大,因此可以考虑对测试结果进行补偿(减去总的中断handler时间),方法是计算出单次interrupt handler耗时(方法?),乘以总中断数(两次times之差)。对linux这样timer频率高、handler较复杂的系统,补偿方案效果明显</li><li>cycle counting方案的优点是,对于小于time slice的程序,计时准确、精度高,无论系统负载如何;而测量耗时长的程序时,对系统负载敏感,此时如果能将负载控制住,那么通过K best、interrupt compensate、cache warmed up,可以提高测量准确性</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,5节,基于gettimeofday函数的测量</li><li style="list-style: none; display: inline"><ul><li>gettimeofday,高精度计时,达到us,可以作为rdtsc的跨平台替代品(当然精度比rdtsc低)</li><li>部分平台的gettimeofday实现不是cycle counter而是interval counter,此时还是得直接使用汇编的rdtsc才能提供高精度。比如windows-nt的实现是interval counting精度低,函数调用本身开销也大,考虑用QueryPerformanceCounter来替代</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,6节,综合:一个实验协议</li><li style="list-style: none; display: inline"><ul><li>注意cycle couting的使用必须进行processor affinity,而且不能用于测极短的指令序列(influence of out of order),最后由于降频、睿频的影响,cycles不能简单的和秒进行转换</li><li>如果X运行时间很长,大于1s:用interval counter计时,比如times、clock。该方案是系统负载无关的</li><li>如果X运行时间很短,在1 time slice~1s之间:用cycle couting计时,并辅以优化方案,如K best、interrupt compensate、cache warmed up。如果gettimeofday是interval counting实现,则寻找平台专有方案(如QueryPerformanceCounter)或直接用汇编的rdtsc。注意cycle counter的使用约束。该方案是系统负载相关的</li><li>如果X运行时间极端,小于1 time slice:简单的用cycle counting计时,足够准确。(可能需要warm up cache)。该方案是系统负载无关的</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,7节,展望未来</li><li style="list-style: none; display: inline"><ul><li>部分系统已经开始实现进程专有的cycle counter(而非processor specific),它在非活跃态时被冻结,因此是高精度、系统负载无关的方案</li><li>可变时钟频率导致秒和cycle不能简单转换,可能用户使用秒做单位,而优化器用cycle做单位</li></ul></li><li>21 - 深入理解计算机系统,9,测量程序执行时间,9节,得到的经验教训</li><li style="list-style: none; display: inline"><ul><li>每个系统都是不同的:包括cpu属性、系统函数实现方案</li><li>高负载系统上的高精度计时很困难</li></ul></li><li>23 - Binding times</li><li style="list-style: none; display: inline"><ul><li>Many different times when binding occurs</li><li style="list-style: none; display: inline"><ul><li>Language definition time (earliest)</li><li style="list-style: none; display: inline"><ul><li>Set of types that are allowed – not all languages support user-defined types –instead only a primitive set is allowed</li><li>Binding of properties to operators – not all languages support overloaded operators – instead they are fixed</li><li>General acceptable structure of the language, character set</li></ul></li><li>Language implementation time</li><li style="list-style: none; display: inline"><ul><li>This is “when the compiler is compiled or the interpreter is compiled”</li><li>Binding of type to representation (BigEndian, LittleEndian)</li><li>How types are defined (is an integer 32 bits or 64 bits?)</li><li>Binding of operations to hardware instructions</li></ul></li><li>Compile time</li><li style="list-style: none; display: inline"><ul><li>Commonly, Types to variables</li><li>A name to a variable</li><li>Operators to implementation, when using overloaded operators</li><li>Set of types that are allowed, when using user defined types such as classes or structs</li><li>Compiler/Linkers often deal with relative addressing issues – how to assign parts of the address space for the program</li></ul></li><li>Run time (latest)</li><li style="list-style: none; display: inline"><ul><li>Values to variables</li><li>Memory addresses for data being held in memory</li><li>Type for languages where type of a variable can be set at runtime</li></ul></li></ul></li><li>Can bindings change?</li><li style="list-style: none; display: inline"><ul><li>static</li><li style="list-style: none; display: inline"><ul><li>In C, type to a variable</li></ul></li><li>dynamic</li><li style="list-style: none; display: inline"><ul><li>Value to a variable</li></ul></li></ul></li></ul></li><li>23 - Binding and binding times</li><li style="list-style: none; display: inline"><ul><li>The most common binding times for attributes are (in chronological order):</li><li style="list-style: none; display: inline"><ul><li>Language definition</li><li>Language implementation</li><li>Compile time</li><li>Link edit</li><li>Load</li><li>Run time</li></ul></li><li>Some attributes and their binding times for C are shown in the table below:</li><li style="list-style: none; display: inline"><ul><li>Type of a variable identifier -> Compile time</li><li>Value of a variable identifier -> Runt time</li><li>General meaning of + -> Language definition</li><li>Specific meaning of + -> Compile time</li><li>Meaning of literal (constant) 23 -> Language definition</li><li>Internal representation of literal 23 -> Language implementation</li><li>Specific computation performed by an external function -> Link edit</li><li>Value of a "global" data identifier -> Runtime</li></ul></li></ul></li></ul>