亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

PHP內核介紹及擴展開發指南—基礎知識

系統 2251 0

一、 基礎知識

本章簡要介紹一些Zend引擎的內部機制,這些知識和Extensions密切相關,同時也可以幫助我們寫出更加高效的PHP代碼。

1.1 PHP變量的存儲

1.1.1 zval結構

Zend使用zval結構來存儲PHP變量的值,該結構如下所示:

    typedef union _zvalue_value {
	long lval;				/* long value */
	double dval;				/* double value */
	struct {
		char *val;
		int len;
	} str;
	HashTable *ht;				/* hash table value */
	zend_object_value obj;
} zvalue_value;

struct _zval_struct {
	/* Variable information */
	zvalue_value value;		/* value */
	zend_uint refcount;
	zend_uchar type;			/* active type */
	zend_uchar is_ref;
};

typedef struct _zval_struct zval;
<SPAN id=more-597></SPAN>Zend根據type值來決定訪問value的哪個成員,可用值如下:

  


  

IS_NULL

N/A

IS_LONG

對應value.lval

IS_DOUBLE

對應value.dval

IS_STRING

對應value.str

IS_ARRAY

對應value.ht

IS_OBJECT

對應value.obj

IS_BOOL

對應value.lval.

IS_RESOURCE

對應value.lval

根據這個表格可以發現兩個有意思的地方:首先是PHP的數組其實就是一個HashTable,這就解釋了為什么PHP能夠支持關聯數組了;其次,Resource就是一個long值,它里面存放的通常是個指針、一個內部數組的index或者其它什么只有創建者自己才知道的東西,可以將其視作一個handle

1.1.1 引用計數

引用計數在垃圾收集、內存池以及字符串等地方應用廣泛,Zend就實現了典型的引用計數。多個PHP變量可以通過引用計數機制來共享同一份zval,zval中剩余的兩個成員is_ref和refcount就用來支持這種共享。

很明顯,refcount用于計數,當增減引用時,這個值也相應的遞增和遞減,一旦減到零,Zend就會回收該zval。

那么is_ref呢?

1.1.2 zval狀態

在PHP中,變量有兩種——引用和非引用的,它們在Zend中都是采用引用計數的方式存儲的。對于非引用型變量,要求變量間互不相干,修改一個變量時,不能影響到其他變量,采用Copy-On-Write機制即可解決這種沖突——當試圖寫入一個變量時,Zend若發現該變量指向的zval被多個變量共享,則為其復制一份refcount為1的zval,并遞減原zval的refcount,這個過程稱為“zval分離”。然而,對于引用型變量,其要求和非引用型相反,引用賦值的變量間必須是捆綁的,修改一個變量就修改了所有捆綁變量。

可見,有必要指出當前zval的狀態,以分別應對這兩種情況,is_ref就是這個目的,它指出了當前所有指向該zval的變量是否是采用引用賦值的——要么全是引用,要么全不是。此時再修改一個變量,只有當發現其zval的is_ref為0,即非引用時,Zend才會執行Copy-On-Write。

1.1.3 zval狀態切換

當在一個zval上進行的所有賦值操作都是引用或者都是非引用時,一個is_ref就足夠應付了。然而,世界總不會那么美好,PHP無法對用戶進行這種限制,當我們混合使用引用和非引用賦值時,就必須要進行特別處理了。

情況I、看如下PHP代碼:

        <!--p $a = 1;  $b = &$a;  $c = &$b;  $d = $c;  // 在一堆引用賦值中,插入一個非引用-->

      

全過程如下所示:

這段代碼的前三句將把a、b和c指向一個zval,其is_ref=1, refcount=3;第四句是個非引用賦值,通常情況下只需要增加引用計數即可,然而目標zval屬于引用變量,單純的增加引用計數顯然是錯誤的, Zend的解決辦法是為d單獨生成一份zval副本。

全過程如下所示:

PHP內核介紹及擴展開發指南—基礎知識

1.1.1 參數傳遞

PHP函數參數的傳遞和變量賦值是一樣的,非引用傳遞相當于非引用賦值,引用傳遞相當于引用賦值,并且也有可能會導致執行zval狀態切換。這在后面還將提到。

1.2 HashTable結構

HashTable是Zend引擎中最重要、使用最廣泛的數據結構,它被用來存儲幾乎所有的東西。

1.1.1 數據結構

HashTable數據結構定義如下:

    typedef struct bucket {
	ulong h;				// 存放hash
	uint nKeyLength;
	void *pData;			// 指向value,是用戶數據的副本
	void *pDataPtr;
	struct bucket *pListNext;	// pListNext和pListLast組成
	struct bucket *pListLast;	// 整個HashTable的雙鏈表
	struct bucket *pNext;		// pNext和pLast用于組成某個hash對應
	struct bucket *pLast;		// 的雙鏈表
	char arKey[1];				// key
} Bucket;

typedef struct _hashtable {
	uint nTableSize;
	uint nTableMask;
	uint nNumOfElements;
	ulong nNextFreeElement;
	Bucket *pInternalPointer;	/* Used for element traversal */
	Bucket *pListHead;
	Bucket *pListTail;
	Bucket **arBuckets;			// hash數組
	dtor_func_t pDestructor;	// HashTable初始化時指定,銷毀Bucket時調用
	zend_bool persistent;		// 是否采用C的內存分配例程
	unsigned char nApplyCount;
	zend_bool bApplyProtection;
#if ZEND_DEBUG
	int inconsistent;
#endif
} HashTable;

  


總的來說,Zend的HashTable是一種鏈表散列,同時也為線性遍歷進行了優化,圖示如下:

PHP內核介紹及擴展開發指南—基礎知識

HashTable中包含兩種數據結構,一個鏈表散列和一個雙向鏈表,前者用于進行快速鍵-值查詢,后者方便線性遍歷和排序,一個Bucket同時存在于這兩個數據結構中。

關于該數據結構的幾點解釋:

l 鏈表散列中為什么使用雙向鏈表?
一般的鏈表散列只需要按key進行操作,只需要單鏈表就夠了。但是,Zend有時需要從鏈表散列中刪除給定的Bucket,使用雙鏈表可以非常高效的實現。

l nTableMask是干什么的?
這個值用于hash值到arBuckets數組下標的轉換。當初始化一個HashTable,Zend首先為arBuckets數組分配nTableSize大小的內存,nTableSize取不小于用戶指定大小的最小的2^n,即二進制的10*。nTableMask = nTableSize – 1,即二進制的01*,此時h & nTableMask就恰好落在 [0, nTableSize – 1] 里,Zend就以其為index來訪問arBuckets數組。

l pDataPtr是干什么的?
通常情況下,當用戶插入一個鍵值對時,Zend會將value復制一份,并將pData指向value副本。復制操作需要調用Zend內部例程 emalloc來分配內存,這是個非常耗時的操作,并且會消耗比value大的一塊內存(多出的內存用于存放cookie),如果value很小的話,將會造成較大的浪費??紤]到HashTable多用于存放指針值,于是Zend引入pDataPtr,當value小到和指針一樣長時,Zend就直接將其復制到pDataPtr里,并且將pData指向pDataPtr。這就避免了emalloc操作,同時也有利于提高Cache命中率。

arKey大小為什么只有1?為什么不使用指針管理key?
arKey是存放key的數組,但其大小卻只有1,并不足以放下key。在HashTable的初始化函數里可以找到如下代碼:

1 p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);

可見,Zend為一個Bucket分配了一塊足夠放下自己和key的內存,

l 上半部分是Bucket,下半部分是key,而arKey“恰好”是Bucket的最后一個元素,于是就可以使用arKey來訪問key了。這種手法在內存管理例程中最為常見,當分配內存時,實際上是分配了比指定大小要大的內存,多出的上半部分通常被稱為cookie,它存儲了這塊內存的信息,比如塊大小、上一塊指針、下一塊指針等,baidu的Transmit程序就使用了這種方法。
不用指針管理key,是為了減少一次emalloc操作,同時也可以提高Cache命中率。另一個必需的理由是,key絕大部分情況下是固定不變的,不會因為key變長了而導致重新分配整個Bucket。這同時也解釋了為什么不把value也一起作為數組分配了——因為value是可變的。

1.2.2 PHP數組

關于HashTable還有一個疑問沒有回答,就是nNextFreeElement是干什么的?

不同于一般的散列,Zend的HashTable允許用戶直接指定hash值,而忽略key,甚至可以不指定key(此時,nKeyLength為0)。同時,HashTable也支持append操作,用戶連hash值也不用指定,只需要提供value,此時,Zend就用nNextFreeElement作為hash,之后將nNextFreeElement遞增。

HashTable的這種行為看起來很奇怪,因為這將無法按key訪問value,已經完全不是個散列了。理解問題的關鍵在于,PHP數組就是使用HashTable實現的——關聯數組使用正常的k-v映射將元素加入HashTable,其key為用戶指定的字符串;非關聯數組則直接使用數組下標作為hash值,不存在key;而當在一個數組中混合使用關聯和非關聯時,或者使用array_push操作時,就需要用nNextFreeElement了。

再來看value,PHP數組的value直接使用了zval這個通用結構,pData指向的是zval*,按照上一節的介紹,這個zval*將直接存儲在pDataPtr里。由于直接使用了zval,數組的元素可以是任意PHP類型。

數組的遍歷操作,即foreach、each等,是通過HashTable的雙向鏈表來進行的,pInternalPointer作為游標記錄了當前位置。

1.2.3 變量符號表

除了數組,HashTable還被用來存儲許多其他數據,比如,PHP函數、變量符號、加載的模塊、類成員等。

一個變量符號表就相當于一個關聯數組,其key是變量名(可見,使用很長的變量名并不是個好主意),value是zval*。

在任一時刻PHP代碼都可以看見兩個變量符號表——symbol_table和active_symbol_table——前者用于存儲全局變量,稱為全局符號表;后者是個指針,指向當前活動的變量符號表,通常情況下就是全局符號表。但是,當每次進入一個PHP函數時(此處指的是用戶使用PHP代碼創建的函數),Zend都會創建函數局部的變量符號表,并將active_symbol_table指向局部符號表。Zend總是使用active_symbol_table來訪問變量,這樣就實現了局部變量的作用域控制。

但如果在函數局部訪問標記為global的變量,Zend會進行特殊處理——在active_symbol_table中創建symbol_table中同名變量的引用,如果symbol_table中沒有同名變量則會先創建。

1.3 內存和文件

程序擁有的資源一般包括內存和文件,對于通常的程序,這些資源是面向進程的,當進程結束后,操作系統或C庫會自動回收那些我們沒有顯式釋放的資源。

但是,PHP程序有其特殊性,它是基于頁面的,一個頁面運行時同樣也會申請內存或文件這樣的資源,然而當頁面運行結束后,操作系統或C庫也許不會知道需要進行資源回收。比如,我們將php作為模塊編譯到apache里,并且以prefork或worker模式運行apache。這種情況下apache進程或線程是復用的,php頁面分配的內存將永駐內存直到出core。

為了解決這種問題,Zend提供了一套內存分配API,它們的作用和C中相應函數一樣,不同的是這些函數從Zend自己的內存池中分配內存,并且它們可以實現基于頁面的自動回收。在我們的模塊中,為頁面分配的內存應該使用這些API,而不是C例程,否則Zend會在頁面結束時嘗試efree掉我們的內存,其結果通常就是crush。

emalloc()
efree()
estrdup()
estrndup()
ecalloc()
erealloc()

另外,Zend還提供了一組形如VCWD_xxx的宏用于替代C庫和操作系統相應的文件API,這些宏能夠支持PHP的虛擬工作目錄,在模塊代碼中應該總是使用它們。宏的具體定義參見PHP源代碼”TSRM/tsrm_virtual_cwd.h”。可能你會注意到,所有那些宏中并沒有提供close操作,這是因為close的對象是已打開的資源,不涉及到文件路徑,因此可以直接使用C或操作系統例程;同理,read/write之類的操作也是直接使用C或操作系統的例程。

轉自:百度搜索研發部官方博客 http://stblog.baidu-tech.com/?p=597

PHP內核介紹及擴展開發指南—基礎知識


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产一区二区不卡视频 | 2021国产精品自产拍在线观看 | 成人国产视频在线观看 | 国产精品久久久久久久久免费hd | 久久久久国产精品免费免费 | 久久午夜青青草原影院 | 热久久久 | 精品九九久久 | 奇米影视7777久久精品人人爽 | 久久88香港三级台湾三级中文 | 极品精品国产超清自在线观看 | 99热欧美| 久久综合图片 | 色www国产阿娇 | 亚洲天堂三级 | 成年午夜视频免费观看视频 | 欧美高清免费精品国产自 | 高清一区二区亚洲欧美日韩 | 97精品久久久久中文字幕 | 99精品在线播放 | 天天爽天天狼久久久综合 | 亚洲va精品中文字幕动漫 | 午夜在线精品不卡国产 | 国产欧美精品综合一区 | 日本免费新一区二区三区 | 国产亚洲精品国看不卡 | 奇米色奇米 | 亚洲精品久荜中文字幕 | 97久久曰曰久久久 | 国产国产精品人在线视 | 久久国产午夜精品理论片34页 | 国产福利一区二区在线观看 | 久久福利一区二区三区 | 午夜视频在线网站 | 性大特级毛片视频 | www.中文字幕 | 四虎影视在线影院在线观看 | 青青久在线视频免费视频 | 久久精品国产一区二区 | 四虎影院免费在线播放 | 1000部羞羞禁止免费观看视频 |