Month: Monday June 25th, 2012

2012端午之大明山

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/dragon-boat-festival-2012-damingshan/ 2012端午在浙江临安大明山的小折腾行,大明山有被称为 “小黄山”,由于不是自驾游,在深山里住的农家乐,和朋友一起辗转腾挪倒车来回的,小感到木有车的不便,不过在大明山玩了一小通之后还是深感值得。去时路线:苏州 - 临安 - 昌化 - 大明山,回时班车太慢没赶上班车,改成:大明山 - 昌化 - 临安 - 杭州 - 苏州,OMGM~~~。 PPPPS: 由于深深感觉到眼睛实际看到的效果是任何镜头记录下的都无法比拟的,直接贴几张中意的图不码字咯,完整相册请猛击下面,都是手机直接拍的,也没在意效果 ^_^: http://photo.56.com/album/?do=Plist&did=96759235 住的网上订好的农家乐外,就在大明山下~~: 景区内爬上山的路: 中间明显的 “分明石”: 接近吊桥: 已经开始云雾缭绕准备升仙了 ^_^: 高处看到的树与泉: 雾气下的 “龙门飞渡”: 几十米高 “龙门飞渡” 上的效果,好晃~~~: 走过了~~~: 前面是整个泉水的发源处 “千亩草甸”,可惜是沼泽地,正在开发中,不能过去看下端倪: “龙门飞渡” 外云雾中的群山: 远处的 “悬空栈道”: 开始走 “悬空栈道”,这时雾气也终于消散了点: 雨停之后的效果灰常好 : 好吧,这简直就是迎客松哦 ~~~~: 注意此石头山的右边形状,嘻嘻: 隐约中的 “悬空栈道”: 继续: 再来: 传说中的怪石头,哈哈: 不知道中间为何这么亮(手机的效果不明显): OOO: 画面中抓到的飞鸟,好 HAPPY: 之前走的路又云里雾里了: 继续: 下山前: 旁边的大明湖和紫霞峰由于时间不够就木有去了,不过也很知足咯。上山前一天其实都在下雨,开始比较担忧,结果第二天雨停并且效果比往常更好,哈哈,人品爆棚哇,待空闲时再出去走动咯 ^_^ ~~~

Windows Server 8 ReFS 测试初探

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/windows-server-8-refs-basic-test/ 微软最新的 Windows Server 8(现在已改名为 Windows Server 2012,坑爹的命名,汗下)中支持了微软引入的新文件系统 ReFS 用于替换存在了 N 年了的 NTFS 文件系统。大家可能依稀还记得当前 Longhorn(其实就是后来的 Vista 操作系统的开发代号) 操作系统发布时传闻要引入的新文件系统 WinFS 被阉割的消息,到了 2012 年终于推出了新文件系统 ReFS。 ReFS 全名为 Resilient File System(弹性文件系统),按照微软官方的说明,ReFS 相对 NTFS 主要的改进有: 1、改进磁盘上的数据结构,ReFS 也跟着主流文件系统的脚步使用 B+ 树来存储元数据和实际数据了,文件大小、卷大小等的限制也还是 64 位,元数据和实际数据被以类似关系数据库形式的表组织起来,文件名和路径长度的限制也扩大为 32KB 的 Unicode 字符串; 2、元数据增加校验和,增强数据的验证和自动更正处理,支持 COW 实现可靠磁盘数据更新; 3、扩大卷大小、文件大小、文件夹大小; 4、数据条带提高性能,并支持错误容忍; 5、可以在不同的计算机间共享存储池以增强容错性和负载平衡; 6、也是非常重要的,对 NTFS API 及使用方式上的很大程度上的兼容,包括 ACL、符号链接、挂载点、oplock 等,但也有一些 NTFS 的特性例如 文件压缩、硬链接、用户磁盘配额 等没有被支持。 有关 ReFS 的详细原理及说明请参考这个 MSDN 的 blog: http://blogs.msdn.com/b/b8/archive/2012/01/16/building-the-next-generation-file-system-for-windows-refs.aspx 需要注意的是 Windows 7 及之前的系统都不支持 ReFS,而且 Windows 8 似乎也只有服务器版本才支持。借着前几天下载安装的 Windows Server 8 测试版本系统笔者就先简单测试下 ReFS 相对 NTFS 的性能,由于我这实际应用时以顺序读写为主,先做顺序读写性能对比。 测试环境: Intel S5500BC 服务器主板; Intel Xeon 5506 CPU * 1; Kingston DDR3 1066 4G 服务器内存 * 1; Adaptec RAID 51645 PCIe SAS RAID卡; WD WD10EVDS 1TB SATA 监控硬盘 * 16; Windows Server 8 Beta Datacenter Build 8250 64位中文版; Iometer 1.1.0-rc1 Windows 64位版本 16 个 1TB 的 SATA 盘建 RAID0,RAID0 的块值为 256KB,并启用 read 和 write cache。然后在 Windows Server 8 中的磁盘阵列设备上分别建 2TB 大小的简单卷,格式化为 NTFS 和 ReFS,分别测试其连续读写性能。 在磁盘管理里新建分区格式化时就可以看到 ReFS 的选项: 测试结果: 测试项目 ReFS(MB/s) NTFS(MB/s) 64KB 连续写 192.85 225.02 64KB 连续读 230.79 227.82 256KB 连续写 388.94 417.79 256KB 连续读 495.48 548.49 1MB 连续写 690.77 738.42 1MB 连续读 965.39 821.82 4MB 连续写 860.45 858.28 4MB 连续读 1129.54 1092.30 从测试结果可以看到,ReFS 相对 NTFS 来说在小一些的块值的连续读写上并没有什么优势,而在 RAID0 的整个 stripe 条带大小(256KB * 16 = 4MB)的读写性能上则确实比 NTFS 有一点改进。 另外说下 ReFS 暂时的不足之处: 1、Windows 8 虽然已经支持 ReFS,但并没有 NTFS 和 ReFS 之间转换的工具,你如果需要从 NTFS 切换到 ReFS,暂时只能手工中转拷贝了; 2、不支持从 ReFS 引导启动系统; 3、可移动磁盘或者 U 盘之类的设备不能使用 ReFS; 4、不支持文件压缩和用户磁盘配额,虽然这两个用的可能不是非常多,但还是有点不可想象; 5、NTFS 中原来支持的扩展属性 ReFS 也不再支持,有一些软件会依赖这个,感觉会有些不便。 总之现在看起来微软新引入的 ReFS 并没有像 ZFS 那样的新文件系统(支持 RAID-Z、原生快照、128位文件系统等特性)那样吸引人,如果要吸引用户转移到 ReFS 感觉还是任重道远,不知道 Windows Server 2012 最终版本中的 ReFS 到底有何改善。 本文章中介绍的只是测试了连续读写的性能,ReFS 重点支持的容错等功能下次有机会时再来测试,文中有任何问题欢迎指正咯。 ^_^

Linux kernel percpu变量解析

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-percpu-variable/ Linux 2.6 kernel 中的 percpu 变量是经常用到的东西,因为现在很多计算机都已经支持多处理器了,而且 kernel 默认都会被编译成 SMP 的,相对于原来多个处理器共享数据并进行处理的方式,用 percpu 变量在 SMP、NUMA 等架构下可以提高性能,而且很多情况下必须用 percpu 来对不同的处理器做出数据区分。 本文以 kernel 中的 softirq 为例简单说下 percpu 变量,我们先来看看 kernel 中唤醒 ksoftirqd 的实现,ksoftirqd 在 ps 命令看到的进程列表中很容易找到,是每个处理器都有一个(如果有 4 个处理器,则有 4 个 kernel 线程名称分别从 ksoftirqd/0 到 ksoftirqd/3),关于 softirq 本身的实现不在本文讨论范围内,唤醒 ksoftirqd 的实现在 kernel/softirq.c 文件中: 这里就用到了 percpu 变量 ksoftirqd,它是通过 DEFINE_PER_CPU 宏来进程定义的 percpu task_struct 列表,通过 __get_cpu_var 宏来得到相应处理器的 ksoftirqd/n 的 task_struct,然后调用 wake_up_process 函数唤醒进程(也就是 ksoftirqd/n kernel 线程),关于 wake_up_process 等进程调度的相关实现在之前的日志中有介绍的,请参考 [这里]。 __get_cpu_var、DEFINE_PER_CPU 等 percpu 宏的实现在 include/linux/percpu.h、include/asm-generic/percpu.h 等头文件中。先看看 include/asm-generic/percpu.h 中的一些定义: 通常所有的 percpu 变量是一起存放在特定的 section 里的,像上面头文件中的 .data.percpu 基础 section( 当然非 SMP 系统下就是 .data 了)、.shared_aligned、.first section。使用 objdump 可以看到编译 kernel 时的 vmlinux 文件的 section(结果没有完全显示): 可以看到 vmlinux 文件中的 .data 和 .data.percpu section。 percpu 变量的地址实际上就是其在上面说到的 section 里的偏移量,这个偏移量还要加上特定处理器的偏移量(也就是上面头文件中的 per_cpu_offset、my_cpu_offset 等)得到最终的变量地址,并最终以指针引用的方式得到值,这样访问的效果就有点类似于访问全局变量了。percpu 变量通常用于更新非常频繁而访问机会又相对比较少的场合,这样的处理方式可以避免多处理器环境下的频繁加锁等操作。 从上面的注释也可以看到 per_cpu_offset 是在一个 percpu 变量上增加的偏移量,大多数系统架构下使用 __per_cpu_offset 数组来作为偏移量,而 x86_64 等架构下处理方式则不同。my_cpu_offset 是在调用 per_cpu_offset 时使用 smp_processor_id() 得到当前处理器 ID 作为参数,__my_cpu_offset 则是用 raw_smp_processor_id() 的值作为 per_cpu_offset 的参数(smp_processor_id() 在抢占被关闭时是安全的)。SHIFT_PERCPU_PTR 宏用于给指针增加偏移量,它使用的 RELOC_HIDE 宏在不同的编译器下实现不同,在 include/linux/compiler.h 头文件中,看看 gcc 编译下的处理: 可以看到 gcc 中使用内嵌汇编先将 ptr 值赋给 __ptr(unsigned long 类型),然后在 __ptr 基础上增加偏移量,这样可以避免编译报错,ptr 值不变而且最终以 ptr 指定的类型来返回。 include/asm-generic/percpu.h 头文件中定义了 per_cpu、__get_cpu_var、__raw_get_cpu_var、this_cpu_ptr、__this_cpu_ptr 等几个常用的宏。per_cpu 就用于得到某个指定处理器的变量,__get_cpu_var 用于得到当前处理器的 percpu 变量值。 再来看看 DEFINE_PER_CPU 的实现,它在 include/linux/percpu-defs.h 头文件中: 使用 DEFINE_PER_CPU 宏可以静态的定义 percpu 变量。__PCPU_ATTRS 指定输入的 section 类型,DEFINE_PER_CPU_SECTION 用于在特定的 section 上定义特定类型的变量。__typeof__ 和 上面见到的 typeof 是一样的,都用于获取 type 的数据类型。__attribute__((section(xxx))) 表示把定义的变量存储在指定的 section 上。DEFINE_PER_CPU 就用于定义在 PER_CPU_BASE_SECTION section 上(从最开始的代码中也可以看出非 SMP 时用 .data 段,SMP 时用 .data.percpu 段)。 然后是 get_cpu_var 宏的实现,它在 include/linux/percpu.h 头文件中: get_cpu_var 会先禁止抢占然后调用 __get_cpu_var 得到 percpu 变量值。put_cpu_var 则重新启用抢占。 另外在 include/linux/percpu.h 等文件中还定义了 alloc_percpu 和 free_percpu 宏来动态定义和释放 percpu 变量,他们都是通过 percpu memory allocator 来实现的,在 mm/percpu.c 中,动态分配的 percpu 变量可以通过 per_cpu_ptr 宏来得到,为此 kernel 还引入了 this_cpu_ptr、this_cpu_read 等一系列相关机制用寄存器替代内存提高对 percpu 变量的访问速度,关于 percpu memory allocator 等信息以后再来详细分析了。 以上为个人分析结果,有任何问题欢迎指正咯 ^_^

Linux kernel kfifo分析

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-kfifo/ kfifo 是 Linux kernel 中的一个通用队列实现,对于 kernel 中常见的 FIFO 队列应用还是很有用的,本文主要简单介绍分析下 Linux kernel kfifo。实际上 ChinaUnix 上有个 kfifo 的分析文章,但已经比较老(基于 Linux 2.6.10),而且我现在用的 2.6.34 版本 kernel 中 kfifo 实现有很多改动,故自己简单写下,ChinaUnix 上的 kfifo 介绍帖子在这里: http://bbs.chinaunix.net/thread-1994832-1-1.html kfifo 定义在 include/linux/kfifo.h 头文件中,我们经常使用的就是 kfifo 结构,看看它的定义: kfifo 也像其它队列那样提供了两个主要操作:入队列(in) 和 出队列(out),对应于上面结构中的 in 和 out 两个偏移量,in 偏移量为下次入队列的位置,out 为下次出队列的位置,很容易也能想到 out 值必须小于等于 in 值,当 out 值等于 in 值时表示队列为空,kfifo 中 buffer 为队列的空间,size 为空间大小,必须为 2 的 k 次幂值(原因在下面说明)。当然如果 in 值等于队列长度了,就表示队列已经满了。 先看看 kfifo 最简单的一些操作实现,在 kernel/kfifo.c 文件中: 调用 kfifo_alloc 可以自动分配空间并初始化,你也可以调用 kfifo_init 函数使用自己的空间来初始化队列,可以看到这两个函数中都用 is_power_of_2 做了检查队列空间的操作。kfifo_free 释放队列,它会调用 _kfifo_init 函数(参数为 NULL 和 0 清空队列),调用 kfifo_reset 可以重置队列(将 in 和 out 都设为 0)。你也可以用 DECLARE_KFIFO 和 INIT_KFIFO 静态定义一个 kfifo 队列,尽管这不太会被用到。 调用 kfifo_in 函数将数据加入队列,kfifo_out 将数据从队列中取出并从队列中删除(增加 out 值),Linux 还提供了 kfifo_out_peek 函数从队列中取数据但并不删除(不增加 out 值)。kfifo_in 中会先调用 __kfifo_in_data 将输入加入队列,然后调用 __kfifo_add_in 增加 in 的值,kfifo_out 相反则调用 __kfifo_out_data 和 __kfifo_add_out 函数取出数据并删除。 kfifo 中同时提供了 kfifo_from_user 函数用户将用户空间的数据加入到队列中,它会先调用 __kfifo_from_user_data 将用户空间的数据加入队列,再调用 __kfifo_add_in 增加 in 的值。看看 __kfifo_from_user_data 的实现: 可以看到 __kfifo_from_user_data 中是直接调用 copy_from_user 将用户空间的数据拷贝到 kfifo 队列的空间中。相应的也有 kfifo_to_user 函数将队列中的数据取出到用户空间的地址,他就调用 copy_to_user 将队列中数据拷贝到用户空间。 需要注意的是 __kfifo_from_user_data 中用到的 __kfifo_off 函数: __kfifo_off 是根据指定的偏移量得到索引值,由这里也可以看出为什么队列的大小为什么必须是 2 的 k 次幂值,否则无法得到正确的值。而且从代码中可以看到 __kfifo_from_user_data、__kfifo_in_n、__kfifo_in_rec 等函数中都用到了 __kfifo_off 函数指定加入队列时的偏移量。 另外从 include/linux/kfifo.h 中你也可以看到新的 kfifo 实现中默认 EXPORT 了非常多的 API 函数给 kernel 开发者使用。 以上为个人分析结果,有任何问题欢迎指正哦 ^_^

所有SVN程序员都应升级到 Subversion 1.7

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/subversion-update-1-7/ 近日在升级 SVN 服务器之时顺便把 Windows 上的 TortoiseSVN 也升级到最新的 1.7.7 版本,发现不少惊喜,而且在 RHEL6 上顺便也升级到 1.7 之后发现确实很不错,建议所有正在用 SVN 的程序员们都升级到最新 1.7 版本。 Subversion 1.7 版本的主要几个更新有: 1、中心化元数据存储: 啥意思?呵呵,就是原来每个 SVN 版本库下的每个目录中都有一个 .svn 隐藏目录,现在中心化之后,全部集中到版本库根目录的 .svn 目录中,不用产生原来那么多的 .svn 目录。这个类似 Git 的改进是非常好的提升哦,特别在原来需要删除N多的 .svn 目录的时候非常方便,可以明显提高速度。 2、原始内容改进: 1.7 之前的版本中使用 .svn 目录中的 text-base 目录来记录本地副本中的未变化内容,新版本中重新设计并改为 .svn 中的 pristine 目录,由于去中心化,这个 pristine 也能减少数量,而且 1.7 版本中新增加了在 pristine 中共享引用,能进一步节省空间占用。 3、无缝更新元数据: 也就是 1.7 之前的版本中的 .svn 数据可以通过运行新 SVN 工具中的 svn upgrade 命令进行无缝升级(当然 Windows 上 TortoiseSVN 会自动提示你更新滴),升级之后你能马上体验到好处。 ^_^ 4、HTTP协议提升: Subversion 1.7 中增加了一个比现有 HTTP 协议更简单的 HTTPv2 协议解决原来的版本中提交更新之类性能比较差的问题,不过需要 1.7 版本的服务器配合才可以支持。 5、提供 svn patch 命令方便对比更新版本库 上面只是列了一些对我有用的,详细的更新日志可以看这里: http://subversion.apache.org/docs/release-notes/1.7.html 总之更新暂时看起来是有百利而无一害得,哈哈,更新 Subversion 请到这里: http://subversion.apache.org/packages.html Windows 上的小乌龟 TortoiseSVN 请到这里,也对 Subversion 1.7 完美支持了哦: http://tortoisesvn.net/downloads.html

Linux kernel学习-进程调度

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-process-scheduling/ 接着上面进程基本概念的文章,进程调度器决定系统中什么进程需要运行,运行多长时间。Linux kernel 实现的是抢占式的时间片调度方式,而不是进程主动让出时间片的方式。 Linux 从 2.5 开始使用名为 O(1) 的调度器,它解决了 2.4 及之前早期的调度器中很多设计上就存在的问题,O(1) 就表示该算法可以在常数时间内完成工作。在 ULK3 对应的 2.6.11 内核中仍然在使用此调度器,它对于很大的服务器负载的情况是很理想的,但由于 O(1) 调度器对于延迟敏感的程序(被称为交互式进程)而言却有缺陷,因此从 2.6.23 内核开始 Linux 引入一种调度器类的框架,并且默认使用一种新的调度器:Completely Fair Scheduler(CFS)完全公平调度器,鉴于历史的车轮在前进着,本文就主要讨论 CFS 调度器了。 进程通常可以分为两类:I/O密集型 和 计算密集型。I/O密集型进程花费更多的时间在等待 I/O 请求上(不一定是磁盘I/O,也可以是键盘、网络 I/O 等),大多数的 GUI 程序都是 I/O密集型进程。计算密集型的进程则要求运行频率小些但运行时间更多,像各种加解密程序和 MATLAB 这种就是典型的计算密集型进程。一个好的调度策略应该能同时满足低延迟和高吞吐量,Linux 调度器会采取偏向I/O密集型进程的策略。 Linux kernel 实现了两种独立的进程优先级:一种是 nice 值,从 -20 到 +19,默认值为 0,越大的值表示优先级越低(表示你对其它进程更加 “nice”,哈哈),nice 值在所有 Unix 系统中是一个通用的进程优先级范围,运行 ps -el 可以看到进程的 nice 值;第二种是可配置的实时优先级,范围从 0 到 99,越大的值表示优先级越高,实时进程比普通进程的优先级高,Linux 根据 POSIX.1b Unix 标准实现了实现了实时优先级,运行 ps 时增加 rtprio 参数可以在 RTPRIO 栏中看到实时优先级(如果值为 - 表示不是实时进程)。 Linux 2.6.34 默认的 CFS 完全公平调度器并不像传统调度器那样,根据 nice 绝对值为相应的进程分配固定的时间片,它没有明确的时间片概念,而是根据每个进程的 nice 相对差异值作为权重得到进程可以运行的时间在处理器时间中的比例。CFS 设置了一个预定的 targeted latency 值作为调度持续时间来根据比例计算时间片,当然此值越小越接近完全公平。假设 targeted latency 值为 20 毫秒,系统中有两个进程 nice 值分别为 0 和 5,根据权重计算出来的时间片分别为 15 和 5 毫秒,当两个进程为 10 和 15 时,计算出来的仍然为 15 和 5 毫秒,因为 nice 值的相对差异值并没有变。在系统中进程不是特别多时,CFS 调度器可以做到接近完全公平,而进程数量特别多甚至接近无限时,每个进程获得的时间片将非常小,为了避免进程切换导致的开销,CFS 又规定了一个 minimum granularity 值作为每个进程最小的时间片,默认为 1 毫秒,也即即使进程无限,每个进程也最少能运行 1 毫秒的时间,因此进程特别多时 CFS 就不会那么公平了。 1、CFS调度器: CFS 调度器实现在 kernel/sched_fair.c 文件中,这在上面一篇博文:进程基本 中有简单的介绍的。CFS 使用 sched_entity 调度实体结构,task_struct 中就有这个成员,看看 sched_entity 的定义,它定义在 include/linux/sched.h 头文件中: 可以看到此结构中下面很大一部分是开启了 CONFIG_SCHEDSTATS 之后才有用的。其中 vruntime 为进程的虚拟运行时间(实际运行时间经可运行的进程个数进行权重计算后的结果),在理想的 CFS 环境中,处理器都处于理想状态,所有同级别的进程的 vruntime 值应该都相同。但实际上多处理器不能做到完美多任务,CFS 调度器就用 vruntime 记录进程的运行时间并得到它应当还要运行多长时间。 再看到下面会用到的 cfs_rq 运行队列属性的定义,在 kernel/sched.c 中定义: cfs_rq 中的 curr 字段即指向当前队列上正在运行的实体(如果没有则为 NULL 了),rq 字段即为 CPU 运行队列。 来看看 sched_entity 的 vruntime 是如何在 update_curr 函数中更新的: update_curr 会被系统定时器周期性的调用,进程转变为可运行或不可运行状态时都会被调用,而 update_curr 本身则调用 __update_curr 增加实际运行时间和 vruntime 虚拟运行时间。 由于实际情况下,每个进程的 vruntime 并不会像理想状况那样完全一样,CFS 调度器在需要调度时从运行队列里中取 vruntime 最小的那个进程来运行。CFS 调度器使用一个红黑树来管理可运行进程的列表,并用于快速查找最小的 vruntime 进程。 红黑树在 Linux 中被称为 rbtree,可以用于存储任意数据的节点,由特定的关键字来标识。sched_entity 调度实体中的 run_node 就是一个红黑树节点,cfs_rq 中的 rb_leftmost 即是红黑树最左边的节点(缓存在 cfs_rq 结构中以加快访问速度,这样可以避免遍历红黑树),最小的 vruntime 进程就在此节点上,如果找不到此进程(返回 NULL),CFS 唤醒 idle 任务。 看看将进程加到红黑树的实现: enqueue_entity 中更新了当前进程的 vruntime,并最终调用 __enqueue_entity 将进程加到红黑树中。__enqueue_entity 中先通过遍历找到正确位置,遍历过程中就能确定红黑树中最左边的节点是什么,然后设置红黑树中节点左右信息,调用 rb_link_node 添加节点,必要时更新 cfs_rq 中保存的最左边的节点缓存。 好吧,看了添加过程再看从红黑树中删除进程: 同样 dequeue_entity 先调用 update_curr 更新当前进程的 vruntime,删除的实际操作由 __dequeue_entity 来完成。__dequeue_entity 中判断如果最左边节点正是要删除的进程,必须更新最左边的节点缓存,然后调用 rb_erase 删除节点。 Linux 中进程调度入口是 schedule() 函数,定义在 kernel/sched.c 中。对于一个进程,schedule() 会先查找最高优先级的调度器类并调用此调度器类中的函数进行调度。看看 pick_next_task 的实现: 首先由于 Linux 普通进程默认使用 CFS 调度器,pick_next_task 先判断是不是所有进程都在 CFS 调度器中,如果是就直接调用 CFS 的 pick_next_task 函数节省遍历时间。sched_class_highest 的定义在上面也可以看到,就是 RT 调度器类,for_each_class 用于遍历调度器类。而其它类中找不到时,idle 调度器类的 pick_next_task 就返回一个有效 task_struct。在 CFS 调度器中 pick_next_task 会调用 pick_next_entity 函数选择下一个运行的进程。 2、睡眠与唤醒: 进程需要睡眠时,kernel 的处理大体如下:进程将自己标记为睡眠状态,将自己加到等待队列,从记录可运行进程的红黑树上删除自己,调用 schedule() 选择新进程来运行,schedule() 会调用 deactivate_task() 函数将进程从运行队列中移除。唤醒的过程则相反:进程被设置为可运行,从等待队列删除,重新加回到红黑树中。 等待队列在 kernel 中以 wait_queue_head_t 表示,定义在 include/linux/wait.h 头文件中,它其实是一个 __wait_queue_head 结构,下面的头文件中有一些经常用到的声明: 需要注意的是每个等待队列都需要可以在中断时被修改,因此操作等待队列之前必须获得一个自旋锁。而在实际使用中等待时需要处理竟态条件,为此 kernel 定义了几个很好用的等待条件的宏,为调用者减少操作,这些宏也是定义在上面的文件中。常用的有 wait_event 根据条件在队列上无限等待,wait_event_timeout 相对加了超时处理,它是调用 schedule_timeout 进行调度,wait_event_interruptible 即在等待时进程是可以响应信号之类的唤醒。 内核中的 completion 完成量机制也是基于等待队列的,用于等待某一操作结束。__wait_queue 结构的 task_list 成员通过双链表链接到 __wait_queue_head 中,__wait_queue 中的 private 成员指向等待进程的 task_struct。__wait_queue 中 […]

一个比较烂的主机空间 - GigaWebHost

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/webhosting-gigawebhost/ 上次博客空间提供商突然被 GFW 封了一天,想着用这临时的免费主机不太保险,准备随便买个国外空间试试的,发现了这个 GigaWebHost,结果各种经历不吐不快,这空间主页: http://gigawebhost.com/ 看空间主页介绍的似乎很不错的,Features 里写的 30G 空间,似乎无流量限制,3万个子域名,PHP/MySQL/Zend 等都支持,最主要还支持我比较看重的 SSH Shell 功能,而且只要 1欧元 1个月,算起来一年也就 100 RMB,相当划算,就在主页上点了个申请试用,结果这坑爹的所谓两小时试用还必须付款才可以,崩溃ing。后来看这空间似乎是个德国主机,德国主机一向质量还不错,就准备顺便买一个月(这个主机是月付)看看,结果在选域名那发现悲催了,只能选免费的二级域名,使用已有的域名要 30~50欧,还是每年,而主机本身一年才 12欧,FAKE,这简直是。。。 -_-# 好吧,都到这份上,先搞个免费的二级域名看看速度再说吧,然后试试在 CPanel 里 park domain 看看吧,用信用卡付款开通之后进 CPanel,悲剧了,默认的 CPanel 英语管理界面被 “智能” 滴翻译成了阿拉伯语,再汗。切成英语之后,死活找不到 park domain 的地方,oops,我认了,先看看 SSH 咋登录,准备把博客程序通过 SFTP 压缩上传上去再解压,这样备份和恢复网站内容是必须方便的哇。 结果在 SSH Shell 面板,再次吐血,默认 SSH Shell 不开通(FAKE,你 Features list 里写它干啥。。。),提示必须联系客户开通。好吧,我还是有耐性的,大不了联系客户呗,找到 submit ticket,用英文写了一段需求说我为什么要开通 SSH,顺便小问了下能不能帮开通下 park domain(因为这个对 CPanel 来说是非常简单的),提交 ticket。 然后开始等的过程,然后,然后,然后,就等了一个星期,德国人终于回 ticket 了,说开通 SSH 必须提交护照副本(我去,还护照副本,也不看我从哪来的。。。),而关于 park domain 则只字未提,我当即在 ticket 里回复:没有护照副本,park domain 能否开通?然后又是等待,5 天都没回,好吧,折腾不起了,果断取消空间和后续付费,还好也就花了首期的 1欧元,取消原因填上:never wanna use it again, never ~~~~ 结束语:烂空间伤不起,各种选择主机请务必谨慎,作为前车之鉴咯~~~

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:如果有更加简单的方法,欢迎提出指正哦~~~ ^_^