技术Tech分类
PHP的Realpath Cache
前言
PHP的缓存有很多种,包括输出缓冲(ob系列函数),opcode缓存(APC,eAccelerator,XCache等扩展实现),这些大家已经很熟悉了,接下来介绍一下一个不太被人注意的PHP缓存机制:realpath_cache。
介绍
require,require_once,include,include_once这四个语句(并非函数)大家经常会用到,如果用这类语句去包含文件(相对路径)的话,那么PHP会去include_path所指定的路径中去查找相关文件。一个应用中会存在大量的require_once语句调用,如果每次调用都去include_path中查找相应的文件,势必会对应用的性能产生负面影响。为了避免这种负面效应产生的影响,PHPER们会使用文件的绝对路径来包含所需的文件,这样就减少了查询include_path的次数。
其实,PHP自5.1.0起,就引入了RealpathCache。RealpathCache可以把PHP所用到文件的realpath进行缓存,以便PHP再使用这些文件的时候不需要再去include_path中查找,加快PHP的执行速度。
配置
realpath cache的配置项有两个,分别为realpath_cache_size和realpath_cache_ttl,可以在php.ini中进行修改:
01 |
; Determines the size of the realpath cache to be used by PHP. This value should |
02 |
; be increased on systems where PHP opens many files to reflect the quantity of |
03 |
; the file operations performed. |
04 |
; http://php.net/realpath-cache-size |
05 |
;realpath_cache_size = 16k |
06 |
07 |
; Duration of time, in seconds for which to cache realpathinformation for a given |
08 |
; file or directory. For systems with rarely changing files, consider increasing this |
09 |
; value. |
10 |
; http://php.net/realpath-cache-ttl |
11 |
;realpath_cache_ttl = 120 |
其中realpath_cache_size指定了realpath cache的大小,默认为16k,如果你觉得这个容量太小,可以适当增加;realpath_cache_ttl指定了缓存的过期时间,默认为120秒,对于不经常修改的生产环境来说,这个数字可以调整的更大些。
问题
由于realpath会展开symlink(即软连接),所以如果你使用修改symlink目标这种方式发布应用的新版本的话,realpath cache会导致一些问题的出现:当你修改symlink使其指向一个新的release目录时候,由于realpath cache所缓存内容还没有过期,于是就会出现应用使用的还是旧的release,直到realpath cache所缓存内容过期失效为止(默认120秒),或者重启php-fpm。
看个例子:
基础环境:nginx + fastcgi + php-fpm
应用环境:/var/www/app是一个symlink,并做为document_root,在/var/www下存在version0.1,version0.2两个版本的release。初始情况下/var/www/app指向version0.1
1 |
lrwxr-xr-x 1 weizhifeng staff 10 10 22 16:41 app -> version0.1 |
2 |
drwxr-xr-x 3 weizhifeng staff 102 10 22 16:43 version0.1 |
3 |
drwxr-xr-x 3 weizhifeng staff 102 10 22 16:43 version0.2 |
version0.1,version0.2内部各有一个hello.php
1 |
[weizhifeng@Jeremys-Mac www]$ cat version0.1/hello.php |
2 |
<?php |
3 |
echo 'in version0.1'; |
4 |
?> |
5 |
6 |
[weizhifeng@Jeremys-Mac www]$ cat version0.2/hello.php |
7 |
<?php |
8 |
echo 'in version0.2'; |
9 |
?> |
nginx配置文件片段:
01 |
location / { |
02 |
root /var/www/app; #app为symlink |
03 |
index index.php index.html index.htm; |
04 |
} |
05 |
06 |
location ~ \.php$ { |
07 |
root /var/www/app; #app为symlink |
08 |
fastcgi_pass 127.0.0.1:9000; |
09 |
fastcgi_index index.php; |
10 |
include fastcgi_params; |
11 |
} |
此时通过HTTP访问hello.php,得到的内容是’in version0.1′;修改/var/www/app,使其指向version0.2
1 |
[weizhifeng@Jeremys-Mac www]$ rm -f app && ln -s version0.2/ app |
修改完成之后通过HTTP访问hello.php,得到的内容仍旧是”in version0.1″,可见是realpath cache在作祟了,此时你可以重启php-fpm或者等待120秒钟让realpath cache失效。
你可以使用clearstatcache来清除realpath cache,但是这个只对当前调用clearstatcache函数的PHP进程有效,而其他的PHP进程还是无效,由于PHP进程池(php-fpm生成,或者Apache在prefork模式下产生的N个httpd子进程)的存在,这个方法不是很适用。
参考:
http://php.net/manual/en/ini.core.php#ini.sect.performance
http://sixohthree.com/1517/php-and-the-realpath-cache
【翻译】PHP扩展编写第一步:PHP和Zend介绍
http://weizhifeng.net/2011/07/03/extension-writing-part-i-introduction-to-php-and-zend/
原文:http://devzone.zend.com/article/1021-Extension-Writing-Part-I-Introduction-to-PHP-and-Zend
介绍
如果你在读这篇入门文章,那么你可能对写PHP扩展有点兴趣。如果不是… 好吧,那么等我们写完这篇文章,你将会发现一个之前自己完全不知道,但是非常有趣的东西。
这篇入门文章假设你对PHP语言和以及PHP的编写语言C语言都有一定的熟悉。
让我们以“为什么你需要写一个PHP扩展”作为开始。
- 因为PHP语言本身抽象程度有限,有一些库或者操作系统级别的调用,不能用PHP直接调用。
- 你想给PHP添加一些与众不同的行为。
- 你已经写了一些PHP代码,但是当运行的时候你知道它可以更快,更小,消耗的内存更少。
- 你有一部分程序想出售,你可以把它写成扩展,这样程序是可以执行的,但是别人却无法看到源码。
这儿有很多完美的原因,但是要想创建一个扩展,你首先要需要明白什么是扩展。
什么是扩展?
如果你用过PHP,那么你就用过扩展。除了一些极少的特殊情况之外,PHP语言中的每个用户空间函数都是以组的形式分布在一个或多个扩展之中。这些函数中的大部分是位于标准扩展中的 – 总共超过400个。PHP源码中包含86个扩展,平均每个扩展中有30个函数。算一下,大概有2500个函数。如果这个不够用,PECL仓库还提供了超过100个其他扩展,或者还可以在互联网上找到更多的扩展。
「PHP除了扩展中的这些函数之外,剩下的是什么」我听到了你的疑问「扩展是什么?PHP的核心又是什么?」
PHP的核心是由两个独立的部分组成的。在最底层是Zend Engine (ZE)。ZE 负责把人类可以理解的脚本解析成机器可以理解的符号(token),然后在一个进程空间内执行这些符号。ZE还负责内存管理,变量作用域,以及函数调用的调度。另一部分是PHP。PHP负责与SAPI层(Server Application Programming Interface,经常被用来与Apache, IIS, CLI, CGI等host环境进行关联)的交互以及绑定。它也为safe_mode和open_basedir检查提供了一个统一的控制层,就像streams层把文件和网络I/O与用户空间函数(例如fopen(),fread()和fwrite())关联起来一样。
生命周期
当一个给定的SAPI启动后,以/usr/local/apache/bin/apachectl start的响应为例,PHP便以初始化它的核心子系统作为开始。随着SAPI启动程序的结束,PHP开始加载每个扩展的代码,然后调用它们的模块初始化(MINIT)程序。这就给每个扩展机会用来初始化内部变量,申请资源,注册资源处理器,并且用ZE注册自己的函数,这样如果一个脚本调用这些函数中的一个,ZE就知道执行哪些代码。
接下来,PHP会等待SAPI层的页面处理请求。在CGI或者CLI SAPI情况下,这个请求会立即发生并且只执行一次。在Apache, IIS, 或者其他成熟的web服务器SAPI中,请求处理会在远程用户发起请求的时候发生,并且会重复执行很多次,也可能是并发的。不管请求是怎么进来的,PHP以让ZE来建立脚本可以运行的环境作为开始,然后调用每个扩展的请求初始化(RINIT)函数。RINIT给了扩展一个机会,让其可以建立指定的环境变量,分配请求指定的资源,或者执行其他任务例如审计。关于RINIT函数调用最典型的例子是在session扩展中,如果session.auto_start选项是开启的,RINIT会自动触发用户空间的session_start()函数并且预先填充$_SESSION变量。
当请求一旦被初始化,ZE便把PHP脚本翻译成符号(token),最终翻译成可以进行单步调试和执行的opcode。如果这些opcode中的一个需要调用一个扩展函数,ZE将会给那个函数绑定参数,并且临时放弃控制权直到函数执行完成。
当一个脚本完成了执行之后,PHP将会调用每个扩展的请求结束(RSHUTDOWN)函数来执行最后的清理工作(比如保存session变量到磁盘上)。接下来,ZE执行一个清理过程(熟知的垃圾回收),实际上是对上次请求过程中使用的变量调用unset()函数。
一旦完成,PHP等待SAPI发起另一个文档请求或者一个关闭信号。在CGI和CLI SAPI的情况下,没有所谓的“下一个请求”,所以SAPI会立刻执行关闭流程。在关闭过程中,PHP又让每个扩展调用自己的模块关闭(MSHUTDOWN)函数,最后关闭自己的核心子系统。
这个过程第一次听令人有些费解,但是一旦你深入到一个扩展的开发过程中,它就会逐渐的清晰起来。
内存分配
为了避免写的很糟糕的扩展泄露内存,ZE以自己内部的方式来进行内存管理,通过用一个附加的标志来指明持久化。一个持久化分配的内存比单个页面请求存在的时间要长。一个非持久化分配的内存,相比之下,在请求结束的时候就会被释放,不管free函数是否被调用。例如用户空间变量,都是非持久化分配的内存,因为在请求结束之后这些变量都没有用了。
一个扩展理论上可以依靠ZE在每个页面请求结束后自动释放非持久化的内存,但这是不被推荐的。在请求结束的时候,分配的内存不会被立即被回收,并且会持续一段时间,所以和那块内存关联的资源将不会被恰当的关闭,这是一个很糟的做法,因为如果不能适当的清理的话,这会产生混乱。就像你即将要看见的,确定所有分配的数据被恰当的清除了是非常的简单。
让我们把常规的内存分配函数(只应该当和内部库一起工作的时候才会用到)和PHP ZE中的持久化和非持久化内存分配函数进行一个对比。
| Traditional | Non-Persistent | Persistent |
|---|---|---|
malloc(count)calloc(count, num) |
emalloc(count)ecalloc(count, num) |
pemalloc(count, 1)*pecalloc(count, num, 1) |
strdup(str)strndup(str, len) |
estrdup(str)estrndup(str, len) |
pestrdup(str, 1)pemalloc() & memcpy() |
free(ptr) |
efree(ptr) |
pefree(ptr, 1) |
realloc(ptr, newsize) |
erealloc(ptr, newsize) |
perealloc(ptr, newsize, 1) |
malloc(count * num + extr)** |
safe_emalloc(count, num, extr) |
safe_pemalloc(count, num, extr) |
* The pemalloc() family include a ‘persistent’ flag which allows them to behave like their non-persistent counterparts.For example: emalloc(1234) is the same as pemalloc(1234, 0)** safe_emalloc() and (in PHP 5) safe_pemalloc() perform an additional check to avoid integer overflows |
||
建立一个开发环境
现在你已经掌握了一些关于PHP和ZE的工作原理,我估计你希望要深入进去,并且开始写些什么。无论如何在你能做之前,你需要收集一些必要的开发工具,并且建立一个满足自己目标的环境。
第一你需要PHP本身,以及构建PHP所需要的开发工具集合。如果你对于从源码编译PHP不熟悉,我建议你看看http://www.php.net/install.unix。(开发windows下的PHP扩展在以后的文章会介绍)。使用适合自己发行版的PHP二进制包是很诱人的,但是这些版本总是会忽略两个重要的
./configure
选项,这两个选项在开发过程中非常方便。第一个是--enable-debug。这个选项将会用附加符号信息来编译PHP所以,如果一个段错误发生,那么你将可以从PHP收集到一个核心dump信息,然后使用gdb来跟踪这个段错误是在哪里发生的,为什么会发生。另一个选项依赖于你将要进行扩展开发的PHP版本。在PHP4.3这个选项叫--enable-experimental-zts,在PHP5和以后的版本中叫--enable-maintainer-zts。这个选项将会让PHP思考在多线程环境中的行为,并且可以让你捕获常见的程序错误,这些错误在非线程环境中不会引起问题,但在多线程环境中却使你的扩展变得不可用。一旦你已经使用这些额外的选项编译好了PHP,并且已经安装在了你的开发服务器(或者工作站)上,那么你可以开始建立你的第一个扩展了。
Hello World
如果一门语言的入门介绍没有Hello World程序,那么这个介绍就是不完整的。在这种情况下,你将会建立一个扩展,这个扩展会导出一个返回”Hello World”字符串的函数。如果用PHP,你可能这么写:
1 |
<?php |
2 |
function hello_world() |
3 |
{ |
4 |
return 'Hello World'; |
5 |
} |
6 |
?> |
现在你将会把这个逻辑放到一个PHP扩展中。首先让我们在你PHP源码树的ext/目录下创建一个名叫hello的目录,并进入(chdir)到这个目录中。这个目录实际上可以放在任何地方,PHP源码树内或者PHP源码树外,但是我希望你把它放在源码树内为了接下来的文章使用。在这你需要创建三个文件:一个包含你hello_world函数的源文件,一个头文件,其中包含PHP加载你扩展时候所需的引用,一个配置文件,它会被phpize用来准备扩展的编译环境。
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello Enable Hello World support])</code>
if test "$PHP_HELLO" = "yes"; then
AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi
php_hello.h
#ifndef PHP_HELLO_H #define PHP_HELLO_H 1 #define PHP_HELLO_WORLD_VERSION "1.0" #define PHP_HELLO_WORLD_EXTNAME "hello" PHP_FUNCTION(hello_world); extern zend_module_entry hello_module_entry; #define phpext_hello_ptr &hello_module_entry #endif
hello.c
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_hello.h"
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
{NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_FUNCTION(hello_world)
{
RETURN_STRING("Hello World", 1);
}
以上只是一个PHP扩展的大体框架,扩展中的大部分代码只是简单的把几个文件关联在了一起。只有最后四句才像你之前在PHP脚本中调用的“实际代码”。实际上这个层级的代码和我们之前看到的PHP代码非常的相似,从字面上很容易理解:
1. 声明一个名叫hello_world的函数
2. 让那个函数返回一个字符串:“Hello World”
3. ….额…. 1? 那个1是做什么的?
回想一下ZE有一个先进的内存管理层,当脚本退出的时候确保分配的资源被释放掉。在内存管理领域,对同一块内存进行两次释放是大错特错的。这种做法叫做double freeing,是引起段错误的常见原因,因为它让程序去访问一个已经不属于自己的内存块。类似的,你不希望让ZE去释放一个静态字符串buffer(就像我们示例扩展中的”Hello World”),因为它是在程序空间,并不是属于任何进程的数据块。RETURN_STRING()假设任何传递给它的字符串都需要一个拷贝,所以它们可以在之后安全的释放掉。但是由于在一个内部函数中为字符串分配内存,动态填充,然后返回它,这是很平常,RETURN_STRING()允许我们来指定是否有必要对这个字符串值进行拷贝。为了更好的解释这个概念,接下来的代码片段的功能和上面的是一样的:
PHP_FUNCTION(hello_world)
{
char *str;
str = estrdup("Hello World");
RETURN_STRING(str, 0);
}
在这个版本中,你手动为”Hello World”字符串分配了内存,最终返回给调用脚本,然后把内存“给了”RETURN_STRING,第二个参数值0说明不需要为这个字符串做拷贝。
建立你的扩展
这个练习的最后一步是把你的扩展编译成一个动态加载的模块。如果你已经正确的拷贝以上的例子,那么这个工作就是在ext/hello/目录下执行三个命令:
1 |
$ phpize |
2 |
$ ./configure --enable-hello |
3 |
$ make |
在运行了这些命令之后,你将会在ext/hello/modules目录中发现一个hello.so文件。现在,可以像其他PHP扩展一样,你可以把它拷贝到你的扩展目录(默认是/usr/local/lib/php/extensions/,检查你的php.ini文件确定一下)中,然后在你的php.ini文件中加上extension=hello.so这一行,让扩展可以在PHP启动的时候被加载。对于CGI/CLI SAPI来说,这个意味着下一次PHP运行的时候就会生效;对于web server SAPI比如Apache来说,这个意味着web server下次被重启的时候生效。现在让我们以命令行的形式做一个尝试:
1 |
$ php -r 'echo hello_world();' |
如果一切正常,你将会看到由这个脚本输出的Hello World,因为在你加载的扩展中已经定义的hello_world()函数会返回Hello World这个字符串,然后echo命令会打印出任何传递给它的东西。
其他标量也可以用类似的函数返回,用RETURN_LONG()返回整型值,RETURN_DOUBLE()返回浮点型值,RETURN_BOOL()返回布尔型值,RETURN_NULL()返回的值,你懂的,NULL。在hello.c文件中的function_entry结构体中加入几行PHP_FE()代码并且在文件最后加入几行PHP_FUNCTION()代码,让我们真实的看看这些函数。
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE(hello_long, NULL)
PHP_FE(hello_double, NULL)
PHP_FE(hello_bool, NULL)
PHP_FE(hello_null, NULL)
{NULL, NULL, NULL}
};
PHP_FUNCTION(hello_long)
{
RETURN_LONG(42);
}
PHP_FUNCTION(hello_double)
{
RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
RETURN_NULL();
}
你还需要在头文件php_hello.h中为这些函数添加原型声明,添加在hello_world()函数原型旁边,这样构建程序就可以恰当的进行宏替换:
PHP_FUNCTION(hello_world); PHP_FUNCTION(hello_long); PHP_FUNCTION(hello_double); PHP_FUNCTION(hello_bool); PHP_FUNCTION(hello_null);
如果你对config.m4文件没有做过更改,那么这次跳过phpize和./configure步骤,直接make,在技术上来说这是安全的。但是无论如何,为了能够没有问题的构建这个扩展,这次我还是想让你完整的走这三个步骤。另外,这次你应该用make clean,而不是上次用的make,从而确保所有源文件都被重新构建。其实这个还是不必要的,因为你做的修改很有限,但是安全比混乱要好。一旦模块构建好了之后,你可以把它拷贝到你的扩展目录下,替换旧的版本。
此时你可以再一次调用PHP解释器,用一个简单的脚本来测试你刚才加的函数。事实上,为什么你现在不做呢?我在这儿等你….
测试好了?很好。如果你使用var_dump()而不是echo来看每个函数的输出的话,你可能会注意到hello_bool()返回的是true。这是RETURN_BOOL()函数中1所代表的值。就像在PHP脚本中,一个整型的0等于FALSE,同时任何其他的整型值等于TRUE。扩展的作者们经常使用1来表示TRUE,也建议你那样做,但是不要拘泥于此。为了添加可读性,RETURN_TRUE和
RETURN_FALSE宏也是可用的;下面是hello_bool()的重写,这次使用RETURN_TRUE:
PHP_FUNCTION(hello_bool)
{
RETURN_TRUE;
}
注意这没有使用括号。RETURN_TRUE和RETURN_FALSE跟其他RETURN_*()宏不一样,所以别搞错了。
你可能注意到在以上代码示例中,我们没有传递0或者1来指定是否这个值需要被拷贝。这是因为对于这些简单的标量来说,并没有额外的内存被分配或者释放。
这还有三个额外的返回类型:RESOURCE(mysql_connect(),fsockopen()和ftp_connect()等函数返回的类型),ARRAY(也就是HASH表),OBJECT(new关键字返回的)。这些类型我们将会在第二部分也就是深入变量的时候来介绍。
INI设置
Zend引擎提供了两种管理INI值的方法。我们现在先看一下简单的方法,之后当你有机会使用全局变量的时候,再看一下更加完整,更加复杂的方法。
假设你想在你的扩展中声明一个php.ini的配置项,hello.greeting,这个值被你的函数hello_world()所使用。你需要对hello_module_entry做些关键的修改,同时还需要在hello.c和php_hello.h中添加些东西。在php_hello.h的用户区函数原型附近添加如下的函数原型:
PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION(hello); PHP_FUNCTION(hello_world); PHP_FUNCTION(hello_long); PHP_FUNCTION(hello_double); PHP_FUNCTION(hello_bool); PHP_FUNCTION(hello_null);
现在到hello.c文件顶部,用以下内容替换掉hello_module_entry的内容:
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()
PHP_MINIT_FUNCTION(hello)
{
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
现在,你只需要在hello.c文件头部的#inlcude代码后面添加一行,从获取对INI文件支持所需要的正确头文件:
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "php_hello.h"
最后,你可以修改你的hello_world函数来使用INI值:
PHP_FUNCTION(hello_world)
{
RETURN_STRING(INI_STR("hello.greeting"), 1);
}
注意,你拷贝了从INI_STR()返回的值。因为这是一个静态的字符串。事实上,如果你尝试去修改INI_STR返回的这个字符串,PHP执行环境将会变得不稳定,甚至会崩溃。
首先要修改的地方是你非常熟悉的两个函数:MINIT,MSHUTDOWN。就像前面提到的,这些函数会在SAPI层初始化启动和最后关闭的时候被调用。他们不会在请求过程中被调用。在这个例子中,你已经用这些函数在你的扩展中注册了php.ini的配置内容。在接下来的内容中,你将会知道如何用MINIT和MSHUTDOWN函数来注册resource,object和stream handler。
在你的hello_world()函数中,你用INI_STR()来获得了hello.greeting当前的值,字符串格式。在下面表格中列出了一些其他函数,这些函数可以返回long,double和Boolean类型的值,并且还有一些带有ORIG标识的更加原始的函数,这些函数返回php.ini中最初设置的值(在被.htaccess文件或者ini_set()修改之前)。
| Current Value | Original Value | Type |
INI_STR(name) |
INI_ORIG_STR(name) |
char * (NULL terminated) |
INI_INT(name) |
INI_ORIG_INT(name) |
signed long |
INI_FLT(name) |
INI_ORIG_FLT(name) |
signed double |
INI_BOOL(name) |
INI_ORIG_BOOL(name) |
zend_bool |
传递给PHP_INI_ENTRY()的第一个参数是在php.ini中使用的配置项名称。为了避免命名空间的冲突,你应该使用跟你函数命名相同的习惯;在所有的配置项之前都加一个和你扩展名字相同的前缀,就像hello.greeting一样。事实上习惯就是,一个“.”把扩展名字和ini配置的名字分开。
第二个参数是初始化值,不管它是否是数字类型的,总是传递char*字符串类型。这是因为事实上.ini文件中的值都是原生的文本类型。你可以在你的脚本中用INI_INT(),INI_FLT(),或者INI_BOOL()来做类型转换。
你传递的第三个值是一个访问模式标识。这是一个掩码字段,用来决定在什么时候,在什么地方这个INI的配置项可以被修改。一些配置项,比如像register_globals,它就不可能在脚本中用ini_set()来进行修改,因为这个配置项只有在请求启动的时候才有意义,也就是脚本根本就没有机会去修改它。其他的,比如像allow_url_fopen,它是管理员级别的配置项,所以你不希望在共享托管环境中的用户去修改它,不管是通过ini_set()还是用.htaccess指令。这个参数常见的值可能是PHP_INI_ALL,表明这个配置项可以在任何地方修改。还有PHP_INI_SYSTEM|PHP_INI_PERDIR,表明配置项可以在php.ini文件或者在.htaccess文件通过Apache的指令来修改,但是不能使用ini_set()来修改。PHP_INI_SYSTEM,表示这个配置项只能在php.ini中修改,不能在其他地方修改。
当前我们将要跳过第四个参数,只是提一下这个参数允许传递一个回调方法,这个方法会在ini配置被修改的时候触发,无论什么时候,比如用ini_set()修改。这就允许一个扩展可以在配置被修改的时候做一些更准确的控制,或者触发一个需要依赖新配置的动作。
全局变量
通常,一个扩展在一个特殊的请求中需要跟踪一个值,并保证这个值与同一时间其他的请求是独立开来的。在一个非线程SAPI中那很简单:在源文件中直接声明一个全局变量,在需要的时候访问它。麻烦是,自从PHP被设计成可以运行在多线程的web服务器上(像Apache2和IIS),所以需要把一个线程使用的全局变量与其他线程使用的全局变量分离开来。PHP用TSRM (Thread Safe Resource Management)抽象层,有时有也叫ZTS (Zend Thread Safety),非常简单的解决了这个问题。
事实上,你已经用过了TSRM的一部分,只是不知道而已。(先别费劲搜索呢;你将会发现这些东西都被隐藏了。)
创建一个线程安全的全局变量的第一步,和其他全局变量都一样,先声明。由于这个例子的缘故,你必须声明一个long类型值为0的全局变量。每次调用hello_long()函数的时候,你将会增加这个值,然后返回它。在php_hello.h中的#define PHP_HELLO_H代码段后面加上以下的代码:
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
这次你还是要使用RINIT方法,所以你需要在头文件中声明它的原型:
PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION(hello); PHP_RINIT_FUNCTION(hello);
现在让我们回到hello.c中,在你的include块后面加上如下内容:
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "php_hello.h" ZEND_DECLARE_MODULE_GLOBALS(hello)
修改hello_module_entry,添加PHP_RINIT(hello):
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_RINIT(hello),
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
并修改你的MINIT函数,和另一对函数一起,用来在请求开始的时候初始化:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
}
PHP_RINIT_FUNCTION(hello)
{
HELLO_G(counter) = 0;
return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
REGISTER_INI_ENTRIES();
return SUCCESS;
}
最后,你可以修改hello_long()函数来使用这个值:
PHP_FUNCTION(hello_long)
{
HELLO_G(counter)++;
RETURN_LONG(HELLO_G(counter));
}
在php_hello.h添加的内容中,你使用了一对宏ZEND_BEGIN_MODULE_GLOBALS()和ZEND_END_MODULE_GLOBALS() – 用来创建一个包含一个long类型,名为zend_hello_globals的结构体。然后你继续声明了HELLO_G()来从一个线程池中获取值,或者只是从全局空间中获取 - 如果你为一个非线程环境编译的话。
在hello.c中你用了ZEND_DECLARE_MODULE_GLOBALS()宏来真正实例化zend_hello_globals结构体为一个真正的全局变量(如果是以非线程安全编译的话),或者一个线程资源池的一个成员。对于一个扩展的作者来说,这个区别我们不需要担心,因为Zend Engine已经为我们处理了这个事情。最后,在MINIT中,你使用了ZEND_INIT_MODULE_GLOBALS()来分配一个线程安全的资源id – 现在不用担心这个东西是什么。
你可能注意到了那个php_hello_init_globals()函数实际上根本没做任何事情,我们想在其中初始化counter为0,而实际上我们是在RINIT中初始化的。为什么?
关键在于这两个函数什么时候被调用。php_hello_init_globals()只有当一个新的进程或者线程启动的时候才会被调用;而与此同时,每个进程可以处理多个请求,所以用这个函数来初始化我们的counter为0的话,那么这个初始化只会在第一个页面请求到达的时候工作。等随后到达这个相同进程的页面请求,得到的仍然是旧的counter值,因此也就不会从0开始计数了。为了让每个单独的页面请求都能初始化counter为0,我们实现了RINIT函数,就像你之前了解的那样,这个函数在每次页面请求的时候都会被调用。我们在这个时候包含了php_hello_init_globals()函数是因为你将会在一段时间后使用它,同时也是由于如果把一个NULL做为初始化函数传递给ZEND_INIT_MODULE_GLOBALS()将会在非线程平台上引起一个段错误。
INI配置项作为全局变量值
如果你回想起之前,一个用PHP_INI_ENTRY()声明的php.ini的配置项被解析成一个字符串值,并且在需要的时候可以用INI_INT(),INI_FLT()和INI_BOOL()转换成对应的类型。
对于一些配置项,存在很多不必要的重复工作,比如配置项的值在一个脚本执行的时候被一遍又一遍的读取。幸运的是可以让ZE以一种特殊的数据类型来存储INI配置项的值,并且只有值改变的时候才执行类型转换。让我们声明另一个INI配置的值,这次是一个Boolean类型,用来标示counter是否增加或者减少。修改php_hello.h文件的MODULE_GLOBALS块为以下内容:
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_ENG_MODULE_GLOBALS(hello)
接下来,修改PHP_INI_BEGIN()块内容从而来声明INI配置项的值:
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()
现在,在init_globals方法中初始化配置项:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}
最后,在hello_long()函数中使用INI配置项的值来决定是否要增加或者减少counter:
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}
这就是全部了。在INI_ENTRY中指定的OnUpdateBool方法将会自动的转换php.ini,.htaccess文件提供的或者在脚本中通过ini_set()设置的值称为TRUE或者FALSE。STD_PHP_INI_ENTRY的最后三个参数是来告诉PHP修改哪个全局变量,我们扩展的全局变量的数据结构,以及这些全局变量被保存到的全局容器的名称。
稳妥的检查
到现在我们的三个文件看起来应该像下面所列的一样。(一些内容已经被移除了,并且规整到一起,只为了易读)
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi
php_hello.h
#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif
hello.c
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(hello)
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE(hello_long, NULL)
PHP_FE(hello_double, NULL)
PHP_FE(hello_bool, NULL)
PHP_FE(hello_null, NULL)
{NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_RINIT(hello),
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}
PHP_RINIT_FUNCTION(hello)
{
HELLO_G(counter) = 0;
return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_FUNCTION(hello_world)
{
RETURN_STRING("Hello World", 1);
}
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}
PHP_FUNCTION(hello_double)
{
RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
RETURN_NULL();
}
接下来是什么?
在这个教程中,我们探寻了一个简单PHP扩展的结构,这个扩展向用户空间增加了函数,返回了值,声明了INI配置,跟踪了一个请求过程中的内部状态。
在下一个话题中,我们将要探寻PHP变量的内部结构,看看它们在一个脚本环境中是什么怎么样被存储,跟踪,以及维护的。当一个函数被调用时候,我们将要使用zend_parse_parameters来接收参数,然后探寻如何返回更复杂的结果,包括这次教程中所提及的数组,对象,以及资源类型。
OpenERP Magento Integration
几个python的模块:
http://bazaar.launchpad.net/~magentoerpconnect-core-editors/magentoerpconnect/trunk_version/files
https://code.launchpad.net/~magentoerpconnect-core-editors/magentoerpconnect/magentoerpconnect-v6
Magento is a feature-rich eCommerce platform built on open-source technology that provides online merchants with unprecedented flexibility and control over the look, content and functionality of their eCommerce store. Magento’s intuitive administration interface features powerful marketing, search engine optimization and catalog-management tools to give merchants the power to create sites that are tailored to their unique business needs. Designed to be completely scalable and backed by Varien’s support network, Magento offers companies the ultimate eCommerce solution.
A new bridge between OpenERP and Magento initiated by Openlabs team and under rapid improvement with the active support of community is now one of the most popular combinations for the ‘best of breed’ approach in ERP e-commerce deployment. This module allows synchronization of Magento with Open ERP. It supports Synchronization of Customer Groups, Product Categories, Product Attribute Sets, Attribute Groups, Product Attributes, Products, Order Statuses, Image Synchronization and many more
The bridge consists of two components: A Magento side extension and an Open ERP side module. The Magento extension is a PHP based web services improvement which is fully written and maintained by Openlabs. The Open ERP module has achieved further modularity and is today the preferred algorithm to quickly integrate Open ERP with other applications. Our latest in the list is a simple integration with Sales Force CRM.
Openlabs is actively involved in the Magento – Open ERP Projects and ever since the development of the new connector we have been helping customers across the globe to migrate and implement it.At Openlabs we are committed to provide an excellent user experience .The following detailed tutorial will guide you to the installation process of – :
-
- Magento (full version)
-
- Magento – Open ERP Connector module contributed by Openlabs
-
- Magento Extension
Step 1: Required bits & pieces
- Required Open ERP Addons (Available for download from this site):
- Module: product
- Module: base_external_referentials
- Module: base_sale_multichannels
- Module: product_m2mcategories
2. Xampp 1.6.4
http://sourceforge.net/projects/xampp/files/XAMPP%20Linux/1.6.4/xampp-linux-1.6.4.tar.gz/download
After copying the addons to the Open ERP addons path,
- Go to a Linux shell and login as the system administrator root:
- For xampp-linux-1.6.4.tar.gz file follow these steps:-
- a)Extract the downloaded archive file to /opt. using sudo tar xvfz xampp-linux-1.6.4.tar.gz -C /opt
3. Open the page http://localhost.It should show xampp on the page. You may get errors like shown below:
Warning: file_get_contents(lang.tmp) [function.file-get-contents]: failed to open stream: Permission denied in /opt/lampp/htdocs/xampp/index.php on line 2
Warning: Cannot modify header information – headers already sent by (output started at /opt/lampp/htdocs/xampp/index.php:2) in /opt/lampp/htdocs/xampp/index.php on line 4
Above Errors can be Removed by following step – :
Change the permissions by using chmod of the lampp folder in /opt to readable and writable. After this change the permissions of file config.inc.php in /opt/lampp/phpmyadmin to “read-only” and restart lampp.
Step2: Installation of Magento full version
1.Download magento-1.3.2.4.tar.gz and extract it to /opt/lampp/htdocs
sudo tar xvfz magento-1.3.2.4.tar.gz -C /opt/lampp/htdocs
3.To check your Magento installation type http://<yourIPaddress>/magento in the browser.
It should display the “Setup for Magento”/”Magento Installation” for the first time and “Magento Demo Store” from the next time.
Note: Use your IP instead of localhost to avoid issues of magento kicking you from the admin login.
If you get an error: innodb engine Open file xamppmysqlbinmy.cnf (Using sudo gedit or sudo nano)
Find code:
- # Comment the following if you are using InnoDB tables
- Skip-innodb
- #innodb_data_home_dir = “/xampplite/mysql/”
- #innodb_data_file_path = ibdata1:10M: autoextend
- #innodb_log_group_home_dir = “/xampplite/mysql/”
- #innodb_log_arch_dir = “/xampplite/mysql/”
- ## You can set._buffer_pool_size up to 50 – 80 %
- ## Of RAM but beware of setting memory usage too high
- #innodb_buffer_pool_size = 16M
- #innodb_additional_mem_pool_size = 2M
- ## Set._log_file_size to 25 % of buffer pool size
- #innodb_log_file_size = 5M
- #innodb_log_buffer_size = 8M
- #innodb_flush_log_at_trx_commit = 1
- #innodb_lock_wait_timeout = 50
Modify to
- #Comment the following if you are using InnoDB tables
- #skip-innodb
- innodb_data_home_dir = “/xampplite/mysql/”
- innodb_data_file_path = ibdata1:10M: autoextend
- innodb_log_group_home_dir = “/xampplite/mysql/”
- innodb_log_arch_dir = “/xampplite/mysql/”
- ## You can set._buffer_pool_size up to 50 – 80 %
- ## Of RAM but beware of setting memory usage too high
- innodb_buffer_pool_size = 16M
- innodb_additional_mem_pool_size = 2M
- ## Set…_log_file_size to 25 % of buffer pool size
- innodb_log_file_size = 5M
- innodb_log_buffer_size = 8M
- innodb_flush_log_at_trx_commit = 1
- innodb_lock_wait_timeout = 50
Reload the magento page, if some error of “mysql.sock not found” comes then change the rights of my.cnf file to read only, than reload the magento page and it should work now.
Step3:Configuring the Bridge
The Magento-Open ERP bridge comprises of two parts:
-
- Open ERP Module
-
- Magento ERP Extension
Installation of Magento extension
For this in magento admin panel go to:
System >> Magento Connect >> Magento Connect Manager
Key in your username and password again and click on the second tab for settings and change preferred state to ‘Beta’. (As of this date the plug-in is beta).Save your settings and select page 1 (Extensions) and paste the following extension key in the box
“Magento-community/Openlabs_OpenERPConnector” without quotes
Now your installation of magento is almost ready to talk to Open ERP
To enable open ERP to access the resources in magento and synchronize, it is necessary to have a web services user. To create a web services user go to : System >> Web Service >>Roles
Create a new role e.g. ‘admin’
Save the user and set resources access as: ALL
Save the role and now create a web services user at: System >> Web Services >> Users
Create a user, save the user and set the assigned role as the newly created role, in the above example ‘admin’
The same settings have to be entered as credentials in the connector frontend in Open ERP at Magento >> Magento Web
The user id will be the newly created user name and password the newly created password of the web services user.
STEP 4 :Configuration of Magento – Open ERP Connector module
1.Ensure that you have at least 1 Product category in your system or create one. (Required)
2.Install the magentoerpconnect module (For how to install a module refer to doc.openerp.com) (Module available at bzr branch lp: magentoerpconnect and/or bzr branch http://bazaar.launchpad.net/~openlabs-akretion-consortium/magentoerpconnect/magentoerpconnect_generic)
3.Go to Magento Connection > Core Settings > Magento Instances
4.Create a new instance by clicking new
5.Give the connection a name (e.g. My local magento)
6.Referential Type (Only 1.3.2.4 is available now)
7.Location: Your magento URL e.g. (http ://< yourIPaddress>/magento) & Default Product category (Magento allows products without category and those will be classified here)
8.Click on Reload Referential Mapping Templates
9.Enter API Username and password defined in step 4 of magento configuration
10.Click on Synchronize Referential Settings
Openlabs provides standard packages for implementation of Magento OpenERP connector with following services, provided by our expert technical team:
- 1.Installation of Open ERP on production server.
- 2.Implementation of Magento Open ERP Connector.
- 3.Configuration & testing of the complete system as per the standard functionality.
- 4.Detailed user guides & user trainings for using the system.
Creating a Weather Widget with XML and Actionscript 3.0
In this tutorial I’m presenting you a little weather widget, that could come handy for travel websites, or our personal page. If you make some changes on the code, you could show a different interface on your website, according to the weather on your city. Pretty cool stuff. We gonna connect weather.com XML data file with our flash widget. Weather.com offers us a free service to do that.
Adobe Flash CS3
Source Files
The first thing we gonna do is login in our weather.com account:
https://registration.weather.com/ursa/profile

Or if we already don’t have an account we could register for free in here:
https://registration.weather.com/ursa/profile/new?

Once we are in our account, we gonna have a page like this one, where on the left we gonna have all the services we are subscribed and on the right, all the available services, the XML data feed is not there, it’s a little bit tricky.

So, we gonna signup to the XML data feed on this page https://registration.weather.com/ursa/xmloap/Where we gonna tell to weather.com in which way we gonna use the data. If it’s for personal purpose, there’s no problem, but if it’s commercial purpose, you have to contact them, but everything is better explained on the mail you are going to receive.

So if our registration it’s ok, we gonna receive an email like this one (check on the spam inbox too, just in case).

The email explains us about the service, and there’s three important things to see in here:
- Where to download the weather.com SDK (http://download.weather.com/web/xml/sdk.zip)
- The weather.com XML Partner ID & License Key
- How to make a properly formatted XML request for the service

On this package you gonna find a PDF, with the license agreement and some useful information about how to make a request for the services, and two folder, one with the weather.com logos, and the other with the weather icons we gonna use on this tutorial as reference. There’s 3 type of icon sizes, for this tutorial we gonna use the 93×93 icons. The icons are not too fancy, so we could download a free cool set of weather icons from here:
http://liquidweather.net/icons.php#iconsets
This iconsets and backgrounds are from the Liquid Weather++ appi.
We could put all our data inside flash, like the code of our city, our partner id, and so on, but it’s gonna be a pain in the ass every time we want to change it. So, it’s much better if we load this type of data externally form an XML file. The XML structure should looks like this:
ARBA0009 XXXXXXXXX XXXXXXXXXX m
Now our hands on AS. We gonna do all the programming in just 2 frames, pretty easy. On the first frame, we load all the data to use it when we call the weather.com xml service. On the second frame, called “weather”, we load the weather.com xml and display the data. So, in our first frame:
- We define the variables we gonna use
- We call the xml that holds our data
- Then we pass the data from the xml to our variables
- And if everything it’s oki, we could go to the weather frame

stop();
//=================
//ini variables
//=================
//we define our variables to be loaded
var city:String;
var par_id:String;
var key:String;
var units:String;
//call the xml with our data
var data_xml_url:String = "data.xml";
var user_data:XML = new XML();
var data_url:URLRequest = new URLRequest(data_xml_url);
var dataLoader:URLLoader = new URLLoader(data_url);
dataLoader.addEventListener(Event.COMPLETE, dataLoaded);
function dataLoaded(e:Event):void
{
user_data = XML(dataLoader.data);
//pass the data from the XML to our variables
city = user_data.city.toString();
par_id = user_data.parid.toString();
key = user_data.key.toString();
units = user_data.units.toString();
//if everything it's oki, we could go to the weather frame
gotoAndStop("weather");
}
To display the different type of weather, we need a movieClip that holds all the possible weathers, there’s not too much, just 47. So, we create a new movieclip and call it “icons_mc”, and inside the icons_mc we import all the icons from the Weather.com SDK icons folder, or from the cool icons from liquidweather. There’s a lil bit differences between the Weather.com SDK icons and the liquidweather icons. On the weather.com SDK, the icons 25 and 44 are N/A, if you gonna use the liquidweather icons, just replace the 25 and 44 icons with the N/A png. Don’t forget to put a stop(); in the first frame of the icons_mc, so the MC start stopped.

Now we could load the data from weather.com. We allow our swf to make calls from the domain “xoap.weather.com”, we make the icons_mc MovieClip invisible, because we don’t know which icon use yet, and we compose the weather xml url with the variables from the frame 1.
stop();
//=================
//allow domains
//=================
Security.allowDomain("*", "xoap.weather.com");
//=================
//ini settings
//=================
icons_mc.visible = false;
//=================
//XML
//=================
var weather_xml_url:String = "http://xoap.weather.com/weather/local/"+city+"?cc=*&link=xoap&par="+par_id+"&key="+key+"&unit="+units;
The xml from weather.com have this structure with some interesting data. As you can see there’s a lot of useful data to load, but we gonna load just the necessary by now. Later you could play with it and make an advanced one.
en_US
C
km
km/h
mb
mm
Buenos Aires, Argentina
1:39 PM
-34.61
-58.37
8:01 AM
5:52 PM
-3
6/27/08 1:00 PM Local Time
Buenos Aires Air Park, Argentina
13
13
Fog
20
1021.0
falling
11
N/A
360
N
82
2.5
2
Low
10
23
Waning Crescent
To display the data, we have three different sprites:
- The icons MovieClip
- A dynamic text field to load the outside temp called “temp_txt”
- A dynamic text filed in the bottom to make a lil speech about how feels to be outside called “info_txt”

Our main interest is on the XML file on the icon number, so we could show the right icon. We load the xml using our well formatted “weather_xml_url”, and traverse the xml three to find what we are looking for.
In this step we:
- we load the temp on the temp_txt text field
- make the icons visible, load the icon number and tell the icons:mc to stopn on thet icon frame
- and finally we set the complementary text
var weather:XML = new XML();
var weather_url:URLRequest = new URLRequest(weather_xml_url);
var weatherLoader:URLLoader = new URLLoader(weather_url);
weatherLoader.addEventListener(Event.COMPLETE, weatherLoaded);
function weatherLoaded(e:Event):void
{
weather = XML(weatherLoader.data);
//we load the temp on the temp_txt text field
temp_txt.text = weather.cc.tmp;
//we make the icons visible, load the icon number and tell the icons:mc to stopn on thet icon frame
icons_mc.visible = true;
var weather_icon:int = Number(weather.cc.icon.toString())+1;
icons_mc.gotoAndStop(weather_icon);
//and finally we set the complementary text
var ud:String = weather.head.ud;
var us:String = weather.head.us;
var city:String = weather.loc.dnam;
var time:String = weather.loc.tm;
var temp:String = weather.cc.tmp;
var flik:String = weather.cc.flik;
var term:String;
if (temp == flik)
{
term = "and it feels like "+flik+" degrees,";
} else {
term = "but it feels like "+flik+" degrees,";
}
var wind_v:String = weather.cc.wind.s;
var wind_gust:String = weather.cc.wind.gust;
var wind_d:String = weather.cc.wind.d;
var wind_t:String = weather.cc.wind.t;
var hmid:String = weather.cc.hmid;
var vis:String = weather.cc.vis;
info_txt.text = "It's "+time+" here in "+city+". It's "+temp+" degrees out there "+term+" the wind blows from the "+wind_t+" at "+wind_v+" "+us+", the humidity is "+hmid+"% and the visibility is "+vis+" "+ud+".";
}

Now we could export our movie and if it’s cold outside, it’s good to be inside.
We gonna need to know the international code of our city, so we could go tohttp://www.weather.com/common/welcomepage/world.html Search for our city, and find the code on our address bar. I know it’s a lil bit tricky but it works fine.

Hope you enjoyed this lil tutorial as much as I enjoyed writing for you.
5 ImageMagick command line examples – part 1
If you have ever wanted to manipulate images under linux you probably have used Gimp. This isn’t your only option and if you want to do things from the command line a better option is to use ImageMagick‘s convert utility.
I’ve put together 5 simple command line examples that I have found useful. This is just a sample of what you can do with convert. To see more examples and get more explanation of options see: ImageMagick v6 Examples.
I started with the following image as a base for all the examples that follow:

1. Text annotations
Example (simple text in static location):
Produces:

Example (text with background at bottom):
Produces:

Look at these examples to see more.
2. Cropping an image
Example:
Produces:

Look at these examples or -crop for more information.
3. Rotate an image
Example:
Produces:

Look at these examples or -rotate for more information.
4. Image montage
Example:
Produces:

Look at these examples to see more.
5. Animation
Example:
convert flower.jpg -resize 100×100 -font courier -fill white -pointsize 20 -annotate +50+50 ‘Frame 2′ flower_frame2.gif
convert flower.jpg -resize 100×100 -font courier -fill white -pointsize 20 -annotate +50+50 ‘Frame 3′ flower_frame3.gif
convert flower.jpg -resize 100×100 -font courier -fill white -pointsize 20 -annotate +50+50 ‘Frame 4′ flower_frame4.gif
convert -delay 100 -size 100×100 \
-page +0+0 flower_frame1.gif \
-page +0+0 flower_frame2.gif \
-page +0+0 flower_frame3.gif \
-page +0+0 flower_frame4.gif \
-loop 0 flower_animation.gif
Produces:

PHP ImageMagick MagickWand Examples
A while back I explained how to compile the ImageMagick extension for PHPand this past week I got around to creating some example code to make some of the command line examples I have in ImageMagick command line examples part 1 and ImageMagick command line examples part 2.
The first step of course is to make sure the MagickWand extension is installed. You will want to verify that it is listed in a phpinfo() call before trying any of these examples. After verifying that you have the extension installed you might want to read an introduction to using MagickWand before looking at these examples. And of course you will want to know where the MagickWand reference documentation is once you are ready to try more.
For more information on the options used in these examples it is best to look at their corresponding command line example.
Example 1: Simple Annotate
// convert flower.jpg -font courier -fill white -pointsize 20 -annotate +50+50 Flower flower_annotate1.jpg
$resource = NewMagickWand();
$dwand = NewDrawingWand();
$pwand = NewPixelWand();
PixelSetColor($pwand, ”white”);
DrawSetFont($dwand,”/usr/share/fonts/default/TrueType/cour.ttf”);
DrawSetFontSize($dwand, 20);
DrawSetFillColor($dwand, $pwand);
MagickReadImage( $resource, ’small_flower.jpg’ );
if( MagickAnnotateImage( $resource, $dwand, 0, 0, 0, ”Flower” ))
{
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
}
else
{
echo MagickGetExceptionString($resource);
}
?>
One note on the above is that I needed to specify the exact location of the font to get it to show up. I believe this isn’t always needed but if you try to leave it out and nothing shows up you should try specifying the full path to the font.
Example 2: Complex Annotate
// convert flower.jpg -fill white -box “#00770080″ -gravity South -pointsize 20 -annotate +0+5 ” Flower ” flower_annotate2.jpg
$resource = NewMagickWand();
$dwand = NewDrawingWand();
$pwand = NewPixelWand();
PixelSetColor($pwand, ”white”);
DrawSetFont($dwand,”/usr/share/fonts/default/TrueType/cour.ttf”);
DrawSetFontSize($dwand, 20);
DrawSetFillColor($dwand, $pwand);
DrawSetGravity($dwand, MW_SouthGravity);
MagickReadImage( $resource, ’small_flower.jpg’ );
if( MagickAnnotateImage( $resource, $dwand, 0, 0, 0, ”Flower” ))
{
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
}
else
{
echo MagickGetExceptionString($resource);
}
?>
Example 3: Crop an Area
// convert flower.jpg -crop 128×128+50+50 flower_crop.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
if( MagickCropImage( $resource, 128, 128, 50, 50 ) )
{
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
}
else
{
echo MagickGetExceptionString($resource);
}
?>
Example 4: Rotate
// convert flower.jpg -rotate 45 flower_rotate45.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickRotateImage( $resource, null, 45 );
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
Example 5: Resize
// convert flower_original.jpg -resize 640×480 flower.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickResizeImage( $resource, 100, 100, MW_QuadraticFilter, 1.0);
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
Example 6: Apply Resharp Filter
// convert flower.jpg -unsharp 1.5×1.0+1.5+0.02 flower_unsharp.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickUnsharpMaskImage( $resource, 1.5, 1.0, 1.5, 0.02 );
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
Example 7: Compress JPG
// convert flower.jpg -quality 80% flower_quality.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickSetFormat($resource, ’JPG’);
MagickSetImageCompression($resource, MW_JPEGCompression);
MagickSetImageCompressionQuality($resource, 80.0);
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
linux 文件系统 btrfs 在 kernel 3.0的支持
安装的gentoo现在用的是keernel 3.0所以顺便研究了btrfs性能改进情况。 具体这篇文章很有价值,是一个很好的入门指引。
如果基于ssd,或者用raid搭建文件系统,btrfs给了我很大的想法。
Btrfs 简介
文件系统似乎是内核中比较稳定的部分,多年来,人们一直使用 ext2/3,ext 文件系统以其卓越的稳定性成为了事实上的 Linux 标准文件系统。近年来 ext2/3 暴露出了一些扩展性问题,于是便催生了 ext4 。在 2008 年发布的 Linux2.6.19 内核中集成了 ext4 的 dev 版本。 2.6.28 内核发布时,ext4 结束了开发版,开始接受用户的使用。似乎 ext 就将成为 Linux 文件系统的代名词。然而当您阅读很多有关 ext4 的文章时,会发现都不约而同地提到了 btrfs,并认为 ext4 将是一个过渡的文件系统。 ext4 的作者 Theodore Tso 也盛赞 btrfs 并认为 btrfs 将成为下一代 Linux 标准文件系统。 Oracle,IBM, Intel 等厂商也对 btrfs 表现出了极大的关注,投入了资金和人力。为什么 btrfs 如此受人瞩目呢。这便是本文首先想探讨的问题。
Kevin Bowling[1] 有一篇介绍各种文件系统的文章,在他看来,ext2/3 等文件系统属于“古典时期”。文件系统的新时代是 2005 年由 Sun 公司的 ZFS 开创的。 ZFS 代表” last word in file system ”,意思是此后再也不需要开发其他的文件系统了。 ZFS 的确带来了很多崭新的观念,对文件系统来讲是一个划时代的作品。
如果您比较 btrfs 的特性,将会发现 btrfs 和 ZFS 非常类似。也许我们可以认为 btrfs 就是 Linux 社区对 ZFS 所作出的回应。从此往后在 Linux 中也终于有了一个可以和 ZFS 相媲美的文件系统。
回页首
btrfs 的特性
您可以在 btrfs 的主页上 [2] 看到 btrfs 的特性列表。我自作主张,将那张列表分成了四大部分。
首先是扩展性 (scalability) 相关的特性,btrfs 最重要的设计目标是应对大型机器对文件系统的扩展性要求。 Extent,B-Tree 和动态 inode 创建等特性保证了 btrfs 在大型机器上仍有卓越的表现,其整体性能而不会随着系统容量的增加而降低。
其次是数据一致性 (data integrity) 相关的特性。系统面临不可预料的硬件故障,Btrfs 采用 COW 事务技术来保证文件系统的一致性。 btrfs 还支持 checksum,避免了 silent corrupt 的出现。而传统文件系统则无法做到这一点。
第三是和多设备管理相关的特性。 Btrfs 支持创建快照 (snapshot),和克隆 (clone) 。 btrfs 还能够方便的管理多个物理设备,使得传统的卷管理软件变得多余。
最后是其他难以归类的特性。这些特性都是比较先进的技术,能够显著提高文件系统的时间 / 空间性能,包括延迟分配,小文件的存储优化,目录索引等。
扩展性相关的特性
B-Tree
btrfs 文件系统中所有的 metadata 都由 BTree 管理。使用 BTree 的主要好处在于查找,插入和删除操作都很高效。可以说 BTree 是 btrfs 的核心。
一味地夸耀 BTree 很好很高效也许并不能让人信服,但假如稍微花费一点儿时间看看 ext2/3 中元数据管理的实现方式,便可以反衬出 BTree 的优点。
妨碍 ext2/3 扩展性的一个问题来自其目录的组织方式。目录是一种特殊的文件,在 ext2/3 中其内容是一张线性表格。如图 1-1 所示 [6]:
图 1. ext2 directory [6]
图 1 展示了一个 ext2 目录文件的内容,该目录中包含四个文件。分别是 “home1″,”usr”,”oldfile” 和 “sbin” 。如果需要在该目录中查找目录 sbin,ext2 将遍历前三项,直至找到 sbin 这个字符串为止。
这种结构在文件个数有限的情况下是比较直观的设计,但随着目录下文件数的增加,查找文件的时间将线性增长。 2003 年,ext3 设计者开发了目录索引技术,解决了这个问题。目录索引使用的数据结构就是 BTree 。如果同一目录下的文件数超过 2K,inode 中的 i_data 域指向一个特殊的 block 。在该 block 中存储着目录索引 BTree 。 BTree 的查找效率高于线性表,
但为同一个元数据设计两种数据结构总是不太优雅。在文件系统中还有很多其他的元数据,用统一的 BTree 管理是非常简单而优美的设计。
Btrfs 内部所有的元数据都采用 BTree 管理,拥有良好的可扩展性。 btrfs 内部不同的元数据由不同的 Tree 管理。在 superblock 中,有指针指向这些 BTree 的根。如图 2 所示:
图 2. btrfs btree
FS Tree 管理文件相关的元数据,如 inode,dir 等; Chunk tree 管理设备,每一个磁盘设备都在 Chunk Tree 中有一个 item ; Extent Tree 管理磁盘空间分配,btrfs 每分配一段磁盘空间,便将该磁盘空间的信息插入到 Extent tree 。查询 Extent Tree 将得到空闲的磁盘空间信息; Tree of tree root 保存很多 BTree 的根节点。比如用户每建立一个快照,btrfs 便会创建一个 FS Tree 。为了管理所有的树,btrfs 采用 Tree of tree root 来保存所有树的根节点; checksum Tree 保存数据块的校验和。
基于 Extent 的文件存储
现代很多文件系统都采用了 extent 替代 block 来管理磁盘。 Extent 就是一些连续的 block,一个 extent 由起始的 block 加上长度进行定义。
Extent 能有效地减少元数据开销。为了进一步理解这个问题,我们还是看看 ext2 中的反面例子。
ext2/3 以 block 为基本单位,将磁盘划分为多个 block 。为了管理磁盘空间,文件系统需要知道哪些 block 是空闲的。 Ext 使用 bitmap 来达到这个目的。 Bitmap 中的每一个 bit 对应磁盘上的一个 block,当相应 block 被分配后,bitmap 中的相应 bit 被设置为 1 。这是很经典也很清晰的一个设计,但不幸的是当磁盘容量变大时,bitmap 自身所占用的空间也将变大。这就导致了扩展性问题,随着存储设备容量的增加,bitmap 这个元数据所占用的空间也随之增加。而人们希望无论磁盘容量如何增加,元数据不应该随之线形增加,这样的设计才具有可扩展性。
下图比较了 block 和 extent 的区别:
图 3. 采用 extent 的 btrfs 和采用 bitmap 的 ext2/3
在 ext2/3 中,10 个 block 需要 10 个 bit 来表示;在 btrfs 中则只需要一个元数据。对于大文件,extent 表现出了更加优异的管理性能。
Extent 是 btrfs 管理磁盘空间的最小单位,由 extent tree 管理。 Btrfs 分配 data 或 metadata 都需要查询 extent tree 以便获得空闲空间的信息。
动态 inode 分配
为了理解动态 inode 分配,还是需要借助 ext2/3 。下表列举了 ext2 文件系统的限制:
表 1. ext2 限制
限制
最大文件数量 文件系统空间大小 V / 8192
比如 100G 大小的文件系统中,能创建的文件个数最大为 131072
图 4 显示了 ext2 的磁盘布局:
图 4. ext2 layout
在 ext2 中 inode 区是被预先固定分配的,且大小固定,比如一个 100G 的分区中,inode table 区中只能存放 131072 个 inode,这就意味着不可能创建超过 131072 个文件,因为每一个文件都必须有一个唯一的 inode 。
为了解决这个问题,必须动态分配 inode 。每一个 inode 只是 BTree 中的一个节点,用户可以无限制地任意插入新的 inode,其物理存储位置是动态分配的。所以 btrfs 没有对文件个数的限制。
针对 SSD 的优化支持
SSD 是固态存储 Solid State Disk 的简称。在过去的几十年中,CPU/RAM 等器件的发展始终遵循着摩尔定律,但硬盘 HDD 的读写速率却始终没有飞跃式的发展。磁盘 IO 始终是系统性能的瓶颈。
SSD 采用 flash memory 技术,内部没有磁盘磁头等机械装置,读写速率大幅度提升。 flash memory 有一些不同于 HDD 的特性。 flash 在写数据之前必须先执行擦除操作;其次,flash 对擦除操作的次数有一定的限制,在目前的技术水平下,对同一个数据单元最多能进行约 100 万次擦除操作,因此,为了延长 flash 的寿命,应该将写操作平均到整个 flash 上。
SSD 在硬件内部的微代码中实现了 wear leveling 等分布写操作的技术,因此系统无须再使用特殊的 MTD 驱动和 FTL 层。虽然 SSD 在硬件层面做了很多努力,但毕竟还是有限。文件系统针对 SSD 的特性做优化不仅能提高 SSD 的使用寿命,而且能提高读写性能。 Btrfs 是少数专门对 SSD 进行优化的文件系统。 btrfs 用户可以使用 mount 参数打开对 SSD 的特殊优化处理。
Btrfs 的 COW 技术从根本上避免了对同一个物理单元的反复写操作。如果用户打开了 SSD 优化选项,btrfs 将在底层的块空间分配策略上进行优化:将多次磁盘空间分配请求聚合成一个大小为 2M 的连续的块。大块连续地址的 IO 能够让固化在 SSD 内部的微代码更好的进行读写优化,从而提高 IO 性能。
数据一致性相关的特性
COW 事务
理解 COW 事务,必须首先理解 COW 和事务这两个术语。
什么是 COW?
所谓 COW,即每次写磁盘数据时,先将更新数据写入一个新的 block,当新数据写入成功之后,再更新相关的数据结构指向新 block 。
什么是事务?
COW 只能保证单一数据更新的原子性。但文件系统中很多操作需要更新多个不同的元数据,比如创建文件需要修改以下这些元数据:
修改 extent tree,分配一段磁盘空间
创建一个新的 inode,并插入 FS Tree 中
增加一个目录项,插入到 FS Tree 中
任何一个步骤出错,文件便不能创建成功,因此可以定义为一个事务。
下面将演示一个 COW 事务。
A 是 FS Tree 的根节点,新的 inode 的信息将被插入节点 C 。首先,btrfs 将 inode 插入一个新分配的 block C ’中,并修改上层节点 B,使其指向新的 block C ’;修改 B 也将引发 COW,以此类推,引发一个连锁反应,直到最顶层的 Root A 。当整个过程结束后,新节点 A ’变成了 FS Tree 的根。但此时事务并未结束,superblock 依然指向 A 。
图 5. COW transaction 1
接下来,修改目录项(E 节点),同样引发这一过程,从而生成新的根节点 A ’’。
图 6. COW transaction 2
此时,inode 和目录项都已经写入磁盘,可以认为事务已经结束。 btrfs 修改 superblock,使其指向 A ’’,如下图所示:
图 7. COW transaction 3
COW 事务能够保证文件系统的一致性,并且系统 Reboot 之后不需要执行 fsck 。因为 superblock 要么指向新的 A ’’,要么指向 A,无论哪个都是一致的数据。
Checksum
Checksum 技术保证了数据的可靠性,避免 silent corruption 现象。由于硬件原因,从磁盘上读出的数据会出错。比如 block A 中存放的数据为 0×55,但读取出来的数据变是 0×54,因为读取操作并未报错,所以这种错误不能被上层软件所察觉。
解决这个问题的方法是保存数据的校验和,在读取数据后检查校验和。如果不符合,便知道数据出现了错误。
ext2/3 没有校验和,对磁盘完全信任。而不幸的是,磁盘的错误始终存在,不仅发生在廉价的 IDE 硬盘上,昂贵的 RAID 也存在 silent corruption 问题。而且随着存储网络的发展,即使数据从磁盘读出正确,也很难确保能够安全地穿越网络设备。
btrfs 在读取数据的同时会读取其相应的 checksum 。如果最终从磁盘读取出来的数据和 checksum 不相同,btrfs 会首先尝试读取数据的镜像备份,如果数据没有镜像备份,btrfs 将返回错误。写入磁盘数据之前,btrfs 计算数据的 checksum 。然后将 checksum 和数据同时写入磁盘。
Btrfs 采用单独的 checksum Tree 来管理数据块的校验和,把 checksum 和 checksum 所保护的数据块分离开,从而提供了更严格的保护。假如在每个数据 block 的 header 中加入一个域保存 checksum,那么这个数据 block 就成为一个自己保护自己的结构。这种结构下有一种错误无法检测出来,比如本来文件系统打算从磁盘上读 block A,但返回了 block B,由于 checksum 在 block 内部,因此 checksum 依旧是正确的。 btrfs 采用 checksum tree 来保存数据块的 checksum,避免了上述问题。
Btrfs 采用 crc32 算法计算 checksum,在将来的开发中会支持其他类型的校验算法。为了提高效率,btrfs 将写数据和 checksum 的工作分别用不同的内核线程并行执行。
多设备管理相关的特性
每个 Unix 管理员都曾面临为用户和各种应用分配磁盘空间的任务。多数情况下,人们无法事先准确地估计一个用户或者应用在未来究竟需要多少磁盘空间。磁盘空间被用尽的情况经常发生,此时人们不得不试图增加文件系统空间。传统的 ext2/3 无法应付这种需求。
很多卷管理软件被设计出来满足用户对多设备管理的需求,比如 LVM 。 Btrfs 集成了卷管理软件的功能,一方面简化了用户命令;另一方面提高了效率。
多设备管理
Btrfs 支持动态添加设备。用户在系统中增加新的磁盘之后,可以使用 btrfs 的命令将该设备添加到文件系统中。
为了灵活利用设备空间,Btrfs 将磁盘空间划分为多个 chunk 。每个 chunk 可以使用不同的磁盘空间分配策略。比如某些 chunk 只存放 metadata,某些 chunk 只存放数据。一些 chunk 可以配置为 mirror,而另一些 chunk 则可以配置为 stripe 。这为用户提供了非常灵活的配置可能性。
Subvolume
Subvolume 是很优雅的一个概念。即把文件系统的一部分配置为一个完整的子文件系统,称之为 subvolume 。
采用 subvolume,一个大的文件系统可以被划分为多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,类似应用程序调用 malloc() 分配内存一样。可以称之为存储池。这种模型有很多优点,比如可以充分利用 disk 的带宽,可以简化磁盘空间的管理等。
所谓充分利用 disk 的带宽,指文件系统可以并行读写底层的多个 disk,这是因为每个文件系统都可以访问所有的 disk 。传统的文件系统不能共享底层的 disk 设备,无论是物理的还是逻辑的,因此无法做到并行读写。
所谓简化管理,是相对于 LVM 等卷管理软件而言。采用存储池模型,每个文件系统的大小都可以自动调节。而使用 LVM,如果一个文件系统的空间不够了,该文件系统并不能自动使用其他磁盘设备上的空闲空间,而必须使用 LVM 的管理命令手动调节。
Subvolume 可以作为根目录挂载到任意 mount 点。 subvolume 是非常有趣的一个特性,有很多应用。
假如管理员只希望某些用户访问文件系统的一部分,比如希望用户只能访问 /var/test/ 下面的所有内容,而不能访问 /var/ 下面其他的内容。那么便可以将 /var/test 做成一个 subvolume 。 /var/test 这个 subvolume 便是一个完整的文件系统,可以用 mount 命令挂载。比如挂载到 /test 目录下,给用户访问 /test 的权限,那么用户便只能访问 /var/test 下面的内容了。
快照和克隆
快照是对文件系统某一时刻的完全备份。建立快照之后,对文件系统的修改不会影响快照中的内容。这是非常有用的一种技术。
比如数据库备份。假如在时间点 T1,管理员决定对数据库进行备份,那么他必须先停止数据库。备份文件是非常耗时的操作,假如在备份过程中某个应用程序修改了数据库的内容,那么将无法得到一个一致性的备份。因此在备份过程中数据库服务必须停止,对于某些关键应用这是不能允许的。
利用快照,管理员可以在时间点 T1 将数据库停止,对系统建立一个快照。这个过程一般只需要几秒钟,然后就可以立即重新恢复数据库服务。此后在任何时候,管理员都可以对快照的内容进行备份操作,而此时用户对数据库的修改不会影响快照中的内容。当备份完成,管理员便可以删除快照,释放磁盘空间。
快照一般是只读的,当系统支持可写快照,那么这种可写快照便被称为克隆。克隆技术也有很多应用。比如在一个系统中安装好基本的软件,然后为不同的用户做不同的克隆,每个用户使用自己的克隆而不会影响其他用户的磁盘空间。非常类似于虚拟机。
Btrfs 支持 snapshot 和 clone 。这个特性极大地增加了 btrfs 的使用范围,用户不需要购买和安装昂贵并且使用复杂的卷管理软件。下面简要介绍一下 btrfs 实现快照的基本原理。
如前所述 Btrfs 采用 COW 事务技术,从图 1-10 可以看到,COW 事务结束后,如果不删除原来的节点 A,C,E,那么 A,C,E,D,F 依然完整的表示着事务开始之前的文件系统。这就是 snapshot 实现的基本原理。
Btrfs 采用引用计数决定是否在事务 commit 之后删除原有节点。对每一个节点,btrfs 维护一个引用计数。当该节点被别的节点引用时,该计数加一,当该节点不再被别的节点引用时,该计数减一。当引用计数归零时,该节点被删除。对于普通的 Tree Root, 引用计数在创建时被加一,因为 Superblock 会引用这个 Root block 。很明显,初始情况下这棵树中的所有其他节点的引用计数都为一。当 COW 事务 commit 时,superblock 被修改指向新的 Root A ’’,原来 Root block A 的引用计数被减一,变为零,因此 A 节点被删除。 A 节点的删除会引发其子孙节点的引用计数也减一,图 1-10 中的 B,C 节点的引用计数因此也变成了 0,从而被删除。 D,E 节点在 COW 时,因为被 A ’’所引用,计数器加一,因此计数器这时并未归零,从而没有被删除。
创建 Snapshot 时,btrfs 将的 Root A 节点复制到 sA,并将 sA 的引用计数设置为 2 。在事务 commit 的时候,sA 节点的引用计数不会归零,从而不会被删除,因此用户可以继续通过 Root sA 访问 snapshot 中的文件。
图 8. Snapshot
软件 RAID
RAID 技术有很多非常吸引人的特性,比如用户可以将多个廉价的 IDE 磁盘组合为 RAID0 阵列,从而变成了一个大容量的磁盘; RAID1 和更高级的 RAID 配置还提供了数据冗余保护,从而使得存储在磁盘中的数据更加安全。
Btrfs 很好的支持了软件 RAID,RAID 种类包括 RAID0,RAID1 和 RAID10.
Btrfs 缺省情况下对 metadata 进行 RAID1 保护。前面已经提及 btrfs 将设备空间划分为 chunk,一些 chunk 被配置为 metadata,即只存储 metadata 。对于这类 chunk,btrfs 将 chunk 分成两个条带,写 metadata 的时候,会同时写入两个条带内,从而实现对 metadata 的保护。
其他特性
Btrfs 主页上罗列的其他特性不容易分类,这些特性都是现代文件系统中比较先进的技术,能够提高文件系统的时间或空间效率。
Delay allocation
延迟分配技术能够减少磁盘碎片。在 Linux 内核中,为了提高效率,很多操作都会延迟。
在文件系统中,小块空间频繁的分配和释放会造成碎片。延迟分配是这样一种技术,当用户需要磁盘空间时,先将数据保存在内存中。并将磁盘分配需求发送给磁盘空间分配器,磁盘空间分配器并不立即分配真正的磁盘空间。只是记录下这个请求便返回。
磁盘空间分配请求可能很频繁,所以在延迟分配的一段时间内,磁盘分配器可以收到很多的分配请求,一些请求也许可以合并,一些请求在这段延迟期间甚至可能被取消。通过这样的“等待”,往往能够减少不必要的分配,也有可能将多个小的分配请求合并为一个大的请求,从而提高 IO 效率。
Inline file
系统中往往存在大量的小文件,比如几百个字节或者更小。如果为其分配单独的数据 block,便会引起内部碎片,浪费磁盘空间。 btrfs 将小文件的内容保存在元数据中,不再额外分配存放文件数据的磁盘块。改善了内部碎片问题,也增加了文件的访问效率。
图 9. inline file
上图显示了一个 BTree 的叶子节点。叶子中有两个 extent data item 元数据,分别用来表示文件 file1 和 file2 所使用的磁盘空间。
假设 file1 的大小仅为 15 个字节; file2 的大小为 1M 。如图所示,file2 采用普通的 extent 表示方法:extent2 元数据指向一段 extent,大小为 1M,其内容便是 file2 文件的内容。
而对于 file1, btrfs 会把其文件内容内嵌到元数据 extent1 中。如果不采用 inline file 技术。如虚线所示,extent1 指向一个最小的 extent,即一个 block,但 file1 有 15 个字节,其余的空间便成为了碎片空间。
采用 inline 技术,读取 file1 时只需要读取元数据 block,而无需先读取 extent1 这个元数据,再读取真正存放文件内容的 block,从而减少了磁盘 IO 。
得益于 inline file 技术,btrfs 处理小文件的效率非常高,也避免了磁盘碎片问题。
目录索引 Directory index
当一个目录下的文件数目巨大时,目录索引可以显著提高文件搜索时间。 Btrfs 本身采用 BTree 存储目录项,所以在给定目录下搜索文件的效率是非常高的。
然而,btrfs 使用 BTree 管理目录项的方式无法同时满足 readdir 的需求。 readdir 是 POSIX 标准 API,它要求返回指定目录下的所有文件,并且特别的,这些文件要按照 inode number 排序。而 btrfs 目录项插入 BTree 时的 Key 并不是 Inode number,而是根据文件名计算的一个 hash 值。这种方式在查找一个特定文件时非常高效,但却不适于 readdir 。为此,btrfs 在每次创建新的文件时,除了插入以 hash 值为 Key 的目录项外,还同时插入另外一种目录项索引,该目录项索引的 KEY 以 sequence number 作为 BTree 的键值。这个 sequence number 在每次创建新文件时线性增加。因为 Inode number 也是每次创建新文件时增加的,所以 sequence number 和 inode number 的顺序相同。以这种 sequence number 作为 KEY 在 BTree 中查找便可以方便的得到一个以 inode number 排序的文件列表。
另外以 sequence number 排序的文件往往在磁盘上的位置也是相邻的,所以以 sequence number 为序访问大量文件会获得更好的 IO 效率。
压缩
大家都曾使用过 zip,winrar 等压缩软件,将一个大文件进行压缩可以有效节约磁盘空间。 Btrfs 内置了压缩功能。
通常人们认为将数据写入磁盘之前进行压缩会占用很多的 CPU 计算时间,必然降低文件系统的读写效率。但随着硬件技术的发展,CPU 处理时间和磁盘 IO 时间的差距不断加大。在某些情况下,花费一定的 CPU 时间和一些内存,但却能大大节约磁盘 IO 的数量,这反而能够增加整体的效率。
比如一个文件不经过压缩的情况下需要 100 次磁盘 IO 。但花费少量 CPU 时间进行压缩后,只需要 10 次磁盘 IO 就可以将压缩后的文件写入磁盘。在这种情况下,IO 效率反而提高了。当然,这取决于压缩率。目前 btrfs 采用 zlib 提供的 DEFALTE/INFLATE 算法进行压缩和解压。在将来,btrfs 应该可以支持更多的压缩算法,满足不同用户的不同需求。
目前 btrfs 的压缩特性还存在一些不足,当压缩使能后,整个文件系统下的所有文件都将被压缩,但用户可能需要更细粒度的控制,比如针对不同的目录采用不同的压缩算法,或者禁止压缩。我相信,btrfs 开发团队将在今后的版本中解决这个问题。
对于某些类型的文件,比如 jpeg 文件,已经无法再进行压缩。尝试对其压缩将纯粹浪费 CPU 。为此,当对某文件的若干个 block 压缩后发现压缩率不佳,btrfs 将不会再对文件的其余部分进行压缩操作。这个特性在某种程度上提高了文件系统的 IO 效率。
预分配
很多应用程序有预先分配磁盘空间的需要。他们可以通过 posix_fallocate 接口告诉文件系统在磁盘上预留一部分空间,但暂时并不写入数据。如果底层文件系统不支持 fallocate,那么应用程序只有使用 write 预先写一些无用信息以便为自己预留足够的磁盘空间。
由文件系统来支持预留空间更加有效,而且能够减少磁盘碎片,因为所有的空间都是一次分配,因而更有可能使用连续的空间。 Btrfs 支持 posix_fallocate 。
总结
至此,我们对 btrfs 的很多特性进行了较为详细的探讨,但 btrfs 能提供的特性却并不止这些。 btrfs 正处于试验开发阶段,还将有更多的特性。
Btrfs 也有一个重要的缺点,当 BTree 中某个节点出现错误时,文件系统将失去该节点之下的所有的文件信息。而 ext2/3 却避免了这种被称为”错误扩散”的问题。
但无论怎样,希望您和我一样,开始认同 btrfs 将是 Linux 未来最有希望的文件系统。
回页首
BTRFS 使用简介
了解了 btrfs 的特性,想必您一定想亲身体验一下 btrfs 的使用。本章将简要介绍如何使用 btrfs 。
创建文件系统
mkfs.btrfs 命令建立一个 btrfs 格式的文件系统。可以用如下命令在设备 sda5 上建立一个 btrfs 文件系统,并将其挂载到 /btrfsdisk 目录下:
#mkfs.btrfs /dev/sda5
#mkdir /btrfsdisk
#mount – t btrfs /dev/sda5 /btrfsdisk
这样一个 Btrfs 就在设备 sda5 上建立好了。值得一提的是在这种缺省情况下,即使只有一个设备,Btrfs 也会对 metadata 进行冗余保护。如果有多个设备,那么您可以在创建文件系统的时候进行 RAID 设置。详细信息请参见后续的介绍。
这里介绍其他几个 mkfs.btrfs 的参数。
Nodesize 和 leafsize 用来设定 btrfs 内部 BTree 节点的大小,缺省为一个 page 大小。但用户也可以使用更大的节点,以便增加 fanout,减小树的高度,当然这只适合非常大的文件系统。
Alloc-start 参数用来指定文件系统在磁盘设备上的起始地址。这使得用户可以方便的预留磁盘前面的一些特殊空间。
Byte-count 参数设定文件系统的大小,用户可以只使用设备的一部分空间,当空间不足时再增加文件系统大小。
修改文件系统的大小
当文件系统建立好之后,您可以修改文件系统的大小。 /dev/sda5 挂载到了 /btrfsdisk 下,大小为 800M 。假如您希望只使用其中的 500M,则需要减小当前文件系统的大小,这可以通过如下命令实现:
#df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 101086 19000 76867 20% /boot
/dev/sda5 811248 32 811216 1% /btrfsdisk
#btrfsctl – r -300M /btrfsdisk
#df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 101086 19000 76867 20% /boot
/dev/sda5 504148 32 504106 1% /btrfsdisk
同样的,您可以使用 btrfsctl 命令增加文件系统的大小。
创建 Snapshot
下面的例子中,创建快照 snap1 时系统存在 2 个文件。创建快照之后,对 test1 的内容进行修改。再回到 snap1,打开 test1 文件,可以看到 test1 的内容依旧是之前的内容。
#ls /btrfsdisk
test1 test2
#vi test1
This is a test
#btrfsctl – s snap1 /btrfsdisk
#vi test1
Test1 is modified
#cd /btrfsdisk/snap1
#cat test1
This is a test
可以从上面的例子看到,快照 snap1 保存的内容不会被后续的写操作所改变。
创建 subvolume
使用 btrfs 命令,用户可以方便的建立 subvolume 。假设 /btrfsdisk 已经挂载到了 btrfs 文件系统,则用户可以在这个文件系统内创建新的 subvolume 。比如建立一个 /sub1 的 subvolume,并将 sub1 挂载到 /mnt/test 下:
#mkdir /mnt/test
#btrfsctl – S sub1 /btrfsdisk
#mount – t btrfs – o subvol=sub1 /dev/sda5 /mnt/test
Subvolme 可以方便管理员在文件系统上创建不同用途的子文件系统,并对其进行一些特殊的配置,比如有些目录下的文件关注节约磁盘空间,因此需要打开压缩,或者配置不同的 RAID 策略等。目前 btrfs 尚处于开发阶段,创建的 subvolme 和 snapshot 还无法删除。此外针对 subvolume 的磁盘 quota 功能也未能实现。但随着 btrfs 的不断成熟,这些功能必然将会进一步完善。
创建 RAID
mkfs 的时候,可以指定多个设备,并配置 RAID 。下面的命令演示了如何使用 mkfs.btrfs 配置 RAID1 。 Sda6 和 sda7 可以配置为 RAID1,即 mirror 。用户可以选择将数据配置为 RAID1,也可以选择将元数据配置为 RAID1 。
将数据配置为 RAID1,可以使用 mkfs.btrfs 的 -d 参数。如下所示:
#mkfs.btrfs – d raid1 /dev/sda6 /dev/sda7
#mount – t btrfs /dev/sda6 /btrfsdisk
添加新设备
当设备的空间快被使用完的时候,用户可以使用 btrfs-vol 命令为文件系统添加新的磁盘设备,从而增加存储空间。下面的命令向 /btrfsdisk 文件系统增加一个设备 /sda8
#btrfs-vol – a /dev/sda8 /btrfsdisk
SSD 支持
用户可以使用 mount 参数打开 btrfs 针对 SSD 的优化。命令如下:
#mount – t btrfs – o SSD /dev/sda5 /btrfsdisk
开启压缩功能
用户可以使用 mount 参数打开压缩功能。命令如下:
#mount – t btrfs – o compress /dev/sda5 /btrfsdisk
同步文件系统
为了提高效率,btrfs 的 IO 操作由一些内核线程异步处理。这使得用户对文件的操作并不会立即反应到磁盘上。您可以做一个实验,在 btrfs 上创建一个文件后,稍等 5 到 10 秒将系统电源切断,再次重启后,新建的文件并没有出现。
对于多数应用这并不是问题,但有些时候用户希望 IO 操作立即执行,此时就需要对文件系统进行同步。下面的 btrfs 命令用来同步文件系统:
#btrfsctl – c /btrfsdisk
Debug 功能
Btrfs 提供了一定的 debug 功能,对于想了解 Btrfs 内部实现原理的读者,debug 将是您最喜欢的工具。这里简单介绍一下 debug 功能的命令使用。
下面的命令将设备 sda5 上的 btrfs 文件系统中的元数据打印到屏幕上。
#btrfs-debug-tree /dev/sda5
通过对打印信息的分析,您将能了解 btrfs 内部各个 BTree 的变化情况,从而进一步理解每一个文件系统功能的内部实现细节。
比如您可以在创建一个文件之前将 BTree 的内容打印出来,创建文件后再次打印。通过比较两次的不同来了解 btrfs 创建一个文件需要修改哪些元数据。进而理解 btrfs 内部的工作原理。
回页首
结束语
我想,在未来的日子里,EXT 等传统文件系统肯定不会消失。古典文件系统成熟,稳定,经受了历史的考验,必然将在很长一段时间内被继续广泛使用。古典文件系统和新一代文件系统同时并存是非常合理的事情。正如我们在被陈亦迅的歌声触动和感伤之后,还可以在莫扎特的奏鸣曲中得到慰藉和平静。
本人水平有限,对文件系统的了解也并不深入,因此文中必然有很多错误的地方,希望能得到大家的指正。
参考资料
On file system 是一篇非常不错的关于 Linux 文件系统的文章。
btrfs wiki 也是不错的参考资料。
在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们 最受欢迎的文章和教程。
在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。
关于作者
刘明,从事嵌入式软件开发,热爱开源软件。喜欢学习和使用 linux,目前致力于数据库方面的工作和研究
wordpress 作为cms的短板
信息的趋势:
1 动态信息和外部交互(wp需要插件扩展,但是业务流程的扩展在api支持下较为欠缺)
2 系统不支持多语言(这个问题使其只能是blog,不能作为cms,即使是multi-blog也无法解决内容冗余问题)
3 系统的流程部分太呆板,无法流程定制。网站的业务逻辑有限
4 系统的后台围绕内容,没有有效分离内容的各个职能, 很难定义不同内容
symfony2 框架更新
这个框架在ror的时代活到现在,可见生命力旺盛, 但是框架和应用是需要互相呼应的,不然系统的框架再好也没法得到体现。
http://symfony.com
