您的位置:首页 > 编程语言 > PHP开发

php 在 fpm 下生成随机数研究

2015-11-18 16:05 597 查看
本文发表于 rust.love

下面这段代码,在 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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: