尽管ISO C99使用了非常简单的并且具备移植性的样例描述了rand函数和srand函数的实现。但是在具体的C语言函数库的实现上,由于考虑到运行效率以及线程安全,代码就稍微多了一些。
这里以glibc 2.18为例。
在stdlib目录下,我们找到rand.c,内容如下:
在同目录下的random.c,我们找到__random函数,内容如下:
这个函数调用__random_r函数,传入两个参数&unsafe_state和&retval,返回retval。所以我们可以确定retval是即将生成的伪随机数,而unsafe_state是什么呢?
在random.c中,我们找到unsafe_state的定义:
unsafe_state是一个全局的静态变量,类型为random_data的结构体。我们查看stdlib.h,找到struct random_data的定义,发现它其实就是我们一直所说的伪随机数发生器的“种子”,在glibc的实现中,由于在多线程的情况下,如果函数使用一个静态变量,则这个函数不具备有“可重入”性,就是在多线程调用的情况下会发生意想不到的情形。所以glibc对这种情况作出了修正,保证了rand函数的“可重入性”。首先我们来看random_data的定义:
那么glibc是如何保证函数的可重入性的呢?其实就是__random函数中的两行代码__libc_lock_lock (lock)和__libc_lock_unlock (lock),这个lock保证了线程在访问&unsafe_state资源的互斥性,从而保证了函数的可重入性。那么这个lock的机制是从何而来的呢?在random.c文件中我们可以读到lock的初始化语句:
初始化锁(__libc_lock_define_initialized)、加锁(__libc_lock_lock)、解锁(__libc_lock_unlock)的操作属于宏,我们可以在bits目录下的libc-lock.h中找到宏的定义(这里说明一下,在我下载的glic源码中的该文件是stub version,缺少具体的定义,仅有宏名称。我在unbuntu12.04上找到了相应的bits目录下的libc-lock.h,属于NPTL version,有宏的定义):
而lll_lock和lll_unlock属于底层的对互斥锁进行操作的宏,这里不深究。
在保证了函数的“可重入性”之后,rand函数调用链条上的最后一环就是__random_r这个函数(在random_r.c中),它真正进行对unsafe_state和retval的操作,产生一个伪随机数,并且对“种子”进行更新。
在看完了rand函数之后,让我们来看看srand函数。在目录中,我们找不到srand.c这样的文件,但是在random.c中,我们可以看到:
这两行代码的意思就是为__srandom这个符号设置一个弱符号的别名。什么是弱符号,这里不深究。大致的意思就是如果你在其他的文件中定义了srand函数和srandom函数,你可以放心使用你定义的函数,而不必担心被这里的弱符号别名所影响。weak_alias的定义在libc-symbols.h当中:
所以要看srand函数,就看__srandom函数,这个函数接收一个x,在编程当中我们调用srand((unsigned int)time(NULL)),所以x就是当前的时间距离1970年1月1日0时的秒数。函数如下:
我们在random_r.c中找到__srandom_r函数,这个函数根据传入的x来改变全局静态变量unsafe_state的状态,就是改变了“种子”,所以能够使伪随机数发生器根据这个“种子”来产生伪随机数序列。函数的实现如下:
至此,我们应该可以说自己对glibc中rand函数和srand函数的实现有了初步的认识。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/13399.html