php 在 fpm 下生成随机数研究
2015-11-18 16:05
597 查看
本文发表于 rust.love
下面这段代码,在 fpm 下,交替打开注释 [1] 和 [2] ,你会发现多次请求随机数相同的现象。
带着这个疑问,我探寻了一下源代码,最后大体上整理了一个源码的流程出来
通过代码可以看出,
通过上面的结构体,我们看到了一些状态等信息,上面的这些代码主要展示了生成随机数的一些关键环节。下面我们说一说产生随机数不随机相关情况:
先全部重启 php-fpm
第一个实验,在调用 mt_rand(1000, 9999) 之前,调用 mt_srand(10),此时所有请求的随机数相同,这个没有问题。
去掉 mt_srand(10),如果你fpm fork 了3个进程,那么根据pid查看这三个进程的随机数都是相同的,这三个进程执行一轮以后,就会生成新的随机数。
把 mt_srand(10) 换成 mt_srand() 恢复了正确的随机数
再进行第1步操作后,只调用 mt_rand(1000, 9999) 一切正常
那么为什么 mt_srand(seed) 以后,就出现不随机的状况了呢?
因为fork出来的进程,当我们mt_srand(seed) 以后,进程所共享的 seed 是一样的,所以不同进程生成的随机数才是一样的。而 mt_srand() 这种调用,系统是根据GENERATE_SEED()来生成的seed,这个seed是包含了一些pid等其他信息,所以每个进程的seed是不同的,最后生成的随机数也是不同的。
下面是 GENERATE_SEED() 的定义:
最后的代码是一个简单的扩展函数,输出执行
下面这段代码,在 fpm 下,交替打开注释 [1] 和 [2] ,你会发现多次请求随机数相同的现象。
echo str_repeat("=", 100), '<br>'; echo getmypid(), '<br>'; echo str_repeat("=", 100), '<br>'; // [1] mt_srand(10); // [2] mt_srand(); for ($i = 0; $i < 10; $i ++) { */ echo mt_rand(1000, 9999), '<br>'; */ }
带着这个疑问,我探寻了一下源代码,最后大体上整理了一个源码的流程出来
#define N MT_N /* length of state vector MT_N = 624*/ #define M (397) /* a period parameter */ #define hiBit(u) ((u) & 0x80000000U) /* mask all but highest bit of u */ #define loBit(u) ((u) & 0x00000001U) /* mask all but lowest bit of u */ #define loBits(u) ((u) & 0x7FFFFFFFU) /* mask the highest bit of u */ #define mixBits(u, v) (hiBit(u)|loBits(v)) /* move hi bit of u to hi bit of v */ #define twist(m,u,v) (m ^ (mixBits(u,v)>>1) ^ ((php_uint32)(-(php_int32)(loBit(u))) & 0x9908b0dfU)) PHP_FUNCTION(mt_rand) { ...... if (!BG(mt_rand_is_seeded)) { php_mt_srand(GENERATE_SEED() TSRMLS_CC); } ...... } PHP_FUNCTION(mt_srand) { long seed = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &seed) == FAILURE) return; if (ZEND_NUM_ARGS() == 0) seed = GENERATE_SEED(); php_mt_srand(seed TSRMLS_CC); } PHPAPI void php_mt_srand(php_uint32 seed TSRMLS_DC) { /* Seed the generator with a simple uint32 */ php_mt_initialize(seed, BG(state)); php_mt_reload(TSRMLS_C); /* Seed only once */ BG(mt_rand_is_seeded) = 1; } static inline void php_mt_initialize(php_uint32 seed, php_uint32 *state) { /* Initialize generator state with seed See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier. In previous versions, most significant bits (MSBs) of the seed affect only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto. */ register php_uint32 *s = state; register php_uint32 *r = state; register int i = 1; *s++ = seed & 0xffffffffU; for( ; i < N; ++i ) { *s++ = ( 1812433253U * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffU; r++; } } static inline void php_mt_reload(TSRMLS_D) { /* Generate N new values in state Made clearer and faster by Matthew Bellew (matthew.bellew@home.com) */ register php_uint32 *state = BG(state); register php_uint32 *p = state; register int i; for (i = N - M; i--; ++p) *p = twist(p[M], p[0], p[1]); for (i = M; --i; ++p) *p = twist(p[M-N], p[0], p[1]); *p = twist(p[M-N], p[0], state[0]); BG(left) = N; BG(next) = state; }
通过代码可以看出,
mt_rand和
mt_srand其实没有明显的差别,无非是
seed的处理上有一些差别。而存储随机数相关的数据结构在一个叫
_php_basic_globals的结构体里,代码如下(摘抄了部分):
/* rand.c */ php_uint32 state[MT_N+1]; /* state vector + 1 extra to not violate ANSI C */ php_uint32 *next; /* next random value is computed from here */ int left; /* can *next++ this many times before reloading */ unsigned int rand_seed; /* Seed for rand(), in ts version */ zend_bool rand_is_seeded; /* Whether rand() has been seeded */ zend_bool mt_rand_is_seeded; /* Whether mt_rand() has been seeded */
通过上面的结构体,我们看到了一些状态等信息,上面的这些代码主要展示了生成随机数的一些关键环节。下面我们说一说产生随机数不随机相关情况:
先全部重启 php-fpm
第一个实验,在调用 mt_rand(1000, 9999) 之前,调用 mt_srand(10),此时所有请求的随机数相同,这个没有问题。
去掉 mt_srand(10),如果你fpm fork 了3个进程,那么根据pid查看这三个进程的随机数都是相同的,这三个进程执行一轮以后,就会生成新的随机数。
把 mt_srand(10) 换成 mt_srand() 恢复了正确的随机数
再进行第1步操作后,只调用 mt_rand(1000, 9999) 一切正常
那么为什么 mt_srand(seed) 以后,就出现不随机的状况了呢?
因为fork出来的进程,当我们mt_srand(seed) 以后,进程所共享的 seed 是一样的,所以不同进程生成的随机数才是一样的。而 mt_srand() 这种调用,系统是根据GENERATE_SEED()来生成的seed,这个seed是包含了一些pid等其他信息,所以每个进程的seed是不同的,最后生成的随机数也是不同的。
下面是 GENERATE_SEED() 的定义:
#ifdef PHP_WIN32 #define GENERATE_SEED() (((long) (time(0) * GetCurrentProcessId())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #else #define GENERATE_SEED() (((long) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #endif
最后的代码是一个简单的扩展函数,输出执行
test_mt_rand过程中内核的一些相关数据,php版本5.5.30
PHP_FUNCTION(test_mt_rand) { long min; long max; long number; int argc = ZEND_NUM_ARGS(); if (argc != 0) { if (zend_parse_parameters(argc TSRMLS_CC, "ll", &min, &max) == FAILURE) { return; } else if (max < min) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "max(%ld) is smaller than min(%ld)", max, min); RETURN_FALSE; } } zval *opt_array; MAKE_STD_ZVAL(opt_array); array_init(opt_array); add_assoc_long(opt_array, "MIN", min); add_assoc_long(opt_array, "MAX", max); add_assoc_long(opt_array, "NUMBER0", number); add_assoc_long(opt_array, "BG_SEEDED", BG(mt_rand_is_seeded)); add_assoc_long(opt_array, "GEN_SEED", GENERATE_SEED()); if (!BG(mt_rand_is_seeded)) { php_mt_srand(GENERATE_SEED() TSRMLS_CC); } /* * Melo: hmms.. randomMT() returns 32 random bits... * Yet, the previous php_rand only returns 31 at most. * So I put a right shift to loose the lsb. It *seems* * better than clearing the msb. * Update: * I talked with Cokus via email and it won't ruin the algorithm */ int index = 0; php_uint32 *st= BG(state); zval *subArr; MAKE_STD_ZVAL(subArr); array_init(subArr); for (index = 0; index < MT_N; index ++) { add_index_long(subArr, index, *st++); } add_assoc_zval(opt_array, "state1", subArr); add_assoc_long(opt_array, "BG(next)", *BG(next)); number = (long) (php_mt_rand(TSRMLS_C) >> 1); php_uint32 *st1 = BG(state); zval *subArr1; MAKE_STD_ZVAL(subArr1); array_init(subArr1); for (index = 0; index < MT_N; index ++) { add_index_long(subArr1, index, *st1 ++); } add_assoc_zval(opt_array, "state2", subArr1); add_assoc_long(opt_array, "NUMBER_php_mt_rand", number); if (argc == 2) { RAND_RANGE(number, min, max, PHP_MT_RAND_MAX); add_assoc_long(opt_array, "PHP_MT_RAND_MAX", PHP_MT_RAND_MAX); add_assoc_long(opt_array, "RETURN", number); } add_assoc_long(opt_array, "SEED", BG(rand_seed)); add_assoc_long(opt_array, "LEFT", BG(left)); RETURN_ZVAL(opt_array, 1, 0); }
相关文章推荐
- sftp 工具类
- PHP 特殊方法 __set()、__get()
- PHP的加密解密字符串函数
- php构建RSS
- VirtualBox下Ubuntu上网设置,能够使用cuteftp传输资料
- PHP 5.3-5.5 新特性
- PHP - 唯一标示符
- PHPExcel出现错误101 net::ERR_CONNECTION_RESET
- PHP获取目录下的文件(包括子目录中的文件)
- PHP json_decode中文转义的问题
- yii2 打印sql语句
- 常用 php server
- PHP有关函数的编程思想(递归与迭代)
- PHP - 判断php是否对表单数据内的特殊字符自动转义
- directoryContentsAtPath is deprecated.
- php pdo sqlserver分页sql的处理
- PHPstorm注释修改
- 【ThinkPHP】U()函数和I()函数
- php学习之连接数据库微型博客
- PHP7新特性 What will be in PHP 7/PHPNG