堅持為客戶提供有價值的服務(wù)和內(nèi)容

[北京網(wǎng)站制作]PHP內(nèi)核介紹及擴(kuò)展開發(fā)指南—基礎(chǔ)知識

商企云 | 2021-06-18 | 分享至:

PHP內(nèi)核介紹及擴(kuò)展開發(fā)指南—基礎(chǔ)知識

一、 基礎(chǔ)知識

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

  1.1 PHP變量的存儲

  1.1.1 zval結(jié)構(gòu)

  Zend使用zval結(jié)構(gòu)來存儲PHP變量的值,該結(jié)構(gòu)如下所示:

  1. typedef?union?_zvalue_value?{?
  2. ????long?lval;??????????????/*?long?value?*/?
  3. ????double?dval;????????????????/*?double?value?*/?
  4. ????struct?{?
  5. ????????char?*val;?
  6. ????????int?len;?
  7. ????}?str;?
  8. ????HashTable?*ht;??????????????/*?hash?table?value?*/?
  9. ????zend_object_value?obj;?
  10. }?zvalue_value;?
  11. ?
  12. struct?_zval_struct?{?
  13. ????/*?Variable?information?*/?
  14. ????zvalue_value?value;?????/*?value?*/?
  15. ????zend_uint?refcount;?
  16. ????zend_uchar?type;????????????/*?active?type?*/?
  17. ????zend_uchar?is_ref;?
  18. };?
  19. ?
  20. typedef?struct?_zval_struct?zval;?
  21. Zend根據(jù)type值來決定訪問value的哪個成員,可用值如下:?

  IS_NULLN/A

  IS_LONG對應(yīng)value.lval

  IS_DOUBLE對應(yīng)value.dval

  IS_STRING對應(yīng)value.str

  IS_ARRAY對應(yīng)value.ht

  IS_OBJECT對應(yīng)value.obj

  IS_BOOL對應(yīng)value.lval.

  IS_RESOURCE對應(yīng)value.lval

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

  1.1.1 引用計數(shù)

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

  很明顯,refcount用于計數(shù),當(dāng)增減引用時,這個值也相應(yīng)的遞增和遞減,一旦減到零,Zend就會回收該zval。

  那么is_ref呢?

  1.1.2 zval狀態(tài)

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

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

  1.1.3 zval狀態(tài)切換

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

  情況I、看如下PHP代碼:

  1. ?

  全過程如下所示:

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

  1.1.1 參數(shù)傳遞

  PHP函數(shù)參數(shù)的傳遞和變量賦值是一樣的,非引用傳遞相當(dāng)于非引用賦值,引用傳遞相當(dāng)于引用賦值,并且也有可能會導(dǎo)致執(zhí)行zval狀態(tài)切換。這在后面還將提到。

  1.2 HashTable結(jié)構(gòu)

  HashTable是Zend引擎中最重要、使用最廣泛的數(shù)據(jù)結(jié)構(gòu),它被用來存儲幾乎所有的東西。

  1.1.1 數(shù)據(jù)結(jié)構(gòu)

  HashTable數(shù)據(jù)結(jié)構(gòu)定義如下:

  1. typedef?struct?bucket?{?
  2. ????ulong?h;????????????????//?存放hash?
  3. ????uint?nKeyLength;?
  4. ????void?*pData;????????????//?指向value,是用戶數(shù)據(jù)的副本?
  5. ????void?*pDataPtr;?
  6. ????struct?bucket?*pListNext;???//?pListNext和pListLast組成?
  7. ????struct?bucket?*pListLast;???//?整個HashTable的雙鏈表?
  8. ????struct?bucket?*pNext;???????//?pNext和pLast用于組成某個hash對應(yīng)?
  9. ????struct?bucket?*pLast;???????//?的雙鏈表?
  10. ????char?arKey[1];??????????????//?key?
  11. }?Bucket;?
  12. ?
  13. typedef?struct?_hashtable?{?
  14. ????uint?nTableSize;?
  15. ????uint?nTableMask;?
  16. ????uint?nNumOfElements;?
  17. ????ulong?nNextFreeElement;?
  18. ????Bucket?*pInternalPointer;???/*?Used?for?element?traversal?*/?
  19. ????Bucket?*pListHead;?
  20. ????Bucket?*pListTail;?
  21. ????Bucket?**arBuckets;?????????//?hash數(shù)組?
  22. ????dtor_func_t?pDestructor;????//?HashTable初始化時指定,銷毀Bucket時調(diào)用?
  23. ????zend_bool?persistent;???????//?是否采用C的內(nèi)存分配例程?
  24. ????unsigned?char?nApplyCount;?
  25. ????zend_bool?bApplyProtection;?
  26. #if?ZEND_DEBUG?
  27. ????int?inconsistent;?
  28. #endif?
  29. }?HashTable;?

  總的來說,Zend的HashTable是一種鏈表散列,同時也為線性遍歷進(jìn)行了優(yōu)化。

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

  關(guān)于該數(shù)據(jù)結(jié)構(gòu)的幾點(diǎn)解釋:

  l 鏈表散列中為什么使用雙向鏈表?

  一般的鏈表散列只需要按key進(jìn)行操作,只需要單鏈表就夠了。但是,Zend有時需要從鏈表散列中刪除給定的Bucket,使用雙鏈表可以非常高效的實(shí)現(xiàn)。

  l nTableMask是干什么的?

  這個值用于hash值到arBuckets數(shù)組下標(biāo)的轉(zhuǎn)換。當(dāng)初始化一個HashTable,Zend首先為arBuckets數(shù)組分配nTableSize大小的內(nèi)存,nTableSize取不小于用戶指定大小的最小的2^n,即二進(jìn)制的10*。nTableMask = nTableSize – 1,即二進(jìn)制的01*,此時h & nTableMask就恰好落在 [0, nTableSize – 1] 里,Zend就以其為index來訪問arBuckets數(shù)組。

  l pDataPtr是干什么的?

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

  arKey大小為什么只有1?為什么不使用指針管理key?

  arKey是存放key的數(shù)組,但其大小卻只有1,并不足以放下key。在HashTable的初始化函數(shù)里可以找到如下代碼:

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

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

  l 上半部分是Bucket,下半部分是key,而arKey“恰好”是Bucket的最后一個元素,于是就可以使用arKey來訪問key了。這種手法在內(nèi)存管理例程中最為常見,當(dāng)分配內(nèi)存時,實(shí)際上是分配了比指定大小要大的內(nèi)存,多出的上半部分通常被稱為cookie,它存儲了這塊內(nèi)存的信息,比如塊大小、上一塊指針、下一塊指針等,baidu的Transmit程序就使用了這種方法。

  不用指針管理key,是為了減少一次emalloc操作,同時也可以提高Cache命中率。另一個必需的理由是,key絕大部分情況下是固定不變的,不會因?yàn)閗ey變長了而導(dǎo)致重新分配整個Bucket。這同時也解釋了為什么不把value也一起作為數(shù)組分配了——因?yàn)関alue是可變的。

  1.2.2 PHP數(shù)組

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

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

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

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

  數(shù)組的遍歷操作,即foreach、each等,是通過HashTable的雙向鏈表來進(jìn)行的,pInternalPointer作為游標(biāo)記錄了當(dāng)前位置。

  1.2.3 變量符號表

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

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

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

  但如果在函數(shù)局部訪問標(biāo)記為global的變量,Zend會進(jìn)行特殊處理——在active_symbol_table中創(chuàng)建symbol_table中同名變量的引用,如果symbol_table中沒有同名變量則會先創(chuàng)建。

  1.3 內(nèi)存和文件

  程序擁有的資源一般包括內(nèi)存和文件,對于通常的程序,這些資源是面向進(jìn)程的,當(dāng)進(jìn)程結(jié)束后,操作系統(tǒng)或C庫會自動回收那些我們沒有顯式釋放的資源。

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

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

  emalloc()

  efree()

  estrdup()

  estrndup()

  ecalloc()

  erealloc()

  另外,Zend還提供了一組形如VCWD_xxx的宏用于替代C庫和操作系統(tǒng)相應(yīng)的文件API,這些宏能夠支持PHP的虛擬工作目錄,在模塊代碼中應(yīng)該總是使用它們。宏的具體定義參見PHP源代碼”TSRM/tsrm_virtual_cwd.h”??赡苣銜⒁獾剑心切┖曛胁]有提供close操作,這是因?yàn)閏lose的對象是已打開的資源,不涉及到文件路徑,因此可以直接使用C或操作系統(tǒng)例程;同理,read/write之類的操作也是直接使用C或操作系統(tǒng)的例程。 標(biāo)簽:北京網(wǎng)站制作 高端網(wǎng)站建設(shè)

掃二維碼與項(xiàng)目經(jīng)理溝通

7*24小時為您服務(wù)

解答:網(wǎng)站優(yōu)化,網(wǎng)站建設(shè),APP開發(fā),小程序開發(fā),H5開發(fā),品牌推廣,新聞推廣,輿情監(jiān)測等

  非常感謝您有耐心的讀完這篇文章:"[北京網(wǎng)站制作]PHP內(nèi)核介紹及擴(kuò)展開發(fā)指南—基礎(chǔ)知識",更多內(nèi)容請繼續(xù)瀏覽,我們將為您提供更多參考使用或?qū)W習(xí)交流的信息。我們還可為您提供:網(wǎng)站建設(shè)與開發(fā)、網(wǎng)站優(yōu)化、品牌推廣、APP開發(fā)、小程序開發(fā)新聞推廣等服務(wù),我們以“降低營銷成本,提高營銷效果”的服務(wù)理念,自創(chuàng)立至今,已成功服務(wù)過不同行業(yè)的1000多家企業(yè),獲得國家高新技術(shù)企業(yè)認(rèn)證,且擁有14項(xiàng)國家軟件著作權(quán),將力爭成為國內(nèi)企業(yè)心目中值得信賴的互聯(lián)網(wǎng)產(chǎn)品及服務(wù)提供商。如您需要合作,請掃碼咨詢,我們將誠摯為您服務(wù)。
我要咨詢
姓名 :
電話 :
文章分類