Session是以擴展的形式嵌入到PHP內核的,所以我們可以把Session當成擴展來看待。
一般擴展被載入到PHP會經過下面幾個過程
-
#define PHP_MINIT_FUNCTION ZEND_MODULE_STARTUP_D // 初始化module時運行
-
#define PHP_MSHUTDOWN_FUNCTION ZEND_MODULE_SHUTDOWN_D // 當module被卸載時運行
-
#define PHP_RINIT_FUNCTION ZEND_MODULE_ACTIVATE_D // 當一個REQUEST請求初始化時運行
-
#define PHP_RSHUTDOWN_FUNCTION ZEND_MODULE_DEACTIVATE_D // 當一個REQUEST請求結束時運行
-
#define PHP_MINFO_FUNCTION ZEND_MODULE_INFO_D // 這個是設置phpinfo中這個模塊的信息
-
#define PHP_GINIT_FUNCTION ZEND_GINIT_FUNCTION // 初始化全局變量時
-
#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超全局變量:
-
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文件中的相關配置。
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而言,實際上包括:
-
-
-
PHP_INI_BEGIN()
-
STD_PHP_INI_ENTRY("session.save_path", "", PHP_INI_ALL, OnUpdateSaveDir,save_path, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.name", "PHPSESSID", PHP_INI_ALL, OnUpdateName, session_name, php_ps_globals, ps_globals)
-
PHP_INI_ENTRY("session.save_handler", "files", PHP_INI_ALL, OnUpdateSaveHandler)
-
STD_PHP_INI_BOOLEAN("session.auto_start", "0", PHP_INI_PERDIR, OnUpdateBool, auto_start, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.gc_probability", "1", PHP_INI_ALL, OnUpdateLong, gc_probability, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.gc_divisor", "100", PHP_INI_ALL, OnUpdateLong, gc_divisor, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.gc_maxlifetime", "1440", PHP_INI_ALL, OnUpdateLong, gc_maxlifetime, php_ps_globals, ps_globals)
-
PHP_INI_ENTRY("session.serialize_handler", "php", PHP_INI_ALL, OnUpdateSerializer)
-
STD_PHP_INI_ENTRY("session.cookie_lifetime", "0", PHP_INI_ALL, OnUpdateLong, cookie_lifetime, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.cookie_path", "/", PHP_INI_ALL, OnUpdateString, cookie_path, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateString, cookie_domain, php_ps_globals, ps_globals)
-
STD_PHP_INI_BOOLEAN("session.cookie_secure", "", PHP_INI_ALL, OnUpdateBool, cookie_secure, php_ps_globals, ps_globals)
-
STD_PHP_INI_BOOLEAN("session.cookie_httponly", "", PHP_INI_ALL, OnUpdateBool, cookie_httponly, php_ps_globals, ps_globals)
-
STD_PHP_INI_BOOLEAN("session.use_cookies", "1", PHP_INI_ALL, OnUpdateBool, use_cookies, php_ps_globals, ps_globals)
-
STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1", PHP_INI_ALL, OnUpdateBool, use_only_cookies, php_ps_globals, ps_globals)
-
STD_PHP_INI_BOOLEAN("session.use_strict_mode", "0", PHP_INI_ALL, OnUpdateBool, use_strict_mode, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.referer_check", "", PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals, ps_globals)
-
#if HAVE_DEV_URANDOM
-
STD_PHP_INI_ENTRY("session.entropy_file", "/dev/urandom", PHP_INI_ALL, OnUpdateString, entropy_file, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.entropy_length", "32", PHP_INI_ALL, OnUpdateLong, entropy_length, php_ps_globals, ps_globals)
-
#elif HAVE_DEV_ARANDOM
-
STD_PHP_INI_ENTRY("session.entropy_file", "/dev/arandom", PHP_INI_ALL, OnUpdateString, entropy_file, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.entropy_length", "32", PHP_INI_ALL, OnUpdateLong, entropy_length, php_ps_globals, ps_globals)
-
#else
-
STD_PHP_INI_ENTRY("session.entropy_file", "", PHP_INI_ALL, OnUpdateString, entropy_file, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.entropy_length", "0", PHP_INI_ALL, OnUpdateLong, entropy_length, php_ps_globals, ps_globals)
-
#endif
-
STD_PHP_INI_ENTRY("session.cache_limiter", "nocache", PHP_INI_ALL, OnUpdateString, cache_limiter, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.cache_expire", "180", PHP_INI_ALL, OnUpdateLong, cache_expire, php_ps_globals, ps_globals)
-
PHP_INI_ENTRY("session.use_trans_sid", "0", PHP_INI_ALL, OnUpdateTransSid)
-
PHP_INI_ENTRY("session.hash_function", "0", PHP_INI_ALL, OnUpdateHashFunc)
-
STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4", PHP_INI_ALL, OnUpdateLong, hash_bits_per_character, php_ps_globals, ps_globals)
-
-
-
STD_PHP_INI_BOOLEAN("session.upload_progress.enabled",
-
"1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_enabled, php_ps_globals, ps_globals)
-
STD_PHP_INI_BOOLEAN("session.upload_progress.cleanup",
-
"1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_cleanup, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.upload_progress.prefix",
-
"upload_progress_", ZEND_INI_PERDIR, OnUpdateSmartStr, rfc1867_prefix, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.upload_progress.name",
-
"PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateSmartStr, rfc1867_name, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.upload_progress.freq", "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq, php_ps_globals, ps_globals)
-
STD_PHP_INI_ENTRY("session.upload_progress.min_freq",
-
"1", ZEND_INI_PERDIR, OnUpdateReal, rfc1867_min_freq,php_ps_globals, ps_globals)
-
-
-
-
PHP_INI_END()
-
如果在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
-
static PHP_RINIT_FUNCTION(session)
-
{
-
return php_rinit_session(PS(auto_start) TSRMLS_CC);
-
}
-
-
-
-
-
static int php_rinit_session(zend_bool auto_start TSRMLS_DC)
-
{
-
php_rinit_session_globals(TSRMLS_C);
-
-
if (PS(mod) == NULL) {
-
char *value;
-
-
value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
-
if (value) {
-
PS(mod) = _php_find_ps_module(value TSRMLS_CC);
-
}
-
}
-
-
if (PS(serializer) == NULL) {
-
char *value;
-
-
value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
-
if (value) {
-
PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
-
}
-
}
-
-
if (PS(mod) == NULL || PS(serializer) == NULL) {
-
-
PS(session_status) = php_session_disabled;
-
return SUCCESS;
-
}
-
-
if (auto_start) {
-
php_session_start(TSRMLS_C);
-
}
-
-
return SUCCESS;
-
}
正如上面的代碼所寫,PHP_RINIT_FUNCTION(session)主要經過下面幾個步驟:
(1).初始化session相關的全局變量,這是通過php_rinit_session_globals來完成的:
-
-
static inline void php_rinit_session_globals(TSRMLS_D)
-
{
-
PS(id) = NULL;
-
PS(session_status) = php_session_none;
-
PS(mod_data) = NULL;
-
PS(mod_user_is_open) = 0;
-
-
PS(http_session_vars) = NULL;
-
}
-
(2).根據ini的配置查找session.save_handler,從而確定是使用files還是user(
或者是其他的擴展方式)來處理session:
-
if (PS(mod) == NULL) {
-
char *value;
-
-
value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
-
if (value) {
-
PS(mod) = _php_find_ps_module(value TSRMLS_CC);
-
}
-
}
確定是user還是files來處理session的邏輯是由_php_find_ps_module來完成的,這個函數會依次查找ps_modules中預定義的module, 一旦查找成功,立即返回:
-
PHPAPI ps_module *_php_find_ps_module(char *name TSRMLS_DC)
-
{
-
ps_module *ret = NULL;
-
ps_module **mod;
-
int i;
-
-
for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) {
-
if (*mod && !strcasecmp(name, (*mod)->s_name)) {
-
ret = *mod;
-
break;
-
}
-
}
-
return ret;
-
}
-
ps_module 定義:
-
-
-
-
-
#define MAX_MODULES 10
-
#define PREDEFINED_MODULES 2
-
-
static ps_module *ps_modules[MAX_MODULES + 1] = {
-
ps_files_ptr,
-
ps_user_ptr
-
};
ps_files_ptr和ps_user_ptr,是在mod_*.h中定義
-
ps_files_ptr和ps_user_ptr,是在mod_*.h中定義
-
-
ext/session/mod_files.h
-
extern ps_module ps_mod_files;
-
#define ps_files_ptr &ps_mod_files
-
-
ps_mod_files的內容是
-
ps_module ps_mod_files = {
-
PS_MOD_SID(files)
-
};
而每一個ps_module,實際上是一個struct:
-
typedef struct ps_module_struct {
-
const char *s_name;
-
int (*s_open)(PS_OPEN_ARGS);
-
int (*s_close)(PS_CLOSE_ARGS);
-
int (*s_read)(PS_READ_ARGS);
-
int (*s_write)(PS_WRITE_ARGS);
-
int (*s_destroy)(PS_DESTROY_ARGS);
-
int (*s_gc)(PS_GC_ARGS);
-
char *(*s_create_sid)(PS_CREATE_SID_ARGS);
-
} ps_module;
特別說明一下:上面的struct中的int (*s_open)(PS_OPEN_ARGS); 等 爲函數指針,也就是說只需要對其進行函數名的賦值就可以調用了,關於函數指針不瞭解的可以百度。
PS_MOD_SID(files)是一個宏定義:
-
#define PS_MOD_SID(x) \
-
#x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \
-
ps_delete_##x, ps_gc_##x, ps_create_sid_##x
所以,ps_mod_files展開後就是:
-
ps_module ps_mod_files = {
-
files,
-
ps_open_files,
-
ps_close_files,
-
ps_read_files,
-
ps_write_files,
-
ps_delete_files,
-
ps_gc_files,
-
php_session_sid_files
-
};
所以 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數據時需要對文件的內容進行反序列化:
-
session_save_path('/tmp/session');
-
session_start();
-
-
$_SESSION['key'] = 'value';
-
session_write_close();
則相應session文件的內容是:
查找serializer的過程與查找PS(mod)的方式類似:
-
if (PS(serializer) == NULL) {
-
char *value;
-
-
value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
-
-
if (value) {
-
PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
-
}
-
}
_php_find_ps_serializer也是在預定義的ps_serializers數組中查找:
-
PHPAPI const ps_serializer *_php_find_ps_serializer(char *name TSRMLS_DC) {
-
const ps_serializer *ret = NULL;
-
const ps_serializer *mod;
-
-
for (mod = ps_serializers; mod->name; mod++) {
-
if (!strcasecmp(name, mod->name)) {
-
ret = mod;
-
break;
-
}
-
}
-
return ret;
-
}
-
-
static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = {
-
PS_SERIALIZER_ENTRY(php_serialize),
-
PS_SERIALIZER_ENTRY(php),
-
PS_SERIALIZER_ENTRY(php_binary)
-
};
同樣,每一個serializer都是一個struct:
-
typedef struct ps_serializer_struct {
-
const char *name;
-
int (*encode)(PS_SERIALIZER_ENCODE_ARGS);
-
int (*decode)(PS_SERIALIZER_DECODE_ARGS);
-
} ps_serializer;
這時,如果mod不存在(設置的session.save_handler錯誤)或者serializer不存在,那麼直接標記session_status爲php_session_disabled,並返回,後面的代碼不再執行。否則,確定了mod和serializer,如果設置了session.auto_start,那麼就自動開啓session:
-
if (auto_start) {
-
php_session_start(TSRMLS_C);
-
}
3.session_start
session_start用於開啓或者重用現有的會話,在底層,其實現爲:
-
-
-
static PHP_FUNCTION(session_start)
-
{
-
-
php_session_start(TSRMLS_C);
-
-
if (PS(session_status) != php_session_active) {
-
RETURN_FALSE;
-
}
-
RETURN_TRUE;
-
}
-
內部是調用php_session_start完成session相關上下文的設置, 其基本步驟是:
(1). 檢查當前會話的session狀態。
php_session_status用於標誌所有可能的會話狀態,它是一個enum:
-
-
typedef enum {
-
php_session_disabled,
-
php_session_none,
-
php_session_active
-
} 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的過程中,前面我們看到:
-
if (PS(mod) == NULL || PS(serializer) == NULL) {
-
-
-
PS(session_status) = php_session_disabled;
-
return SUCCESS;
-
}
如果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,這樣與第一次訪問頁面時的邏輯就基本一致了:
-
if (PS(id) && strpbrk(PS(id), "\r\n\t <>'\"\\")) {
-
efree(PS(id));
-
PS(id) = NULL;
-
}
(3). 執行php_session_initialize完成session的初始化工作。
-
static void php_session_initialize(TSRMLS_D)
-
{
-
char *val = NULL;
-
int vallen;
-
-
-
if (!PS(mod)) {
-
php_error_docref(NULL TSRMLS_CC, E_ERROR, "No storage module chosen - failed to initialize session");
-
return;
-
}
-
-
-
if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name) TSRMLS_CC) == FAILURE) {
-
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path));
-
return;
-
}
-
-
-
-
if (!PS(id)) {
-
PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC);
-
if (!PS(id)) {
-
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to create session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
-
return;
-
}
-
if (PS(use_cookies)) {
-
PS(send_cookie) = 1;
-
}
-
}
-
-
-
-
-
if (!PS(use_strict_mode)) {
-
php_session_reset_id(TSRMLS_C);
-
PS(session_status) = php_session_active;
-
}
-
-
-
-
php_session_track_init(TSRMLS_C);
-
if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, &vallen TSRMLS_CC) == FAILURE) {
-
-
-
-
-
-
}
-
-
-
-
if (PS(use_strict_mode) && PS(session_status) != php_session_active) {
-
php_session_reset_id(TSRMLS_C);
-
PS(session_status) = php_session_active;
-
}
-
-
if (val) {
-
php_session_decode(val, vallen TSRMLS_CC);
-
str_efree(val);
-
}
-
-
-
if (!PS(use_cookies) && PS(send_cookie)) {
-
if (PS(use_trans_sid) && !PS(use_only_cookies)) {
-
PS(apply_trans_sid) = 1;
-
}
-
PS(send_cookie) = 0;
-
}
-
}
-
這裏需要對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
-
-
typedef struct {
-
int fd;
-
char *lastkey;
-
char *basedir;
-
size_t basedir_len;
-
size_t dirdepth;
-
size_t st_size;
-
int filemode;
-
} ps_files;
在第二步中通過PS_OPEN_FUNC(files)對參數賦值
-
ps_files *data;
-
data = ecalloc(1, sizeof(*data));
-
-
data->fd = -1;
-
data->dirdepth = dirdepth;
-
data->filemode = filemode;
-
data->basedir_len = strlen(save_path);
-
data->basedir = estrndup(save_path, data->basedir_len);
而在第五步讀取session文件中的數據的時候纔打開文件,即調用PS_READ_FUNC(files)
而這裏面有一段很重要的語句:
-
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:
-
if ((PS(mod_data) || PS(mod_user_implemented)) && PS(gc_probability) > 0) {
-
int nrdels = -1;
-
-
nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
-
if (nrand < PS(gc_probability)) {
-
PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC);
-
#ifdef SESSION_DEBUG
-
if (nrdels != -1) {
-
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "purged %d expired session objects", nrdels);
-
}
-
#endif
-
}
-
}
從代碼中可以看到,在判別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
-
static PHP_RSHUTDOWN_FUNCTION(session)
-
{
-
int i;
-
<span style="white-space:pre"> </span>
-
zend_try {
-
php_session_flush(TSRMLS_C);
-
} zend_end_try();
-
php_rshutdown_session_globals(TSRMLS_C);
-
-
-
for (i = 0; i < 7; i++) {
-
if (PS(mod_user_names).names[i] != NULL) {
-
zval_ptr_dtor(&PS(mod_user_names).names[i]);
-
PS(mod_user_names).names[i] = NULL;
-
}
-
}
-
-
return SUCCESS;
-
}
-
從上面的代碼中我們可以知道,php在腳本執行過程中,並不會對session的數據進行文件寫入,而是在請求結束後,再進行寫入,並關閉句柄,這裏不做深入研究,大家可以查看源碼來進一步深入。
5. session擴展模塊結束 MSHUTDOWN PHP_MSHUTDOWN_FUNCTION
模塊結束後,主要對於一些全局變量和配置的銷燬
-
static PHP_MSHUTDOWN_FUNCTION(session)
-
{
-
UNREGISTER_INI_ENTRIES();
-
-
#ifdef HAVE_LIBMM
-
PHP_MSHUTDOWN(ps_mm) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
-
#endif
-
-
-
php_session_rfc1867_orig_callback = NULL;
-
if (php_rfc1867_callback == php_session_rfc1867_callback) {
-
php_rfc1867_callback = NULL;
-
}
-
-
ps_serializers[PREDEFINED_SERIALIZERS].name = NULL;
-
memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *));
-
-
return SUCCESS;
-
}
-
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