linux 下 php 连接mssql 相关【转】

2012年5月17日 没有评论

转载请注明出处 作者:阿东 来源:代码人生 www.code-life.com

Linux下使PHP支持MSSQL(SQL Server),需要先安装FreeTDS。
PHP官方网站:http://www.php.net 当前版本5.26
FreeTDS官方网站:http://www.freetds.org 当前版本0.82

1. 编译FreeTDS

下载FreeTDS并解压,进入该目录。
./configure –prefix=/usr/local/freetds
make
make install

2. 编译PHP

使PHP支持MSSQL,可以使用两种方式,一种是直接编译进PHP,一种是编译成PHP可载入的模块。如果还没有编译安装PHP,建议使用前一种方式,因为这样PHP的执行速度快。如果已经安装了PHP,又嫌重新编译PHP麻烦,用后一种方式吧。

(1) 编译进PHP

如何编译PHP,请参考LAMP环境配置(http://www.code-life.com/read.php?50)。要增加对MSSQL的支持,只要在编译参数上加上–with-mssql=/usr/local/freetds即可。

(2) 编译成模块

cd /path/to/php/source 进入PHP源码目录
cd ext/mssql 进入MSSQL模块源码目录
/usr/local/php/bin/phpize 生成编译配置文件
./configure –with-php-config=/usr/local/php/bin/php-config –with-mssql=/usr/local/freetds
make
make install
编译完成生成mssql.so,修改php.ini,将该模块载入:
extension=”/path/to/extension/mssql.so”

!! IMPORTANT 找不到FreeTDS编译失败的解决

在编译配置的时候可能会遇到找不到FreeTDS的错误:
configure: error: Directory /usr/local/freetds is not a FreeTDS installation directory
按照官方的解释(http://www.freetds.org/news.html),PHP检测一些文件以确定FreeTDS是否安装,由于新版的FreeTDS不再安装这些文件,导致PHP编译失败。只需建立两个空文件即可:
touch /usr/local/freetds/include/tds.h
touch /usr/local/freetds/lib/libtds.a

PS:使用心得

634 wget http://www.ibiblio.org/pub/Linux/ALPHA/freetds/stable/release_candidates/freetds-0.91RC2.tar.gz
635 tar zxvf freetds-0.91RC2.tar.gz
636 cd freetds-0.91RC2
637 ./configure –prefix=/usr/local/freetds –with-tdsver=8.0
638 make
639 make install
640 ./configure –prefix=/usr/local/freetds –with-tdsver=8.0
641 /usr/local/php5/bin/phpize
642 ./configure –with-php-config=/usr/local/php5/bin/php-config –with-mssql=/usr/local/freetds/
643 cd /usr/local/php5/
644 /usr/local/php5/bin/
645 cd /usr/local/php5/bin/phpize
646 /usr/local/php5/bin/phpize
647 cd /home/tools/
648 ll
649 cd php-5.2.17
650 ll
651 cd /home/tools/php-5.2.17/ext/mssql/
652 /usr/local/php5/bin/phpize
653 ./configure –with-php-config=/usr/local/php5/bin/php-config –with-mssql=/usr/local/freetds/
654 make
系统报错 缺失文件 ~ 等待项目V0.1版本开发完毕后 研究

分类: 经验之道, 网络转载 标签:

windows下用PHP连接SQLSERVER2008相关资料【转】

2012年5月17日 没有评论

最近由于工作需要用于SQL Server,想做一个测试环境,SQL Server 2008不是刚出来,图个新鲜,就下载了2008版的,用了很多方法一直没能连接上SQL Server 2008。在网上找了一些资料,终于连接上了SQL Server 2008,整理了一下收集到的资料,利用中午休息时间,写个贴子发出来,让大家看看。呵呵!!

PHP自带的MSSQL扩展php_mssql.dll原来是给SQL Server 2000用的,难怪连接不上2008?! -_-!!要使用SQL Server 2005以上版本,就要用到微软为PHP提供的最新第三方扩展SQL Server Driver for PHP才行,最新版本是2.0的,下载地址:

SQL Server Driver for PHP 1.1 (2009-10)

http://www.microsoft.com/downloads/details.aspx?familyid=CCDF728B-1EA0-48A8-A84A-5052214CAAD9&displaylang=en

SQL Server Driver for PHP 2.0 CTP1(2010-04)

http://www.microsoft.com/downloads/details.aspx?familyid=DF4D9CC9-459C-4D75-A503-AE3FCEB85860&displaylang=en

或者:http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx
想看中文详细使用资料可参考MSDN的,地址:

http://msdn.microsoft.com/zh-cn/library/cc296170(SQL.90).aspx

先来复习一下怎么样用PHP自带的扩展php_mssql.dll连接SQL Server 2000,用mssql系列函数来操作,和操作MySQL的一样。
1、打开PHP配置文件php.ini,找到;extension=php_mssql.dll把前面的分号去掉。
2、找到mssql.secure_connection = Off修改成 mssql.secure_connection = On。
3、把PHP文件夹下的php_mssql.dll和ntwdblib.DLL这两个文件拷贝到C:\windows\system32\文件夹下。
4、重启Apache,OK

测试一下:

//用mssql_connect
$conn=mssql_connect(‘localhost’,'pandao’,’1987′) or die(‘数据库连接不上’);
mssql_select_db(‘test’,$conn);
//query语句
$Query=”select * from news”;
$AdminResult=mssql_query($Query);
//输出结果
$Num=mssql_num_rows($AdminResult);
for($i=0;$i {
$Row=mssql_fetch_array($AdminResult);
echo($Row[1]);
echo(”
“);
}
?>
复制代码
当然还可以用pdo和ODBC来连接SQL Server。

准备工作

下载驱动程序,我下载的是2.0的,下载后安装释放程序,里面有以下文件:
php_pdo_sqlsrv_52_nts.dll
php_pdo_sqlsrv_52_ts.dll
php_pdo_sqlsrv_53_nts_vc6.dll
php_pdo_sqlsrv_53_nts_vc9.dll
php_pdo_sqlsrv_53_ts_vc6.dll
php_pdo_sqlsrv_53_ts_vc9.dll
php_sqlsrv_52_nts.dll
php_sqlsrv_52_ts.dll
php_sqlsrv_53_nts_vc6.dll
php_sqlsrv_53_nts_vc9.dll
php_sqlsrv_53_ts_vc6.dll
php_sqlsrv_53_ts_vc9.dll
SQLServerDriverForPHP.chm(手册,英文够好的话,可以看看,嘿嘿)
SQLServerDriverForPHP_License.rtf
SQLServerDriverForPHP_Readme.htm(自述文件)

下面是自述文件里的内容,我翻译一下:

驱动程序文件
PHP 版本
线程安全性
用于PHP .dll
php_sqlsrv_53_nts_vc6.dll
php_pdo_sqlsrv_53_nts_vc6.dll
5.3
no
php5.dll
php_sqlsrv_53_nts_vc9.dll
php_pdo_sqlsrv_53_nts_vc9.dll
5.3
no
php5.dll
php_sqlsrv_53_ts_vc6.dll
php_pdo_sqlsrv_53_ts_vc6.dll
5.3
yes
php5ts.dll
php_sqlsrv_53_ts_vc9.dll
php_pdo_sqlsrv_53_ts_vc9.dll
5.3
yes
php5ts.dll
php_sqlsrv_52_nts.dll
php_pdo_sqlsrv_52_nts.dll
5.2
no
php5.dll
php_sqlsrv_52_ts.dll
php_pdo_sqlsrv_52_ts.dll
5.2
yes
php5ts.dll

关于VC6和VC9的区别,还有非线程安全与线程安全

VC6 版本是使用 Visual Studio 6 编译器编译的,如果你是在windows下使用Apache+PHP的,请选择VC6版本。
VC9 版本是使用 Visual Studio 2008 编译器编译的,如果你是在windows下使用IIS+PHP的,请选择VC9版本。
Non Thread Safe就是非线程安全,在执行时不进行线程(Thread)安全检查;
Thread Safe 是线程安全,执行时会进行线程(Thread)安全检查,以防止有新要求就启动新线程的 CGI 执行方式而耗尽系统资源;

开始配置

我用的是APMserv 搭建的PHP环境(Apache 2.2.9+PHP 5.2.6+MySQL 5.1.28),想用线程安全的,所以选择php_sqlsrv_52_ts.dll和php_pdo_sqlsrv_52_ts.dll。
开始工作,先把文件拷贝到PHP文件夹下的ext目录下,然后在配置文件php.ini
的Extensions后面加上:
;extension=php_sqlsrv_52_ts.dll
;extension=php_pdo_sqlsrv_52_ts.dll
再重启Apache。
最后测试一下是否成功,看一下phpinfo的sqlsrv

下载 (16.53 KB)
sqlsrv
2010-6-5 13:13

如图所示这样就表示连接成功了!!开始工作喽!

连接数据库

使用过SQL Server的人应该都清楚,SQL Server常用的有两种认证方式,一种是本地系统账户认证(Windows Authentication ),一种是使用用户名和密码(SQL Server Authentication ),第二种认证方式必须启用SQL Server的混合模式。

1.Windows Authentication连接部分代码段

“test”,”ConnectionPooling”=>false);
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn == false)
{
echo “连接失败!”;
die( print_r( sqlsrv_errors(), true));
}
?>
复制代码

2.SQL Server Authentication连接部分代码段

$uid, “PWD”=>$pwd, “Database”=>”test”);
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn == false)
{
echo “连接失败!”;
die( print_r( sqlsrv_errors(), true));
}
$query = sqlsrv_query($conn, “SELECT TOP 10 nid,title,content FROM test.dbo.news”);
while($row = sqlsrv_fetch_array($query)){
echo $row['nid'].”—–”.$row['title'].”
“;
}
?>
复制代码

由于我电脑没装SQL Server 2005,所以没有测试,SQL Server Driver for PHP是给2005和2008用的,应该是一样的,大家在时间可以测试一下,看是不是一样。
微软为PHP新增加了操作SQL Server 2005/2008的函数sqlsrv系列,下面是sqlsrv API,用法跟操作MySQL的基本一样,可以参考学习一下。
在PHPchina混了N久了,还是第一次在PHPchina发原创贴,在下文笔不行,写得不好,欢迎大家拍砖!
相关参考文章:
微软提供给PHP5的SQL Server扩展: sqlsrv (转载)
PHP连接SQL Server 2005 和 SQL Server 2008

API 参考 (SQL Server Driver for PHP)

用于 SQL Server Driver for PHP 的 API 名称是 sqlsrv。所有 sqlsrv函数都以 sqlsrv_打头,后跟动词或名词。后跟动词的函数用于执行特定操作,而后跟名词的函数用于返回特定形式的元数据。
SQL Server Driver for PHP 包含以下函数:

函数 说明
sqlsrv_begin_transaction 开始事务。
sqlsrv_cancel 取消语句;并放弃相应语句的所有未决结果。
sqlsrv_client_info 提供有关客户端的信息。
sqlsrv_close 关闭连接。释放与相应连接关联的所有资源。
sqlsrv_commit 提交事务。
sqlsrv_configure 更改错误处理和日志记录配置。
sqlsrv_connect 创建一个连接,并将其打开。
sqlsrv_errors 返回关于上一操作的错误和/或警告信息。
sqlsrv_execute 执行预定义语句。
sqlsrv_fetch 使下一行的数据可供读取。
sqlsrv_fetch_array 以数值索引数组、关联数组或这两种数组的形式检索下一行的数据。
sqlsrv_fetch_object 以对象形式检索下一行的数据。
sqlsrv_field_metadata 返回字段元数据。
sqlsrv_free_stmt 关闭语句。释放与相应语句关联的所有资源。
sqlsrv_get_config 返回指定配置设置的值。
sqlsrv_get_field 按索引检索当前行中的字段。可以指定 PHP 返回类型。
sqlsrv_has_rows 检测结果集是否具有一行或多行。
sqlsrv_next_result 使下一结果可供处理。
sqlsrv_num_rows 报告结果集中的行数。
sqlsrv_num_fields 检索活动结果集中的字段数。
sqlsrv_prepare 准备 Transact-SQL 查询,但不执行该查询。隐式绑定参数。
sqlsrv_query 准备 Transact-SQL 查询,并将其执行。
sqlsrv_rollback 回滚事务。
sqlsrv_rows_affected 返回有所修改的行的数目。
sqlsrv_send_stream_data 在每次调用函数时向服务器发送最多八千字节 (8 KB) 的数据。
sqlsrv_server_info 提供有关服务器的信息。

另可参考 PHP手册

概念

SQLSRV 常量

其他资源

SQL Server Driver for PHP 介绍
编程指南
入门

PS:经测试这个扩展支持 MSSQL 2000/2005/2008全系列,这样完全可以不用PHP原来自带的php_mssql.dll了,而且最新的PHP5.3也没带php_mssql.dll了 2010-06-28

PS:使用心得
php_pdo_sqlsrv_52_nts.dll
php_pdo_sqlsrv_52_ts.dll
php_pdo_sqlsrv_53_nts_vc6.dll
php_pdo_sqlsrv_53_nts_vc9.dll
php_pdo_sqlsrv_53_ts_vc6.dll
php_pdo_sqlsrv_53_ts_vc9.dll
php_sqlsrv_52_nts.dll
php_sqlsrv_52_ts.dll
php_sqlsrv_53_nts_vc6.dll
php_sqlsrv_53_nts_vc9.dll
php_sqlsrv_53_ts_vc6.dll
php_sqlsrv_53_ts_vc9.dll

上述压缩包内文件 放置PHP目录后 开启php.ini的对应配置参数 发现 无法解析
后来回忆记得本机上安装的PHP版本是VC9的(php-5.3.5-Win32-VC9-x86.zip) 因此修改了资料中的配置参数的部分内容
将VC6-》VC9 连接顺利 GOOD

分类: 经验之道, 网络转载 标签:

浅析 PHP 官方自动化测试方法(转)

2012年4月12日 没有评论

PHP 官方自动化测试方法简述

转载自http://www.ibm.com/developerworks/cn/opensource/os-cn-php-autotest/?S_TACT=105AGX52&S_CMP=tec-csdn

以下介绍以 PHP 最新官方版本 5.2.8 的源码在 LINUX 系统平台上的分析为例展开。首先来看一下 PHP 的自动化测试脚本 PHPT 脚本。

自动化测试脚本 PHPT 示例

PHP 的测试脚本是以“ .phpt ”为后缀,包含 TEST,FILE,EXPECT 等多个段落的文件,简称 PHPT 。在各个段落中,TEST,FILE,EXPECT 是基本的段落,每个测试脚本都必须至少包括这三个段落。其中,TEST 段可以用来填写测试用例的名字; FILE 段是一个 PHP 脚本实现的测试用例; EXPECT 段则是测试用例的期待值。测试用例的运行中,PHP 将用被测试的 PHP 可执行对象去运行 FILE 段中的测试用例,用实际的结果去比对测试用例中 EXPECT 段所列的期待值;如果实际结果和期待值一致,则测试通过;如果不一致,则测试失败。

表 1 列出的是常用的段落名和其相应的填充内容说明。
表 1. PHP 测试脚本中的段落说明

段落名 填充内容 备注
TEST 测试用例名称 必填段落
ARGS FILE 段的输入参数 选填段落
SKIPIF 跳过这个测试的条件 选填段落
POST 传入测试脚本的 POST 变量 选填段落。如果使用 POST 段,建议配合使用 SKIPIF 段,如:
–SKIPIF–
<?php if (php_sapi_name()==’cli’) echo ‘skip’; ?>
GET 传入测试脚本的 GET 变量 选填段落。如果使用 POST 段,建议配合使用 SKIPIF 段,如:
–SKIPIF–
<?php if (php_sapi_name()==’cli’) echo ‘skip’; ?>
INI 应用于测试脚本的 ini 设置 选填段落。例如 foo=bar 。其值可通过函数 ini_get(string name_entry) 获得。
FILE 测试脚本语句 必填段落。应用 PHP 语言书写的脚本语句。其执行的结果将与 EXPECT* 段的期待值做对比。
EXPECT 测试脚本的期待值 必填段落
EXPECTF 测试脚本的期待值,可用函数 sscanf() 中的格式表达期待值 EXPECT 段的变体
EXPECTREGEX 测试脚本的期待值,可用正则式表达期待值 EXPECT 段的变体

 

以官方包里自带的测试脚本 “ 001.phpt ” 为例(见清单 1),从 TEST 段的内容看来这是一个对 PHP 版本进行验证的测试用例。 SKIPIF 段的内容写在了 “ skipip.inc ” 文件里。在 FILE 段里,测试用例将环境变量 TEST_PHP_EXECUTABLE 里设置的那个 PHP 的版本打印出来,这个结果将和 EXPECTF 中的字串进行格式匹对。
清单 1. PHPT 测试脚本 “ 001.phpt ” 示例

			--TEST--
 version string
 --SKIPIF--
 <?php include "skipif.inc"; ?>
 --FILE--
 <?php 

 $php = getenv('TEST_PHP_EXECUTABLE'); 

 var_dump(`$php -n -v`); 

 echo "Done\n";
 ?>
 --EXPECTF--
 string(%d) "PHP %s (cli) (built: %s)%s
 Copyright (c) 1997-20%d The PHP Group
 Zend Engine v%s, Copyright (c) 1998-20%d Zend Technologies
 "
 Done

 

如何运行 PHP 自动化测试脚本

在运行测试脚步前,首先要将被测试的 PHP 源码编译为可执行对象。

然后要导入若干环境变量。表 2 中介绍了主要的几个环境变量如何设置。
表 2. PHP 自动化测试中的环境变量设置

环境变量名 环境变量值 例子
TEST_PHP_EXECUTABLE 设定被测试对象 PHP,或者 “ auto ” 。当设置 “ auto ” 时,如果是 CGI 模式,即为 “ ./sapi/cgi/php-cgi ” ;如果是 CLI 模式,即为 “ ./sapi/cli/php ” 。 TEST_PHP_EXECUTABLE=
$HOME/php-5.2.8/sapi/cli/php
TEST_PHP_DETAILED 设定是否需要详细的日志输出。设置值为 1 或者 0 。 TEST_PHP_DETAILED=1
TEST_PHP_USER 设定是否需要特制的用户目录。 TEST_PHP_DETAILED= “ /usr/test1 ”
TEST_PHP_LOG_FORMAT 设定日志的格式。设置值为 “ LEOD ” 子串的子集。其中 L 代表测试后需要生成 “ .log ” 文件,E 代表 “ .exp ” ,O 代表 “ .out ” ,D 代表 “ .diff ” 。 TEST_PHP_LOG_FORMAT=”LD”

 

在本例中,在 Bash 环境中设置环境变量如下:
清单 2. 设置环境变量示例

export HOME=/home/user_dir/
 export TEST_PHP_EXECUTABLE=$HOME/php-5.2.8/sapi/cli/php
 export TEST_PHP_DETAILED=1
 export TEST_PHP_LOG_FORMAT="LEOD"

 

经过这样设置后,被测试的 PHP 可执行对象就是放在目录 “ $HOME/php-5.2.8/sapi/cli/ ” 下编译好的那个 “ php ” 可执行文件。

执行测试前,还需将测试脚本 PHPT 编辑好,存为 “ .phpt ” 文件。这里以官方包里自带的测试脚本 “ 001.phpt ” 为例,运行如下:
清单 3. PHPT 测试总结报告实例

bash-2.03$ cd $HOME/php-5.2.8/
 bash-2.03$ $HOME/php-5.2.8/sapi/cli/php run-tests.php  \
 $HOME/php-5.2.8/sapi/cli/tests/001.phpt

 

如果该测试用例的实际输出与期待值一致,则在屏幕上输出测试结果如下:
清单 4. PHPT 测试总结报告实例

=====================================================================
 CWD         : /home/user_dir/php-5.2.8/sapi/cli/php
 PHP         : /home/user_dir/php-5.2.8/sapi/cli/php
 PHP_SAPI    : cli
 PHP_VERSION : 5.2.8
 ZEND_VERSION: 2.1.0
 PHP_OS      : Linux rhas05 2.6.9-55.ELhugemem #1
                       SMP Fri Apr 20 17:20:11 EDT 2007 i686 i686 i386 GNU/Linux
 INI actual  :
 More .INIs  :
 Extra dirs  :
 =====================================================================
 Running selected tests.
 PASS Test version string [001.phpt]
 =====================================================================
 Number of tests :    1                 1
 Tests skipped   :    0 (  0.0%) --------
 Tests warned    :    0 (  0.0%) (  0.0%)
 Tests failed    :    0 (  0.0%) (  0.0%)
 Tests passed    :    1 (100.0%) (100.0%)
 ---------------------------------------------------------------------
 Time taken      :    0 seconds
 =====================================================================

 

如果该测试失败了,则除了屏幕输出失败结果外,当前运行目录下还会生成若干文件,以供用户分析测试失败的原因。生成的日志文件的种类是由环境变量 TEST_PHP_LOG_FORMAT 设定的,详见表 2 。如果设定的是 “ LEOD ” 则生成日志文件包括表 3 中列出的五种文件。
表 3. PHP 自动化测试的输出脚本文件

日志文件名 日志文件内容 对应的TEST_PHP_LOG_FORMAT里的设置
001.out 运行测试语句后得到的实际输出结果。 O
001.exp 脚本中的期待结果,即测试脚本中 EXPECT* 段的内容。 E
001.log 实际运行的输出结果和脚本中的期待结果,即 “ .exp ” 和 “ .out ” 的合集。 L
001.diff 实际运行的输出结果和脚本中的期待结果通过 diff 命令得到比对后的结果。 D
001.php 实际执行的 PHP 测试语句,在测试中解析测试脚本的 FILE 段得到。 总会生成

 

上面的例子是一次只运行一个脚本。 PHP 也支持多测试脚本一起运行。表 4 列出了三种 PHP 支持的测试方式。
表 4. PHP 自动化测试方式

测试方式 参数 举例
只执行单个测试用例 测试脚本名 001.phpt 002.phpt
执行某个目录下的测试用例 测试用例的目录名 test_dir/
执行某个文件中列出的测试用例 -r 加列举测试用例的文件名 -r record_file

 

回页首

PHP 自动化测试框架的原理和实现

清单 3中的运行示例中,实际的执行语句是 “ $HOME/php-5.2.8/sapi/cli/php run-tests.php $HOME/php-5.2.8/sapi/cli/tests/001.phpt ” 。其中,“ 001.phpt ” 是测试脚本;“ run-tests.php ” 为 PHP 测试的驱动脚本,是官方脚本;而 “ $HOME/php-5.2.8/sapi/cli/php ” 是运行驱动脚本的 PHP 可执行对象。这里需要指出的一点是,环境变量 TEST_PHP_EXECUTABLE 中设置的 PHP 可执行对象和这里运行测试脚本中的 “ $HOME/php-5.2.8/sapi/cli/php ” 虽然指向的是同一个可执行对象,但其意义是不一样的。环境变量 TEST_PHP_EXECUTABLE 中设置的 PHP 是被测试对象,而运行测试中使用的 “ $HOME/php-5.2.8/sapi/cli/php ” 只是为了用来驱动测试脚本运行。

在图 1 中可以具体的看出 PHP 测试过程中两个 PHP 可执行对象及两个 PHP 脚本的关系。在测试的过程中,首先是由 “ $HOME/php-5.2.8/sapi/cli/php ” 去运行脚本 “ run-tests.php ” (第 1 步)。 在 “ run-tests.php ” 脚本中,将解析环境变量的值 “ TEST_PHP_EXECUTABLE =$HOME/php-5.2.8/sapi/cli/php ” (第 2 步)。这里 PHP 可执行对象是被测试的 PHP 。“ run-tests.php ” 脚本中还将把测试脚本 “ 001.phpt ” 中的 FILE 段解析为 PHP 脚本 “ 001.php ” (第 3 步), 用 “ TEST_PHP_EXECUTABLE ” 设置的那个 PHP 可执行对象去执行 “ 001.php ” (第 4 步)并得到实际输出结果,在比对实际输出结果和 EXPECT 段的期待结果后,输出测试结果(第 5 步)。以上运行过程可以从对 “ run-tests.php ” 脚本的分析中得出。
图 1. PHP 测试运行关系图
PHP 测试运行关系图

通过以上实例分析可以看出,PHP 官方测试的自动化主要依赖于 “ run-tests.php ” 脚本和测试用例脚本 PHPT 。而 “ run-tests.php ” 脚本正是 PHP 测试自动化框架的搭建者。从代码清单 5 中,可以略微看出 PHP 自动化测试框架的主要工作。
清单 5. PHP 自动化测试代码片段

$test_cnt = count($test_files); 

	 if ($test_cnt) {
		 putenv('NO_INTERACTION=1');
		 verify_config();
		 write_information($html_output);
		 usort($test_files, "test_sort");
		 $start_time = time(); 

		 if (!$html_output) {
			 echo "Running selected tests.\n";
		 } else {
			 show_start($start_time);
		 } 

		 $test_idx = 0;
		 run_all_tests($test_files, $environment);
		 $end_time = time(); 

		 if ($html_output) {
			 show_end($end_time);
		 } 

		 if ($failed_tests_file) {
			 fclose($failed_tests_file);
		 } 

		 if (count($test_files) || count($test_results)) {
			 compute_summary();
			 if ($html_output) {
			 fwrite($html_file, "<hr/>\n" . get_summary(false, true));
			 }
			 echo "=================================================";
			 echo get_summary(false, false);
		 } 

		 if ($html_output) {
			 fclose($html_file);
		 } 

		 if ($output_file != '' && $just_save_results) {
			 save_or_mail_results();
		 } 

        if (getenv('REPORT_EXIT_STATUS') == 1 and preg_match('/FAILED(?: |$)/',   \
                    implode(' ', $test_results))) {
			 exit(1);
		 }

 

在测试前,这个自动化测试的框架按照用户指定的有效测试文件数目来决定具体将有多少个测试用例,并写入 test_cnt 变量中以备计数使用。函数 verify_config() 分别用来验证和准备各种环境变量的设置及 ini 设置。函数 write_information() 将输出本次测试环境的各种信息,包括被测试 PHP 的版本号,使用的扩展( Extention ),ZEND 版本,INI 设置,测试所在的系统版本信息,以及测试时间记录。

测试用例的运行发生在函数 run_all_tests() 中调用的函数 run_test() 中。 run_test() 将具体解析测试脚本中各个段落的含义,清除所以上次测试的记录与设置将干净的测试环境准备完毕,并把各种中间文件和日志文件准备好,然后用环境变量 TEST_PHP_EXECUTABLE 指定的 PHP 可执行对象运行实际的测试语句。最后将运行后的结果和测试脚本中期待值进行比对,如果比对失败,则将结果信息一一记录到用户设置的日志文件中。

在所有的测试都运行结束后,变量 end_time 将记录测试的结束时间,并用函数 compute_summary() 计算成功的、失败的、跳过的等各种情况的测试数目,并将结果输出。

由此可以总结出,这个自动化测试的框架主要包括如下几个部分:

  1. 测试前的准备:包括环境准备以及测试脚本的解析等,如对上次测试遗留下的环境的清理,本次测试所必须的环境变量的读取与设置,对测试参数的解析,测试脚本名的解析,各种输出文件的准备等等。
  2. 测试中的脚本运行:解析测试脚本中的各个段落,组织出测试语句,执行测试语句,得到实际运行结果。
  3. 测试后的结果比对及输出:测试后完成实际输出结果和期待值的比对,包括各种格式化的比对和正则表达式方式的比对。按要求将结果存写入指定的文件中,并输出测试结果的总结报告。

回页首

PHP 自动化测试方法的应用

了解了 PHP 官方自动化测试的运作方法后,用户可以创建自己的 PHPT 测试脚本以帮助 PHP 进行函数黑盒测试。然而,应用 PHP 官方自动化测试的思想和框架搭建方法,用户还可以构建自己的自动化测试工具,以应用于其他程序开发测试中。在定制自动化测试框架时,需要注意的一些问题,而这些问题在 PHP 官方自动化测试框架中就得到了很好的解决。比如:

  1. 测试脚本的结构。 PHP 官方自动化测试框架只需使用一个简单的 PHPT 脚本,就可完成测试用例所有必须信息的生成。 PHPT 脚本中的段落使测试名,测试条件,测试步骤,期待值等信息一目了然,非常有利于阅读和分析。当测试人员在测试过程中需要分析某个测试的失败原因时,只需要打开对应的 PHPT 脚本就能够清楚地理解测试用例的用意以及具体测试语句。从而大大减少测试人员的分析时间和难度。而且,这种段落式的组织是具有可扩展性的。
  2. 测试环境的保证。在测试中,环境的“干净”与否直接影响到测试结果的准确性。好的自动化框架要力争给测试一个“干净”的环境。这里的环境包括:系统设置的环境变量,用户设置的环境变量,测试运行中可能用到的临时文件等。如果前一个测试改变了全局的环境,那么在这个环境里运行后一个测试所得到的结果就是不准确的。不准确的测试结果可能带来极高的误报率。
  3. 测试结果对比中的艺术。在将测试的实际输出结果和期待结果进行比对中,PHP 官方自动化测试方法巧妙地应用了正则式比对和格式比对方法。这两种方法的引入使得测试的误报率大大降低。因为如果没有正则式比对和格式比对方法,则测试人员只能将当前本次测试期待结果记录在期待值中,而这种期待值将可能成为测试脚本的硬码( hard code ),在回归测试或期待结果是随机值等情况下造成测试的误失败。
  4. 保留充分的日志信息。测试结束后,对于那些失败的测试,测试人员要分析失败的原因。此时,测试日志将发挥很重要的作用。充分的日志信息保留了测试失败的现场,将帮助测试人员尽快找到失败的原因并报告给开发人员。 PHP 官方自动化测试框架给予用户灵活的设置方式去选择需要的日志种类。在各种日志中,不仅将实际测试结果保留在了 “ .out ” 日志文件中,还将 “ .diff ” 留给用户。“ .diff ” 文件能帮助测试人员更快地发现期待值与实际测试结果的差别,以更快发现测试失败的问题所在。

然而,在 PHP 官方自动化测试框架中也存在一些对于用户来说不完美的地方,比如:在测试异常的情况下,不能输出完整的测试总结报告。如果测试人员需要完成一个长达十个小时的测试,而测试在第九小时发生了异常,但测试却无法给出所有跑过的测试的测试报告,这将是很恼人的。所以在自动化测试框架的实现中要考虑好测试异常情况下的处理。

回页首

结束语

PHP 官方自动化测试方法给黑盒测试人员提供了一个很好的自动化测试框架的范例。测试人员不仅可以利用 PHPT 测试脚本完善对 PHP 的测试,更可以通过学习这个范例构建自己的自动化测试框架和工具,让测试的自动化更好地服务于测试工作。

 

参考资料

  • 参考 PHP-QAT, 了解更多 PHP 官方测试项目内容。
  • 参考 wikipedia,了解更多关于测试自动化的知识。
  • 了解 PHP,请参考其官方网站:http://www.php.net
  • 查看 IBM developerWorks 的 PHP 项目资源 来扩展您的 PHP 技能。
  • 访问 developerWorks 开放源码专区,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。

关于作者

林容容,IBM 软件工程师,曾从事 PHP 在 IBM Z 系统上的开发和测试。现从事 IBM 存储产品的测试工作。


分类: 网络转载 标签:

1 Billion queries per minute and much more – free webinar on MySQL Cluster 7.2 GA

2012年2月23日 没有评论

Oracle announced the General Availability of MySQL Cluster 7.2 today. Join this live webinar to learn about what’s new in the production-ready, GA release of MySQL Cluster 7.2, enabling the latest generation of web and telecoms applications to take advantage of high write scalability, SQL and NoSQL interfaces and 99.999% availability, including:

  • Performance enhancements delivering 1 billion queries per minute, using just 8 data nodes
  • 70x higher JOIN performance with Adaptive Query Localization, enabling real-time analytics across live data sets
  • New NoSQL API via Memcached, creating a persistent, key-value datastore for schema and schemaless data
  • Auto-sharding across data centers with synchronous replication for scaling of highly available, global services
  • Simplified ease-of-use with new options for on-premise and cloud deployments
  • Integration with the latest MySQL 5.5 GA release

The webinar takes place on Thursday 23rd February at 09:00 PST, 17:00 GMT, 18:00 CET. Mat Keep and I will be presenting.

As always, the webinar is free but you’ll need to register here in advance - even if you can’t make the live event, this will make sure that you get emailed a link to the recording.

分类: 网络转载 标签:

简述Linux文件搜索(转载)

2012年2月23日 没有评论

转一篇搜索命令的 方便有需要的同学

简述Linux文件搜索

作者:北南南北
来自:LinuxSir.Org
摘要:本文简单介绍了搜索文件的方法,一是通过文件名来查看文件所处的位置,二是在一个文件或输出中查找指定的内容。

目录

1 、关于搜索;
2、通过搜索文件名来查找文件; 

3、在一个文件或输出中查找;
4、关于本文;
5、参考文档;
6、相关文档;


+++++++++++++++++++++++++++++++++++++++
正文
+++++++++++++++++++++++++++++++++++++++


1 、关于搜索;

在Linux文件系统中,搜索概念有两种,一种是搜索文件名,另一种是在一个文件中搜索指定的内容;这两种搜索,我们都简要的介绍一下,但不一定极为专业。想到哪写到哪;


2、通过搜索文件名来查找文件;

有的弟兄经常会说文件都放在哪里了,我怎么查找文件?其实这里说的意思就是通过文件名来查找文件所处的位置;搜索文件名,我们常用的工具有find、locate、where等。现在我们简单的说一说这几个工具在文件系统中,通过查找文件名的方法来查看其所在的位置;


2.1 find ;

 

find  路径    -name  文件名

 

举例:比如在/etc中搜索vsftpd.conf文件;

 

[root@localhost ~]# find     /etc  -name vsftpd.conf

find 更为详细的说明请参考: 《Linux文件查找命令find,xargs详述》 

find 是最强劲的搜索工具,用法也比较灵活,而不是仅仅找一个文件名这个功能。


2.2 locate

locate 是用来通过文件名搜索文件的工具,它的用法也比较简单。locate 的应用,首先要通过updatedb建立索引数据库,然后才能应用;如果您新安装了软件或者存放了新的文件,也要先运行updatedb命令,以生成最新索引库。

用法:

 

locate 文件名

 

我们要先运行updatedb;

 

[root@localhost ~]# updatedb

 

举例: 我们要找vsftpd.conf文件都位于哪个位置;

 

[root@localhost ~]# locate vsftpd.conf
/etc/vsftpd/vsftpd.conf
/home/beinan/vsftpd.conf
/root/.vsftpd.conf.swp
/root/vsftpd.conf
/root/vsftpd.config
/usr/share/doc/vsftpd-2.0.4/EXAMPLE/INTERNET_SITE/vsftpd.conf
/usr/share/doc/vsftpd-2.0.4/EXAMPLE/INTERNET_SITE_NOINETD/vsftpd.conf
/usr/share/doc/vsftpd-2.0.4/EXAMPLE/VIRTUAL_USERS/vsftpd.conf
/usr/share/logwatch/default.conf/logfiles/vsftpd.conf
/usr/share/logwatch/default.conf/services/vsftpd.conf
/usr/share/man/man5/vsftpd.conf.5.gz

 


2.3 whereis 和which


2.3.1 where

whereis 是来寻找命令的二进制文件,同时也会找到其帮助文件;

比如我们不知道fdisk工具放在哪里,我们就可以用whereis fdisk 来查找;

 

[root@localhost ~]# whereis fdisk
fdisk: /sbin/fdisk /usr/share/man/man8/fdisk.8.gz

 

如果我们寻找grub在哪里,应该用如下命令,说明grub位于/sbin目录,他的帮助文件是grub.8.gz ,也就是man grub;

 

[root@localhost ~]# whereis grub
grub: /sbin/grub /etc/grub.conf /usr/share/grub /usr/share/man/man8/grub.8.gz

 


2.3.2 which

which 和where 相似,只是我们所设置的环境变量中设置好的路径中寻找;比如;

 

[root@localhost ~]# which fdisk
/sbin/fdisk

 


3、在一个文件或输出中查找;

有时我们为了管理服务器,可能要查看一些日志文件或管理指令的输出,并抽取出来;这时我们要用到more和grep、egrep、|(管道),如果要输出到一个文件中, 还要用到 > 。

比如我们查看/var/log/message 文件,并查找5月8号的日志;我们应该用如下的命令组合;

 

[root@localhost ~]# cat  /var/log/messages |grep 'May  8' |more

 

注意:因为May和8之间有两个空格,所以得用”号括起来。

如果我们想把查看到的结果输出到一个文件中,应该用 > 输出到文件;

 

[root@localhost ~]# cat  /var/log/messages |grep 'May  8' > ~/message0508.txt
[root@localhost ~]# more ~/message0508.txt

 

上面的例子,是我们首先用 cat 来查看/var/log/message 的内容,然后抽取带有 May 8字样的行,然后输出到用户家目录下的 message0508.txt文件中,然后再用more来查看message0508.txt文件内容;

从一个输出结果中查找

我们也可以从一个输出的结果中查找所需要的内容,请看下面的例子;

 

[root@localhost ~]# ls -lh
总计 24M
-rwxr-xr-x 1 root root  545 04-25 11:21 adduml02.sh
-rwxr-xr-x 1 root root  545 2004-01-18 adduml.sh
-rw-rw-rw- 1 root root    0 04-25 14:26 dood
drwxr-xr-t 2 root root 4.0K 04-24 21:59 googledir
-rwxr-xr-x 1 root root    7 04-21 12:47 lsfile.sh
-rw-r--r-- 1 root root  31K 05-08 13:47 message0508.txt
drwxr-xr-x 2 root root 4.0K 04-21 12:46 mkuml-2004.07.17
-rwxr-xr-x 1 root root  67K 04-22 14:13 mkuml-2004.07.17-ananas.tar.bz2
drwxr-xr-x 2 root  502 4.0K 04-25 09:08 mydir
-rw-r--r-- 1 root root 7.9M 04-27 20:35 myfile.img
-rw-r--r-- 1 root root 4.0M 04-27 20:37 myfileSpaa
-rw-r--r-- 1 root root 3.9M 04-27 20:37 myfileSpab
-rw-r--r-- 1 root root 7.9M 04-27 20:38 newmyfile.img
drwxrw-rw- 2 root root 4.0K 04-25 14:22 sundir
drwxr-xr-x 2 root root 4.0K 04-25 09:20 testdir
-rwxr-xr-x 1 root root  613 03-26 18:41 upgrade.log
-rw------- 1 root root 4.0K 04-29 20:12 vsftpd.conf
-rw-r--r-- 1 root root 4.0K 04-29 20:23 vsftpd.config
-rw-r--r-- 1 root root  100 04-27 16:14 xaa

 

在ls -lh 的输出结果中,查看带有04-27字样的行,我们看到带有04-27字样的行都出来了。04-27在这里表示文件创建或最后修改(访问)的时间。符合这一特征的都在里面。

 

[root@localhost ~]# ls -lh |grep '04-27' |more
-rw-r--r-- 1 root root 7.9M 04-27 20:35 myfile.img
-rw-r--r-- 1 root root 4.0M 04-27 20:37 myfileSpaa
-rw-r--r-- 1 root root 3.9M 04-27 20:37 myfileSpab
-rw-r--r-- 1 root root 7.9M 04-27 20:38 newmyfile.img
-rw-r--r-- 1 root root  100 04-27 16:14 xaa

 

查找正在运行的程序:

这也是从一个输出中查找的例子,先用ps 来列出所有正在运行中的进程,然后通过grep 来提取。下面的例子中查找是否有gaim程序在运行;

 

[root@localhost ~]# ps -aux |grep gaim
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.6/FAQ
beinan    2682  0.0  4.0 152644 30188 ?        S    08:59   0:16 gaim
root      5660  0.0  0.0   5160   720 pts/1    S+   13:58   0:00 grep gaim

 

从上面的例子,我们可以看到的确有gaim运行,进程号是2682 。如果想杀掉gaim怎么办?应该有kill 2682 或killall gaim

 

[root@localhost ~]# kill 2682

[root@localhost ~]# killall gaim

 

对于进程的查找,也可以用pgrep 来进行;比如我们查找gaim;

 

[root@localhost ~]# pgrep gaim
2682

 

等价于;

 

[root@localhost ~]# ps -aux |grep gaim
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.6/FAQ
beinan    2682  4.4  2.3 105000 17504 ?        S    14:05   0:02 gaim
root      5716  0.0  0.0   5156   712 pts/1    R+   14:06   0:00 grep gaim

 


4、关于本文;

关于查找的命令和工具说起来比较复杂,本文也仅仅是一个入门性的文档。如果只是通过文件名来查找到他位置,我感觉还是用locate好一点。有时find太费时间 ;

这篇文章本来仅仅是写文件和目录的搜索的,后来我想到:可能有的弟兄要学一点指定关健字在一个文件或输出中查找。所以又写了一点在一个文件或输出中查找所需要的内容。看上去本文有点拼凑的感觉,是不是太随意了?

分类: 经验之道, 网络转载 标签:

Apache中预创建Preforking MPM机制剖析(三)(转载)

2012年2月22日 没有评论
在预创建MPM中由于存在多个子进程侦听指定的套接 字,因此如果不加以控制可能会出现几个子进程同时对一个连接进行处理的情况,这是不允许的。因此我们必须采取一定的措施确保在任何时候一个客户端连接请求 只能由一个子进程进程处理。为此Apache中引入了接受互斥锁(Accept Mutex)的概念。接受互斥锁是控制访问TCP/IP服务的一种手段,它能够确保在任何时候只有一个进程在等待TCP/IP的连接请求,从而对于指定的 连接请求也只会有一个进程进行处理。
为此MPM紧接着必须创建接受互斥锁。
    if (!is_graceful) {
        if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
            mpm_state = AP_MPMQ_STOPPING;
            return 1;
        }
        ap_scoreboard_image->global->running_generation = ap_my_generation;
    }
多 数的MPM紧接着会立即创建记分板,并将它设置为共享,以便所有的子进程都可以使用它。记分板在启动的时候被创建一次,直到服务器终止时才会被释放。上面 的代码就是用于创建记分板,但是你可能很奇怪,因为你看不到我们描述的记分板创建函数ap_create_scoreboard()。事实上,创建过程由 挂钩pre_mpm完成,通过使用pre_mpm,服务器就可以让其他的模块在分配记分板之前访问服务器或者在建立子进程之前访问记分板。
ap_run_pre_mpm() 运行挂钩 pre_mpm,该挂钩通常对应类似ap_hook_name之类的函数,对于pre_mpm挂钩,对应的函数则是ap_hook_pre_mpm。在 core.c中的ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE)设定挂钩pre_mpm的对应处理函数则正是记分板创建函数ap_create_scoreboard。
ap_run_pre_mpm 挂钩也只有在进行重新启动的时候才会调用,而在进行平稳启动的时候,并不调用这个挂钩,这样做会丢失所有的仍然正在为长期请求提供服务的子进程的信息。挂 钩的引入是 Apache2.0版本的一个新的实现机制,也是理解Apache核心的一个重要机制之一,关于挂钩的具体的实现细节我们在后面的部分会详细分析。
对于每次冷启动,Apache启动之后,内部的记分板的家族号都是从0开始,而每一次平稳启动后家族号则是在原先的家族号加一。
    set_signals();
当分配了记分板之后,MPM就应该设置信号处理器,一方面允许外部进程通过信号通知其停止或者重新启动,另一方面服务器应该忽略尽可能多的信号,以确保它不会被偶然的信号所中断。正常情况下,父进程需要处理三种信号:正常的重新启动、非正常的重新启动以及关闭的信号。
SIGTERM:该信号用于关闭主服务进程。信号处理函数sig_term中设置shutdown_pending=true;
SIGHUP:该信号用于重启服务器,信号处理函数中设置restart_pending=true和graceful_mode=false
SIGUSR1:该信号用于平稳启动服务器,信号处理函restart数中设置restart_pending=true和graceful_mode=true
至于信号SIGXCPU、SIGXFSZ则由默认的信号处理函数SIG_DFL处理,SIGPIPE则会被忽略。
在 Apache主程序的循环中,程序不断的检测 shutdown_pending,restart_pending和graceful_mode三个变量的值。通常并不是外部程序一发送信 号,Apache就立即退出。最差的情况就是信号是在刚检测完就发送了,这样,主程序需要将该次循环执行完毕后才能发现发送的信号。
    if (one_process) {
        AP_MONCONTROL(1);
        make_child(ap_server_conf, 0);
    }
至此大部分准备工作已经完成,剩下的任务就是创建进程。进程的创建包括两种模式:单进程模式和多进程模式。
单进程模式通常用于Apache调试,由于不管多进程还是单进程,对HTTP请求处理以及模块等的使用都是完全相同的,区别仅仅在于效率。而多线程的调试要比单进程复杂的多。
如 果是单进程调试模式,那么上面的两句程序将被程序。我们首先解释一下AP_MONCONTROL宏的含义。对于调试,一方面可能比较关心执行的正确与否, 内存是否溢出等等,另外一方面就是能够找出整个服务器的运行瓶颈,只有找到了运行的瓶颈才能进行改善,从而提高效率。例如,假设应用程序花了 50% 的时间在字符串处理函数上,如果可以对这些函数进行优化,提高 10% 的效率,那么应用程序的总体执行时间就会改进 5%。因此,如果希望能够有效地对程序进行优化,那么精确地了解时间在应用程序中是如何花费的,以及真实的输入数据,这一点非常重要。这种行为就称为代码剖析(code profiling)。
An executable program compiled using the -pg option tocc(1)automatically cally includes calls to collect statistics for thegprof(1)call-graph execution profiler. In typical operation, profiling begins at program startup and ends when the program calls exit. When the program exits, the profiling data are written to the file gmon.out, thengprof(1)can be used to examine the results.
一个可执行的应用程序可以在使用gcc编译的时候利用-pg选项自动的调用相关函数收集一些执行统计信息以便gprof execution profiler使用。
moncontrol() selectively controls profiling within a program. When the program starts, profiling begins. To stop the collection of histogram ticks and call counts use moncontrol(0); to resume the collection of his-histogram togram ticks and call counts use moncontrol(1). This feature allows the      cost of particular operations to be measured. Note that an output file will be produced on program exit regardless of the state of moncontrol().
Programs that are not loaded with -pg may selectively collect profiling statistics by calling monstartup() with the range of addresses to be pro-profiled. filed. lowpc and highpc specify the address range that is to be sampled; the lowest address sampled is that of lowpc and the highest is just below highpc. Only functions in that range that have been compiled with the -pg option tocc(1)will appear in the call graph part of the output;
however, all functions in that address range will have their execution time measured.  Profiling begins on return from monstartup().
单进程的另外一个任务就是调用make_child。对于单进程,make_child非常的简单:
static int make_child(server_rec *s, int slot)
{
    int pid;
   ……
    if (one_process) {
        apr_signal(SIGHUP, sig_term);
        apr_signal(SIGINT, sig_term);
        apr_signal(SIGQUIT, SIG_DFL);
        apr_signal(SIGTERM, sig_term);
       child_main(slot);
        return 0;
    }
    /*多进程处理代码*/
}
从代码中可以看出,单进程直接调用了child_main,该函数用于直接处理与客户端的请求。在整个系统中只有一个主服务进程存在。
    else {
    if (ap_daemons_max_free < ap_daemons_min_free + 1) /* Don’t thrash… */
        ap_daemons_max_free = ap_daemons_min_free + 1;
    remaining_children_to_start = ap_daemons_to_start;
    if (remaining_children_to_start > ap_daemons_limit) {
        remaining_children_to_start = ap_daemons_limit;
    }
    if (!is_graceful) {
        startup_children(remaining_children_to_start);
        remaining_children_to_start = 0;
    }
    else {
        hold_off_on_exponential_spawning = 10;
    }
对于多进程模式而言,处理要复杂的多。与多进程类似,上面的代码负责创建子进程。在创建之前对其中使用到的几个核心变量进行必要的调整,这几个变量的含义分别如下:
ap_daemons_max_free:服务器中允许存在的空闲进程的最大数目,一旦超过这个数目,一部分空闲进程就会被迫终止,直到最后的空闲进程数目降低到该值。
ap_daemons_min_free:服务器中允许存在的空闲进程的最小数目,一旦低于这个数目,服务器将创建新的进程直到最后空闲进程数目抵达这个数目。任何时候空闲进程的数目都维持在ap_daemons_max_free和ap_daemons_min_free之间。
ap_daemons_limit:服务器中允许存在的进程的最大数目。包括空闲进程、忙碌进程以及当前的记分板中的空余插槽。
ap_daemons_to_start:服务器起始创建的进程数目。这个值不能超出ap_daemons_limit。
remaining_child_to_start: 需要启动的子进程的数目。对于初始启动,remaining_child_to_start的值就是ap_daemons_to_start的值。因此服 务器是刚启动,那么函数直接调用start_children创建remaining_child_to_start个子进程,同时将 remaing_chil d_to_start设置为零。
对于平稳启动,remaining_child_to_start的含义则要发生一些变化。 
如果我们所进行的是平稳启动,那么在我们进入下面的主循环之前应该可以观察到相当多的子进程立即陆续退出,其中 的原因则是因为我们向它们发出了AP_SIG_GRACEFUL信号。这一切发生的非常的快。对于每一个退出的子进程,我们都将启动一个新的进程来替换它 直到进程数目达到ap_daemons_min_free。因此
    restart_pending = shutdown_pending = 0;
    mpm_state = AP_MPMQ_RUNNING;
    while (!restart_pending && !shutdown_pending) {
        int child_slot;
        apr_exit_why_e exitwhy;
        int status, processed_status;
        apr_proc_t pid;
至 此,主服务进程则可以进入循环,它所作的事情只有两件事情,一个是负责对服务器重新启动或者关闭,另一个就是负责监控子进程的数目,或者关闭多余的空闲子 进程或者在空闲子进程不够的时候启动新的子进程。restart_pending用于指示服务器是否需要进行重新启动,为1的话表明需要重 启;shutdown_pending则指示是否需要关闭服务器,为1则表明需要关闭。除此之外,graceful用于指示是否进行平稳启动。当外界需要 对主进程进行控制的时候只需要设置相应的变量的值即可,而主进程中则根据这些变量进行相应的处理。
        ap_wait_or_timeout(&exitwhy, &status, &pid, pconf);
如 果restart_pending=0并且shutdown_pending=0的话意味着外部进程不需要服务器终止或者重新启动,此时主服务进程将进入 无限循环,监视子进程。对于平稳启动而言,正常情况下,在每一轮循环中,主服务进程都会调用 ap_wait_or_timeout()等待子进程终止。通常情况下,子进程的退出有三种可能,分别枚举类型apr_exit_why_e进行描述
1.正常退出,此时APR_PROC_EXIT=1,这种情况通常是进程所有任务完成后退出
2.信号退出,此时APR_PROC_SIGNAL=2,这种情况通常是进程在执行过程中接受到信号半途退出
3.非正常退出,此时APR_PROC_SIGNAL_CORE=4,通常是进程意外中断,同时生成core dump文件。
        if (pid.pid != -1) {
            processed_status = ap_process_child_status(&pid, exitwhy, status);
           if (processed_status == APEXIT_CHILDFATAL) {
                mpm_state = AP_MPMQ_STOPPING;uvwxy
                return 1;
            }
主 进程通过ap_wait_or_timeout监视等待每一个子进程退出,同时在exitwhy中保存它们退出的原因。尽管如此,主进程并不会无限制的等 待下去。主进程会给出一个等待的超时时间,一旦超时,主进程将会不再理会那些尚未结束的进程,继续执行主循环的剩余部分。如果子进程在规定的时间内完成, 那么即等待成功,此时该被终止的子进程的pid.pid将不为-1,否则pid.pid将为-1。
一旦等待到子进程退出,那么进程退出的原因保存在processed_status中。如果processed_satus为APEXIT_CHILDFATAL,则表明发生了致命性的错误,这时候将导致整个服务器的崩溃,此时主进程直接退出,不对记分板做任何的处理,如u所示;
            child_slot = find_child_by_pid(&pid);
            if (child_slot >= 0) {
               (void) ap_update_child_status_from_indexes(child_slot, 0, SERVER_DEAD,
                                                           (request_rec *) NULL);u
                if (processed_status == APEXIT_CHILDSICK) {
                    idle_spawn_rate = 1;v
                }
                else if (remaining_children_to_start
                    && child_slot < ap_daemons_limit) {
                    make_child(ap_server_conf, child_slot);
                    –remaining_children_to_start;
                }
#if APR_HAS_OTHER_CHILD
            }
如果子进程发生的错误并不是致命性的,那么一切都得按部就班的处理——更新记分板中对应的插槽中的信息。首要的前提就是在记分板中找到该进程对应的插槽,这由函数find_child_by_pid()完成。
如果能够成功找到终止进程对应的插槽,那么直接在记分板中将该终止进程的状态更新为SERVER_DEAD,这样,该插槽将会再次可用,如u所示。
如果进程退出是因为资源受限,比如磁盘空间不够,内存空间不够等等,此时Apache必须降低生成子进程的速度至最低。如v所示。
如 果Apache进行的是平稳重启,那么在进入主循环之前,通过发送终止信号,很多的子进程都将被终止,这些被终止的进程在系统重启后必须被新的进程替换, 直到总的进程数目达到daemons_min_free。 remaining_children_to_start记录了当前需要重启的子进程的数目。
            else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, status)
== APR_SUCCESS) {
#endif
            }
            else if (is_graceful) {
                ap_log_error(APLOG_MARK, APLOG_WARNING,
                            0, ap_server_conf,
                            “long lost child came home! (pid %ld)”, (long)pid.pid);
            }
            continue;
        }
如果进程在公告板中没有找到相应记录,此时检查子进程是否是“其余子进程”(reap_other_child)。 一些情况下,主服务进程会创建一些进程,这些进程并不是用来接受并处理客户端连接的,而是用作其余用途,通常称之为“其余进程”,并用一个单独的列表进行 登记。比如,一般情况下,Apache会将日志写入到文件中,但是有的时候Apache则希望将数据写入到一个给定的应用程序中。因此主服务器进程必须为 该应用程序创建该进程,并且将该进程的标准输入STDIN关联道主服务进程的日志流中。这种进程并不是用来执行处理HTTP请求的子服务进程,因此称之为 “其余进程”。任何时候只要服务器重启,日志应用进程都会接受到SIGHU和SIGUSR1信号,然后终止退出,对应的模块必须重新创建这种进程。对于其 余进程,主服务进程不做任何事情,因为这不是主进程所管辖的范围。
如果既不是“ 其余子进程”,又没有在公告板中找到相应记录,同时管理员还设置了热启动选项,那么发生这种情况只有一个可能性:管理员减少了允许的子进程的数目同时强制 执行了热启动。而一个正在忙碌的子进程拥有的入口记录号比允许的值大。此时它终止的时候自然就不可能在公告板中找到相应的记录入口。
        else if (remaining_children_to_start) {
            startup_children(remaining_children_to_start);
            remaining_children_to_start = 0;
            continue;
        }
如果当所有的终止的子进程都被替换之后,remaining_children_to_start还不为零,此时意味着主服务进程必须创建更多的子进程,这个可以通过函数startup_children()实现。
        perform_idle_server_maintenance(pconf);
#ifdef TPF
        shutdown_pending = os_check_server(tpf_server_name);
        ap_check_signals();
        sleep(1);
#endif /*TPF */
    }
    } /* one_process */
一旦启动完毕,那么主进程将使用perform_idle_server_maintenance进入空闲子进程维护阶段,同时主进程还得监视相关的信号,比如关闭信号,重启信号等等。空闲进程的维护在4.2.1.2中详细描述。
当主进程退出循环while (!restart_pending && !shutdown_pending) 的时候只有两种情况发生,或者被通知关闭,或者被通知重启。一旦如此,Apache将着手进行相关的清除工作。
    mpm_state = AP_MPMQ_STOPPING;
    if (shutdown_pending) {
        if (unixd_killpg(getpgrp(), SIGTERM) < 0) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
“killpg SIGTERM”);
        }
       ap_reclaim_child_processes(1);          /* Start with SIGTERM */
        /* cleanup pid file on normal shutdown */
        {
            const char *pidfile = NULL;
            pidfile = ap_server_root_relative (pconf, ap_pid_fname);
            if ( pidfile != NULL && unlink(pidfile) == 0)
                ap_log_error(APLOG_MARK, APLOG_INFO,
                                0, ap_server_conf,
                                “removed PID file %s (pid=%ld)”,
                                pidfile, (long)getpid());
        }
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
                    “caught SIGTERM, shutting down”);
        return 1;
    }
如果Apache需要进行关闭,那么Apache的清除工作工作包括下面的几个方面:
■ 如果服务器被要求关闭,那么主服务进程将向整个进程组中的所有子进程发送终止信号,通知其调用child_exit正常退出。
■ 调用ap_reclain_child_process回收相关的子进程。
■ 清除父子进程之间通信的“终止管道”。
    apr_signal(SIGHUP, SIG_IGN);
    if (one_process) {
        /* not worth thinking about */
        return 1;
    }
    ++ap_my_generation;
    ap_scoreboard_image->global->running_generation = ap_my_generation;
   
    if (is_graceful) {
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
                    “Graceful restart requested, doing restart”);
        /* kill off the idle ones */
        ap_mpm_pod_killpg(pod, ap_max_daemons_limit);
        /* This is mostly for debugging… so that we know what is still
         * gracefully dealing with existing request. This will break
         * in a very nasty way if we ever have the scoreboard totally
         * file-based (no shared memory)
         */
        for (index = 0; index < ap_daemons_limit; ++index) {
            if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) {
                ap_scoreboard_image->servers[index][0].status = SERVER_GRACEFUL;
            }
        }
    }
如 果Apache被要求的是重新启动,那么对于平稳启动和非平稳启动处理则不太相同。对于平稳启动而言,主进程需要终止的仅仅是那些目前空闲的子进程,而忙 碌的进程则不进行任何处理。空闲进程终止通过 ap_mpm_pod_killpg实现;同时由于记分板并不销毁,因此对于那些终止的进程,必须更新其在记分板中的状态信息为SERVER_DEAD; 而对于那些仍然活动的子进程,则将其状态更新为SERVER_GRACEFUL。
    else {
       /* Kill ‘em off */
        if (unixd_killpg(getpgrp(), SIGHUP) < 0) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, “killpg SIGHUP”);
        }
        ap_reclaim_child_processes(0);          /* Not when just starting up */
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
                    “SIGHUP received. Attempting to restart”);
    }
如果服务器被要求强制重启,那么所有的子进程包括那些仍然在处理请求的都将被统统终止。
分类: 网络转载 标签:

Apache中预创建Preforking MPM机制剖析(二)(转载)

2012年2月22日 1 条评论
6.3.3.3主服务进程管理
6.3.3.3.1主服务进程概述
所 有的MPM都是从ap_mpm_run()函数开始执行,对此预创建MPM也不例外。 ap_mpm_run()函数通常由Apache核心在main()中进行调用,一旦调用,运行服务器的职责就从Apache核心移交给了MPM。这个函 数是所有的MPM都必须实现的。通常情况下,ap_mpm_run的实现会比较复杂。对于Preforking MPM,它的执行流程可以用下图描述:

图 主进程工作流程
从上图中可以看出,主服务进程的功能主要包括下面几部分:
1)、接受进程外部信号进行重启,关闭以及平稳启动等等操作。外部进程通过发送信号给主服务进程以实现控制主服务进程的目的。
2)、在启动的时候创建子进程或者在平稳启动的时候用新进程替代原有进程。
3)、监控子进程的运行状态,并根据运行负载自动调节空闲子进程的数目:在存在过多空闲子进程的时候终止部分空闲进程;在空闲子进程较少的时候创建更多的空闲进程以便将空闲进程的数目维持在一定的数目之内。
上图的上半部分主要处理子进程的终止以及平稳启动的一些内容,而标有”维护空闲子进程数目”的下半部分则主要处理平稳启动的一些内容。在该循环中,主进程统计空闲子进程的数目并从记分板中获得详细的空闲进程列表,然后将统计得到的空闲进程数目idle_count与系统设置的极限ap_daemons_max_free和ap_daemons_min_free)进行对比。如果有过多的空闲子进程,那么它将每次循环中终止一个子进程;反之它将一次性创建足够的空闲进程。
下面的部分我们概要的描述一下主服务进程是如何处理平稳启动,子进程终止以及如何维持空闲子进程数目的。
平稳启动以及处理子进程终止
u设 置remaining_children_to_start(以后简称 rem.child2start)。变量rem.child2start只有在平稳启动的时候才会用到。它记录的是服务器启动后需要启动的子进程的数目。 需要注意的是主服务进程并没有使用startup_children过程创建子进程。对于每一个被终止的子进程,主服务进程通过调用wait()可以得到 终止通知。如果初始的子进程的数目在配置文件中被更改了,那么仅仅用新进程替代终止的子进程肯定会出错,因此主服务进程用rem.child2start 控制这个数目。
vpid = wait 或者超时。对于使用fork创建的子进程,主服务进程调用wait等待它们的终止,这种做法确保没有僵尸进程的产生。如果在指定的时间内进程仍然没有终止,那么主服务进程算超时处理,即使没有等到终止通知,主服务进程将继续执行它的循环。
w等待成功的情况。如果在指定的时间内子进程终止,此时主服务器进程将完成下面的各项工作:
-                process_child_status:获取子进程终止的原因。子进程的终止可能有很多情况,比如正常终止,异常终止等等。正常终止对于主服务进程到无所谓,异常终止主进程则必须知道具体原因。
-                find_child_by_pid: 当进程终止后,必须在记分板中更新它的状态信息,因此首先必须在记分板中查找该进程对应的插槽信息。如果查找,则将进程的状态信息设置为 SERVER_DEAD。如果remaining_children_to_start不为零的话,创建一个新的子进程来代替终止的子进程。
-                如 果没有在记分板中没有找到终止进程的对应插槽,那么检查该进程是否是“其余子进程”。一些情况下,主服务进程会创建一些非子服务进程的进程,它们称之为“ 其余进程”,并用一个单独的列表进行登记。比如,一般情况下,Apache会将日志写入到文件中,但是有的时候Apache则希望将数据写入到一个给定的 应用程序中。因此主服务器进程必须为该应用程序创建该进程,并且将该进程的标准输入STDIN关联道主服务进程的日志流中。这种进程并不是用来执行处理 HTTP请求的子服务进程,因此称之为“其余进程”。任何时候只要服务器重启,日志应用进程都会接受到SIGHU和SIGUSR1信号,然后终止退出,对 应的模块必须重新创建这种进程。如果进程既不是“其余进程”,在记分板中也找不到对应的插槽,并且设置了平稳启动模式,那么肯定发生了下面的情况:管理员 减少了允许的子进程数目同时进行了平稳启动或者。。。
x等待超时:如果所有的终止了的进程被新创建的新进程代替之后rem.child2start变量的值仍然不为零,那么这意味着必须创建更多的子进程。创建由startup_children()函数完成。
yz
空闲子进程维护
下面的一节我们将详细的分析主进程以及子进程相关的源代码。
6.3.3.3.2主服务进程概述
int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
{
    int index;
    int remaining_children_to_start;
    apr_status_t rv;
    ap_log_pid(pconf, ap_pid_fname);
对 于所有的Apache MPM而言,其应该首先完成的工作就是在文件pidfile中记录进程的ID。因为启动和终止Apache的默认脚本通常会读取pidfile文件,从中 查找所有记录的进程然后逐个终止它。因此如果不进行记录的话,启动的这些进程可能无法通过脚本进行终止,这项操作进行的越快越好。
   first_server_limit= server_limit;
    if (changed_limit_at_restart) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
                     “WARNING: Attempt to change ServerLimit “
                     “ignored during restart”);
        changed_limit_at_restart = 0;
    }
理解上面这段代码的关键在于理解两个变量first_server_limit和changed_limit_at_restart的作用。
server_limit 变量用以记录服务器内允许同时存在的子服务进程的数目,用过通过配置文件中的 ServerLimit指令可以修改这个值。当每次Apache启动的时候,通过读取配置文件这个指令的参数值最终保存到了server_limit变量 中并影响服务器的进程产生。由于当Apache重新启动(restart)的时候也会读取配置文件,因此如果服务器重新启动之前修改了配置文件中的 ServerLimit指令参数,那么毫无疑问,这种变化Apache重启的时候肯定会看到的。那么Apache该如何处理这种变化呢?是使用新的 server_limit还是使用原有的server_limit?Apache的做法是不允许在重启的时候修改server_limit值,即使你修改 了Apache也会忽略。
为了能够检查出这种修改,在Apache第一次启动的时 候,ServerLimit的值就被记录在 first_server_limit变量中,在整个Apache运行期间即使重新启动,这个值也不会变化。 first_server_limit=server_limit就是保存初始的值。
按 正常的处理策略,对于每次重启后都应该把server_limit与first_server_limit的值进行比较判断是否发生变化,如果发生变化就 给出警告,但是上面的代码中并没有这种比较。那么比较在哪儿发生的呢?钥匙在 change_limit_at_restart变量上。当重新启动后读取配置文件的时候,遇到ServerLimit指令会调用函数 set_server_limit处理该指令,该函数中会将指令参数后面的值与first_server_limit进行比较:
    inttmp_server_limit;
    tmp_server_limit = atoi(arg);//ServerLimit指令后的参数值
    if (first_server_limit &&tmp_server_limit != server_limit) {
        changed_limit_at_restart = 1;
        return NULL;
    }
    server_limit = tmp_server_limit;
从 上面的代码中可以看出,changed_limit_at_restart反映了ServerLimit在重启期间是否发生了更改。对于这种更 改,Apache并不理会,只是简单的警告,并将changed_limit_at_restart设置为零,这样下次重启就不要进行判断了。
    /* Initialize cross-process accept lock */
    ap_lock_fname = apr_psprintf(_pconf, “%s.%” APR_PID_T_FMT,
                                 ap_server_root_relative(_pconf, ap_lock_fname),
                                 ap_my_pid);
    rv = apr_proc_mutex_create(&accept_mutex, ap_lock_fname,
                               ap_accept_lock_mech, _pconf);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
                     “Couldn’t create accept lock (%s) (%d)”,
                     ap_lock_fname, ap_accept_lock_mech);
        mpm_state = AP_MPMQ_STOPPING;
        return 1;
    }
#if APR_USE_SYSVSEM_SERIALIZE
    if (ap_accept_lock_mech == APR_LOCK_DEFAULT ||
        ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
#else
    if (ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
#endif
        rv = unixd_set_proc_mutex_perms(accept_mutex);
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
                         “Couldn’t set permissions on cross-process lock; “
                         “check User and Group directives”);
            mpm_state = AP_MPMQ_STOPPING;
            return 1;
        }
    }
分类: 网络转载 标签:

Apache中预创建Preforking MPM机制剖析(一)(转载)

2012年2月22日 没有评论
6.3.1 Leader/Follow模式
在了解Preforking MPM之前有必要首先了解Leader/Follow模型。Preforking模型本质上也属于Leader/Follow模型。通常情况下,L/F可以用下图进行描述:


通常情况下,对于服务器中的进程采用的都是即时创建的策略,即一旦有一个新的客户端请求立即创建一个新的进程或 者线程,而当进程或者线程执行完毕后,进程和线程也随之退出。显然这种策略对于小规模的服务器还能接受,但是如果对于大规模的服务器而言,创建进程或者线 程的时间将增加,最终会导致响应时间变长,单位时间内请求处理效率降低。L/F模式则不同,它首先一次性创建多个进程或者线程,包括到系统中,这些进程或 者线程担任三种不同的角色:侦听者、工作者以及空闲者,其含义分别如下:
1)、侦听者的角色。该线程负责侦听客户端的请求。在L/F模式中它属于Leader的角色。通常情况下只允许一个进程或者线程担当侦听者的角色。
2)、工作者的角色。当侦听者侦听到客户端的请求之后,它将立即转换为工作者角色并开始处理客户端的请求。工作者角色的线程可以有多个。
3)、 空闲者的角色。当工作者执行任务完毕后它并不会立即退出,而是转变它的角色为空闲者,并呆在空闲队列中。空闲者出现的原因是因为客户端请求不够多。空闲者 们等待变为侦听者。而当侦听者变为工作者之后,空闲者中的每一个都相互竞争,最终将会有一个线程变为侦听者,其余的继续保持空闲者的状态。
4)、几个极端的情况也是可能出现的:所有线程都变为工作者,忙于处理客户端的请求,没有线程担任侦听者的角色,因此此时客户端的请求都被拒绝;没有工作者,如果没有任何请求到达,那么所有的线程都处于空闲状态。
线程的三个角色的相互转换关系可以用上图的红线进行描述。
6.3.2 Preforking MPM概述
UNIX平台上可以使用的第一个MPM就是预先派生(Preforking)MPM,也是默认的MPM。该模型在功能上等同于Apache1.3上的模型,该MPM的示意图如下所示: 

该MPM中,存在一个主进程和多个子进程。每个子进程 都会为所进行的请求侦听一个套接字。当接受到请求之后,子进程就会接受它并且提供响应。父进程会监控所有的子进程以确保总是可以使用最少数量的进程来处理 请求,并且确保等候请求到达的闲置进程不能过少。如果没有足够的空闲进程来处理潜在的请求高峰,那么父进程就会启动新的子进程。如果存在过多的进程,那么 父进程会每次终止一个空闲进程,直到服务器回到最大空闲子进程数量之下。通过保持一定数量的空闲子进程来接受所引入的请求,服务器就可以避免在接受到请求 时再去启动新进程的开销。
父进程和子进程之间通过记分板进行通信。对于每一个产生的子进程,它的状态信息都写入到记分板中,父进程通过读取记分板可以了解子进程的状态。当需要关闭子进程的时候它将通过终止管道发送终止信息给子进程,另外的一种通知方法就是通过信号。
预 先派生模型有一些优点,例如健壮性以及可靠性。 Apache允许使用动态模块将第三方的代码加入到服务器。这意味着如果管理员在服务器中增加了第三方软件,而且模块导致了子进程出现段故障,那么服务器 就会丢失一个连接,而且仅仅丢失一个连接。服务器的其余部分还将继续运行,并且可以为请求提供服务。唯一可以注意到问题出现的用户就是不幸地进行了导致问 题地请求的用户。另一方面必须要意识到,可能并不是遭遇了故障的请求而导致了问题,可能会是因为有一系列请求而导致了问题的出现。
这 个模型的另外一个优点就是可以很容易地编写采用这种方式运行的MPM。如果每个进程每次只需要处理一次请求,那么就没有过多的边界条件需要考虑。例如,如 果管理员想平稳重新启动服务器,那么她就要等待所有的子进程完成当前请求,并且适时强制其终止来完成任务,然后父进程就可以启动新的进程来代替旧的进程 了。
不过预派生模型也有自 己的缺点,比如扩充性。因为预先派生模型是依赖于进程,所以在某些平台上并不能很好的执行,比如在Window平台上则由于进程的代价太高,耗时太长的原 因则弃用该方案。当然 Window并不是唯一的遭遇该问题的OS。比如在高负载的情况下AIX也会遇到这个问题。
预 先派生模型的另外一个问题就是安全性。许多ISP都会在相同的计算机上使用Apache来为多个公司的Web站点提供需要的Web服务。为了完成这个任 务,每个公司都要被赋予一个虚拟主机,但是因为所有的站点都需要访问Web服务器,所以必须通过运行子进程的用户ID来读取所有的页面。大多数 Apache用户运行Apache的方式是作为超级用户运行父进程,然后作为专用于Web服务器的用户运行子进程。这样就可以让服务器打开特权端口80, 而且还可以确保侦听网络的进程不作为根用户运行,因此就减少了发现安全漏洞所带来的风险。因为所有的虚拟主机都会使用相同的用户ID运行,所以它们的 CGI教本也会使用这个ID运行。这意味着任何为这个站点存储信息的数据库都必须要通过这个用户可读。这样就会让任何站点访问其他站点的私有信息。当 然,Apache已经针对这个问题提供了解决方案,它可以让站点规定它们的CGI脚本作为哪个用户运行但是这只是解决了实际的CGI教本的问题,并没有解 决PHP,Apache模块或者通过mod_perl运行的Perl脚本的问题。
这 种设计的最后问题就是它会消弱某些优化。因为每个请求都会在它自己的进程中运行,所以进程之间很难共享任何信息。增加Web服务器性能的常见方式就是缓存 最近所发送的所有的页面。但是,如果每个进程都要保存它自己的缓存,那么缓存的作用就会降低。只有多次获取缓存中所缓存的页面缓存才会有作用。大多数情况 下,这种方式会随着时间的推移逐渐显示其作用,缓存总是会缓存最常受到请求的页面。这意味着,到缓存起作用的时候,进程就要受到没有缓存的新进程的替换。 Apache中的子进程会在由配置文件中的 MaxRequestsPerChild指令所控制的指定时间间隔终止。Apache在指定的请求数量之后强制终止子进程的原因是为了防止内存泄漏。因为 Apache要在相当长的时间内运行,所以很小的内存泄漏也会导致服务器出现问题。而且因为子进程是唯一分配内存的进程——所以强制其退出,并且偶尔对其重新启动——就有可能避免由于内存泄漏而导致的问题。
在下面的部分,我们将描述MPM的实现细节。首先我们分析主进程管理细节,然后分析子进程的工作细节,最后我们分析主进程是如何与子进程进行通信并对其进行管理的。
6.3.3 Preforking MPM实现
6.3.3.1 内部结构
在对Preforking MPM进行深入的分析之前,我们先从整体上了解Preforking MPM的内部结构,从而能够从整体上有一个认识,这样在后面的具体分析中不至于迷失方向。下图给出的则是Preforking MPM的中的内部大概的实现机制: 

 

一个完整的MPM包括下面几个数据流程:
1)、第一次初始化
第一次初始化的时候是分配资源(主要是内存池),读取以及检查配置文件,然后服务器进程将其变为一个后台进程。
2)、重启循环
重启循环是指不关闭Apache而进行的启动。重启循环中主要重新读取配置文件防止配置文件发生变化,创建子进程池并进入服务器主循环。
3)、服务器主进程循环
服务器主循环主要是控制进程池中空闲子进程的数目,具体到细节中则是循环监控记分板,并根据记分板中子进程的状态作出反应。
4)、客户端请求/响应循环
这个循环只适合于子进程。在该循环中,子进程等待自己变为侦听者,然后等待客户端的连接,一旦获取到连接则变为工作者开始处理请求,同时进入Keep-alive循环中。
5)、Keep-alive循环
Keep-alive循环主要是处理客户端的请求,该循环仅仅适合子进程。
6)、在退出之前进行清理工作
6.3.3.2 MPM中的定义
预创建MPM所对应的文件为prefork.c。通常情况下,MPM的名称总是和MPM的文件具有相同的名称,这样做可以让配置更合理一些。对于每一个MPM,其遇到的第一件事情就是定义一些全局变量,它们各自的含义描述在右边的注释中:
int ap_threads_per_child=0;                                       /*每个进程对应的线程数目 */
static apr_proc_mutex_t *accept_mutex;                          /*连接接受互斥锁,用以确保在任何时候只有一个连
接被接受*/
static int ap_daemons_to_start=0;                            /*初始启动的进程数目*/
static int ap_daemons_min_free=0;                                   /*可以接受的空闲进程的最小数目*/
static int ap_daemons_max_free=0;                                  /*允许空闲的进程的最大数目*/
static int ap_daemons_limit=0;                                          /*允许同时运行的进程的最大值*/
static int server_limit = DEFAULT_SERVER_LIMIT;       /**/
static int first_server_limit;
static int changed_limit_at_restart;
static int mpm_state = AP_MPMQ_STARTING;                     /*描述当前*/
static ap_pod_t *pod;
int ap_max_daemons_limit = -1;
server_rec *ap_server_conf;
static int one_process = 0;
static apr_pool_t *pconf;                               /* Pool for config stuff */
static apr_pool_t *pchild;                             /* Pool for httpd child stuff */
static pid_t ap_my_pid;                          /* it seems silly to call getpid all the time */
static pid_t parent_pid;
#ifndef MULTITHREAD
static int my_child_num;
#endif
ap_generation_t volatile ap_my_generation=0;
static int die_now = 0;
另 外一个所有的MPM都用到的函数就是ap_mpm_query(),外界调用该函数通常是想了解当前MPM的一些私有属性。比如在mod_snake(在 Apache进程中嵌入python解释器的模块)中就会使用这个函数来查询给定的MPM是否进行了线程化。如果进行了线程化,那么该模块就必须同步某些 python函数,否则就不需要进行同步了。
Apache中关于MPM的状态分类可以归结为两大类:运行状态和内部状态。
#define AP_MPMQ_STARTING              0
#define AP_MPMQ_RUNNING               1
#define AP_MPMQ_STOPPING              2
上面三种属于运行状态,分别表示Apache处理启动、运行和停止状态。
#define AP_MPMQ_MAX_DAEMON_USED       1 /*所有的进程都已经满了         */
#define AP_MPMQ_IS_THREADED          2 /* MPM能够支持线程化           */
#define AP_MPMQ_IS_FORKED             3 /* MPM能够调用fork产生子进程 */
#define AP_MPMQ_HARD_LIMIT_DAEMONS    4 /* The compiled max # daemons   */
#define AP_MPMQ_HARD_LIMIT_THREADS    5 /* The compiled max # threads   */
#define AP_MPMQ_MAX_THREADS           6 /* # of threads/child by config */
#define AP_MPMQ_MIN_SPARE_DAEMONS     7 /* Min # of spare daemons       */
#define AP_MPMQ_MIN_SPARE_THREADS     8 /* Min # of spare threads       */
#define AP_MPMQ_MAX_SPARE_DAEMONS     9 /* Max # of spare daemons       */
#define AP_MPMQ_MAX_SPARE_THREADS    10 /* Max # of spare threads       */
#define AP_MPMQ_MAX_REQUESTS_DAEMON 11 /* Max # of requests per daemon */
#define AP_MPMQ_MAX_DAEMONS          12 /* Max # of daemons by config   */
#define AP_MPMQ_MPM_STATE            13 /* starting, running, stopping */
上面的都属于Apache的内部状态。预创建MPM中的函数定义如下:
AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
{
    switch(query_code){
        case AP_MPMQ_MAX_DAEMON_USED:
            *result = ap_daemons_limit;
            return APR_SUCCESS;
        case AP_MPMQ_IS_THREADED:
            *result = AP_MPMQ_NOT_SUPPORTED;
            return APR_SUCCESS;
        case AP_MPMQ_IS_FORKED:
            *result = AP_MPMQ_DYNAMIC;
            return APR_SUCCESS;
        case AP_MPMQ_HARD_LIMIT_DAEMONS:
            *result = server_limit;
            return APR_SUCCESS;
        case AP_MPMQ_HARD_LIMIT_THREADS:
            *result = HARD_THREAD_LIMIT;
            return APR_SUCCESS;
        case AP_MPMQ_MAX_THREADS:
            *result = 0;
            return APR_SUCCESS;
        case AP_MPMQ_MIN_SPARE_DAEMONS:
            *result = ap_daemons_min_free;
            return APR_SUCCESS;
        case AP_MPMQ_MIN_SPARE_THREADS:
            *result = 0;
            return APR_SUCCESS;
        case AP_MPMQ_MAX_SPARE_DAEMONS:
            *result = ap_daemons_max_free;
            return APR_SUCCESS;
        case AP_MPMQ_MAX_SPARE_THREADS:
            *result = 0;
            return APR_SUCCESS;
        case AP_MPMQ_MAX_REQUESTS_DAEMON:
            *result = ap_max_requests_per_child;
            return APR_SUCCESS;
        case AP_MPMQ_MAX_DAEMONS:
            *result = server_limit;
            return APR_SUCCESS;
        case AP_MPMQ_MPM_STATE:
            *result = mpm_state;
            return APR_SUCCESS;
    }
    return APR_ENOTIMPL;
}
这 个是ap_mpm_query函数是所有的MPM都要求的函数,它可以让模块发现MPM的运行特性。尽管这个函数在所有的MPM中看起来很相似,但是细节 还是十分重要,因为每个MPM都必须实现自己的这个函数。使用这个函数最常见的原因就是要通过web或者管理应用程序报告信息。可以采用很多方式来使用这 个函数中的信息,所以应该保证它正确无误。比如mod_snake(在Apache中嵌入python解释器的模块)就会使用这个查询模块来确定MPM是 否进行了线程化,如果进行了线程化,那么该模块可能就必须同步某些python函数;否则就不需要进行同步。
分类: 网络转载 标签:

linux fork函数的精辟解说

2012年2月22日 没有评论

此文原文来源于一个blog,文章的名称为:linux fork函数的精辟解说原文地址:http://blog.chinaunix.net/space.php?uid=12461657&do=blog&id=3062996感觉这篇文章不错,在此分享下来,在原文的基础上增加了自己的一些理解和说明。

开始演示:[plain]view plaincopyprint?

[root@test code]# cat fork.c

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

main()

{

pid_t pid;

pid=fork();

if (pid < 0)

printf(”Error in fork!”);

else if (pid == 0)

printf(”I am the child process. The process id is %d\n”,getpid());

else

printf(”I am the parent process. The process id is %d\n”,getpid());

}

[root@test code]# gcc fork.c -o fork [root@test code]

# ./fork

I am the child process. The process id is 6260 I am the parent process. The process id is 6259

先说什么是进程一个进程,就是一个可执行程序的一次执行过程中的一个状态。

打个比方

我们把一个大学数学老师比作一个可执行程序,老师就是一个人,相当于一份源码,这个数学老师每个学期可能要教多个班,假设他教1班和2班,时间是一个学期,那么他从开学到期末教这两个班这个过程就是两个进程,两个进程的周期都是一个学期。

在稍微理解了进程的概念之后,我们说在正在运行的计算机中,不管是Linux或者是Windows系统都运行有很多进程,虽然我们的系统中进程比较多,但是对于单核的CPU而言,每一时刻只能有一个进程占用CPU,其他的进程就可能处在等待执行、就绪、结束等状态(状态名称可能随不同操作系统而相异)。

那么要使我们的操作系统能够正常执行,操作系统对这些进程的管理,典型的情况,是通过内存中的进程表完成的(这里的进程表不是源码的意思)。进程表中的每一个表项,记录的是当前操作系统中一个进程的情况,比如执行到哪里,下一步要执行什么命令等。

一个称为“程序计数器(program counter, pc)”的寄存器,指出当前占用CPU的进程要执行的下一条指令的位置。当分给某个进程的CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;然后把将要接替掉当前进程的那个进程的上下文从内存的进程表中读入,并更新相应的寄存器。这个过程称为“上下文交换(process context switch)”,实际的上下文交换需要涉及到更多的数据,那和fork无关,不再多说,主要要记住程序寄存器pc指出程序当前已经执行到哪里,是进程上下文的重要内容,换出 CPU的进程要保存这个寄存器的值,换入CPU的进程,也要根据进程表中保存的本进程执行上下文信息,更新这个寄存器)。

好了,有这些概念打底,可以说fork了。当你的程序执行到下面的语句:pid=fork();操作系统会进行如下的大概过程:创建一个新的子进程,而这个子进程是父进程的副本,接下来这两个进行就由操作系统调度,直到程序执行结束。

那么这个过程,我们可以对其进行更加详细的分析。在执行fork以后,操作系统复制一份当前执行的进程的数据,包括进程的数据空间、堆和栈等,并且在进程表中相应为它建立一个新的表项。上下文也是原进程(父进程)的拷贝。但是父、子进程共享正文段,也就是CPU执行的机器指令部分,这个可共享的,在存储器中只需要一个副本,而且这个副本通常是只读的。

那在什么时候父子进程中就分道扬镳呢?

从上面的实验结果我们看出那两条打印出来的语句不是在同一个进程里面的,因为在同一个进程里面不可能存在,不通结果的getpid(),事实说明是两个不同进程返回的结果,那么在执行pid=fork()以后,开始出现父、子进程,既然是两个进程,那么接下来谁先被调度,也就是说执行pid=fork()以后,在单核CPU下,只会有一个进程被调度,假设是我们的父进程占用CPU时间,父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。所以输出I am the parent process……。

子进程在之后的某个时候得到调度,它的上下文被换入。我们上面分析过,在子进行创建的时候也会复制父进程的上下文,所以子进程不会从头开始执行,而是从pid=fork()开始执行,基于操作系统对fork的实现,使得子进程中fork调用返回0.所以在这个进程(中pid=0.这个进程继续执行的过程中,if语句中 pid<0不满足,但是pid==0是true.所以输出I am the child process……。

我们下面来看一个和我在上面分析的一个结论似乎存在矛盾的现象

[plain]view plaincopyprint?

[root@test code]# cat fork.c

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

main()

{

pid_t pid;

printf(”fork!”);

pid=fork();

if (pid < 0)

printf(”Error in fork!\n”);

else if (pid == 0)

printf(”I am the child process. The process id is %d\n”,getpid());

else

printf(”I am the parent process. The process id is %d\n”,getpid());

}

[root@test code]# gcc fork.c -o fork

[root@test code]# ./fork fork!

I am the child process. The process id is 7378 fork!I am the parent process. The process id is 7377 [root@test code]#

这里我添加了printf(”fork!”)这一行,执行了以后我们发现,“fork!”打印了两次,我们上面不是说,fork以后的子进程的上下文不是和父进程一样吗,也就是说子进程不会从头开始执行,应该从fork执行,那么fork!的出现不是有矛盾吗?我们再来看看下面的现象

[plain]view plaincopyprint?

[root@test code]# cat fork.c

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

main()

{

pid_t pid;

printf(”fork!\n”);

pid=fork();

if (pid < 0)

printf(”Error in fork!\n”);

else if (pid == 0)

printf(”I am the child process. The process id is %d\n”,getpid());

else

printf(”I am the parent process. The process id is %d\n”,getpid());

}

[root@test code]# gcc fork.c -o fork [root@test code]# ./fork fork!

I am the child process. The process id is 7458 I am the parent process. The process id is 7457

这里我在printf(”fork!”)这一行的fork!后面添加了一个换行符,变成printf(”fork!\n”)执行以后发现只打印一个fork!这个到底是什么原因呢?

主要的区别是因为有了一个\n 回车符号,说起真正的原因,这和printf的缓冲机制有关了,printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上,但是只要看到有\n 则会立即刷新stdout,因此就马上能够打印了。

还没有执行到fork()的时候,原进程运行了printf(”fork!”) 后,fork!仅仅被放到了缓冲里,执行了fork,这时候子进程复制一份父进程的数据,包括这个stdout缓冲,在子进程度stdout缓冲里面就也有了fork!。执行到后面这两个缓冲都打印到屏幕上,所以出现两次,并不是printf执行两次。而运行 printf(”fork!\n”)后, fork!被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有fork!内容,因此你看到的结果会是fork!被printf了1次!!!

分类: 心路里程 标签:

Overview of new features in Apache HTTP Server 2.4

2012年2月22日 没有评论

APACHE2.4的新特性前瞻 期待

This document describes some of the major changes between the 2.2 and 2.4 versions of the Apache HTTP Server. For new features since version 2.0, see the 2.2 new featuresdocument.

 

Core Enhancements

Run-time Loadable MPMs
Multiple MPMs can now be built as loadable modules at compile time. The MPM of choice can be configured at run time.
Event MPM
The Event MPM is no longer experimental but is now fully supported.
Asynchronous support
Better support for asynchronous read/write for supporting MPMs and platforms.
Per-module and per-directory LogLevel configuration
The LogLevel can now be configured per module and per directory. New levels trace1 to trace8 have been added above the debug log level.
Per-request configuration sections
<If><ElseIf>, and <Else> sections can be used to set the configuration based on per-request criteria.
General-purpose expression parser
A new expression parser allows to specify complex conditions using a common syntax in directives like SetEnvIfExprRewriteCondHeader<If>, and others.
KeepAliveTimeout in milliseconds
It is now possible to specify KeepAliveTimeout in milliseconds.
NameVirtualHost directive
No longer needed and is now deprecated.
Override Configuration
The new AllowOverrideList directive allows more fine grained control which directives are allowed in .htaccess files.
Config file variables
It is now possible to Define variables in the configuration, allowing a clearer representation if the same value is used at many places in the configuration.
Reduced memory usage
Despite many new features, 2.4.x tends to use less memory than 2.2.x.
top

New Modules

mod_proxy_fcgi
FastCGI Protocol backend for mod_proxy
mod_proxy_scgi
SCGI Protocol backend for mod_proxy
mod_proxy_express
Provides dynamically configured mass reverse proxies for mod_proxy
mod_remoteip
Replaces the apparent client remote IP address and hostname for the request with the IP address list presented by a proxies or a load balancer via the request headers.
mod_heartmonitormod_lbmethod_heartbeat
Allow mod_proxy_balancer to base loadbalancing decisions on the number of active connections on the backend servers.
mod_proxy_html
Formerly a third-party module, this supports fixing of HTML links in a reverse proxy situation, where the backend generates URLs that are not valid for the proxy’s clients.
mod_sed
An advanced replacement of mod_substitute, allows to edit the response body with the full power of sed.
mod_auth_form
Allows to do form-based authentication.
mod_session
Allows to keep session state for clients, using cookie or database storage.
mod_allowmethods
New module to restrict certain HTTP methods without interfering with authentication or authorization.
mod_lua
Embeds the Lua language into httpd, for configuration and small business logic functions. (Experimental)
mod_log_debug
Allows to add customizable debug logging at different phases of the request processing.
mod_buffer
Provides for buffering the input and output filter stacks
mod_data
Convert response body into an RFC2397 data URL
mod_ratelimit
Provides Bandwidth Rate Limiting for Clients
mod_request
Provides Filters to handle and make available HTTP request bodies
mod_reflector
Provides Reflection of a request body as a response via the output filter stack.
mod_slotmem_shm
Provides a Slot-based shared memory provider (ala the scoreboard).
mod_xml2enc
Formerly a third-party module, this supports internationalisation in libxml2-based (markup-aware) filter modules.
top

Module Enhancements

mod_ssl
mod_ssl can now be configured to use an OCSP server to check the validation status of a client certificate. The default responder is configurable, along with the decision on whether to prefer the responder designated in the client certificate itself.
mod_ssl now also supports OCSP stapling, where the server pro-actively obtains an OCSP verification of its certificate and transmits that to the client during the handshake.
mod_ssl can now be configured to share SSL Session data between servers through memcached
EC keys are now supported in addition to RSA and DSA.
mod_proxy
The ProxyPass directive is now most optimally configured within a Location or LocationMatch block, and offers a significant performance advantage over the traditional two-parameter syntax when present in large numbers.
The source address used for proxy requests is now configurable.
mod_proxy_balancer
More runtime configuration changes for BalancerMembers via balancer-manager
Additional BalancerMembers can be added at runtime via balancer-manager
Runtime configuration of a subset of Balancer parameters
BalancerMembers can be set to ‘Drain’ so that they only respond to existing sticky sessions, allowing them to be taken gracefully offline.
Balancer settings can be persistent after restarts.
mod_cache
The mod_cache CACHE filter can be optionally inserted at a given point in the filter chain to provide fine control over caching.
mod_cache can now cache HEAD requests.
Where possible, mod_cache directives can now be set per directory, instead of per server.
The base URL of cached URLs can be customised, so that a cluster of caches can share the same endpoint URL prefix.
mod_cache is now capable of serving stale cached data when a backend is unavailable (error 5xx).
mod_cache can now insert HIT/MISS/REVALIDATE into an X-Cache header.
mod_include
Support for the ‘onerror’ attribute within an ‘include’ element, allowing an error document to be served on error instead of the default error string.
mod_cgimod_includemod_isapi, …
Translation of headers to environment variables is more strict than before to mitigate some possible cross-site-scripting attacks via header injection. Headers containing invalid characters (including underscores) are now silently dropped. Environment Variables in Apache has some pointers on how to work around broken legacy clients which require such headers. (This affects all modules which use these environment variables.)
mod_authz_core Authorization Logic Containers
Advanced authorization logic may now be specified using the Require directive and the related container directives, such as <RequireAll>.
mod_rewrite
mod_rewrite adds the [QSD] (Query String Discard) and [END] flags for RewriteRule to simplify common rewriting scenarios.
Adds the possibility to use complex boolean expressions in RewriteCond.
Allows to use SQL queries as RewriteMap functions.
mod_ldapmod_authnz_ldap
mod_authnz_ldap adds support for nested groups.
mod_ldap adds LDAPConnectionPoolTTLLDAPTimeout, and other improvements in the handling of timeouts. This is especially useful for setups where a stateful firewall drops idle connections to the LDAP server.
mod_ldap adds LDAPLibraryDebug to log debug information provided by the used LDAP toolkit.
mod_info
mod_info can now dump the pre-parsed configuration to stdout during server startup.
top

Program Enhancements

fcgistarter
New FastCGI deamon starter utility
htcacheclean
Current cached URLs can now be listed, with optional metadata included.
Allow explicit deletion of individual cached URLs from the cache.
File sizes can now be rounded up to the given block size, making the size limits map more closely to the real size on disk.
Cache size can now be limited by the number of inodes, instead of or in addition to being limited by the size of the files on disk.
rotatelogs
May now create a link to the current log file.
May now invoke a custom post-rotate script.
top

Documentation

mod_rewrite
The mod_rewrite documentation has been rearranged and almost completely rewritten, with a focus on examples and common usage, as well as on showing you when other solutions are more appropriate. The Rewrite Guide is now a top-level section with much more detail and better organization.
mod_ssl
The mod_ssl documentation has been greatly enhanced, with more examples at the getting started level, in addition to the previous focus on technical details.
Caching Guide
The Caching Guide has been rewritten to properly distinguish between the RFC2616 HTTP/1.1 caching features provided by mod_cache, and the generic key/value caching provided by the socache interface, as well as to cover specialised caching provided by mechanisms such as mod_file_cache.
top

Module Developer Changes

Check Configuration Hook Added
A new hook, check_config, has been added which runs between the pre_config and open_logs hooks. It also runs before the test_config hook when the -t option is passed to httpd. The check_config hook allows modules to review interdependent configuration directive values and adjust them while messages can still be logged to the console. The user can thus be alerted to misconfiguration problems before the core open_logs hook function redirects console output to the error log.
Expression Parser Added
We now have a general-purpose expression parser, whose API is exposed in ap_expr.h. This is adapted from the expression parser previously implemented in mod_ssl.
Authorization Logic Containers
Authorization modules now register as a provider, via ap_register_auth_provider(), to support advanced authorization logic, such as <RequireAll>.
Small-Object Caching Interface
The ap_socache.h header exposes a provider-based interface for caching small data objects, based on the previous implementation of the mod_ssl session cache. Providers using a shared-memory cyclic buffer, disk-based dbm files, and a memcache distributed cache are currently supported.
Cache Status Hook Added
The mod_cache module now includes a new cache_status hook, which is called when the caching decision becomes known. A default implementation is provided which adds an optional X-Cache and X-Cache-Detail header to the response.

The developer documentation contains a detailed list of API changes.

分类: 网络转载 标签: