关闭

php 在 fpm 下生成随机数研究

标签: phpc
917人阅读 评论(0) 收藏 举报
分类:

本文发表于 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_randmt_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 */

通过上面的结构体,我们看到了一些状态等信息,上面的这些代码主要展示了生成随机数的一些关键环节。下面我们说一说产生随机数不随机相关情况:

  1. 先全部重启 php-fpm
  2. 第一个实验,在调用 mt_rand(1000, 9999) 之前,调用 mt_srand(10),此时所有请求的随机数相同,这个没有问题。

  3. 去掉 mt_srand(10),如果你fpm fork 了3个进程,那么根据pid查看这三个进程的随机数都是相同的,这三个进程执行一轮以后,就会生成新的随机数。

  4. 把 mt_srand(10) 换成 mt_srand() 恢复了正确的随机数
  5. 再进行第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);
}
0
0

猜你在找
【套餐】Hadoop生态系统零基础入门
【套餐】嵌入式Linux C编程基础
【套餐】2017软考系统集成项目——任铄
【套餐】Android 5.x顶级视频课程——李宁
【套餐】深度学习入门视频课程——唐宇迪
【直播】广义线性模型及其应用——李科
【直播】从0到1 区块链的概念到实践
【直播】计算机视觉原理及实战——屈教授
【直播】机器学习之凸优化——马博士
【直播】机器学习&数据挖掘7周实训--韦玮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:232101次
    • 积分:3762
    • 等级:
    • 排名:第8493名
    • 原创:127篇
    • 转载:8篇
    • 译文:8篇
    • 评论:47条
    文章分类
    最新评论