Tag: Programming

Linux kernel学习-内存管理

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-memory-management/ 接着之前的 Linux kernel 学习步伐,来到极其重要的内存管理部分,继续本文内容,需要先了解内存寻址的基础知识,见之前的 [内存寻址] 博文。 1、内存页及内存区域: 正如之前所说,Linux kernel 使用物理页作为内存管理的基本单位,其中重要的线程地址和物理地址的转换操作由页单元 MMU 来完成,系统的页表也由 MMU 来维护。kernel 使用 struct page 来表示一个物理页,它的定义在 include/linux/mm_types.h 头文件中: 其中的 flags 用于表示页的状态(是否为脏或者被锁定等),_count 即为页的引用计数,kernel 一般使用 page_count 宏调用 atomic_read 函数原子的读取此值,page_count 返回 0 表示此页可用。如果一个页被作为 page cache 使用,则 page 的 mapping 字段指向映射的 inode 的 address_space 对象,如果页被作为私有数据(作为 buffer_heads 缓冲、buddy 系统等),则 private 常包含对应的信息。注意其中的 virtual 字段为页的虚拟地址,结合之前的知识,对于高端内存来说,其并没有被固定映射到 kernel 地址空间中,因此如果 virtual 字段为 NULL,则表示此页必须被动态映射。 kernel 使用 page 结构记录系统中的所有页,因此 struct page 的大小应该要尽量小以减少内存占用,另外 kernel 必须知道页是否空闲,如果不空闲则拥有者是谁。 由于实际硬件限制,Linux kernel 不可能使用全部的物理内存,kernel 为此将内存划分为不同的区域,一个区域中的内存属性应该也相同。kernel 中常见的内存区域有 ZONE_DMA(可用于 DMA 的页)、ZONE_DMA32(与 ZONE_DMA 类似,但只对 32 位设备可用)、ZONE_NORMAL、ZONE_HIGHMEM(并没有被固定映射的高端内存区域),这些内存区域一般都是硬件相关的,例如在 x86 架构下,ZONE_DMA 的范围为 0MB - 16MB,ZONE_HIGHMEM 为高于 896MB 的物理内存,而在 x86_64 架构下 ZONE_HIGHMEM 则为空。需要注意的是内存的分配不会跨域这些不同的内存区域。内存区域在 kernel 中由 struct zone 结构来表示,其中的 name 字段即为内存区域名称。 2、获取页: 分配和释放内存是 Linux kernel 中极其重要又用的极多的接口。先看看 kernel 提供的直接获取以内存页面为单位的 alloc_pages 函数: struct page * alloc_pages(gfp_t gfp_mask, unsigned int order) 此函数是最基本的用于分配大小为 2^order 并且连续的物理页的函数,其返回分配到的第一个页面的 page 指针。 来看看比较重要的 gfp_t 类型的 gfp_mask 值: gfp_t 实际上就是 unsigned int 类型,gfp_mask 常用于指定行为方式、区域方式、类型等信息。常见的行为方式标志有:__GFP_WAIT(标志分配器可以睡眠,明显不适用于中断上下文中)、__GFP_IO(分配器可以启动磁盘 I/O)等。区域方式指定内存从哪里分配,对应的就有:__GFP_DMA、__GFP_DMA32、__GFP_HIGHMEM(从高端内存或普通内存中分配)。类型标志则用于简化分配时的指定操作,常见的有:GFP_ATOMIC(高优先级并不可睡眠,常用于中断、中断下半部、持有自旋锁等环境中)、GFP_NOIO(表示分配可中断但不可以发起 I/O 操作)、GFP_NOFS(分配时不可发起文件 I/O 操作)、GFP_KERNEL(最常见的分配标志,常用于可以睡眠的进程上下文中)、GFP_USER(用于分配内存给用户进程)、GFP_DMA 等。 需要注意的是对 __get_free_pages 和 kmalloc 函数(下面会分别说明)不能指定 __GFP_HIGHMEM 标志,因为它们都是直接返回的虚拟地址,而非 page 结构指针,如果指定了 __GFP_HIGHMEM,则他们可能分配到的内存并没有被映射到 kernel 地址空间,因此这样得不到虚拟地址。只有 alloc_page 函数可以分配高端内存,这个限制在下面的 __get_free_pages 函数的实现中可以看到。 使用 page_address 函数可以将 page 指针转换为虚拟地址(非物理地址)。实际使用中经常会用到 __get_free_pages 函数直接在分配页时直接得到虚拟地址,其参数为 alloc_pages 完全一样,看看它的实现就一目了然了: 另外 kernel 还 “好心” 的提供了两个只分配一个页的函数:alloc_page 和 __get_free_page,可以想象只是把 order 参数设为 0 而已。你可以使用 get_zeroed_page 函数分配一个页并自动清零(gfp_mask 指定 __GFP_ZERO)。 对应的释放页可以用 __free_pages(page 指针为参数)、free_pages(虚拟地址为参数)、free_page(只释放一个页)这些函数。 下面是常用的分配非整数倍页大小的内存的函数。首先是最常用的 kmalloc 函数: void *kmalloc(size_t size, gfp_t flags) kmalloc 用于分配最少指定的 size 字节大小的内存(实际分配的可能比 size 多),这与用户空间的 malloc 函数很相似,但需要注意的是 kmalloc 分配的内存物理地址是连续的,这非常重要。 相应的释放内存函数是 kfree: void kfree(const void *objp) kfree 用于释放 kmalloc 分配的内存,注意如果使用 kfree 在不是的 kmalloc 分配的内存地址或者已经 kfree 过的地址上,都可能导致 kernel 出错。 紧接着就是大名鼎鼎的 vmalloc 函数了。它与 kmalloc 类似,但它分配的内存只是虚拟连续的而物理地址却不一定连续,这也类似于用户空间的 malloc 函数的效果。vmalloc 由于需要做页表转换之类的操作,性能比 kmalloc 差,而且 vmalloc 得到的页还必须由单独的页来做映射,对 TLB 缓存的效率也会有影响(有关 TLB 缓存参考之前的文章 [内存寻址]),由于这些原因,vmalloc 在 kernel 中用到的机会并不是很多,其常用于分配大量的内存,常见的一个例子就是内核模块的代码就是通过 vmalloc 加载到 kernel 中的。vmalloc 的原型为: void * vmalloc(unsigned long size) 与之对应的,使用 vfree 释放分配的内存。另外 vmalloc 和 vfree 都是可以睡眠的,因此它们对中断上下文是不适用的。 3、Slab分配器: Slab 也是 Linux kernel 中非常重要的组成部分,它用于简化内存的分配和释放,它相当于一个可用内存列表,里面包含一堆已经分配好的数据结构,当 kernel 需要分配一个数据结构时,可以直接从这个可用内存列表中取出而节省分配的时间,不需要的时候又可以还给这个列表而不需要释放,因此这个列表用于缓存经常访问的某种类型的数据。为了统一管理和释放,Linux kernel 引入 Slab 分配器作为通用的数据结构缓存层给经常访问的数据结构使用。需要说明的是 kmalloc 就是在 Slab 分配器基础上实现的。 这里简单对 Slab 分配器做个介绍,有关其细节请参考这篇 PDF 文档: The Slab Allocator: An Object-Caching Kernel Memory Allocator Slab 层将不同的对象划分到名为 cache 的不同组中,每个组存储不同类型的数据,也就是每种数据类型都有一个 cache。每个 cache 然后被划分为多个 slab,slab 由一个或多个连续的物理页组成(通常只有一个页),每个 slab 又包含一些数量的对象,也就是实际缓存的数据。每个 slab 的状态可以是这三个中的一个:满、部分满、空。当 kernel 请求一个新对象时,优先从状态为 部分满 的 slab 中取,如果没有则从状态为 […]

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 开发者使用。 以上为个人分析结果,有任何问题欢迎指正哦 ^_^

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

本文同步自(如浏览不正常请点击跳转):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

WordPress友言近期评论Widget更新(v1.1)

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1 前两天写了一个友言近期评论的 WordPress Widget,初步测试还能用,但多次进行刷新,会出现不能正常登录之类的问题(Failed to authentication with uyan.cc、Invalid comments data from uyan.cc 之类的报错),而且原来每次都需要登录友言的后台管理严重影响速度,我也比较怕怕这样不断发请求会导致友言把我的脚本和博客给封掉,嘿嘿。 由于友言没有公开 API 给我等码农来调用,因此今天又花了点时间做了一些改进,弄了个 1.1 版本,原介绍文章在这: https://zohead.com/archives/wordpress-uyan-recent-comment-widget/ 1.1版本更新说明: 1、登录友言后台管理,得到近期评论等地方增加错误处理,如果服务端返回的 JSON 数据不对则报错 2、减少 PHP 本身的 error message 输出 3、将得到近期评论的功能分离为一个函数单独调用 4、登录成功之后保存得到的 cookies 到同目录下的 uyan_cookie.php 文件中方便下次直接使用,而无需再重复登录,如果 cookie 过期下次获取评论时失败则再重新登录 5、发查询近期评论请求时增加友言本身的管理地址作为 referer 此次增加了一个 uyan_cookie.php 文件用于保存 cookie,从下面的链接下载解压缩之后,请将此文件也上传到同一目录中(不上传的话 PHP 可能会报 require_once 出错),此文件默认为空,详细代码就不贴了,直接在下面下载查看吧。友言 的登录邮箱、密码、域名之类的修改请参考原始的介绍文章了。 这个是 1.1 版本的下载地址: http://miseal.googlecode.com/files/uyan_comments_v1.1.zip 当前的通过临时文件保存 cookie 方式应该还有改进空间,有任何问题欢迎指正咯 ^_^

PHP实现WordPress友言近期评论Widget

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/wordpress-uyan-recent-comment-widget/ 现在有很多人都使用 WordPress 来搭建自己的博客系统,其中有一些是像我这样使用 友言 这个社交评论插件来替代系统原始的评论框的,友言 评论框插件对一些主流的社交网站支持比较好,很是推荐,友言官网: http://uyan.cc/ 之前我写过一个修改 友言 插件实现完美与 WordPress Mobile Pack 插件配合实现移动版博客的文章,在这里仅供参考: https://zohead.com/archives/modify-plugin-wordpress-mobile-pack/ 但现在发现还是有一点不足,由于使用了友言评论框替代 WordPress 本身的,导致 WordPress 的 ”近期评论“ Widget(小工具)显示不了,因此小折腾一个晚上,靠着还依稀记得的 PHP 编程经历,写了一个简单的显示 友言 近期评论的 WordPress Widget,先看效果图(本博客右侧就有 ^_^): 原理及代码: 实现原理比较简单,先用工具分析 友言 评论的后台登录和评论显示之类的 HTTP 包信息,然后用 PHP 的 fsockopen 来自己发送 GET、POST 请求实现在 WordPress 上列举显示已经存在的友言评论(备注:默认只显示已经经过审核的评论)。 由于中间有几步 GET 和 POST 请求,就找了个现成的 post-to-host 这个很小的 PHP 脚本来发 GET 和 POST 请求,下载地址: http://code.google.com/p/post-to-host/ 下面是主程序 uyan_comments.php 的代码: 代码本身比较简单,有些 PHP 基础就可以看懂了,把 uyan_comments.php 文件最上面的 $email、$password、$domain、$maxcomments 改为实际的 友言 后台管理的登录邮箱、密码、你的域名、显示的最多评论数(备注:默认为10条,如果改为小于0的值则不限制显示的评论条数),就可以使用了。你应该已经发现这个 uyan_comments.php 其实和 WordPress 没太大关系,完全也可以直接单独使用。 需要注意的是为了避免使用明文密码而可能导致的问题(安全第一 ^_^),上面的 $password 是 友言 后台管理时实际用到的加密过的密码。这个加密过的密码可以通过 Firefox 的 Live HTTP headers 插件之类的抓取 HTTP 协议头的插件或工具来得到。 得到友言的加密登录密码: 下面以 Firefox 的 Live HTTP headers 插件为例说明如何得到 友言 的实际加密的密码,打开 Live HTTP headers,该插件会自动开始抓取,然后用正确的邮箱和密码登录 友言 的后台管理,停止 Live HTTP headers 的抓取,在输出里就能找到地址为如下格式的 GET 请求,请求参数中就有加密的密码: http://uyan.cc/index.php/youyan_login/userAutoLoginCrossDomain?callback=jsonpxxxxxxx&email=xxxxx@xxxxx.com&loginPassword=xxxxxxxxxxxxxxxxx&rem=1&domain=xxx.com 其中的 email 段就是登录邮箱,loginPassword 段即为加密的密码,保存下该密码,修改 uyan_comments.php 文件中的 $email 和 $password 值。 Live HTTP headers 的抓取截图如下所示(后面的未显示完整): 如何加入 WordPress Widget 列表中: 你如果有真正实现一个 WordPress Widget 的心思,可以用本代码加上 WordPress 的 register_widget 之类的接口来实现。无奈我是一个超级懒人,懒人就有懒人的办法,下面介绍的就是懒人的办法,哈哈。 首先下载本文最下面下载链接中的 post_to_host.php 和 uyan_comments.php,将 uyan_comments.php 中对应的 登录邮箱、密码、域名 改掉(参考上面),将这两个文件上传到 WordPress 根目录中(位置也可以自己修改),然后给 WordPress 安装 PHP Code Widget 插件,这是一个通用 Widget,添加之后,可以自行添加 文本、HTML、PHP 代码等,比较方便,插件地址: http://wordpress.org/extend/plugins/php-code-widget/ 安装之后,在 WordPress 管理后台的 外观 - 小工具 里就能看到名为 “PHP Code”  的小工具,将其托至右侧的 “第一小工具区域”,输入自定义的标题,然后加入以下代码保存即可(如果上传的位置不在 WordPress 根目录那请自行修改): 重新访问 WordPress,如果上面的 登录邮箱、密码、域名 设定都正确的话,应该就可以出现类似上面效果图的评论列表。 不足和改进: 1、uyan_comments.php 每次访问时都需要登录 友言 管理后台,请求评论列表,因此速度会有些影响,这个有空再改进了。 2、由于需要在 Web服务器中使用 PHP 的 fsockopen 来发 GET、POST 请求得到评论列表,因此可能对 WordPress 博客的访问速度造成一些影响,如果 PHP 空间在国内还可以,像我这样空间在国外的就稍微悲剧点了,所以建议安装 WP Super Cache WordPress 之类的插件来实现更好的缓存加速,将影响尽量降低。 下载地址(115网盘): http://115.com/file/beezjk3b 写博客好累,准备休息,HOHO,本文件为个人作品,有任何问题欢迎指正。 ^_^

bind2nd普通二元函数时无法使用引用类型参数的问题

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/cplusplus-bind2nd-reference/ 在使用 STL 的 find_if、count_if 等函数时会发现这些函数使用的参数不是普通的函数指针,而是 functional 函数对象,实际使用函数对象时可以自己定义一个仿函数来实现(实现带参数的 operator () 即可),这个相对比较简单就不写出来了。但有些情况需要直接使用普通的二元函数指针,这时可以使用 ptr_fun 将函数指针转换为函数对象作为 find_if、count_if 等的参数。 先看一个能正常工作的二元函数不使用引用类型参数的代码: 程序很简单,从 vector 中查找符合条件的 sss 对象,find_sss 就是要转换的二元函数指针,第一个参数是 sss 类,通过 ptr_fun 可以使本代码正常工作。 通过下面的运行输出能看出调用 find_sss 时进行了拷贝构造(本程序的编译环境为:Windows 7 32bit, Mingw gcc 3.4.5,Visual Studio 2010中稍有不同,主要在前面的拷贝次数上): ---copy 0x22ff10 to 0x552a58 ---copy 0x552a58 to 0x552ad8 ---copy 0x22ff10 to 0x552ae0 ---copy 0x552ad8 to 0x552af0 ---copy 0x552ae0 to 0x552af8 ---copy 0x22ff10 to 0x552b00 ---copy 0x22ff10 to 0x552b08 ---copy 0x552af0 to 0x552b18 ---copy 0x552af8 to 0x552b20 ---copy 0x552b00 to 0x552b28 ---copy 0x552b08 to 0x552b30 ---copy 0x22ff10 to 0x552b38 before find_if ---copy 0x552b18 to 0x22fdd0 ---copy 0x22fdd0 to 0x22fd40 ---copy 0x552b20 to 0x22fdd0 ---copy 0x22fdd0 to 0x22fd40 ---copy 0x552b28 to 0x22fdd0 ---copy 0x22fdd0 to 0x22fd40 index: 2, value: 13 接下来就是实际碰到的问题了,如果将 find_sss 的第一个参数改为 sss 的引用,即第 24 行改为: bool find_sss(sss& s_chk, int val) 上面的代码就会编译出错(以 Visual Studio 2010 的错误输出为例): C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(341) : error C2535: “bool std::binder2nd<_Fn2>::operator ()(sss &) const”: 已经定义或声明成员函数 with [ _Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)> ] C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(335) : 参见“std::binder2nd<_Fn2>::operator ()”的声明 with [ _Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)> ] test.cpp(43): 参见对正在编译的类 模板 实例化“std::binder2nd<_Fn2>”的引用 with [ _Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)> ] 网上的码农和攻城师们基本都认为是 STL 本身的问题,传递引用类型参数会造成 reference to reference 问题。 几番尝试之后,发现的解决方法如下: 1、Visual Studio 2010下的不完美解决方法: 将第 43 行改为:     iii = find_if(vvv.begin(), vvv.end(), bind2nd(pointer_to_binary_function<sss, int, bool, bool(*)(sss&, int)>(find_sss), 13)); 这样通过自己给 pointer_to_binary_function 设置模板参数避免编译出错,实际运行中会发现 find_if 查找可以正常工作了,但第 27 行中修改引用类的值会没有效果,因为这种方式不是真正的引用,仍然有拷贝构造,由于需要用到这些二元函数的场合一般不需要修改数据,使用起来没有太大问题。 2、gcc下的不完美解决方法: 如果 gcc 下用上面的改动,你会发现由于 gcc 下 pointer_to_binary_function 只有 3 个参数,无法顺利修改编译,但可以这样折中,将第 43 行改为:     iii = find_if(vvv.begin(), vvv.end(), bind2nd(ptr_fun((bool(*)(sss, int)) find_sss), 13)); 通过强制类型转换来实现,稍显恶心,查找可以正常工作,但依然是拷贝构造。需要注意此方法如果在 Visual Studio 中使用会出错。 3、终极解决方案- 使用 boost 库: 首先头文件中增加: #include <boost/functional.hpp> 然后原来的第 43 行改为:     iii = find_if(vvv.begin(), vvv.end(), boost::bind2nd(boost::ptr_fun(find_sss), 13)); 编译运行之后发现 find_if 查找可以正常工作,而且现在是真正的引用,只能说 boost 的 functional 相比 STL 的实在是好强大,哈哈。 运行输出如下(使用 Mingw gcc 3.4.5 编译),find_if 找到的值已经被 find_sss 修改: ---copy 0x22ff10 to 0x7f2a58 ---copy 0x7f2a58 to 0x7f2ad8 ---copy 0x22ff10 to 0x7f2ae0 ---copy 0x7f2ad8 to 0x7f2af0 ---copy 0x7f2ae0 to 0x7f2af8 ---copy 0x22ff10 to 0x7f2b00 ---copy 0x22ff10 to 0x7f2b08 […]

C++虚基类的实现验证

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/cplusplus-virtual-base-class/ 在C++的某些应用环境中,如果有多个派生类同时继承同一个基类,但只要想基类成员的一份拷贝,这样可以把对基类的继承声明为虚拟(virtual)的,以达到需要的目的,用 virtual 限定的继承即为虚继承,对应的基类即为虚基类,没有则为非虚基类。在派生类产生的对象中,同名的虚基类只产生一个虚基类对象,非虚基类则有各自的对象,需要注意虚基类和抽象类的区分。 另外特别需要注意的是一个基类即可以被用作虚基类,也可以被用作非虚基类,这纯粹看是否是虚继承(是否有 virtual 限定)。虚基类子对象是由最远派生类(也就是建立对象时所指定的类)的构造函数调用虚基类的构造函数进行初始化。如果一个派生类继承了多个基类,其中既有虚基类,也有非虚基类,则虚基类构造函数优先于非虚基类的构造函数,而不管实际书写顺序,而且如果该派生类的构造函数初始化列表中同时有对虚基类和非虚基类构造函数的调用,同样也是虚基类优先。 有关虚基类的其它说明请参考其它书籍咯,下面是测试的代码: 比较简单,Base2 和 Base3 分别派生自 Base,而且都是虚继承,Base4 同时派生自 Base2 和 Base3,但 Base3 前有 virtual,表示 Base4 对 Base3 是虚继承,Base4 对 Base2 是非虚继承。另外代码中比较特殊的一点是 Base 基类中去掉了默认构造函数,改为一个 int 参数的构造函数(第 8 行),因此 Base2 和 Base3 的构造函数的初始化列表中都需要增加对基类 Base 构造函数的调用,同样 Base4 需要调用 Base、Base2 和 Base3。 主程序中只是生成一个 Base4 的对象,上述代码的运行输出为: init Base 0x22ff34, value:22 init Base3 init Base2 destory Base 0x22ff34, value:22 由上面的输出可以验证虚基类的概念: 由于 Base2 和 Base3 都是对 Base 的虚继承,虽然 Base4 中分别调用了 Base2 和 Base3 的构造函数,但虚继承就表示基类 Base 只有一份拷贝,Base 只需要构造一次。另外由于 Base4 对 Base2 是非虚继承,对 Base3 是虚继承,尽管 Base2 看起来在前面调用,但由于 Base3 对 Base4 是虚基类,它的构造函数就需要先于 Base2 对调用,因此 ”init Base3“ 在 ”init Base2“ 前面。 接着对代码稍作改动,将 Base2 和 Base3 都改为非虚继承,Base4 中去掉对 Base 基类构造函数的调用: 第22行:class Base2 : public Base 第31行:class Base3 : public Base 第43行:    Base4(int val) : Base2(val), Base3(val) 编译运行输出,结果为: init Base 0x22ff38, value:22 init Base3 init Base 0x22ff34, value:22 init Base2 destory Base 0x22ff34, value:22 destory Base 0x22ff38, value:22 可以看到由于 Base2 和 Base3 都不是非虚继承,Base 就被构造了两次。 在上面的基础上再做个小改动,把 Base4 对 Base3 的派生也改为非虚继承: 第40行:class Base4 : public Base2, public Base3 再次运行输出,结果即为: init Base 0x22ff30, value:22 init Base2 init Base 0x22ff34, value:22 init Base3 destory Base 0x22ff34, value:22 destory Base 0x22ff30, value:22 由于 Base4 对 Base2 和 Base3 都不是虚继承了,对 Base2 和 Base3 的构造函数调用就按实际顺序了。 这样虚基类的概念就算是比较清楚咯。 ^_^

vector的push_back拷贝构造和空间占用分析

本文同步自:https://zohead.com/archives/vector-push-back-space-copy/ 这两天在实际程序中使用 STL 的 vector push_back 类对象时出现问题,偶尔发现 vector 在 push_back 时的调用类对象的拷贝构造函数和析构函数有点特别,简单做下分析。 程序代码: 功能很简单,main 中定义一个 sss 类对象和对应的 vector,然后在循环中改类成员的值,并依次 push_back 到 vector 中,类的构造函数、析构函数、拷贝构造函数中都加了对应的打印输出。循环运行了5次,往 vector 中增加了5个类成员。 实际运行输出如下: ---init sss 0x22ff20, value:11 ---copy 0x22ff20 to 0x5d2a58 size: 1, capacity: 1 ---copy 0x5d2a58 to 0x5d2ad8 ---copy 0x22ff20 to 0x5d2adc ---destory sss 0x5d2a58, value:12 size: 2, capacity: 2 ---copy 0x5d2ad8 to 0x5d2ae8 ---copy 0x5d2adc to 0x5d2aec ---copy 0x22ff20 to 0x5d2af0 ---destory sss 0x5d2ad8, value:12 ---destory sss 0x5d2adc, value:13 size: 3, capacity: 4 ---copy 0x22ff20 to 0x5d2af4 size: 4, capacity: 4 ---copy 0x5d2ae8 to 0x5d2b00 ---copy 0x5d2aec to 0x5d2b04 ---copy 0x5d2af0 to 0x5d2b08 ---copy 0x5d2af4 to 0x5d2b0c ---copy 0x22ff20 to 0x5d2b10 ---destory sss 0x5d2ae8, value:12 ---destory sss 0x5d2aec, value:13 ---destory sss 0x5d2af0, value:14 ---destory sss 0x5d2af4, value:15 size: 5, capacity: 8 ---destory sss 0x5d2b00, value:12 ---destory sss 0x5d2b04, value:13 ---destory sss 0x5d2b08, value:14 ---destory sss 0x5d2b0c, value:15 ---destory sss 0x5d2b10, value:16 ---destory sss 0x22ff20, value:16 结果分析: vector 每次调用 push_back 时都会拷贝一个新的参数指定的 sss 类对象,这会调用 sss 的拷贝构造函数,第一次的 copy 正常,而且 vector 的实际容量也由 0  变为 1。 第二次调用 push_back,通过输出会发现调用了两次拷贝构造函数,一次析构函数,原来 vector 此时判断容量不够,将容量扩大为原来的两倍,变为 2,并将原来的元素再次拷贝一份存放到新的内存空间,然后拷贝新加的类对象,最后再释放原来的元素。 第三次调用 push_back 时,vector 自动扩大为4,因此拷贝构造函数调用了3次,析构函数调用了2次,程序最终退出了时就析构了 5 次加本身的 sss 类对象一共 6 次。 参考: 由此看来,vector 的 push_back 在发现空间不足时自动将空间以 2 的指数增长:0 -> 1 -> 2 -> 4 -> 8 -> 16 -> 32 … 查找资料后得知,如此设计的主要目的是为了尽可能的减小时间复杂度;如果每次都按实际的大小来增加 vector 的空间,会造成时间复杂度很高,降低 push_back 的速度。 另外关于 push_back 为什么会执行拷贝构造函数,push_back 的原型为: void push_back(const _Ty& _Val) 参数是以引用方式传递,按说不会拷贝,但 push_back 实际实现中判断空间不足时是调用 insert 函数添加元素: void push_back(const _Ty& _Val) {     // insert element at end     if (size() < capacity())     #if _HAS_ITERATOR_DEBUGGING     {         // room at end, construct it there         _Orphan_range(_Mylast, _Mylast);         _Mylast = _Ufill(_Mylast, 1, _Val);     }     #else /* _HAS_ITERATOR_DEBUGGING */         _Mylast = _Ufill(_Mylast, 1, _Val);     #endif /* _HAS_ITERATOR_DEBUGGING */     else         insert(end(), _Val); } 更新: 2012-05-10:       近期在 Visual Studio 2010 中发现 vector 的实际空间增加顺序为:1 - 2 - 3 - 4 […]

Linux下出现 pam_get_item 的原因分析

本文博客链接:https://zohead.com/archives/linux-pam-get-item-error-library/ 近日在将一个 RHEL6 上的服务程序移植到老的 Fedora Core 2 Linux 上时出现了一些问题,该程序的功能为预先加载几个动态库,这几个动态库中再需要根据不同的需求做 PAM 用户验证操作,程序在 RHEL6 上编译和使用都没有问题,换到 Fedora Core 2 环境之后,编译时一切正常,但在运行时出现比较奇怪的 pam_get_item 错误: pam: PAM [dlerror: /lib/security/../../lib/security/pam_env.so: undefined symbol: pam_get_item] pam: PAM [dlerror: /lib/security/../../lib/security/pam_unix.so: undefined symbol: pam_get_item] pam: PAM [dlerror: /lib/security/../../lib/security/pam_succeed_if.so: undefined symbol: pam_get_item] … 程序所加载的动态库中使用的 PAM 配置文件 /etc/pam.d/system-auth 里就用到了 pam_env.so、pam_unix.so、pam_succeed_if.so 等几个 PAM 模块。 但实际系统中还有很多服务都使用 /etc/pam.d/system-auth 这个 PAM 配置来进行用户验证,都能正常工作,因此做下简单分析。 由于此服务程序太复杂,就打定写个简单的模拟程序来进行分析。 1、动态库部分(checkpam.c): PAM 验证的实现,调用 PAM 函数进行验证,具体 PAM 函数的调用就不详细写出了。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <security/pam_appl.h> int check_pam(const char * user, const char * pass) {     int ret = -1;     pam_start("system-auth", user, …, …);     …     // 实际 PAM 代码省略,成功返回0,失败返回1 //     …     pam_end(…);     return ret; } 和实际服务程序一致,通过 system-auth PAM 配置进行验证,编译生成动态库: cc -c checkpam.c cc -shared -fPIC -lpam -o libcheckpam.so checkpam.o ldd libcheckpam.so,查看此动态库的依赖列表: [root@linux root]# ldd libcheckpam.so         linux-gate.so.1 =>  (0x00c67000)         libpam.so.0 => /lib/libpam.so.0 (0x0025b000)         libc.so.6 => /lib/tls/libc.so.6 (0x0041d000)         libdl.so.2 => /lib/libdl.so.2 (0x00153000)         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00876000) 2、主程序部分(pamtest.c): 加载动态库,调用 PAM 进行用户验证,比较丑陋,只需能验证,HOHO。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> int main(int argc, char ** argv) {     int ret = 0;     void * lpso = NULL;     int (* lpfunc)(const char *, const char *) = NULL;     lpso = dlopen("./libcheckpam.so", RTLD_LAZY);     if (lpso == NULL) {         printf("Library load failed: %s.\n", dlerror());         return 1;     }     lpfunc = dlsym(lpso, "check_pam");     if (lpfunc == NULL) {         printf("Load symbol failed: %s.\n", dlerror());         dlclose(lpso);         return 2;     }     ret = lpfunc(argv[1], argv[2], NULL);     dlclose(lpso);     printf("pam ret: %d.\n", ret);     return ret; } 功能很简单,dlopen 加载刚才生成的 libcheckpam.so,然后 dlsym 找到 check_pam 函数,接着调用函数进行用户验证。 编译程序: cc -o pamtest pamtest.c -ldl ldd pamtest,查看程序的依赖列表,可以看到此时已不需要 pam 库,和实际使用的服务程序的实现方式一致了: [root@linux root]# ldd pamtest         linux-gate.so.1 =>  (0x00631000)         libdl.so.2 => /lib/libdl.so.2 (0x009ac000)         libc.so.6 => /lib/tls/libc.so.6 (0x0088f000)         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00876000) […]