Author: Uranus Zhou

Linux kernel学习-进程基本

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-process/ Linux 中进程通过 fork() 被创建时,它差不多是和父进程一样的,它得到父进程的地址空间拷贝,运行和父进程一样的代码,从 fork() 的后面开始执行,父进程和子进程共享代码页,但子进程的 data 页是独立的(包括 stack 和 heap)。 早期的 Linux kernel 并不支持多线程的程序,从 kernel 来看,一个多线程的程序只是一个普通的进程,它的多个执行流应该完全在 user mode 来完成创建、处理、调度等操作,例如使用 POSIX pthread 库。当然这样的实现是无法让人满意的,Linux 为此使用轻量级进程为多线程程序提供更好的支持,两个轻量级进程可以共享资源(例如:地址空间、打开的文件等等),一个比较简单的方法是将为每个线程关联一个轻量级进程,这样每个线程可以被 kernel 单独调度,使用 Linux 轻量级进程的库有:LinuxThreads、NPTL、NGPT 等。Linux kernel 同时也支持线程组(可以理解为轻量级进程组)的概念。 1、进程描述符: 进程描述符由 task_struct 结构来表示,一般来说,每个可以被独立调度的执行上下文都必须有自己的进程描述符,因此尽管轻量级进程共享了很大一部分 kernel 数据结构,它也必须有自己的 task_struct。task_struct 中包含关于一个进程的差不多所有信息,它定义在 include/linux/sched.h 文件中,你会看到这是非常大的结构,其中还包含指向其它结构的指针。访问进程自身的 task_struct 结构,使用宏操作 current。 task_struct 中的 struct mm_struct *mm 即指向进程的地址空间。task_struct 的 state 字段表示进程的运行状态,取值有 TASK_RUNNING(正在运行或正在队列中等待运行,进程如果在用户空间只能为此状态)、TASK_INTERRUPTIBLE(可响应信号)、TASK_UNINTERRUPTIBLE(不响应信号)、TASK_STOPED 等,另外 state 还有特殊的两个值是 EXIT_ZOMBIE(僵尸进程) 和 EXIT_DEAD(进程将被系统移除)。kernel 提供 set_task_state 宏修改进程状态,set_task_state 最终调用 set_mb,set_current_state 用于当前进程的状态。task_struct 的 pid 字段就是咱们喜闻乐见的进程 ID 了。 这是一个典型的 Linux 进程状态机图: POSIX 1003.1c 标准规定一个多线程程序的每个线程都应该有相同的 PID,这样的好处是例如发一个信号给一个 PID,一个线程组里的所有线程都能收到。同一线程组中的线程有相同的线程组号(Thread Group ID),线程组组号放在 task_struct 的 tgid 成员变量中,一般是线程组里的第一个轻量级进程的 PID。特别需要注意 getpid() 系统调用返回的就是 tgid 的值,而不是 pid 值,这样一个多线程程序的所有线程可以共享一个 PID。 对每个进程,kernel 在通过 slab 分配器分配 task_struct 时,通常是实际分配了两个连续的物理页面(8KB),以 thread_union 联合表示,其中包括一个 thread_info 结构(其 task 成员是指向 task_struct 的指针)以及 kernel 模式的进程堆栈。esp CPU 堆栈指针即表示此进程堆栈的栈顶地址,进程从用户模式切换到 kernel 模式时,kernel 堆栈会被清空。为了效率考虑,kernel 会将这两个连续的物理页面的第一个页面按 2^13(也就是 8KB) 对齐,为了避免内存较少时产生问题,kernel 提供配置选项(就是下面的 THREAD_SIZE 了)可以将 thread_info 和堆栈包含在一个页面也就是 4KB 的内存区域里。一般来说,8KB 的堆栈对于内核程序已经够用。 看看 Linux 2.6.34 中 thread_union 的定义: 由于 thread_info 和内核堆栈是合并在连续的页面里的,kernel 就可以从 esp 指针得到 thread_info 结构地址,这是通过 current_thread_info 函数来实现的。 假设 thread_union 是 8KB 大小,也即 2^13,将 esp 的最低 13 位屏蔽掉即可得到 thread_info 的地址,如果是 4KB 的栈大小,屏蔽掉最低 12 位即可(和上面的代码一致),这样通过 current_thread_info()->task 就能得到当前的 task_struct,这就是 current 宏的实现了。 系统中进程的列表保存在 init_task 所在的双向链表中,task_struct 的 tasks 字段就是 list_head,init_task 表示的就是 PID 为 0 的 swapper 进程(或者叫 idle 进程),其 tasks 会依次指向下一个 task_struct,PID 为 1 的进程就是 init 进程,这两个进程都由 kernel 来创建。 而关于可以运行的进程的调度,Linux 2.6.34 和 ULK 上说的已经有很大的不同了。2.6.34 上加上了 struct sched_class 结构体表示不同类型的调度算法类,目前 2.6.34 上实现了三种:Completely Fair Scheduling (CFS) Class(完全公平算法,见 kernel/sched_fair.c)、Real-Time Scheduling Class(实时算法,见 kernel/sched_rt.c)和 idle-task scheduling class(见 kernel/sched_idletask.c),这三个源文件都被 include 在 kernel/sched.c 中进行编译了。CFS Class 使用 sched_entity 结构作为调度实体,其中包含权重、运行时间等信息,比 RT Class 复杂,其中还有专门的红黑树。RT Class 使用 sched_rt_entity 作为调度实体。 每个 task_struct 中都包含了 sched_entity 和 sched_rt_entity 这两个字段,sched_class 中则有 enqueue_task、dequeue_task 等函数指针指向对应调度算法中的实现函数,enqueue_task 将进程加入运行队列,dequeue_task 将进程从队列中移除,由于这段变化较大而且比较复杂,有关这三种调度算法的具体实现以后再来介绍了。 task_struct 的 real_parent 字段指向创建该进程的进程(如果父进程已不存在则为 init 进程),parent 指向当前进程的父进程,children 为该进程子进程列表,sibling 为该进程的兄弟进程列表,group_leader 字段指向该进程的线程组长。与 ULK 不同的是,ULK 中 ptrace_children 为被调试器 trace 的该进程的子进程列表,2.6.34 中 ptraced 字段包含该进程原本的子进程和 ptrace attach 的目标进程,ptrace_list 改为 ptrace_entry。另外 2.6.34 kernel 中已经引入 namespace 的概念,获得进程组 ID 和会话期 ID 的方式也于 ULK 中的有不少区别。 kernel 中进程的 PID 散列表存在 pid_hash 中以加快根据 PID 搜索 task_struct 的速度,pidhash_init 函数初始化此 PID 散列表,由于 2.6.34 中已有 namespace,pid_hashfn 也由原来的一个参数变为两个参数(增加一个 ns 参数表示哪个 namespace)。Linux kernel 也增加了 pid 和 […]

Linux中拷贝目录跳过指定文件的方法

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-copy-directory-ignore-files/ 近日在 Linux 环境中做版本迁移的时候遇到一个问题:需要将一个目录遍历拷贝到另一个目录中,但需要忽略其中的某些文件,由于目录中东西比较多,忽略的项也不好一一指定。普通的 cp 命令并没有排除某个文件或文件夹的参数,比较丑陋点可以 cp -r 拷贝完目录之后再去删除无用的,但如果做批量脚本操作就不爽了,经过实际试验之后暂时找到两个比较好的方法。 1、使用 rsync 进行拷贝: rsync 本来是文件同步备份的工具,相对于普通的 cp 命令,rsync 在控制方面就强多了,而且 rsync 对遍历目录也支持,有 --exclude 参数可以忽略指定的文件或文件夹。 rsync -vaP --exclude=”.*” --exclude=”Makefile” dir1 /home/dir2 如上面演示的就可以排除掉隐藏文件和 Makefile 文件,-a 参数已经包含遍历处理参数 -r。 2、使用 find 加 cpio 进行拷贝: 备注:此方法来自 Advanced Bash-Scripting Guide,需要了解的童鞋自己去参考了。 用过 find 的童鞋都知道,find 对文件的过滤那是非常强大的,配合 cpio 来进行目录的遍历拷贝就可以实现过滤指定的文件或文件夹,当然也可以做到只备份特定的文件或文件夹,你可以用 find 的各种过滤参数达到拷贝哪天的文件,拷贝近期更改的文件等特殊效果,而且 find 支持正则表达式,这种方式想比第一种使用 rsync 跳过文件的方式更加灵活,因此非常推荐使用此方式进行目录拷贝。 cd dir1 find . -regextype posix-egrep -mindepth 1 ! -regex ‘./(dev|tmp)($|/.*)’ ! -name Makefile -a ! -name .svn | cpio -admvp /home/dir2 小解释下: find 的 -regextype 参数指定正则表达式类型,posix-egrep 为 egrep 用的扩展正则表达式,-mindepth 使 find 的输出中不包括目录本身,-regex 参数指定过滤的文件的正则表达式,-regex 前面的感叹号表示跳过,’./(dev|tmp)($|/.*)’ 这个正则表达式即表示跳过目录中的第一层 dev 和 tmp 目录以及下面所有的文件和文件夹,最后两个 -name 指定要跳过文件名为 Makefile 和 .svn 的文件,这样在备份版本库的时候非常有用。 cpio 命令将 find 的输出文件列表依次拷贝到 /home/dir2 目标目录中,-a 表示不更新文件的访问时间,-d 指定自动创建目录,-m 指定保留文件的修改时间,-p 指定 cpio 工作在 Copy-pass 模式,这是专门用来拷贝目录树的一种模式。 PS:如果有更加简单的方法,欢迎提出指正哦~~~ ^_^

Linux kernel学习-内存寻址

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-memory-addressing/ 近日在看 Understanding the Linux kernel(慢慢啃E文原版,以下简称 ULK),这本书虽然已经是第三版了,但它基于的 Linux kernel 版本却不是很新,现在 Linux kernel 都已经出到 3.4 版本了,这本书还是基于 2.6.11 的 kernel,不得不说 Linux kernel 的更迭速度太快了。 下面准备以我正在用的 2.6.34 版本的 kernel 为基础进行学习,这本书中不对应的地方我会尽量找到新 kernel 中的实现,并尽量自己做个了解,日后的相同日志如无意外也基于 2.6.34 版本 Linux kernel。 首先已完成第一章:Introduction(这一章没有 Linux kernel 代码),来到第二章 Memory Addressing,开始是介绍逻辑地址、线性地址、物理地址的对应关系,虽然之前用汇编写过 Linux 的 bootloader,用到过实模式和保护模式,但对 GDT、LDT 的概念并没有深入了解过。这一章开篇就介绍了 Intel 80X86 硬件上内存分段的实现,包括段选择子,段寄存器,段描述符。 1、段式内存管理: 每个内存段由 8 个字节的段描述符来表示段的特征。段描述符被存储在 GDT 或者 LDT 中。内存中 GDT 的地址和大小包含在 gdtr 控制寄存器中,LDT 的地址和大小包含在 ldtr 控制寄存器中。段寄存器的高 13 位为段描述符在 GDT 或者 LDT 中的索引,GDT 或者 LDT 结构中包含基地址、段长度等信息。通过检查指令地址和段长度并确定没有越界以及权限是否正确之后,由于 线性地址 = 段基指 + 偏移地址,GDT 或者 LDT 中的基地址加上指令中的偏移量就可以得到需要的线性地址。 备注:由于每个进程都可以有 LDT,而 GDT 只有一个,为满足需求 Intel 的做法是将 LDT 嵌套在 GDT 表中。 Linux kernel 中的内存分段: Linux中所有进程使用相同的段寄存器值,因此它们的线性地址集也是相同的,不管在用户模式还是内核模式,都可以使用相同的逻辑地址,32位 kernel下为 4G 的地址空间。 ULK 中介绍的 user code、user data、kernel code、kernel data 这四个段对应的段选择子的宏为:__USER_CS、__USER_DS、__KERNEL_CS、__KERNEL_DS,2.6.11 中这4个宏定义在 include/asm-i386/segment.h 头文件中,2.6.34 中已经挪到 arch/x86/include/asm/segment.h 里,因为 2.6.34 中 i386 和 x86_64 的代码已经尽可能的合并到 x86 目录中,而不像老版本的代码那样弄成两个目录。定义如下: 下面是 Linux kernel GDT 的实现: 由于 kernel 中每个内核需要有一个 GDT,因此就有一个 GDT table,ULK 中说的是存在 cpu_gdt_table 中,GDT 的地址和大小存在 cpu_gdt_descr 中,2.6.11 kernel 里都是放在 arch/i386/kernel/head.S,使用的地方: 到了 2.6.34 中已经改为: 可以看到 2.6.34 中去掉了原来的 cpu_gdt_table 变量(详见 kernel commit bf50467204b435421d8de33ad080fa46c6f3d50b),新增了一个 gdt_page 结构存放 GDT table,而且提供 get_cpu_gdt_table 函数取得某个 CPU 的 GDT。cpu_gdt_descr 也已去掉,新增了 desc_ptr 结构存放每个 CPU 的 GDT 信息,cpu_gdt_descr 也改为 early_gdt_descr。 看下简单看下新的切换 GDT 的实现: load_gdt 最终调用 lgdt 汇编指令。 2、页式内存管理: Intel 从 80386 开始支持页式内存管理,页单元将线性地址翻译为物理地址。当 CR0 控制寄存器中的 PG 位置为 1 时,启动分页管理功能,为 0 时,禁止分页管理功能,并且把线性地址作物理地址使用。 32 位线性地址的高 10 位为页表目录的下标(指向页表),中间 10 位为页表的下标(指向页面),低 12 位为该地址在页面(通常大小为 4 KB)中的偏移量,这样的二层寻址设计主要为了减少页表本身所占用的内存,由于页表目录和页表都为 10 位,因此都最多包含 1024 个项。正在使用的页表目录的物理地址存在 cr3 控制寄存器中。 在 32 位大小的页表目录(页表)的结构中,其高 20 位为页表(页面)基地址的高 20 位,其它的 flag 中包含一个 Present 标志,如果该值为 1,表示指向的页面或者页表在内存中,如果为 0,页单元会将线性地址存在 cr2 控制寄存器中,并产生异常号 14: page fault。 页表目录结构中另外有一个 Page Size 标志(页表结构没有此标志),如果设为 1,则页面大小可以为 2MB 或者 4MB,这样可以跳过页表转换,将 cr4 寄存器的 PSE 标志启用即可启用大页面支持,此时 32 位线程地址由高 10 位页表目录下标和低 22 位的偏移量。 为满足寻址超过 4GB 的需求,Intel 从 Pentium Pro 处理器开始,将处理器的地址引脚数量由原来的 32 个提升为 36 个,处理器的寻址空间也从 4GB 增到 64GB,并增加 PAE 页面机制(设置 cr4 寄存器的 PAE 标志启用):64G内存可以划分为 2^24 个页面,页表中的基地址由 20 位增为 24 位,页表结构的大小由 32 位增为 64 位,增加 PDDT 表从而使用三层寻址设计来解释 32 位的线性地址等等。PAE 机制稍显复杂,而且由于仍然使用 32 位线性地址,因此对于应用程序来说,仍然无法使用超过 4GB 的地址空间,64GB 只是对于 kernel 而言的。 顺带说下不同的 64 位架构下的页面寻址级别,见下表,可以看到常用的 x86_64 架构只用了 48 位的线性地址空间,但也达到了 256TB 咯 ^_^ 3、硬件cache: 由于现在 CPU 速度太快,频率已经动辄多少 GHz,而相对的 DRAM 内存频率就慢很多,而且 DRAM 由于设计上电容存在不可避免的漏电原因,DRAM 的数据只能保持很短的时间,必须隔一段时间就刷新一次,不刷新的话会造成存储的信息丢失;而 […]

STL中const_iterator、reverse_iterator转换为iterator

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/stl-const-reverse-iterator/ STL中的容器类(Container)一般提供了4个迭代器:iterator、const_iterator、reverse_iterator、const_reverse_iterator,对于 container<T> 而言,其中 const_iterator 相当于 const T *,const_iterator 指向的元素不能做修改操作。 STL 容器的 begin() 和 end() 默认都提供了 iterator 和 const_iterator 的迭代器,相应的 rbegin() 和 rend() 则也分别提供了 reverse_iterator 和 const_reverse_iterator 的迭代器用于从容器的尾端反向遍历。 最近写的代码中刚好要用到 const_iterator 迭代器,发现由于 STL 提供的一些容器操作函数像 insert、erase 的参数必须为 iterator,这时就不能用 const_iterator 做参数(不能直接转换),以下为个人经验结果。 1、const_iterator 转换为 iterator: iterator 可以隐式转换为 const_iterator,但反过来就不行,就算祭出强制类型转换的杀招应该也会编译报错,这是可以用折中的办法解决: vector<sss> v_test; vector<sss>::iterator i_test; vector<sss>::const_iterator c_test; … c_test = …. … i_test = v_test.begin(); advance(i_test, distance<vector<sss>::const_iterator>(i_test, c_test)); i_test 指向第一个元素,先通过 distance 得到 c_test 和 i_test 的偏移量,然后用 advance 将 i_test 往后移对应的偏移量即可。注意 distance 的模板类型必须为 const_iterator 类型,否则按照 STL 的默认类型推导,distance 中的 i_test 和 c_test 类型不同还是会出现编译报错。 当然如果你想偷懒简单点,也可以这样写,道理是一样的,这时类型推导就显得很好用了: i_test = v_test.begin() + (c_test - v_test.begin()); 2、reverse_iterator 转换为 iterator: 对于 reverse_iterator 可以调用 base() 得到 “与之对应” 的 iterator(与上面的环境一样): vector<sss> v_test; vector<sss>::iterator i_test; vector<sss>::reverse_iterator r_test; … r_test = …. … i_test = r_test.base(); 但需要注意的是由于 STL 的 begin() 和 end()、rbegin() 和 rend() 是两个半闭合的区间,end() 并不是最后一个元素,rend() 也不是第一个元素,因此 end() 和 rbegin()、rend() 和 begin() 之间都是差了一个元素的。我们来看看 STL 标准上 base() 的说明: The base iterator is an iterator of the same type as the one used to construct the reverse_iterator, but pointing to the element next to the one the reverse_iterator is currently pointing to (a reverse_iterator has always an offset of -1 with regards to its base iterator). 也就是 reverse_iterator 的 base() 返回的元素都是 reverse_iterator 所指向元素的下一个元素,这也是前面 “与之对应” 加引号的原因。因此在使用时要特别注意,例如如果要删除 reverse_iterator 指向的元素就需要这样(因为 erase 必须要 iterator 类型的参数): v_test.erase((++r_test).base()); 3、关于随机访问容器: STL 中随机访问容器是一个迭代器类型为随机访问迭代器的可逆容器,它提供常量缓冲时间来访问随机元素。而可逆容器是一个有双向迭代器的前向容器,它可以向后向后迭代通过容器。 常用到的顺序容器是将单一类型的元素聚集起来,然后根据位置来存储和访问这些元素。顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。 平时经常用的 string、vector、deque 之类就是典型的随机访问容器,map、set、list 之类的就不是。 因此需要注意 STL 中的 sort 等函数的参数必须是随机访问迭代器,所以对 map、set、list 等容器是无效的。 以上为个人见解,有任何问题欢迎指正交流咯 ^_^。

最新可用Facebook hosts(2012.5.23)

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/fb-hosts-20120523/ 今天加班改完有问题的 perl 脚本之后,发现 Gmail 里有 Facebook 的邮件,打开链接发现之前一段时间都一直好用的 Facebook hosts 今天也挂了,又找到了最新的 hosts,已经经过测试,请通过 https 打开,Firefox 等浏览器中可以安装 HTTPS-Everywhere 等插件实现访问 Facebook 时自动用 https 方式打开。 为防止被 GFW 扫描并再次墙掉,就不直接贴出 hosts 内容了,请猛击下面的福利网址下载,替换你的 hosts 文件的对应项即可,小心扩散~~~ ^_^。 http://miseal.googlecode.com/files/fb-hosts

忧伤的父爱-法国动画电影《魔术师》

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/lillusionniste-film/ 今天看到的这部法国动画电影《魔术师》,这只是一部手绘风格与CG结合的二维动画,没有现在3D动画的华丽,甚至全片也没有几句台词,所以虽然是法语的片子,你也能不借助任何字幕看懂,但这种手绘却能将片子的淡雅忧伤表现的更好,片子中体现的对旧日的缅怀,欧洲的手绘风光都值得留恋。 主人公 Tatischeff(简称 Tati) 是一个老牌魔术师,表演的都是一些非常老套的魔术,像帽子里变兔子,手绢里藏花,变酒杯之类,无奈在电视等新媒体冲击下,人们更爱电视明星、摇滚歌星之类,主人公为了生计只能到处奔波。上面的海报里头两个片段就是魔术师在不同表演场合的场景,都几乎没有观众,在电影刚开始的片段里,最深刻的就是魔术师在幕后不断地等一个摇滚乐团反复疯狂表演之后才能登台,但等到他登台时,原本台下狂热的摇滚观众已经无影无踪转到场外继续追星,而台下只剩两三人影。 魔术师辗转到苏格兰一个小乡村酒馆表演时,受到了相对不错的欢迎。他也是在这遇到女主人公 - 酒馆的小女孩招待员 Alice,她以为魔术师的表演就是魔法,甚至想让魔术师用魔法修已经坏了的玩具。魔术师为 Alice 买了一双红色的新鞋,但是以变魔术的方式给他的,女孩在 Tati 离开时也偷偷跟了出来,并开始跟着 Tati 辗转来到爱丁堡。 在这个小乡村的一段里也有我最中意的一个场景之一,这种画一般的风格灰常吸引人滴: 来到大城市之后,女孩开始对城市的繁华世界向往,魔术师却要为了生计想尽办法赚钱,更为了以魔术的方式将女孩想要的新大衣、连衣裙、高跟鞋等 “变” 出来,他沦落到在化妆品内衣橱窗里通过表演魔术吸引顾客,在洗车公司里做夜间看守,Alice 则每天在他们租住的旅馆里,似乎仍在认为 Tati 给他的都是魔术赐予的。这是他们在一起时比较温馨的片段: 同一间旅馆里还有一个小丑,一个拿着道具的腹语表演者,3个杂技演员,都是同样的落魄潦倒,小丑甚至在 Alice 送汤给他之前差点自杀,腹语表演者最后则是以乞讨和醉酒度日。 Tati 觉得在橱窗里的魔术表演并不适合自己,而在这时,他无意发现 Alice 已经与旁边的一个小伙陷入热恋中,他明白这已经是需要自己离开的时候,他在山上将一直跟随他的兔子(一只从头到尾一直在咬人的兔子啊)放生,然后拿上行李乘上火车离开。而 Alice 在回到旅馆时只看到 Tati 留下的 “Magicians donot exist” 字条,Alice 看着字条,拿上行李箱关上灯,与恋人一起离开。而片尾的火车上,魔术师则在给邻座的小女孩变出笔之后,拿出一张婴儿照片看了看,影片就此结束。 幕后故事(这部分就完全摘录自时光网咯): 这是一部向法国最伟大的喜剧大师雅克·塔蒂(Jacques Tatischeff)致敬的片子,本片就拍自雅克·塔蒂创作于1956年但未曾拍摄的剧本。影片中主角的形象就来自塔蒂本人,你会发现和主人公的名字是一样的。原剧本被认为是写给他当时才十几岁的私生女海尔格(Helga Marie-Jeanne Schiel)的一封特殊的信。这个剧本一直没有被搬上荧幕,是因为塔蒂本人认为片子蕴含太多私人情绪,隐晦而且让人感到不愉快。塔蒂与妻子米谢琳·温特的女儿索菲(Sophie Tatischeff)认为除了他的父亲塔蒂先生没人可以更好的演绎出这个片子。因此 2000 年,索菲将这个剧本交给法国动画导演 Chomet 时,索菲提出一个让 Chomet 制作一个动画中的 Tati,她不希望任何人来演绎他的父亲。 片中有一个片刻就是 Tati 在街头发现在热恋中的 Alice,他就想躲起来,结果退出一堆衣服误进了一家电影院,电影院中放映的片刻就正是 雅克·塔蒂 本人的表演。 至于本片中为什么 Tati 会对素不相识的 Alice 这么溺宠,恐怕潜意识里,他把这个小姑娘当成是自己的女儿,那个他没有尽到父亲职责的女儿,片尾魔术师看到的照片应该就是他的女儿。 而对于 Alice,或许有一些网友认为她完全是一个爱慕虚荣的小“叛徒”或者小“白眼狼”,但在她眼里,Tati 似乎就是如魔术师般的父亲的存在。Tati 对 Alice 体现出来也只是最单纯的父爱,只不过这父爱在片尾 Tati 独自离开之后体现的格外忧伤。 有关 雅克·塔蒂 本人,请参考百科和 Wiki: http://en.wikipedia.org/wiki/Jacques_Tati 另外,这里是电影《魔术师》的官方网站,上面有 Tati 和 Alice 的手绘稿之类的好玩东西哦: http://www.lillusionniste-lefilm.com/ 这是我摘下的两张 Tati 和 Alice 的原始手绘图 ^_^:

未能看到整个森林-编程学习中所犯的错误

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/fail-to-see-the-big-picture/ 备注:本文根据 pongba 大哥的这篇E文文章翻译并结合自己体会总结而来,pongba 的E文原文请猛击这里: http://blog.csdn.net/pongba/article/details/2143245 人类的一个普遍的天性是容易被自己感兴趣的东西所吸引。 不论是本文要说的编程学习还是日常事务都是这样,包括美女之类(哈哈),这似乎是一个难以打破的公理。人类自文明开始以来就对非凡的自然现象下的本质非常好奇,我们渴望理解,渴望知道原因。人类天性就是用来解决问题的,我们热衷于解决问题,热衷于发现问题的本质。不过悲哀的是,我们也是问题的主要创造者。 具体说到编程学习这一块,pongba 的原文中用 interesting(感兴趣) 和 mundane(平凡普通的)这两个词来区分编程学习中的两类知识。 我们最开始学习编程时用到的最经典的 hello world 就是 interesting 的一种,看到自己敲的一段字符能让计算机打印出来 hello world 确实能激发我们的兴趣。这就是所谓的 Under the Hood,这是一个在英文技术文章里经常见到的词,原意是钻进魔术师的帐篷,屏住呼吸,瞪大眼睛,把那些奇妙的魔法看个通透,让自己的理解和技艺获得巨幅的提升,在IT界里就是深入理解的意思。 你在学会设计程序和了解程序能正确运行的原因之后,接下来你会做什么?你会继续写程序,发现你所用的编程语言的越来越多的细节。你会越来越了解你用的编程语言,能知道该语言能方便的做些什么,哪些不能很方便的实现。接下来各种语言的窍门就开始进入你的脑海,注意这些窍门最吸引人的地方在于能让你做到本来做不到的事情,能让你荷尔蒙迸发,让你很 happy。 从程序设计语言的角度来看,我们热衷于解决的问题往往是我们自己创造的。例如,最近有一种争论关于设计模式是语言中缺失的一种特性。首先我们创造一门程序设计语言,由于一些设计时没有预料到的缺点,在使用中发现了,我们使用包括设计模式在内的一些语言窍门来解决它。但随着时间推移,我们就会发现这些模式不但没有价值,反而变成一种沉重的负担,这时通常会把这些作为新特性加入到语言中。 通常我们在解决以前的语言造成的问题过程中,我们通常又会造成新的问题。例如,现在总有 DSL 和 GPL 的争论,注意这里的 GPL 不是 GPL 开放授权协议,而是 Gerneral-purpose Language(通用语言),DSL 是 Domain-specific Language(领域专用语言)。DSL在很多人心目中是“非程序员的编程语言”,其首要目的是使程序尽可能接近领域中遇到的问题,消除不必要的间接性和复杂性,而其最终受众一般不是普通的程序员。一方面,将领域专用的一些特性加入到语言中,对那些需要对特定领域编程的人来说会非常便利;而另一方面,它会限制语言的使用范围。相对于DSL,GPL的最大优势在于可以为理论上无限的应用领域服务。GPL最大的妥协在于当面对领域相关的问题时,它只相当于一个 second-class language,这是为什么微软要搞一个CLR(通用语言运行时,Common Language Runtime)运行环境,也是为什么 Martin Fowler 要倡导面向语言编程LOP(Language-oriented Programming)了。 因此,在这总结一下,我们创造了各种语言抽象概念以使语言更加易用,但周尔反复的是我们总是在解决一个问题时创造时另一个新的问题。由于我们的程序设计语言都存在着不可避免的缺陷,这样语言窍门之类的东西就会登堂入室,并偷走我们的关注点(原文如此,嘿嘿),这也是为什么市面上有如此多的编程语言技巧书,语言陷阱介绍之类的,而且销量似乎都甚好。你可以看看任何C++编程学习的推荐书列表中,都不乏这样的例子。 然而到底是什么导致我们在编程学习时如在一堆树木里迷失,而导致没有看到整个森林?为了什么我们要学习这些奇淫技巧呢?实际上我们不是真正的需要,但我们内心里趋向于学习这些技巧,因为像文章开头说的,我们天生就是问题解决者,我们喜欢解决问题,即使这些问题是我们自己创造的。但这些奇淫技巧实际上只要在真正需要时按需学习即可,我们被这些东西吸引的原因在于: 1、我们喜欢新事物,如果什么东西是新事物就很有趣; 2、我们喜欢赶时髦(jump on the bandwagon)。 这就引出人类的第二个普遍的天性:赶时髦,如果所有人都做一件事,那我无论如何也得做。 不光是一些公司或者团体使用这个策略引诱我们去赶时髦,我们还热衷于创造自己的潮流。每当有新的语言或者技巧出来的时候,我们总是欢呼雀跃,总是被这些新带来的特性的光晕笼罩,而忘了它实际包含的问题,我们总是把它当做是万能灵药,开始万般饥渴地学习它。程序员是一种聪明的动物(原谅我如此直白,哈哈),不过有时显得过于聪明。他们总是渴望于新的事物(在任何编程论坛上找一圈就能得到验证,你会发现成千上万的程序语言技术细节的问题,学习这些东西是永无止境的,但程序员就是如此地欲罢不能),就像野兽永远不能停止对于食物的饥渴一样。 下面说说程序员普遍不爱的平凡的东西,什么是大多数程序员不喜欢的东西? 大多数程序员不爱的东西包括:编程原则,从小的编码规则(例如:永远给变量起一个有意义的名字)到大的项目设计原则(例如:在写代码之前先写测试文档)都有,还有文档的编制之类的,这些都是比较乏味的,不会显得古怪有味道,显得没有挑战性,显得没有那么酷。我们无法向外界展示遵从一些愚蠢的规则是多么聪明的一件事。我们尤其钟爱的是写一些疯狂的技巧代码或使用一些耀眼的模式以使别人都不知道我们在做什么(或者每个人都知道我们在做什么)。 接下来是人类的第三个天性:自私的偏见,我们热爱我们所做的,或者我们是谁,我们讨厌与之对抗或相反的东西。 不管你是否愿意承认这点,我们都有过这个体验。当我们对某些语言或平台非常熟悉时,我们就容易产生自私的偏见,它会影响我们想学习和不想学习的东西。你应该可以在一些论坛上感受到关于编程语言的争论是如此普遍。我们总是被蒙蔽了双眼,没有看到自己所用的语言或平台上的缺点和其它语言上的优点。我们限制了学习新的语言的能力,从某种意义上来说,我们限制了自己的潜能。 翻译的总结: 一方面,大多数时候我们学的东西有点太多了。我们像飞蛾扑火般被新事物吸引,我们经常是在学习周围的人在学习的,或者是别人告诉我们要学习的。但如果我们抓牢了一些本质的知识,其它的东西就完全可以按需学习。别再沉迷于技术技巧,除非它是必备的或者你马上就要用到的。因为要学习的技术技巧总是无穷无尽的,你应该将你的时间花在更有用的东西上(学些本质知识,学习编程思想,或者学习另一门编程语言)。 但另一方面,我们学的东西又太少了。我们总是忽视看起来乏味但又非常重要的东西,例如以下观点(可能很多人都有过): 测试? --- 就像做爱前戴套一样不爽; 重构? --- 为什么要做这种不能带来新功能也不酷的东西; 防御式编程? --- 对不起,我知道自己在写什么; API设计? --- 拜托,我在写这么华丽的代码的时候去考虑别人怎么使用我的代码也太TMD难了; 新语言? --- 你是说我现在用的这个不够好?没看到我能随意折腾这门语言来实现我想做的? 这是我首次完整翻译别人写的E文文章,也加了一些自己的观点,希望读者和我都能在未来看到编程学习中的整个森林 ^_^,有任何错误欢迎指正咯。 参考: 1、DSL领域专用语言: http://en.wikipedia.org/wiki/Domain-specific_programming_language 2、LOP面向语言编程: http://en.wikipedia.org/wiki/Language-oriented_programming

page cache诊断控制工具 vmtouch 源代码分析

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/vmtouch-code-analysis/ vmtouch 是一个 portable 的 page cache 诊断和控制工具,可以查看文件或者设备中有多少在 page cache 中,知道之后对这些在 page cache 中的内存引用可以避免 page fault,支持将文件的内容从 page cache 逐出,同时还可以将文件手工 touch 到 page cache 中,支持 lock 文件部分区域到 memory 中防止被交换出去从而提高。 vmtouch 可以在 Linux、BSD 等系统上使用,在这下载编译: http://hoytech.com/vmtouch/ 今天简单看了下 vmtouch 的代码,发现还比较简单,自己写个类似的程序验证之后,将代码分析结果写下。vmtouch 的代码比较少,我只贴出最关键的一个函数 vmtouch_file(关键部分已经高亮显示),这个函数做 分析 page cache 使用、touch、lock 的操作,其它部分只是加了读了目录的遍历处理之类的。 稍微有点基础就可以看明白了,先 mmap 映射文件到当前进程,按 page size 对齐之后,调用 mincore 函数就可以得到文件中每一个 page 是否在 page cache 中,结果保存在 mincore_array 数组中,该数据中每个字节的第一位即表示是否在 page cache 中。 将文件内容逐出(指定 o_evict)出 page cache 是通过 posix_fadvise 函数调用 fadvise 系统调用来实现的(BSD通过 msync 实现,这个在 Linux 上没有效果)。fadvise 系统调用可以告诉 kernel 要操作的文件在接下来要干什么,kernel 可以提前做一些操作而提高性能,Linux kernel 里实现了以下几种控制方式: POSIX_FADV_NORMAL - 正常操作,对文件使用底层设备的默认 readahead 值; POSIX_FADV_SEQUENTIAL - 顺序I/O,对文件使用两倍的 readahead 值; POSIX_FADV_RANDOM - 随机I/O,禁用文件上的 readahead; POSIX_FADV_NOREUSE - 只使用一次 POSIX_FADV_WILLNEED - 很快需要使用,对文件使用非阻塞读到 page cache POSIX_FADV_DONTNEED - 不再需要使用文件,从 page cache 中逐出 posix_fadvise 加 POSIX_FADV_DONTNEED 参数就可以将文件从 page cache 中逐出,需要注意的是如果需要确保文件从 page cache 中逐出,还需要在调用 fadvise 之前用 fsync/fdatasync/sync_file_range 之类的函数将 dirty page 清理。 下面是我在 Linux 下用 posix_fadvise 的一个测试程序测试的结果: 从 free 命令的结果可以很明显的看到,dd 之后基本文件都在 page cache 中,fadvise 之后从 page cache 中正确逐出。 接着是 vmtouch 中的 touch 操作(指定 o_touch)就更简单了,对 mmap 到的地址直接遍历引用,不在 page cache 的内容会自动产生 page fault 到 page cache 中。 lock 内存(指定 o_lock)也则直接使用 mlock 函数来实现,mlock 对于对安全性和实时性有很高要求的程序非常有用,可以保证指定的文件区域在内存中,不被 swap 出去。 以上为个人分析结果,有任何问题欢迎指正咯 ^_^

RAID5布局方式介绍及对性能的影响

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/raid5-layout-performance 本文主要介绍 RAID5 内部的数据布局方式,不对 RAID5 的基本原理做过多介绍。磁盘阵列中的 RAID5 类型是通过 XOR 奇偶校验算法来实现阵列中的数据冗余的,可以允许在坏一块磁盘的情况下继续使用,虽然在正常使用过程中写的额外开销比读要多很多,但对于一般企业来说还是很不错的选择。 布局方式: RAID5的校验和信息是平均分布到 RAID5 的每个硬盘上的,实际使用中有以下4种分布方式: 1、左对称(Left-symmetric); 2、左不对称(Left-asymmetric); 3、右对称(Right-symmetric); 4、右不对称(Right-asymmetric) 说这几个名词肯定不好理解,下面是一个4个硬盘组成的 RAID5 内部数据和校验和(parity)在不同布局方式下的分布图(每一行表示是一个条带 stripe): RAID5 几种布局方式中的“左”、“右”表示校验和(也就是图中的红色的P)如何分布,如图中,“左”的校验和都是从最后一个磁盘开始,依次前推,“右”的校验和从第一个磁盘开始,依次后推。 “对称”、“不对称”标识数据(也就是图中的 D0、D1、D2…)的分布方式,“不对称”方式都是直接将数据按照磁盘的数据直接排下来的(图中的左不对称和右不对称的前后数据明显不对称),而“对称”方式则将每个 stripe 中的第一个数据放在校验和的后面,然后往后排列,需要时再绕回第一个磁盘,这样“对称”的数据排列方式就能始终按照磁盘的顺序依次下来。 性能影响: 在实际使用测试中,校验和的左右布局方式对 RAID5 性能没有太大影响。由于实际读写 RAID5 时命令时均匀发到每个磁盘上的,从图中也可以推断出“对称”方式在处理大块值的连续 I/O 读时有更好的性能,实际测试中对随机 I/O 性能也有提升。 需要注意的是上面所说的性能影响相对于 RAID5 中的磁盘个数以及 stripe 条带大小参数来说是比较细微的。 其它说明: 现在很多的RAID卡中都使用了 左不对称 这种 RAID5 布局方式,另外 Linux MD RAID、Windows动态磁盘(LDM)、Veritas Volume Manager(VxVM)等实现中都默认使用了 左不对称 方式。了解 RAID5 的布局方式对 RAID 的性能调试之类有一定帮助哦,HOHO。 除了 Linux MD RAID,其它 RAID5 实现中的布局方式似乎没见到可以让用户选择,Linux MD RAID 可以在使用 mdadm 命令创建时指定 --layout 参数指定布局方式,详细请参考 man mdadm。 如果上面还是看不明白,可以参考这些E文的说明,特别第一篇画的很详细: http://www.accs.com/p_and_p/RAID/LinuxRAID.html http://www.z-a-recovery.com/art-raid5-variations.htm http://sfdoccentral.symantec.com/sf/5.0MP3/solaris/html/vxvm_admin/ch01s04s09s03.htm

刚迷上screen就不得不马上移情tmux

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/terminal-screen-and-tmux 一个星期之前调试代码时用上 GNU screen,这个多终端模拟器,对我而言很大程度上摆脱了调试时必须开N个 SecureCRT 远程登录标签的郁闷,而且很多 Linux 发行版上都已经自带 screen 包,直接安装即可,也可以在这下载自己编译: http://www.gnu.org/software/screen/ screen 的快捷键操作非常简单(文本模式下的 Linux 必须要N多快捷键的说),安装之后,在一个终端里运行 screen 命令就可以启动,可以在一个终端里再创建新的 shell,使用快捷键在不同的 shell 之间切换,使用 screen 的 detach shell 可实现更好的后台任务处理,用起来还是比较 happy,无奈 GNU screen 也有一些缺陷: 1、不支持在一个 shell window 里再拆分子窗口,也就是像 vim 的多窗口编辑效果; 2、对 xterm 终端支持一般; 3、不能自己根据启动 screen 时的窗口大小调节新 shell 的大小,这个小郁闷; …… 今天看到一哥们分享的 tmux 终端复用工具,立马感觉我要不淡定的移情了,在此下载: http://sourceforge.net/projects/tmux/ tmux 对比 screen 的优点:单个 shell window 拆分很强大,移动切换 shell pane 很强大,完美支持 xterm 终端和集成原 shell 大小,BSD license,命名 shell window,OOO,受不了先上图: 图中是我实际使用的一个环境,pane 0 查看 man 帮助,pane 1 修改代码,pane 2 命令操作,pane 3 监控系统。 tmux 的快捷键和 screen 的有点类似,只是初始快捷键改为了 Ctrl+B: Ctrl-B-C :创建新窗口; Ctrl-B-D :detach窗口,和 screen 的类似; Ctrl-B-”  :将当前窗口拆分为上下两行; Ctrl-B-%:将当前窗口拆分为左右两列; Ctrl-B-上下左右:切换 shell pane …… 具体请看 man tmux,果断强烈推荐了,tmux现在除了不是在每个 Linux 发行版中默认就附带这点似乎没有什么相比 screen 的缺点了(不过我还是下到了编译好的 RHEL6 上的 RPM 包,哈哈)。 tmux得在以后使用时继续研究咯,screen暂时抛弃,玩的开心~~~ ^_^