變形金剛:原大祖國合金版平頭OP

| Comments

我不太喜歡電影版變形金剛,因為太像人就缺少科技感,我至今都認為G1是不可踰越的經典。

不過變4的這款V級平頭OP確實是個經典的模具,一個好的變形金剛玩具應有標準幾乎都符合。

人形身材勻稱、沒有冗餘部件,關鍵是無背包,很少有能做到這一點的模具,幾乎所有大黃蜂最大的敗筆都是有個巨大的背包。塗裝鮮豔、視覺效果強烈,作為一個Prime,搞得滿身火焰紋、花裡胡哨的真的好麼?

車形也是個亮點。我一直不喜歡電影版裡那種狗頭車,太張揚,反而G1裡那種平頭車更顯內斂,才有大哥氣質。

做工和用料很不錯。我不是孩控,做工和價格都很重要,祖國版用料很紮實,而且有合金部件,做工也不弱於孩版,再加上這白菜價,孩版就呵呵噠了。

附加值也比較高。因為沒有冗餘部件,關節的可動性很好,該做的動作都能做。變形的複雜度恰到好處。槍是可以發射子彈的,可玩性有加分。另買的審判之劍真的很帥,否則堂堂Prime單持一把大手槍顯得太素了。

這是款很經典的模具,真的很期待放大的祖國合金版。

PHP全局變量的實現和操作

| Comments

擴展內部的全局變量

php_donie.h
1
2
3
4
ZEND_BEGIN_MODULE_GLOBALS(donie)
  unsigned long global_long;
  char *global_string;
ZEND_END_MODULE_GLOBALS(donie)
donie.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ZEND_DECLARE_MODULE_GLOBALS(donie);

static void php_donie_init_globals(zend_donie_globals *donie_globals)
{
  donie_globals->global_long = 2015;
  donie_globals->global_string = "Long live Donie Leigh !";
}
static void php_donie_globals_dtor(zend_donie_globals *donie_globals)
{
  php_printf("php_donie_globals_dtor triggered.");
}

PHP_MINIT_FUNCTION(donie)
{
  /* init extension globals */
  ZEND_INIT_MODULE_GLOBALS(donie, php_donie_init_globals, php_donie_globals_dtor);

  return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(donie)
{
#ifndef ZTS
  php_donie_globals_dtor(&donie_globals);
#endif

  return SUCCESS;
}

ZEND_FUNCTION(donie_test_ext_globals)
{
  php_printf("%s", DONIE_G(global_string));
}

聲明

ZEND_BEGIN_MODULE_GLOBALS和ZEND_END_MODULE_GLOBALS及其間的內容實際上聲明了一個結構體zend_donie_globals。

根據是否開啟線程安全的情況,ZEND_DECLARE_MODULE_GLOBALS做不同的事:未開啟線程安全,直接聲明zend_donie_globals類型的變量;已開啟線程安全,聲明一個整形變量donie_globals_id。

初始化

在未開啟線程安全時,ZEND_INIT_MODULE_GLOBALS調用第二個參數指定的函數初始化全局變量;已開啟線程安全時,調用ts_allocate_id()分配一個資源ID,並調用第二個參數代表的函數。

訪問

DONIE_G在擴展的頭文件裡,生成擴展框架時默認就有。

銷毀

開啟線程安全時,ZEND_INIT_MODULE_GLOBALS的第三個參數指定的析構函數會自動被調用。未開啟線程安全時,由於該宏只調用第二個參數初始化全局變量,第三個參數沒有用,所以需要在MSHUTDOWN中手工調用析構函數。

用戶空間的超級全局變量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static zend_bool php_donie_autoglobal_callback(const char *name, uint name_len TSRMLS_DC)
{
  zval *donie_val;
  MAKE_STD_ZVAL(donie_val);
  array_init(donie_val);
  add_next_index_string(donie_val, "Hello autoglobals !", 1);
  ZEND_SET_SYMBOL(&EG(symbol_table), "_DONIE", donie_val);
  return 0;
}

PHP_MINIT_FUNCTION(donie)
{
  /* declare userspace super globals */
  zend_register_auto_global("_DONIE", sizeof("_DONIE")-1, 0, php_donie_autoglobal_callback TSRMLS_CC);

  return SUCCESS;
}

zend_register_auto_global()註冊了$_DONIE這樣一個全局變量。在代碼的編譯時,如果PHP內核發現代碼中沒有使用這個全局變量,不會進行初始化;若有使用,會調用php_donie_autoglobal_callback進行初始化。PHP4中沒有php_donie_autoglobal_callback這個參數。

php_donie_autoglobal_callback做的事就是初始化一個zval並加入符號表。如果此函數返回0,則只會被調用一次,如果返回非0,在代碼編譯時,每發現一次該全局變量,就調用一次這個函數。

PHP常量的實現和操作

| Comments

存儲結構

常量存儲在哈希表EG(zend_constants)中。

常量的結構定義為:

1
2
3
4
5
6
7
typedef struct _zend_constant {
  zval value;
  int flags;
  char *name;
  uint name_len;
  int module_number;
} zend_constant;

value是常量的值,是一個zval。name是常量名。module_number是模塊被加載時,PHP內核在MINIT和RINIT方法的原型裡默認傳遞的一個值,作為模塊清理時的線索,在註冊常量的接口裡直接傳遞即可。

flags是常量的標識或標識組合:

  • CONST_CS
  • CONST_PERSISTENT
  • CONST_CT_SUBST

CONST_CS表示常量名對大小寫敏感,對應PHP函數define()的第三個參數,TRUE、FALSE、NULL這些常量名對大小寫是不敏感的。CONST_PERSISTENT表示常量在請求結束後被保存,只在PHP進程結束時才銷毀,一般在MINIT中定義的常量應該指定此參數,RINIT中定義的不指定。CONST_CT_SUBST表示在編譯時可替換,TRUE、FALSE、NULL、ZEND_THREAD_SAFE、ZEND_DEBUG_BUILD屬於此類。

常量的聲明

常量的聲明方法有兩種,簡單的使用宏函數族REGISTER_*_CONSTANT():

REGISTER_NULL_CONSTANT(name, flags) REGISTER_BOOL_CONSTANT(name, bval, flags) REGISTER_LONG_CONSTANT(name, lval, flags) REGISTER_DOUBLE_CONSTANT(name, dval, flags) REGISTER_STRING_CONSTANT(name, str, flags) REGISTER_STRINGL_CONSTANT(name, str, len, flags)

由於不需指定常量名長度,所以name參數應直接使用字符串,而不是char*。

如需使用變量作為name參數,使用zend_register_*_constant()函數族,並指定變量名長度(sizeof(name))。上面的宏函數其實是對這族函數的封裝。

void zend_register_long_constant(char *name, uint name_len, long lval, int flags, int module_number TSRMLS_DC) void zend_register_double_constant(char *name, uint name_len, double dval, int flags, int module_number TSRMLS_DC) void zend_register_bool_constant(const char *name, uint name_len, zend_bool bval, int flags, int module_number TSRMLS_DC) void zend_register_string_constant(char *name, uint name_len, char *strval, int flags, int module_number TSRMLS_DC) void zend_register_stringl_constant(char *name, uint name_len, char *strval, uint strlen, int flags, int module_number TSRMLS_DC)

除此之外,還有REGISTER_MAIN_*_CONSTANT和REGISTER_NS_*_CONSTANT兩組宏函數。前者用於定義像E_ERROR這樣的PHP標準常量,後者定義有命令空間的常量。

define()和const

  • define()是函數,在運行時定義常量
    • 不能定義類常量
    • 可以在條件語句中使用
    • 可以指定常量是否對大小寫敏感
    • 可以用表達式作為常量值
    • 只定義全局常量,不支持命名空間
  • const是語句,在編譯時定義常量
    • 可以定義類常量
    • 不能在條件語句中使用
    • 定義的常量對大小寫敏感
    • 不支持表達式作為常量值
    • 若腳本定義了命名空間,聲明的常量屬於該命名空間

魔術常量

__LINE__
__FILE__
__DIR__
__FUNCTION__
__CLASS__
__METHOD__
__NAMESPACE__

魔術常量是在編譯時(具體地說是詞法分析時,見Zend/zend_language_scanner.l)被替換,確切地說,這些不是真正意義上的常量,只是個模板佔位符。

PHP资源的實現和操作

| Comments

存儲結構

資源變量也是一個zval結構,zval->type == IS_RESOURCE,zval->value->lval存儲一個整數,此整數為資源數據在存儲資源的哈希表中的索引。

資源數據的結構為:

1
2
3
4
5
6
typedef struct _zend_rsrc_list_entry
{
    void *ptr;
    int type;
    int refcount;
}zend_rsrc_list_entry;

常規資源與持久資源

有兩個存儲資源數據的哈希表。EG(regular_list)存儲常規資源,EG(persistent_list)存儲持久資源。

常規資源對應的變量在作用域結束後會被內核回收,對應的資源數據也會被銷毀。持久資源可以保持並被多次請求使用。持久資源的自動析構發生在PHP進程退出時。

實現

重新實現基本的文件句柄和相關操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// 資源名稱
#define PHP_DONIE_RES_NAME_FILE "Donie's File Descriptor"

// 資源類型
static int le_donie_file_descriptor;
static int le_donie_file_descriptor_persist;

// 資源析構函數
static void php_donie_file_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_CC)
{
  FILE *fp = (FILE*)rsrc->ptr;
  fclose(fp);
}

// 在擴展的MINIT方法裡創建資源類型
PHP_MINIT_FUNCTION(donie)
{
  /* create a new resource type */
  le_donie_file_descriptor = zend_register_list_destructors_ex(
      php_donie_file_descriptor_dtor, NULL, PHP_DONIE_RES_NAME_FILE, module_number
  );

  /* create a persistent resource type */
  le_donie_file_descriptor_persist = zend_register_list_destructors_ex(
      NULL, php_donie_file_descriptor_dtor, PHP_DONIE_RES_NAME_FILE, module_number
  );

  return SUCCESS;
}

// 文件打開操作
PHP_FUNCTION(donie_fopen)
{
  FILE *fp;
  char *filename, *mode;
  int filename_len, mode_len;
  zend_bool persist = 0;
  char *hash_key;
  int hash_key_len;
  list_entry *persist_file;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|b", &filename, &filename_len, &mode, &mode_len, &persist) == FAILURE)
  {
      RETURN_NULL();
  }
  if (!filename_len || !mode_len)
  {
      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid file name or mode.");
      RETURN_FALSE;
  }

  /* reuse persistent resource if exists */
  hash_key_len = spprintf(&hash_key, 0, "php_donie_file_descriptor:%s-%s", filename, mode);
  if (zend_hash_find(&EG(persistent_list), hash_key, hash_key_len+1, (void **)&persist_file) == SUCCESS)
  {
      ZEND_REGISTER_RESOURCE(return_value, persist_file->ptr, le_donie_file_descriptor_persist);
      efree(hash_key);
      return;
  }

  fp = fopen(filename, mode);
  if (!fp)
  {
      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed opening %s with mode %s.", filename, mode);
      RETURN_FALSE;
  }

  /* this is the key point for registering resources */
  if (persist)
  {
      ZEND_REGISTER_RESOURCE(return_value, fp, le_donie_file_descriptor_persist);
      list_entry le;
      le.type = le_donie_file_descriptor_persist;
      le.ptr = fp;
      zend_hash_update(&EG(persistent_list), hash_key, hash_key_len+1, (void*)&le, sizeof(list_entry), NULL);
  }
  else
  {
      ZEND_REGISTER_RESOURCE(return_value, fp, le_donie_file_descriptor);
  }
  efree(hash_key);
}

// 文件寫操作
PHP_FUNCTION(donie_fwrite)
{
  FILE *fp;
  zval *file_resource;
  char *data;
  int data_len;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &file_resource, &data, &data_len) == FAILURE)
  {
      RETURN_NULL();
  }

  ZEND_FETCH_RESOURCE2(fp, FILE*, &file_resource, -1, PHP_DONIE_RES_NAME_FILE, le_donie_file_descriptor, le_donie_file_descriptor_persist);
  RETURN_LONG(fwrite(data, 1, data_len, fp));
}

// 文件關閉操作
PHP_FUNCTION(donie_fclose)
{
  FILE *fp;
  zval *file_resource;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &file_resource) == FAILURE)
  {
      RETURN_NULL();
  }

  ZEND_FETCH_RESOURCE2(fp, FILE*, &file_resource, -1, PHP_DONIE_RES_NAME_FILE, le_donie_file_descriptor, le_donie_file_descriptor_persist);
  zend_hash_index_del(&EG(regular_list), Z_RESVAL_P(file_resource));
  RETURN_TRUE;
}

創建新資源類型

zend_register_list_destructors_ex()創建新資源類型,並註冊該資源類型的析構函數、資源名稱。第一個參數是常規資源的析構函數,第二個是持久資源的析構函數,此處創建的是常規資源類型,故第二個參數不指定。

註冊資源

宏函數ZEND_REGISTER_RESOURCE()註冊新生成的資源到EG(regular_list),並保存資源的索引到zval->value->lval中。

雙重引用計數

資源變量zval中存在一個引用計數,資源數據zend_rsrc_list_entry中也存在一個。前者遵循與其它變量一致的計數原則,後者取決於資源數據被幾個資源變量zval引用。

例如對於以下場景:

1
2
3
4
5
<?php
$a = donie_fopen('/tmp/donie.txt', 'r');
$b = $a;
$c = &$a;
?>

a賦值給b時,zval的引用計數加一。a的引用賦值給c時,發生zval的拆分,b獲得新的zval,引用計數是1,a和c共用一個zval,引用計數是2。此時,資源數據的引用計數加一。

獲取資源

ZEND_FETCH_RESOURCE()根據資源變量zval取出資源數據的ptr並驗證資源類型。ZEND_FETCH_RESOURCE2()可以同時指定兩個資源類型,任一類型匹配成功都可以。

銷毀資源

根據上述二重計數原則,只有當資源數據的引用計數為0時,資源的析構函數才會被調用,而銷毀資源變量不一定能銷毀資源,所以需要手工強制銷毀資源。

zend_hash_index_del()從EG(regular_list)中刪除資源時,該資源類型註冊的dtor會被自動調用,從而析構資源。

持久資源

存儲

EG(persistent_list)是個用字符串索引的哈希表。需要自行定義鍵的命名規則,做到全局唯一。

創建持久資源類型

zend_register_list_destructors_ex()註冊資源類型時,將析構函數指定為第二個參數,第一個參數為NULL。析構持久資源時,會自動調用該函數。

註冊持久資源

EG(persistent_list)中的資源數據並不被直接使用,對資源的操作仍然使用EG(regular_list)。故在註冊持久資源時,兩個哈希表中都需要保存一份。

往EG(persistent_list)中存資源數據:

1
2
3
4
5
6
7
char *hash_key;
int hash_key_len;
zend_rsrc_list_entry le;
le.type = le_donie_file_descriptor_persist;
le.ptr = fp;
hash_key_len = spprintf(&hash_key, 0, "php_donie_file_descriptor:%s-%s", filename, mode);
zend_hash_update(&EG(persistent_list), hash_key, hash_key_len+1, (void*)&le, sizeof(list_entry), NULL);

獲取持久資源

對持久資源的常規操作和操作常規資源一樣,仍使用EG(regular_list),因為變量zval中存儲的是EG(regular_list)中的索引。所以需要先在EG(persistent_list)中查詢,若資源存在,先註冊到EG(regular_list)中,再進行後續操作。

手動析構持久資源

用zend_hash_del()從EG(persistent_list)中刪除資源數據即可自動觸發析構函數。

PHP對象的實現和操作

| Comments

Object的存儲結構

對象實例用zval存儲。zval->type == IS_OBJECT,zval->value->obj存儲zend_object_value類型的結構體變量。

1
2
3
4
typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;

zend_object_handle是一個unsigned int,是對象的ID。zend_object_handlers存儲對象所有的行為。

Object的實例化過程

Object的初始化用以下幾個宏函數:

  • object_init(zval *arg)
  • object_init_ex(zval *arg, zend_class_entry *class_type)
  • object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties)

底層都是調用_object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties)實現。這個函數做以下幾件事:

  • 檢查類是否可實例化(例如接口、抽象類等不允許初始化)
  • 處理類常量
  • 檢查類是否存在自定義實例化邏輯
    • 若存在,調用自定義實例化邏輯
    • 若不存在,調用缺省的函數zend_objects_new(zend_object **object, zend_class_entry *class_type)
  • 把實例化的zend_object類型的數據存入zval中

zend_objects_new()做這些事:

  • 分配一個zend_object類型的內存空間
  • 初始化zend_object類型數據
  • 把zend_object類型數據存入對象倉庫(Objects Store)
    • zend_objects_store_put(void *object, zend_objects_store_dtor_t dtor, zend_objects_free_object_storage_t free_storage, zend_objects_store_clone_t clone)

zend_object的存儲結構

1
2
3
4
5
6
typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards; /* protects from __get/__set ... recursion */
} zend_object;

ce是類的定義。properties_table存儲類裡預定義的屬性。properties存儲非預定義屬性。

guards存儲屬性名到zend_guard結構的映射關係。

1
2
3
4
5
6
7
typedef struct _zend_guard {
    zend_bool in_get;
    zend_bool in_set;
    zend_bool in_unset;
    zend_bool in_isset;
    zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */
} zend_guard;

此結構用於在操作屬性時,防止遞歸調用。例如給對象一個新屬性賦值時,__set()函數理論上會遞歸調用自己,所以此結構用於判斷該屬性是否已在__set()中。

屬性的存儲結構

在zend_object的存儲結構裡,哈希表properties存儲類的非預定義屬性的名称和值。

對於預定義的屬性,由於PHP的哈希表的存儲開銷很大,所以把屬性信息(即下面的zend_property_info結構體)存儲在zend_class_entry裡,對象裡用C的數組存儲所有預定義屬性的zval的指針,並把偏移量記錄在屬性信息裡,這就是properties_table。

1
2
3
4
5
6
7
8
9
10
typedef struct _zend_property_info {
    zend_uint flags;
    const char *name;
    int name_length;
    ulong h;                 /* hash of name */
    int offset;              /* storage offset */
    const char *doc_comment;
    int doc_comment_len;
    zend_class_entry *ce;    /* CE of declaring class */
} zend_property_info;

屬性名的編碼

在類的繼承關係中,同名不同類型(public,private等)的屬性各自單獨存儲,所以屬性名在底層是經過編碼的,規則如下:

class Foo { private $prop; } => “\0Foo\0prop”
class Bar { private $prop; } => “\0Bar\0prop”
class Rab { protected $prop; } => “\0*\0prop”
class Oof { public $prop; } => “prop”

大部分情況下,對屬性操作的API自動處理屬性名的編碼。只有當需要直接訪問property_info->name或zobj->properties時才需要自行處理,此時使用zend_(un)mangle_property_name()函數。

Objects Store的存儲結構

對象倉庫是一個可變數組,存儲多個zend_object_store_bucket結構。

1
2
3
4
5
6
typedef struct _zend_objects_store {
    zend_object_store_bucket *object_buckets;
    zend_uint top;
    zend_uint size;
    int free_list_head;
} zend_objects_store;

size是對象倉庫的容量。top是下一個可用的對象句柄,對象句柄從1開始,以保證所有句柄都為真。對象倉庫通過每個Bucket的free_list結構維護一個可用的Bucket鏈表,free_list_head記錄鏈表的頭部。

zend_object_store_bucket的存儲結構

每個對象的信息存儲在一個bucket裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;
} zend_object_store_bucket;

桶被佔用的時候,valid為1,否則為0。

對象被銷毀時,dtor被調用後,destructor_called被置為1,防止在被free時重複調用dtor,具體見Object的二階銷毀邏輯

_store_object裡存儲對象的主要信息。zend_objects_store_put()傳入的zend_object結構體存儲在object裡。dtor和free_storage見Object的二階銷毀邏輯。clone是對象的克隆函數。handlers存儲對象的一系列操作函數,缺省為std_object_handlers。refcount是對象的引用計數。buffered是垃圾回收需要用到的數據。

free_list記錄對象倉庫中可用的Bucket鏈表中下一個可用的Bucket。

Object Store的操作

  • zend_objects_store_put():註冊對象到倉庫
  • zend_object_store_get_object_by_handle():通過對象句柄取對象
  • zend_object_store_get_object():通過zval取對象,返回void*
  • zend_objects_get_address():和zend_object_store_get_object()一樣,但返回zend_object*

Object的二階銷毀邏輯

對象的銷毀分兩個步驟,一是對象的析構,一是內存的釋放。前者調用對象的dtor,後者調用free_storage。一般先析構,再釋放內存,但兩者可各自分開執行。

dtor中可以執行用戶空間的PHP代碼,主要是PHP類的__destruct()。PHP腳本執行完成後銷毀對象並結束進程(executor shutdown),在這個過程進行到一半的時候執行用戶空間代碼可能會出問題,所以這麼區別主要是為了在進程結束過程中不會調用用戶空間代碼。

此外,dtor並不是必須執行的,如果一個對象的dtor調用的用戶空間代碼裡執行了die(),後續對象的dtor不會被執行。所以大部分情況下,開發者可以自定義free_storage函數,而使用缺省的zend_objects_destroy_object作為dtor。

PHP類和接口的實現

| Comments

Class的實現

類的註冊是在擴展的MINIT方法裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
 * this pointer should be put into the header file,
 * so other modules can access this class.
 */
zend_class_entry *c_leigh;

/* just a simple method. */
PHP_METHOD(Leigh, helloWorld)
{
    if (zend_parse_parameters_none() == FAILURE) {
        return;
    }

    php_printf("Hello World !\n");
}

/* getting handle of this object. */
PHP_METHOD(Leigh, getObjectHandle)
{
  zval *obj;

  if (zend_parse_parameters_none() == FAILURE)
  {
      return;
  }

  obj = getThis();

  RETURN_LONG(Z_OBJ_HANDLE_P(obj));
}

/* get value of the property 'bloodType' */
PHP_METHOD(Leigh, getBloodType)
{
  zval *obj, *blood_type;

  if (zend_parse_parameters_none() == FAILURE) {
        return;
    }

  obj = getThis();

    blood_type = zend_read_property(c_leigh, obj, "bloodType", sizeof("bloodType") - 1, 1 TSRMLS_CC);

    RETURN_ZVAL(blood_type, 1, 0);
}

/* set value of the property 'bloodType' */
PHP_METHOD(Leigh, setBloodType)
{
  zval *obj, *new_value;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &new_value) == FAILURE)
  {
      return;
  }

  obj = getThis();

  zend_update_property(c_leigh, obj, "bloodType", sizeof("bloodType")-1, new_value TSRMLS_CC);
}

const zend_function_entry leigh_functions[] = {
  PHP_ME(Leigh, helloWorld, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Leigh, getObjectHandle, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Leigh, getBloodType, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Leigh, setBloodType, NULL, ZEND_ACC_PUBLIC)
  /* PHP_ABSTRACT_ME(Leigh, abstractMethod, NULL)      // abstract method */
  PHP_FE_END
};

/*
 * create a new class inheriting Leigh
 */
zend_class_entry *c_hero;

/*
 * create an interface
 */
zend_class_entry *i_superman;

const zend_function_entry superman_functions[] = {
  PHP_ABSTRACT_ME(ISuperman, saveEarth, NULL)
  PHP_FE_END
};

/*  PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(donie)
{
  /* If you have INI entries, uncomment these lines
 REGISTER_INI_ENTRIES();
 */

  time_of_minit = time(NULL);

  /* register a class */
  zend_class_entry tmp_leigh;
  INIT_CLASS_ENTRY(tmp_leigh, "Leigh", leigh_functions);
  c_leigh = zend_register_internal_class(&tmp_leigh TSRMLS_CC);

  /* declare a property initialized as null */
  zend_declare_property_null(c_leigh, "bloodType", sizeof("bloodType")-1, ZEND_ACC_PUBLIC TSRMLS_CC);
  /* declare a class constant */
  zend_declare_class_constant_double(c_leigh, "PI", sizeof("PI")-1, 3.1415926 TSRMLS_CC);

  /* declare an interface */
  zend_class_entry tmp_superman;
  INIT_CLASS_ENTRY(tmp_superman, "ISuperman", superman_functions);
  i_superman = zend_register_internal_interface(&tmp_superman TSRMLS_CC);

  /* inherit a class and implement an interface*/
  zend_class_entry tmp_hero;
  INIT_CLASS_ENTRY(tmp_hero, "Hero", NULL);
  c_hero = zend_register_internal_class_ex(&tmp_hero, c_leigh, NULL TSRMLS_CC);
  zend_class_implements(c_hero TSRMLS_CC, 1, i_superman);

  return SUCCESS;
}

方法修飾符

ZEND_ACC_PUBLIC
ZEND_ACC_PROTECTED
ZEND_ACC_PRIVATE
ZEND_ACC_STATIC
ZEND_ACC_FINAL
ZEND_ACC_ABSTRACT

不直接在PHP_ME裡使用ZEND_ACC_ABSTRACT定義抽象方法,用PHP_ABSTRACT_ME()。

取對象句柄

在方法的定義裡使用getThis()拿當前對象的句柄。

屬性的聲明和存取

zend_declare_property_null(… TSRMLS_DC)
zend_declare_property_bool(…, long value TSRMLS_DC)
zend_declare_property_long(…, long value TSRMLS_DC)
zend_declare_property_double(…, double value TSRMLS_DC)
zend_declare_property_string(…, const char value TSRMLS_DC)
zend_declare_property_stringl(…, const char
value, int value_len TSRMLS_DC)

屬性的修飾符和方法相同。

屬性的獲取使用zend_read_property_*()這組函數。

屬性的更新使用zend_update_property_*()這組函數。

靜態屬性的獲取和更新分別使用zend_read_static_property_*()函數組和zend_update_static_property_*()函數組。與以上不同的是,參數中不需要對象句柄。

類常量的聲明使用zend_declare_class_constant_*()函數組,參數與聲明屬性相同,只是不需要修飾符。

繼承類

用zend_register_internal_class_ex()。

聲明接口

和聲明類一樣,先聲明一組抽象方法,然後用zend_register_internal_interface()註冊。

實現接口

用zend_class_implements()。

變形金剛:俺鋼鎖才是老大

| Comments

變形金剛是工業藝術的巔峰之作。不僅僅是懷舊和補童年。理工宅兩大圖騰,恐龍和機器人。從沒有一個東西像汽車人恐龍部隊一樣,把這兩樣完美地融為一體。

孩之寶的東西很多時候不盡如人意,價格又太貴。有一些第三方的仿品,不乏精品。像威將的這個鋼鎖,是孩之寶V級的放大版,幾乎與L級等身,頭部和大腿根部是合金部件,很有質感。有美版和黑暗兩個塗裝,結合放大後的形象,黑暗塗裝更顯霸氣。

美版L級人形。整體精緻,但是太瘦,兩條腿細得跟小兒麻痺似的,盈盈一掬的小蠻腰,這是要走長腿歐巴路線啊。這個比例,如果放大一號效果會更好,不過鋼鎖老大比其它L級大一號不是理所當然的麼。最討厭的是電鍍,又俗又易脫落。

美版L級龍形。非常失敗。身體比例失調,趴耳朵,塌脊梁,短尾巴。不忍直視。

祖國版V級人形。V級鋼鎖是不以人形見長的,不過放大後觀感尚可。

祖國版V級龍形。V級鋼鎖的龍形完爆L級,體形霸氣,立耳,隆背,翹尾。孩版V級最大的遺憾就是體形太小,祖國版龍形堪稱完美。

不知道是不是我中獎了,祖國版人形的大腿根部雖然是球齒關節,但是球齒無效,關節比較松,容易劈叉。不過這個做工、用料,只要幾十軟妹幣,相對幾倍價格的孩版來說,威將良心之作啊。

PHP數組的實現與操作

| Comments

存儲結構

和其它變量一樣,PHP的數組也是一個zval。存儲數據的哈希表存放在zval->value->ht中。

符號表操作

為實現可轉換成整數的字符串鍵與整數鍵指向同一個元素,在哈希表操作的基礎上封裝了一層,對可轉換成整數的字符串鍵轉換成整數,然後調用zend_hash_index_*操作,否則調用zend_hash_*操作。這就是符號表操作。

用ZEND_HANDLE_NUMERIC處理整數字符串鍵:

1
2
3
4
5
6
static inline int zend_symtable_find(
    HashTable *ht, const char *arKey, uint nKeyLength, void **pData
) {
    ZEND_HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_find(ht, idx, pData));
    return zend_hash_find(ht, arKey, nKeyLength, pData);
}

其它符號表操作函數:

1
2
3
4
5
6
7
8
static inline int zend_symtable_exists(HashTable *ht, const char *arKey, uint nKeyLength);
static inline int zend_symtable_del(HashTable *ht, const char *arKey, uint nKeyLength);
static inline int zend_symtable_update(
    HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest
);
static inline int zend_symtable_update_current_key_ex(
    HashTable *ht, const char *arKey, uint nKeyLength, int mode, HashPosition *pos
);

數組操作

初始化

1
2
3
4
5
6
7
8
9
// 初始化數組
zval *zv1;
array_init(zv1);

// 初始化數組並指定哈希表nTableSize的值
array_init_size(zv1, 100);

// 在函數中返回數組:把返回值初始化為數組
array_init(return_value);

插入和更新

1
2
3
4
5
6
7
8
/* Insert at next index */
int add_next_index_*(zval *arg, ...);
/* Insert at specific index */
int add_index_*(zval *arg, ulong idx, ...);
/* Insert at specific key */
int add_assoc_*(zval *arg, const char *key, ...);
/* Insert at specific key of length key_len (for binary safety) */
int add_assoc_*_ex(zval *arg, const char *key, uint key_len, ...);

星號表示類型名,可用類型名如下:

Type Additional arguments
null none|
bool int b|
long long n|
double double d|
string const char *str, int duplicate|
stringl const char *str, uint length, int duplicate|
resource int r|
zval zval *value|

字符串長度的處理

上述操作對字符串鍵和字符串值的長度的要求不同。_ex函數要求傳入字符串鍵的長度,此長度包含NUL字節。_stringl函數要求傳入字符串值的長度,此長度不包含NUL字節。

栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PHP_FUNCTION(donie_get_arr)
{
  array_init(return_value);

  // add an integer to the given position
  add_index_long(return_value, 1, 2015);

  // append a string to the array
  add_next_index_string(return_value, "dummy string", 1);

  // add a boolean value to the given key
  add_assoc_bool(return_value, "rightOrWrong", 0);

  // take care of string lengths
  add_assoc_stringl_ex(return_value, "keyStringL\0", sizeof("keyStringL\0")-1, "valueEx\0", sizeof("valueEx\0"), 1);

  // store an object in the array
  zval *obj;
  MAKE_STD_ZVAL(obj);
  object_init(obj);
  add_next_index_zval(return_value, obj);
}

PHP哈希表的實現與操作

| Comments

結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 哈希表結構
typedef struct _hashtable {
    uint nTableSize;
    uint nTableMask;
    uint nNumOfElements;           // 全部元素數
    ulong nNextFreeElement;        // 下一個可用的整數鍵
    Bucket *pInternalPointer;      // 枚舉操作時使用,指向當前Bucket
    Bucket *pListHead;
    Bucket *pListTail;
    Bucket **arBuckets;
    dtor_func_t pDestructor;       // 元素的析構函數
    zend_bool persistent;          // 是否在本次請求結束後保留哈希表
    unsigned char nApplyCount;     // 循環級別,防止循環引用導致遍歷哈希表時死循環
    zend_bool bApplyProtection;    // 是否防止死循環
#if ZEND_DEBUG
    int inconsistent;
#endif
} HashTable;

// Bucket結構
typedef struct bucket {
    ulong h;
    uint nKeyLength;
    void *pData;
    void *pDataPtr;
    struct bucket *pListNext;
    struct bucket *pListLast;
    struct bucket *pNext;
    struct bucket *pLast;
    char *arKey;
} Bucket;

哈希衝突處理

哈希表通過計算鍵值的哈希值,將對應的數據映射到對應的槽上。理論上會存在不同的鍵的哈希值相同的情況。

處理哈希衝突的方法一般有兩種:開放尋址和鏈表。開放尋址法是將衝突的元素順序放到下一個空槽,理論上會導致衝突越來越多,性能快速下降。鏈表法是將衝突的元素插入對應的槽,與前一個元素組成一個鏈表。PHP使用鏈表法。

PHP的哈希表中的Buckets組成兩種雙向鏈表。一種由每個槽中的所有Bucket分別組成,一種是整個哈希表中的Bucket組成一個。Bucket結構裡,pNext指向該槽的鏈表中的下一個Bucket,pLast指向上一個;pListNext指向整個哈希表鏈表的下一個Bucket,pListLast指向上一個。

pData與pDataPtr

賦值到Bucket時,數據會被複製一份,pData中保存指向該數據拷貝的指針。特別地,如果保存一個指針到Bucket,會先將該指針保存到pDataPtr,然後將pData指向pDataPtr,即pData中保存的是指向pDataPtr中保存的指針的指針。這樣可以避免一次拷貝數據時分配內存的操作,提高效率。

nTableSize與nTableMask

nTableSize保存的是arBuckets的Bucket個數。它的值永遠是個大於等於8的、2的n次方的整數。當現有容量不滿足需要時,arBuckets會重新分配一個大小是原來兩倍的空間,nTableSize相應地被更新為新的數值。

nTableMask = nTableSize - 1

哈希值h一般比nTableSize大,所以要用哈希值對nTableSize取模,以確定對應的Bucket。由於取模操作運算量大,且nTableSize永遠是2的n次冪,所以用“h & (nTableSize - 1)”替代。

初始化與銷毀

1
2
3
4
5
6
7
8
9
10
11
12
13
// init hashtable
HashTable *myht;
ALLOC_HASHTABLE(myht);
if (zend_hash_init(myht, 100, NULL, NULL, 0) == FAILURE)
{
    FREE_HASHTABLE(myht);
    return FAILURE;
}

// destroy hashtable
zend_hash_destroy(myht);
FREE_HASHTABLE(myht);
return SUCCESS;

初始化哈希表

ALLOC_HASHTABLE就是用emalloc()分配內存。

zend_hash_init()

zend_hash_init(HashTable *ht, uint nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)

nSize是哈希表的初始長度,實際分配為最接近指定值的2的n次方,最小為8。

pDestructor是被存儲數據的析構函數,默認為ZVAL_PTR_DTOR,對於一般情況(Bucket中存儲的是zval)適用。

persistent,1表示本次請求結束後保留哈希表,0反之。

銷毀哈希表

zend_hash_clean()對HT所有Bucket調用析構函數,並重置HT的所有指針。

zend_hash_destroy()除了銷毀所有Bucket存儲的數據,連arBuckets的空間也釋放掉。

FREE_HASHTABLE宏其實就是efree()。

操作數字鍵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// add a string with an integer key 2 to myht
zval *zv1;
MAKE_STD_ZVAL(zv1);
ZVAL_STRING(zv1, "Hello HT !", 1);
zend_hash_index_update(myht, 2, &zv1, sizeof(zval *), NULL);

// get the next free key
php_printf("The next free key will be %ld.\n", zend_hash_next_free_element(myht));

// append an integer to myht
zval *zv2;
MAKE_STD_ZVAL(zv2);
ZVAL_LONG(zv2, 2015);
if (zend_hash_next_index_insert(myht, &zv2, sizeof(zval *), NULL) == FAILURE)
{
    php_printf("HashTable appendation failed.\n");
}
else
{
    php_printf("HashTable appendation succeeded.\n");
}

// get the size
php_printf("HashTable has a size of %d.\n", zend_hash_num_elements(myht));

// check if an integer key exists
int idx = 3;
if (zend_hash_index_exists(myht, idx))
{
    php_printf("HashTable has an index of the value %ld.\n", idx);
}
else
{
    php_printf("HashTable does not have an index of the value %ld.\n", idx);
}

// get a value by its key
zval **zval_dest;
if (zend_hash_index_find(myht, idx, (void **) &zval_dest) == SUCCESS)
{
    php_printf("The value indexed by %ld is %Z.\n", idx, *zval_dest);
}
else
{
    php_printf("The value indexed by %ld does not exist.\n", idx);
}

// delete the specified value from myht
if (zend_hash_index_del(myht, idx) == FAILURE)
{
    php_printf("The value indexed by %ld failed to be deleted.\n", idx);
}
else
{
    php_printf("The value indexed by %ld is deleted.\n", idx);
}

操作字符串鍵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// add an integer indexed by a string key, using zend_hash_update()
zval *zv3;
MAKE_STD_ZVAL(zv3);
ZVAL_LONG(zv3, 1985);
zend_hash_update(myht, "year", sizeof("year"), &zv3, sizeof(zval *), NULL);
php_printf("An integer is updated to the hash-table indexed by a string key.\n");

// add a string indexed by a string key, using zend_hash_add()
zval *zv4;
MAKE_STD_ZVAL(zv4);
ZVAL_STRING(zv4, "Great Donie !", 1);
if (zend_hash_add(myht, "motto", sizeof("motto"), &zv4, sizeof(zval *), NULL) == FAILURE)
{
  php_printf("Cannot add a string indexed by a string key to the hash-table, may be the index already exists.\n");
}
else
{
  php_printf("A string is added to the hash-table indexed by a string key.\n");
}

// get the next free key
php_printf("The next free key will be %ld.\n", zend_hash_next_free_element(myht));

// check if a string key exists
char *key1 = "year";
if (zend_hash_exists(myht, key1, strlen(key1)+1))
{
  php_printf("The key %s exists.\n", key1);
}
else
{
  php_printf("The key %s does not exist.\n", key1);
}

// get the value indexed by a string key
zval **zv_dest2;
if (zend_hash_find(myht, key1, strlen(key1)+1, (void **) &zv_dest2) == SUCCESS)
{
  php_printf("The value indexed by %s is %Z.\n", key1, *zv_dest2);
}
else
{
  php_printf("Failed fetching the value indexed by %s.\n", key1);
}

// delete the value indexed by a string key
if (zend_hash_del(myht, key1, strlen(key1)+1) == SUCCESS)
{
  php_printf("The value indexed by %s is deleted.\n", key1);
}
else
{
  php_printf("The value indexed by %s failed to be deleted.\n", key1);
}

鍵的長度

鍵長包括鍵字符串末尾的NUL字節。如果直接指定,應是sizeof(“key1”);如果是char*類型變量,應是strlen(key1)+1

快速操作

適用於頻繁操作特定鍵的場景,只計算一次哈希值,加速操作。

對應的,有一組名帶“quick”的函數。

1
2
3
4
5
6
7
8
9
// quick operations leveraging a one-time hashed value
zval *zv5;
MAKE_STD_ZVAL(zv5);
ZVAL_STRING(zv5, "Great Donie Leigh !", 1);

ulong h;
h = zend_get_hash_value("motto", sizeof("motto"));
zend_hash_quick_update(myht, "motto", sizeof("motto"), h, &zv5, sizeof(zval *), NULL);
php_printf("The value indexed by motto is updated with the quick operation.\n");

遍歷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int hashtable_traverse_callback(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
{
  zval **zv = (zval **) pDest;
  char *arg1 = va_arg(args, char*);

  php_printf("The first argument is %s.\n", arg1);

  if (hash_key->nKeyLength == 0)
  {
      php_printf("K-V: %d=>%Z\n", hash_key->h, *zv);
  }
  else
  {
      php_printf("K-V: %s=>%Z\n", hash_key->arKey, *zv);
  }

  return ZEND_HASH_APPLY_KEEP;
}

// traverse the hash table.
zend_hash_apply_with_arguments(myht, hashtable_traverse_callback, 1, "nonsense");

三個函數

遍歷哈希表的三個函數:

1
2
3
4
5
6
7
void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC);
void zend_hash_apply_with_argument(
    HashTable *ht, apply_func_arg_t apply_func, void *argument TSRMLS_DC
);
void zend_hash_apply_with_arguments(
    HashTable *ht TSRMLS_DC, apply_func_args_t apply_func, int num_args, ...
);

三個函數接受的回調函數的類型:

1
2
3
4
5
typedef int (*apply_func_t)(void *pDest TSRMLS_DC);
typedef int (*apply_func_arg_t)(void *pDest, void *argument TSRMLS_DC);
typedef int (*apply_func_args_t)(
    void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key
);

zend_hash_key的定義為:

1
2
3
4
5
typedef struct _zend_hash_key {
    const char *arKey;
    uint nKeyLength;
    ulong h;
} zend_hash_key;

nKeyLength為0表示索引是整數,值為h;否則是字符串,值為arKey。

回調函數的返回值:

  • ZEND_HASH_APPLY_KEEP:繼續遍歷。
  • ZEND_HASH_APPLY_REMOVE:遍歷後刪除遍歷過的元素。
  • ZEND_HASH_APPLY_STOP:遍歷當前元素後停止。
  • ZEND_HASH_APPLY_REMOVE | ZEND_HASH_APPLY_STOP:遍歷當前元素後,刪除該元素並停止。

枚舉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// iterating the hash table
php_printf("Begin iterating the hash table:\n");
HashPosition pos;
zval **data;
char *str_idx;
uint str_len;
ulong num_idx;
for (zend_hash_internal_pointer_reset_ex(myht, &pos);
  zend_hash_get_current_data_ex(myht, (void **) &data, &pos) == SUCCESS;
  zend_hash_move_forward_ex(myht, &pos)
) {
  switch (zend_hash_get_current_key_ex(myht, &str_idx, &str_len, &num_idx, 0, &pos)) {
      case HASH_KEY_IS_LONG:
          php_printf("K-V: %d=>%Z\n", num_idx, *data);
          break;
      case HASH_KEY_IS_STRING:
          php_printf("K-V: %s=>%Z\n", str_idx, *data);
          break;
  }
}

三個函數

三個函數均帶“_ex”後綴,使用外部指針。不帶此後綴的函數使用哈希表內部指針,此時嵌套地遍歷哈希表可能導致指針修改錯誤。

取鍵的新方式

PHP 5.5以上版本新增函數,直接取鍵值到zval:

1
2
3
zval *key;
MAKE_STD_ZVAL(key);
zend_hash_get_current_key_zval_ex(myht, key, &pos);

複製與合併

1
zend_hash_copy(ht_target, ht_source, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));

zval_add_ref是適用於zval的回調函數,直接引用原數據。

當目標哈希表已存在對應鍵值的數據時,目標元素會被源元素覆蓋。使用zend_hash_merge()可通過最後一個參數指定是否用源數據覆蓋目標數據。

函數zend_hash_merge_ex()可指定一個回調函數,用於過濾要合併的元素:

1
2
3
4
zend_hash_merge_ex(
    Z_ARRVAL_P(return_value), Z_ARRVAL_P(array2), (copy_ctor_func_t) zval_add_ref,
    sizeof(zval *), (merge_checker_func_t) merge_greater, NULL
);

回調函數的格式為:

1
2
3
typedef zend_bool (*merge_checker_func_t)(
    HashTable *target_ht, void *source_data, zend_hash_key *hash_key, void *pParam
);

比較、排序和極值

比較函數:

1
2
3
4
5
6
int zend_hash_compare(
    HashTable *ht1, HashTable *ht2, compare_func_t compar, zend_bool ordered TSRMLS_DC
);

// 回調函數:
typedef int (*compare_func_t)(const void *left, const void *right TSRMLS_DC);

排序函數:

1
2
3
4
5
6
7
int zend_hash_sort(HashTable *ht, sort_func_t sort_func, compare_func_t compar, int renumber TSRMLS_DC);

// 回調函數
typedef void (*sort_func_t)(
    void *buckets, size_t num_of_buckets, register size_t size_of_bucket,
    compare_func_t compare_func TSRMLS_DC
);

極值函數:

1
2
3
int zend_hash_minmax(
    const HashTable *ht, compare_func_t compar, int flag, void **pData TSRMLS_DC
);

flag=0,極小值寫入pData;flag=1,極大值寫入pData。

自動重映射鍵盤

| Comments

每次鍵盤拔出再插入時,鍵盤映射都會失效,要重新執行映射,而且要對不同的鍵盤應用不同的映射方案。試過直接添加udev規則,即使指定X Display和Xauthority也不成功。所以用pyudev寫個腳本(最新版本):

udev.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python2
# encoding: utf-8

"""
File:        udev.py
Description: udev monitor script.
Author:      Donie Leigh
Email:       donie.leigh at gmail.com
"""

import glib, os, time
from pyudev import Context, Monitor

PID_FILE = "/tmp/udev_monitor.pid"

def remap_pokerii(device):
    """ Do keyboard remapping when PokerII is plugged in.
    """
    if device.get('ID_VENDOR_ID') == '0f39' \
            and device.action == 'add':
        time.sleep(1)
        os.system('setxkbmap')
        os.system('xmodmap ~/.Xmodmap')

def remap_filco(device):
    """ Do keyboard remapping when Filco is plugged in.
    """
    if device.get('ID_VENDOR_ID') == '04d9' \
            and device.action == 'add':
        time.sleep(1)
        os.system('setxkbmap')
        os.system('xmodmap ~/.Xmodmap')

def is_pid_running(pid):
    """ Check if the given pid is running.
    :pid: int
    :returns: bool
    """
    try:
        os.kill(pid, 0)
    except OSError:
        return False
    return True

def write_pid_or_die():
    """ Write the current pid into pid file or exists if there is already a instance running.
    :returns: void
    """
    if os.path.isfile(PID_FILE):
        pid = int(open(PID_FILE).read())
        if is_pid_running(pid):
            print("Process {0} is still running.".format(pid))
            raise SystemExit
        else:
            os.remove(PID_FILE)

    open(PID_FILE, 'w').write(str(os.getpid()))

def main():
    try:
        from pyudev.glib import MonitorObserver
        def device_event(observer, device):
            remap_pokerii(device)
            remap_filco(device)
    except:
        from pyudev.glib import GUDevMonitorObserver as MonitorObserver
        def device_event(observer, action, device):
            remap_pokerii(device)
            remap_filco(device)

    context = Context()
    monitor = Monitor.from_netlink(context)

    monitor.filter_by(subsystem='usb')
    observer = MonitorObserver(monitor)

    observer.connect('device-event', device_event)
    monitor.start()

    glib.MainLoop().run()

if __name__ == '__main__':
    write_pid_or_die()
    try:
        main()
    except KeyboardInterrupt:
        print("Game over.")

有個坑,監測到鍵盤插入事件後要等一秒再應用映射,否則不成功。

這裡只用了設備的Vendor ID,可以直接用lsusb看。看更多的設備屬性的命令如下:

1
2
3
4
5
6
7
8
# 監視設備變動
udevadm monitor --environment --udev

# 查看設備屬性
udevadm info -a -n [device name]

# 查看文件所屬設備的屬性
udevadm info -a -p [file name]