Session是以扩展的形式嵌入到PHP内核的,所以我们可以把Session当成扩展来看待。

一般扩展被载入到PHP会经过下面几个过程

[cpp] view plaincopy
  1. #define PHP_MINIT_FUNCTION      ZEND_MODULE_STARTUP_D       // 初始化module时运行
  2. #define PHP_MSHUTDOWN_FUNCTION  ZEND_MODULE_SHUTDOWN_D      // 当module被卸载时运行
  3. #define PHP_RINIT_FUNCTION      ZEND_MODULE_ACTIVATE_D      // 当一个REQUEST请求初始化时运行
  4. #define PHP_RSHUTDOWN_FUNCTION  ZEND_MODULE_DEACTIVATE_D    // 当一个REQUEST请求结束时运行
  5. #define PHP_MINFO_FUNCTION      ZEND_MODULE_INFO_D          // 这个是设置phpinfo中这个模块的信息
  6. #define PHP_GINIT_FUNCTION      ZEND_GINIT_FUNCTION         // 初始化全局变量时
  7. #define PHP_GSHUTDOWN_FUNCTION  ZEND_GSHUTDOWN_FUNCTION     // 释放全局变量时

具体的执行顺序跟PHP的生命周期相同
MINIT -> RINIT ->RSHUTDOWN -> MSHUTDOWN

1. SESSION模块的初始化  PHP_MINIT_FUNCTION

对于Session而言,PHP_MINIT_FUNCTION主要完成的初始化工作包括(注:不同版本的PHP具体处理过程并不完全相同,如PHP 5.4+提供了SessionHandlerInterface,这样可以通过session_set_save_handler ( SessionHandlerInterface $sessionhandler )的方式自定义Session的处理机制,而不必像之前一样使用冗长的boolsession_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )):

(1).  注册$_SESSION超全局变量:

[cpp] view plaincopy
  1. zend_register_auto_global("_SESSION", sizeof("_SESSION")-1, NULL TSRMLS_CC);

就是说,$_SESSION超全局变量实际上是在session的MINIT阶段被注册的。

相关阅读 :PHP语言中的超级全局变量(Superglobals) http://www.walu.cc/phpbook/12.5.md

(2).  读取ini文件中的相关配置。

[cpp] view plaincopy
  1. REGISTER_INI_ENTRIES();

REGISTER_INI_ENTRIES();实际上是一个宏定义:

#define REGISTER_INI_ENTRIES() zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)

因此,实际上是调用zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)。关于ini文件的解析和配置,已经超出了本文的范畴,可以参考这篇文章:http://www.cnblogs.com/driftcloudy/p/4011954.html 。

  扩展中读取和设置ini的相关配置位于PHP_INI_BEGIN和PHP_INI_END宏之间。对于session而言,实际上包括:

[cpp] view plaincopy
  1. /* {{{ PHP_INI
  2. */
  3. PHP_INI_BEGIN()
  4. STD_PHP_INI_ENTRY("session.save_path",          "",          PHP_INI_ALL, OnUpdateSaveDir,save_path,          php_ps_globals,    ps_globals)
  5. STD_PHP_INI_ENTRY("session.name",               "PHPSESSID", PHP_INI_ALL, OnUpdateName, session_name,       php_ps_globals,    ps_globals)
  6. PHP_INI_ENTRY("session.save_handler",           "files",     PHP_INI_ALL, OnUpdateSaveHandler)
  7. STD_PHP_INI_BOOLEAN("session.auto_start",       "0",         PHP_INI_PERDIR, OnUpdateBool,   auto_start,         php_ps_globals,    ps_globals)
  8. STD_PHP_INI_ENTRY("session.gc_probability",     "1",         PHP_INI_ALL, OnUpdateLong,   gc_probability,     php_ps_globals,    ps_globals)
  9. STD_PHP_INI_ENTRY("session.gc_divisor",         "100",       PHP_INI_ALL, OnUpdateLong,   gc_divisor,         php_ps_globals,    ps_globals)
  10. STD_PHP_INI_ENTRY("session.gc_maxlifetime",     "1440",      PHP_INI_ALL, OnUpdateLong,   gc_maxlifetime,     php_ps_globals,    ps_globals)
  11. PHP_INI_ENTRY("session.serialize_handler",      "php",       PHP_INI_ALL, OnUpdateSerializer)
  12. STD_PHP_INI_ENTRY("session.cookie_lifetime",    "0",         PHP_INI_ALL, OnUpdateLong,   cookie_lifetime,    php_ps_globals,    ps_globals)
  13. STD_PHP_INI_ENTRY("session.cookie_path",        "/",         PHP_INI_ALL, OnUpdateString, cookie_path,        php_ps_globals,    ps_globals)
  14. STD_PHP_INI_ENTRY("session.cookie_domain",      "",          PHP_INI_ALL, OnUpdateString, cookie_domain,      php_ps_globals,    ps_globals)
  15. STD_PHP_INI_BOOLEAN("session.cookie_secure",    "",          PHP_INI_ALL, OnUpdateBool,   cookie_secure,      php_ps_globals,    ps_globals)
  16. STD_PHP_INI_BOOLEAN("session.cookie_httponly",  "",          PHP_INI_ALL, OnUpdateBool,   cookie_httponly,    php_ps_globals,    ps_globals)
  17. STD_PHP_INI_BOOLEAN("session.use_cookies",      "1",         PHP_INI_ALL, OnUpdateBool,   use_cookies,        php_ps_globals,    ps_globals)
  18. STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1",         PHP_INI_ALL, OnUpdateBool,   use_only_cookies,   php_ps_globals,    ps_globals)
  19. STD_PHP_INI_BOOLEAN("session.use_strict_mode",  "0",         PHP_INI_ALL, OnUpdateBool,   use_strict_mode,    php_ps_globals,    ps_globals)
  20. STD_PHP_INI_ENTRY("session.referer_check",      "",          PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals,    ps_globals)
  21. #if HAVE_DEV_URANDOM
  22. STD_PHP_INI_ENTRY("session.entropy_file",       "/dev/urandom",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
  23. STD_PHP_INI_ENTRY("session.entropy_length",     "32",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
  24. #elif HAVE_DEV_ARANDOM
  25. STD_PHP_INI_ENTRY("session.entropy_file",       "/dev/arandom",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
  26. STD_PHP_INI_ENTRY("session.entropy_length",     "32",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
  27. #else
  28. STD_PHP_INI_ENTRY("session.entropy_file",       "",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
  29. STD_PHP_INI_ENTRY("session.entropy_length",     "0",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
  30. #endif
  31. STD_PHP_INI_ENTRY("session.cache_limiter",      "nocache",   PHP_INI_ALL, OnUpdateString, cache_limiter,      php_ps_globals,    ps_globals)
  32. STD_PHP_INI_ENTRY("session.cache_expire",       "180",       PHP_INI_ALL, OnUpdateLong,   cache_expire,       php_ps_globals,    ps_globals)
  33. PHP_INI_ENTRY("session.use_trans_sid",          "0",         PHP_INI_ALL, OnUpdateTransSid)
  34. PHP_INI_ENTRY("session.hash_function",          "0",         PHP_INI_ALL, OnUpdateHashFunc)
  35. STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4",    PHP_INI_ALL, OnUpdateLong,   hash_bits_per_character, php_ps_globals, ps_globals)
  36. /* Upload progress */
  37. STD_PHP_INI_BOOLEAN("session.upload_progress.enabled",
  38. "1",     ZEND_INI_PERDIR, OnUpdateBool,        rfc1867_enabled, php_ps_globals, ps_globals)
  39. STD_PHP_INI_BOOLEAN("session.upload_progress.cleanup",
  40. "1",     ZEND_INI_PERDIR, OnUpdateBool,        rfc1867_cleanup, php_ps_globals, ps_globals)
  41. STD_PHP_INI_ENTRY("session.upload_progress.prefix",
  42. "upload_progress_", ZEND_INI_PERDIR, OnUpdateSmartStr,      rfc1867_prefix,  php_ps_globals, ps_globals)
  43. STD_PHP_INI_ENTRY("session.upload_progress.name",
  44. "PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateSmartStr,      rfc1867_name,    php_ps_globals, ps_globals)
  45. STD_PHP_INI_ENTRY("session.upload_progress.freq",  "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq,    php_ps_globals, ps_globals)
  46. STD_PHP_INI_ENTRY("session.upload_progress.min_freq",
  47. "1",  ZEND_INI_PERDIR, OnUpdateReal,        rfc1867_min_freq,php_ps_globals, ps_globals)
  48. /* Commented out until future discussion */
  49. /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */
  50. PHP_INI_END()
  51. /* }}} */

如果在ini文件中没有配置相关的参数项,在session的MINIT阶段,参数会被初始化为默认的值。

(3).  注册SessionHandler和SessionHandlerInterface这两个Class

自php 5.4起,php提供了SessionHandler和SessionHandlerInterface这两个Class, 因此还需要对这两个Class做相关的初始化工作。这是通过:

INIT_CLASS_ENTRY(ce, PS_IFACE_NAME, php_session_iface_functions);

INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions);

来实现的,有兴趣的同学可以查看具体的实现过程,这里不再赘述。

2. session请求时的准备RINIT   PHP_RINIT_FUNCTION

[cpp] view plaincopy
  1. static PHP_RINIT_FUNCTION(session) /* {{{ */
  2. {
  3. return php_rinit_session(PS(auto_start) TSRMLS_CC);
  4. }
  5. /* ********************************
  6. * Module Setup and Destruction *
  7. ******************************** */
  8. static int php_rinit_session(zend_bool auto_start TSRMLS_DC) /* {{{ */
  9. {// 初始化session相关的全局变量
  10. php_rinit_session_globals(TSRMLS_C);
  11. // 根据ini的配置查找session.save_handler,从而确定是使用files还是user( 或者是其他的扩展方式)来处理session:
  12. if (PS(mod) == NULL) {
  13. char *value;
  14. value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
  15. if (value) {
  16. PS(mod) = _php_find_ps_module(value TSRMLS_CC);
  17. }
  18. }
  19. // 确定完session的save_handler之后。需要确定serializer,Serializer用于完成session数据的序列化和反序列化
  20. if (PS(serializer) == NULL) {
  21. char *value;
  22. value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
  23. if (value) {
  24. PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
  25. }
  26. }
  27. // mod和serializer 如果有一个不成功,更新session_status 状态
  28. if (PS(mod) == NULL || PS(serializer) == NULL) {
  29. /* current status is unusable */
  30. PS(session_status) = php_session_disabled;
  31. return SUCCESS;
  32. }
  33. // 如果ini 中 session.auto_start 为1 自动session_start
  34. if (auto_start) {
  35. php_session_start(TSRMLS_C);
  36. }
  37. return SUCCESS;
  38. } /* }}} */

正如上面的代码所写,PHP_RINIT_FUNCTION(session)主要经过下面几个步骤:

(1).初始化session相关的全局变量,这是通过php_rinit_session_globals来完成的:

[cpp] view plaincopy
  1. /* Dispatched by RINIT and by php_session_destroy */
  2. static inline void php_rinit_session_globals(TSRMLS_D) /* {{{ */
  3. {
  4. PS(id) = NULL;
  5. PS(session_status) = php_session_none;
  6. PS(mod_data) = NULL;
  7. PS(mod_user_is_open) = 0;
  8. /* Do NOT init PS(mod_user_names) here! */
  9. PS(http_session_vars) = NULL;
  10. }
  11. /* }}} */

(2).根据ini的配置查找session.save_handler,从而确定是使用files还是user( 或者是其他的扩展方式)来处理session:

[cpp] view plaincopy
  1. if (PS(mod) == NULL) {
  2. char *value;
  3. value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
  4. if (value) {
  5. PS(mod) = _php_find_ps_module(value TSRMLS_CC);
  6. }
  7. }

确定是user还是files来处理session的逻辑是由_php_find_ps_module来完成的,这个函数会依次查找ps_modules中预定义的module, 一旦查找成功,立即返回:

[cpp] view plaincopy
  1. PHPAPI ps_module *_php_find_ps_module(char *name TSRMLS_DC) /* {{{ */
  2. {
  3. ps_module *ret = NULL;
  4. ps_module **mod;
  5. int i;
  6. for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) {
  7. if (*mod && !strcasecmp(name, (*mod)->s_name)) {
  8. ret = *mod;
  9. break;
  10. }
  11. }
  12. return ret;
  13. }
  14. /* }}} */

ps_module 定义:

[cpp] view plaincopy
  1. /* *******************
  2. * Storage Modules *
  3. ******************* */
  4. #define MAX_MODULES 10
  5. #define PREDEFINED_MODULES 2
  6. static ps_module *ps_modules[MAX_MODULES + 1] = {
  7. ps_files_ptr,
  8. ps_user_ptr
  9. };

ps_files_ptr和ps_user_ptr,是在mod_*.h中定义

[cpp] view plaincopy
  1. ps_files_ptr和ps_user_ptr,是在mod_*.h中定义
  2. ext/session/mod_files.h
  3. extern ps_module ps_mod_files;
  4. #define ps_files_ptr &ps_mod_files
  5. ps_mod_files的内容是
  6. ps_module ps_mod_files = {
  7. PS_MOD_SID(files)
  8. };

而每一个ps_module,实际上是一个struct:

[cpp] view plaincopy
  1. typedef struct ps_module_struct {
  2. const char *s_name;
  3. int (*s_open)(PS_OPEN_ARGS);
  4. int (*s_close)(PS_CLOSE_ARGS);
  5. int (*s_read)(PS_READ_ARGS);
  6. int (*s_write)(PS_WRITE_ARGS);
  7. int (*s_destroy)(PS_DESTROY_ARGS);
  8. int (*s_gc)(PS_GC_ARGS);
  9. char *(*s_create_sid)(PS_CREATE_SID_ARGS);
  10. } ps_module;

特别说明一下:上面的struct中的int (*s_open)(PS_OPEN_ARGS); 等  为函数指针,也就是说只需要对其进行函数名的赋值就可以调用了,关于函数指针不了解的可以百度。

PS_MOD_SID(files)是一个宏定义:

[cpp] view plaincopy
  1. #define PS_MOD_SID(x) \
  2. #x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \
  3. ps_delete_##x, ps_gc_##x, ps_create_sid_##x

所以,ps_mod_files展开后就是:

[cpp] view plaincopy
  1. ps_module ps_mod_files = {
  2. files,
  3. ps_open_files,
  4. ps_close_files,
  5. ps_read_files,
  6. ps_write_files,
  7. ps_delete_files,
  8. ps_gc_files,
  9. php_session_sid_files
  10. };

所以  PS(mod)->s_open(PS_OPEN_ARGS)

其实调用的是  PS(mod)->ps_open_files(PS_OPEN_ARGS)  函数也就是  PS_OPEN_FUNC(files)#define PS_OPEN_FUNC(x)  int ps_open_##x(PS_OPEN_ARGS)

这意味着,每一个处理session的mod,不管是files, user还是其他扩展的模块,都应该包含ps_module中定义的字段,

分别是:

module的名称(s_name),

打开句柄函数(s_open),

关闭句柄函数(s_close),

读取函数(s_read) ,

写入函数(s_write),

销毁函数(s_destroy),

gc函数(s_gc),

生成session_id的函数(s_create_sid)。

我们花费了大量的精力来说session.save_handler, 其实是想说明:原则上,session可以存储在任何可行的存储中的(例如文件,数据库,memcache和redis),如果你自己开发了一个存储系统,比memcache的性能更好,那么OK, 你只要按照session存储的规范,设置好session.save_handler,不管是你在脚本中提供接口还是使用扩展,可以很方便的操作session数据,这块也是分布式。

(3).session数据的序列化和反序列化

确定完session的save_handler之后。需要确定serializer, 这个也是必须的。Serializer用于完成session数据的序列化和反序列化,我们在session.save_handler=files的情况下可以看到,session数据并不是直接写入文件的,而是通过一定的序列化机制序列化之后存储到文件的,在读取session数据时需要对文件的内容进行反序列化:

[cpp] view plaincopy
  1. session_save_path('/tmp/session');
  2. session_start();
  3. $_SESSION['key'] = 'value';
  4. session_write_close();

则相应session文件的内容是:

key|s:5:"value"

查找serializer的过程与查找PS(mod)的方式类似:

[cpp] view plaincopy
  1. if (PS(serializer) == NULL) {
  2. char *value;
  3. value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
  4. if (value) {
  5. PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
  6. }
  7. }

_php_find_ps_serializer也是在预定义的ps_serializers数组中查找:

[cpp] view plaincopy
  1. PHPAPI const ps_serializer *_php_find_ps_serializer(char *name TSRMLS_DC) {
  2. const ps_serializer *ret = NULL;
  3. const ps_serializer *mod;
  4. for (mod = ps_serializers; mod->name; mod++) {
  5. if (!strcasecmp(name, mod->name)) {
  6. ret = mod;
  7. break;
  8. }
  9. }
  10. return ret;
  11. }
  12. static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = {
  13. PS_SERIALIZER_ENTRY(php_serialize),
  14. PS_SERIALIZER_ENTRY(php),
  15. PS_SERIALIZER_ENTRY(php_binary)
  16. };

同样,每一个serializer都是一个struct:

[cpp] view plaincopy
  1. typedef struct ps_serializer_struct {
  2. const char *name;
  3. int (*encode)(PS_SERIALIZER_ENCODE_ARGS);
  4. int (*decode)(PS_SERIALIZER_DECODE_ARGS);
  5. } ps_serializer;

这时,如果mod不存在(设置的session.save_handler错误)或者serializer不存在,那么直接标记session_status为php_session_disabled,并返回,后面的代码不再执行。否则,确定了mod和serializer,如果设置了session.auto_start,那么就自动开启session:

[cpp] view plaincopy
  1. if (auto_start) {
  2. php_session_start(TSRMLS_C);
  3. }

3.session_start

 session_start用于开启或者重用现有的会话,在底层,其实现为:

[cpp] view plaincopy
  1. /* {{{ proto bool session_start(void)
  2. Begin session - reinitializes freezed variables, registers browsers etc */
  3. static PHP_FUNCTION(session_start)
  4. {
  5. /* skipping check for non-zero args for performance reasons here ?*/
  6. php_session_start(TSRMLS_C);
  7. if (PS(session_status) != php_session_active) {
  8. RETURN_FALSE;
  9. }
  10. RETURN_TRUE;
  11. }
  12. /* }}} */

内部是调用php_session_start完成session相关上下文的设置, 其基本步骤是:

(1).  检查当前会话的session状态。

php_session_status用于标志所有可能的会话状态,它是一个enum:

[cpp] view plaincopy
  1. //php_session.h  line: 95
  2. typedef enum {
  3. php_session_disabled,
  4. php_session_none,
  5. php_session_active
  6. } php_session_status;

那么可能的情况有:

  (a). session_status = php_session_active

  表明已经开启了session。那么忽略本次的session_start(), 但同时会产生一条警告信息:

A session had already been started - ignoring session_start()

     (b). session_status = php_session_ disabled

这种情况可能发生在RINIT的过程中,前面我们看到:

[cpp] view plaincopy
  1. if (PS(mod) == NULL || PS(serializer) == NULL) {
  2. /* current status is unusable */
  3. PS(session_status) = php_session_disabled;
  4. return SUCCESS;
  5. }

如果session_status = php_session_ disabled, 无法确定session是否真不可用(比如我们在脚本中设置了session_set_save_handler),还要做进一步的分析。查找mod和serializer的过程与RINIT的类似。

(c). session_status = php_session_none

  在session_status= php_session_ disabled和php_session_none的情况下,都会继续向下执行。

(2).  获取session_id

如果session_id不存在,那么内核会依次尝试下列方法获取session_id

(为了方便起见,我们直接使用了$_COOKIE, $_GET, $_POST,实际上这样是不严谨的,因为这些超级全局变量是php内核生成并提供给应用程序的,内核实际上是在全局的symbol_table中查找)

a.    $_COOKIE中

b.    $_GET中

c.    $_POST中

任何一此查找成功都会设置PS(id),不再继续查找。

如果客户端cookies没有开启,并且没有找到PS(id),将会检查REQUEST_URI,这里就是对 禁用cookie后session是如何设置的 实现机制。

安全性检查

  正常情况下,生成的session_id不会包含html标签,单双引号和空白字符的,如果session_id中包含了这些非法的字符,那么很有可能session_id是伪造的。对于这种情况,处理很简单,释放session_id的空间,并标志为NULL,这样与第一次访问页面时的逻辑就基本一致了:

[cpp] view plaincopy
  1. if (PS(id) && strpbrk(PS(id), "\r\n\t <>'\"\\")) {
  2. efree(PS(id));
  3. PS(id) = NULL;
  4. }

(3).  执行php_session_initialize完成session的初始化工作。

[cpp] view plaincopy
  1. static void php_session_initialize(TSRMLS_D) /* {{{ */
  2. {
  3. char *val = NULL;
  4. int vallen;
  5. // 第一步,验证PS(mod) 是否存在
  6. if (!PS(mod)) {
  7. php_error_docref(NULL TSRMLS_CC, E_ERROR, "No storage module chosen - failed to initialize session");
  8. return;
  9. }
  10. // 第二步,打开session文件
  11. /* Open session handler first */
  12. if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name) TSRMLS_CC) == FAILURE) {
  13. php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path));
  14. return;
  15. }
  16. // 第三步,判断session_id,如果session_id 不存在,创建一个
  17. /* If there is no ID, use session module to create one */
  18. if (!PS(id)) {
  19. PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC);
  20. if (!PS(id)) {
  21. php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to create session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
  22. return;
  23. }
  24. if (PS(use_cookies)) {
  25. PS(send_cookie) = 1;
  26. }
  27. }
  28. // 第四步 session.use_strict_mode指定是否将使用严格的会话ID模式。如果该模式被激活,模块不接受未初始化会话ID。
  29. // 如果未初始化会话ID从浏览器发送的,新的会话ID被发送到浏览器。
  30. // 应用程序通过会议通过严格的方式保护会话固定。默认为0 (禁用) 。
  31. /* Set session ID for compatibility for older/3rd party save handlers */
  32. if (!PS(use_strict_mode)) {
  33. php_session_reset_id(TSRMLS_C);
  34. PS(session_status) = php_session_active;
  35. }
  36. // 第五步 读取session数据到val中
  37. /* Read data */
  38. php_session_track_init(TSRMLS_C);   // 无条件地摧毁现有的session 数组,可能是脏数据
  39. if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, &vallen TSRMLS_CC) == FAILURE) {
  40. /* Some broken save handler implementation returns FAILURE for non-existent session ID */
  41. /* It's better to raise error for this, but disabled error for better compatibility */
  42. /*
  43. php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Failed to read session data: %s (path: %s)", PS(mod)->s_name, PS(save_path));
  44. */
  45. }
  46. // 如果使用严格会话ID模式,在session不活跃状态下重置
  47. // 这里涉及到session安全机制,详细可以看http://php.net/manual/zh/session.security.php
  48. /* Set session ID if session read didn't activated session */
  49. if (PS(use_strict_mode) && PS(session_status) != php_session_active) {
  50. php_session_reset_id(TSRMLS_C);
  51. PS(session_status) = php_session_active;
  52. }
  53. // 第六步 对文件中读取到的session数据进行反序列化
  54. if (val) {
  55. php_session_decode(val, vallen TSRMLS_CC);
  56. str_efree(val);
  57. }
  58. // 第七步 session全局配置参数的值use_cookie_only和use_trans_sid,
  59. // 两者分别表示sessionid在客户端只能通过cookie保存和只能通过url传递
  60. if (!PS(use_cookies) && PS(send_cookie)) {
  61. if (PS(use_trans_sid) && !PS(use_only_cookies)) {
  62. PS(apply_trans_sid) = 1;
  63. }
  64. PS(send_cookie) = 0;
  65. }
  66. }
  67. /* }}} */

这里需要对PS(mod)->s_open(...)的调用需要介绍一下。

还记得我们在PHP_RINIT_FUNCTION是对PS(mod)的分析,PS(mod)其实就是一个struct ps_module_struct的实例

ps_module然后调用对应的ps_open_files即PS_OPEN_FUNC(x)。这块的具体调用实现,可以通过之前对PS(mod)的分析详细了解。

需要注意的是第二步,PS(mod)->s_open(...)函数是对文件相关的属性赋值,并不是真正的打开文件,具体数据结构如下ps_files *data

[cpp] view plaincopy
  1. // mod_files.c  line:59
  2. typedef struct {
  3. int fd;
  4. char *lastkey;
  5. char *basedir;
  6. size_t basedir_len;
  7. size_t dirdepth;
  8. size_t st_size;
  9. int filemode;
  10. } ps_files;

在第二步中通过PS_OPEN_FUNC(files)对参数赋值

[cpp] view plaincopy
  1. ps_files *data;
  2. data = ecalloc(1, sizeof(*data));
  3. // mod_files.c line:303
  4. data->fd = -1;   //初始化资源id
  5. data->dirdepth = dirdepth;   // 目录深度  比如 N;save_path  值就是N
  6. data->filemode = filemode;   // 文件模式filemode
  7. data->basedir_len = strlen(save_path);   //路径长度
  8. data->basedir = estrndup(save_path, data->basedir_len);   //路径地址

而在第五步读取session文件中的数据的时候才打开文件,即调用PS_READ_FUNC(files)

而这里面有一段很重要的语句:

[cpp] view plaincopy
  1. flock(data->fd, LOCK_EX);

PHP在打开文件后,通过flock(data->fd, LOCK_EX);语句对结构体struct flock进行赋值,并通过fcntl来设置排它锁,

因为设置了排它锁,所以在文件锁定期间,即使是读取文件的数据也是不允许的。这就造成要写入或读取的进程必须等待,直到前一进程释放锁(这通常发生在脚本执行完毕或者用户调用session_commit/session_write_close)。

有关文件锁这一块涉及到linux系统文件锁相关知识,如果要深入理解请自行百度。

(4). session的gc

在PHP中, 如果使用file_handler作为Session的save handler, 那么就有概率在每次session_start的时候运行Session的Gc过程。

在session_start的最后,通过下面代码来触发SESSION的GC:

[cpp] view plaincopy
  1. if ((PS(mod_data) || PS(mod_user_implemented)) && PS(gc_probability) > 0) {
  2. int nrdels = -1;
  3. nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
  4. if (nrand < PS(gc_probability)) {
  5. PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC);
  6. #ifdef SESSION_DEBUG
  7. if (nrdels != -1) {
  8. php_error_docref(NULL TSRMLS_CC, E_NOTICE, "purged %d expired session objects", nrdels);
  9. }
  10. #endif
  11. }
  12. }

从代码中可以看到,在判别s_gc是否运行的时候, 有俩个关键变量: PS(gc_divisor)和PS(gc_probability), 这俩个变量分别对应着session的运行时配置项的俩个同名配置项:

session.gc_probability和session.gc_divisor, 他们分别默认为1和100.

而php_combined_lcg是一个随机数发生器, 生成0到1范围的随机数, 所以上面的判别相当于:

rand < probability / gc_divisor

也就是说, 默认情况下, 差不多是100次能调用1次gc过程.

4. session请求结束 RSHUTDOWN   PHP_RSHUTDOWN_FUNCTION

[cpp] view plaincopy
  1. static PHP_RSHUTDOWN_FUNCTION(session) /* {{{ */
  2. {
  3. int i;
  4. <span style="white-space:pre">    </span>// (1). 将session序列化后写入文件,并unlock文件,关闭文件句柄
  5. zend_try {
  6. php_session_flush(TSRMLS_C);
  7. } zend_end_try();
  8. php_rshutdown_session_globals(TSRMLS_C);  //(2) 将http_session_vars变量refcount减一,即destroy全局变量
  9. /* this should NOT be done in php_rshutdown_session_globals() */
  10. for (i = 0; i < 7; i++) {
  11. if (PS(mod_user_names).names[i] != NULL) {
  12. zval_ptr_dtor(&PS(mod_user_names).names[i]);
  13. PS(mod_user_names).names[i] = NULL;
  14. }
  15. }
  16. return SUCCESS;
  17. }
  18. /* }}} */

从上面的代码中我们可以知道,php在脚本执行过程中,并不会对session的数据进行文件写入,而是在请求结束后,再进行写入,并关闭句柄,这里不做深入研究,大家可以查看源码来进一步深入。

5. session扩展模块结束 MSHUTDOWN   PHP_MSHUTDOWN_FUNCTION

模块结束后,主要对于一些全局变量和配置的销毁

[cpp] view plaincopy
  1. static PHP_MSHUTDOWN_FUNCTION(session) /* {{{ */
  2. {
  3. UNREGISTER_INI_ENTRIES();  // 配置销毁
  4. #ifdef HAVE_LIBMM
  5. PHP_MSHUTDOWN(ps_mm) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
  6. #endif
  7. /* reset rfc1867 callbacks */
  8. php_session_rfc1867_orig_callback = NULL;
  9. if (php_rfc1867_callback == php_session_rfc1867_callback) {
  10. php_rfc1867_callback = NULL;
  11. }
  12. ps_serializers[PREDEFINED_SERIALIZERS].name = NULL;
  13. memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *));
  14. return SUCCESS;
  15. }
  16. /* }}} */

6. session文件存储的问题

在session.save_handler=files的情况下,会有哪些性能问题和瓶颈?

  a.文件锁带来的性能问题

  前面我们已经提到, 由于是LOCK_EX(互斥锁),因而在文件锁定期间,即使是读取文件的数据也是不允许的。这就造成要写入或读取的进程必须等待,直到前一进程释放锁(这通常发生在脚本执行完毕或者用户调用session_commit/session_write_close)。

  b.分布式服务器环境下session共享的问题
session文件存储实际上是存储在服务器的磁盘上的,这样在分布式服务器环境下会造成一定的问题:假如你有a,b,c三台服务器。则用户的多次请求可能按照负载均衡策略定向到不同的服务器,由于服务器之间并没有共享session文件,这在表象看来便发生了session丢失。这虽然可以通过用户粘滞会话解决,但会带来更大的问题:无法服务器的负载均衡,增加了服务器的复杂性。

  c.高并发场景下session,大量磁盘I/O

      基于以上一些原因,在实际应用中,很多都是使用分布式内存缓存memcache或者redis来存储和共享session的。当然这个不是本章索讨论的范围。

7.总结

session探索到这里就基本结束了,上面基本对session的时间通过以PHP的生命周期将其实现机制详细介绍了一遍,当然,其中还有很多细节和函数没有涉及到。

感兴趣的同学可以进一步追踪一下源码实现,由于个人水平有限,文中若出现错误,欢迎大家指出交流。

参考资料:

1. http://www.cnblogs.com/driftcloudy/p/4011954.html

2. http://blog.csdn.net/ohmygirl/article/details/43152683

3. http://blog.csdn.net/risingsun001/article/details/44567225

4. http://www.laruence.com/2011/03/29/1949.html

5. http://php.net/manual/en/book.session.php

PHP函数源码之SESSION实现机制相关推荐

  1. OpenCV resize函数源码解析——加速方法

    相信大家应该经常会用到OpenCV中的函数resize(),当我们想放大或者缩小图像的时候,会用到这个函数进行图像缩放,其中最核心的便是对图像的像素进行插值处理. 这里的插值interpolation ...

  2. LMDIF_函数源码

    函数源码: /* lmdif.f -- translated by f2c (version 20020621).You must link the resulting object file wit ...

  3. 【Linux 内核】实时调度类 ⑦ ( 实时调度类核心函数源码分析 | dequeue_task_rt 函数 | 从执行队列中移除进程 )

    文章目录 一.dequeue_task_rt 函数 ( 从执行队列中移除进程 ) 二.update_curr_rt 函数 ( 更新调度信息 ) 本篇博客中 , 开始分析 struct sched_cl ...

  4. 【Linux 内核】实时调度类 ⑥ ( 实时调度类核心函数源码分析 | 插入进程到执行队列 | 从执行队列中选择优先级最高的进程 )

    文章目录 一.enqueue_task_rt 函数 ( 插入进程到执行队列 ) 二.pick_next_task_rt 函数 ( 从执行队列中选择优先级最高的进程 ) 本篇博客中 , 开始分析 str ...

  5. 【Android 逆向】ART 脱壳 ( dex2oat 脱壳 | aosp 中搜索 dex2oat 源码 | dex2oat.cc#main 主函数源码 )

    文章目录 前言 一.搜索 dex2oat 源码 二.dex2oat.cc#main 主函数源码 前言 在 [Android 逆向]ART 脱壳 ( DexClassLoader 脱壳 | exec_u ...

  6. PHP 源码 —— is_array 函数源码分析

    is_array 函数源码分析 本文首发于 https://github.com/suhanyujie/learn-computer/blob/master/src/function/array/is ...

  7. python内置函数源码_如何查看python内置函数源码

    在用Python进行各种分析的时候,我们会用到各种各样的函数,比如,我们用SQL时,经常使用join.max等各种函数,那么想看Python是否有这个函数,这个时候可能大部分人会百度,那么如何不使用百 ...

  8. [OpenGL] 视图矩阵(View)矩阵与glm::lookAt函数源码解析

    一.视图矩阵(View)矩阵 首先明确视图矩阵的作用:在OpenGL的众多坐标系中,存在一个世界坐标系和一个摄像机坐标系,视图矩阵的作用就是将世界坐标系内的坐标转换成摄像机坐标系内的坐标. 如图,空间 ...

  9. 【Linux 内核 内存管理】物理分配页 ⑨ ( __alloc_pages_slowpath 慢速路径调用函数源码分析 | retry 标号代码分析 )

    文章目录 一.retry 标号代码分析 二.retry 标号完整代码 在 [Linux 内核 内存管理]物理分配页 ② ( __alloc_pages_nodemask 函数参数分析 | __allo ...

最新文章

  1. 字符串拼接还在用StringBuilder?快试试Java8中的StringJoiner吧,真香!
  2. sql连oracle链接服务器
  3. PostgreSQL的ecpg程序的调适与运行
  4. 【LeetCode从零单排】No102 Binary Tree Level Order Traversal
  5. system类的 静态方法可以启动垃圾回收器。_Java—System类入门学习
  6. win7将 esc与 capslock 互换
  7. CSS3中的display:grid网格布局介绍
  8. C#中的多文档的使用
  9. 工程图标注粗糙度_Inventor教程之工程图标注实例
  10. android布局配置
  11. Visual Studio2012版安装教程--C++新手初学者
  12. 2021年5大最佳3D动画软件
  13. 泛函分析 04.06 有界线性算子 - 习题课
  14. lightroom安卓_【安卓】多功能视频编辑器和手机专业修图软件
  15. android timepicker分割线颜色,Android TimePicker 的使用
  16. 互联网日报 | 微信聊天时可直接“搜一搜”了;蚂蚁集团9月18日科创板首发上会;谷歌正式发布安卓11系统...
  17. iOS开发UI篇 -- UINagivationController
  18. 华为云服务-应用部署2-创建环境到创建应用
  19. ProcessingJoy —— 字母流光粒子【JAVA】
  20. 4.2 期货每日早盘操作建议

热门文章

  1. 什么是Crunchyroll,它提供什么动漫?
  2. 互联网时代架构的演变
  3. JS 怎么使用十六进制保存100位状态的问题
  4. 北京大学计算机专业保研夏令营,北京大学12院系办夏令营 优秀者可保研
  5. 【ubuntu】htop命令详解
  6. 中国企业数字银行行业市场供需与战略研究报告
  7. 实验六计算机网络通信Socket编程,计算机网络socket编程实验报告(3页)-原创力文档...
  8. 梆梆安全:做以结果为导向的安全服务商
  9. 安装sqlserver2016报错
  10. eclipse突然报错:An internal error occurred during: “Compute launch button tooltip“.并且运行main方法时Run As后面是空