摘要:擴展已經通過,正式成為的捆綁擴展庫。第一步,從頭文件把主要的數據結構和函數聲明復制出來目前不支持預處理器除了和,所以宏定義要自己展開。
FFI擴展已經通過RFC,正式成為PHP 7.4的捆綁擴展庫(Bundled Extensions)。
什么是FFIFFI(Foreign Function Interface),即外部函數接口,是指在一種語言里調用另一種語言代碼的技術。PHP的FFI擴展就是一個讓你在PHP里調用C代碼的技術。
FFI的使用非常簡單,只用聲明和調用兩步就可以,對于有C語言經驗,但是不了解Zend引擎的程序員來說,這簡直是打開了新世界的大門,可以快速地使用C類庫進行原型試驗。
(此處有圖:溜了溜了,要懂C的……)
下面通過3個例子,看一下FFI是怎樣使用的。
Libbloomlibbloom是一個C實現的bloom filter,比較知名的用戶有Shadowsocks-libev,下面看一下怎樣通過FFI在PHP里調用libbloom。
第一步,從頭文件bloom.h把主要的數據結構和函數聲明復制出來:
$ffi = FFI::cdef(" struct bloom { int entries; double error; int bits; int bytes; int hashes; double bpe; unsigned char * bf; int ready; }; int bloom_init(struct bloom * bloom, int entries, double error); int bloom_check(struct bloom * bloom, const void * buffer, int len); int bloom_add(struct bloom * bloom, const void * buffer, int len); void bloom_free(struct bloom * bloom); ", "libbloom.so.1.5");
FFI目前不支持預處理器(除了FFI_LIB和FFI_SCOPE),所以宏定義要自己展開。
之后就可以通過$ffi創建已聲明的數據結構和調用函數:
// 創建一個bloom結構體,然后用FFI::addr取地址 // libbloom的函數都是使用bloom結構體的指針 $bloom = FFI::addr($ffi->new("struct bloom")); // 調用libbloom的初始化函數 $ffi->bloom_init($bloom, 10000, 0.01); // 添加數據 $ffi->bloom_add($bloom, "PHP", 3); $ffi->bloom_add($bloom, "C", 1); // PHP可能存在 var_dump($ffi->bloom_check($bloom, "PHP", 3)); // 1 // Laravel不存在 var_dump($ffi->bloom_check($bloom, "Laravel", 7)); // 0 // 釋放 $ffi->bloom_free($bloom); $bloom = null;Linux Namespace
Linux命名空間是容器技術的基石之一,通過FFI可以直接調用glibc的對應系統調用封裝,從而通過PHP實現容器。下面是一個讓bash在一個新的命名空間里運行的例子。
首先是一些常量,可以從Linux的頭文件得到:
// clone const CLONE_NEWNS = 0x00020000; // mount namespace const CLONE_NEWCGROUP = 0x02000000; // cgroup namespace const CLONE_NEWUTS = 0x04000000; // utsname namespace const CLONE_NEWIPC = 0x08000000; // ipc namespace const CLONE_NEWUSER = 0x10000000; // user namespace const CLONE_NEWPID = 0x20000000; // pid namespace const CLONE_NEWNET = 0x40000000; // network namespace // mount const MS_NOSUID = 2; const MS_NODEV = 4; const MS_NOEXEC = 8; const MS_PRIVATE = 1 << 18; const MS_REC = 16384;
接著時我們要用到的函數聲明:
$cdef=" // fork進程 int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); // 掛載文件系統 int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data); // 設置gid int setgid(int gid); // 設置uid int setuid(int uid); // 設置hostname int sethostname(char *name, unsigned int len); "; $libc = FFI::cdef($cdef, "libc.so.6");
定義我們的子進程:
// 生成一個容器ID $containerId = sha1(random_bytes(8)); // 定義子進程 $childfn = function() use ($libc, $containerId) { usleep(1000); // wait for uid/gid map $libc->mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, null); $libc->setuid(0); $libc->setgid(0); $libc->sethostname($containerId, strlen($containerId)); pcntl_exec("/bin/sh"); };
在子進程里,我們重新掛載了/proc,設置了uid、gid和hostname,然后啟動/bin/sh。
父進程通過clone函數,創建子進程:
// 分配子進程的棧 $child_stack = FFI::new("char[1024 * 4]"); $child_stack = FFI::cast("void *", FFI::addr($child_stack)) - 1024 * 4; // fork子進程 $pid = $libc->clone($childfn, $child_stack, CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWCGROUP | SIGCHLD, null); // 設置UID、GID映射,把容器內的root映射到當前用戶 $uid = getmyuid(); $gid = getmyuid(); file_put_contents("/proc/$pid/uid_map", "0 $uid 1"); file_put_contents("/proc/$pid/setgroups", "deny"); file_put_contents("/proc/$pid/gid_map", "0 $gid 1"); // 等待子進程 pcntl_wait($pid);
glibc的clone函數是clone系統調用的封裝,它需要一個函數指針作為子進程/線程的執行體,我們可以直接把PHP的閉包和匿名函數當作函數指針使用。
運行效果:
$ php container.php sh-5.0# id # 在容器內是root uid=0(root) gid=0(root) groups=0(root),65534(nobody) sh-5.0# ps aux # 獨立的PID進程空間 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 10524 4124 pts/1 S 10:19 0:00 /bin/sh root 3 0.0 0.0 15864 3076 pts/1 R+ 10:19 0:00 ps aux sh-5.0# ip a # 獨立的網絡命名空間 1: lo:raylibmtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
raylib是個特性豐富而且易用的游戲庫,經過簡單的封裝就可以在PHP里使用。下面這個例子實現了一個跟隨鼠標的圓:
不足性能
C類庫性能可能很高,但是FFI調用的消耗也非常大,通過FFI訪問數據要比PHP訪問對象和數組慢兩倍,所以用FFI不一定能提高性能,RFC里給出的一個測試結果:就算用了JIT,還是比不上不用JIT的PHP。
功能
目前(20190301)FFI擴展還沒實現的一些功能:返回struct/union和數組
嵌套的struct(我寫了個簡單的補丁)
使用這些功能的時候,會拋出異常,提示功能未實現,所以只用等等或者馬上貢獻代碼就好:)
參考PHP RFC: FFI - Foreign Function Interface:RFC文檔,有比較完整的API和設計目的
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30912.html
摘要:性能提升當然需要付出代價如果預加載文件的來源發生變化,則必須重新啟動服務器。應該指出,這是一個復雜的主題。默認情況下不啟用由于不再維護,核心團隊決定使用刪除其默認安裝。將在錯誤情況下拋出異常。請注意,強制轉換不受影響。 新特性 預加載預加載是PHP核心的一個驚人的補充,可以帶來一些重大的性能改進。簡而言之:如果您今天使用的是框架,則必須在每次請求時加載和重新編譯其文件。 預加載允許服務...
摘要:原文來源預計在年年末就會正式發布了,本文先來看看一下的新特性。預加載預加載的實現理論上是可以為帶來很大的性能提升的。最后,你需要注意的向后不兼容特性,可以通過此鏈接來查看詳細內容 原文來源:https://geixue.com/blogs/chan... PHP 7.4 預計在 2019 年年末就會正式發布了,本文先來看看一下 PHP 7.4 的新特性。 1.預加載預加載的實現理論上是...
摘要:預加載在框架啟動時在內存中加載文件,而且在后續請求中永久有效。缺點性能的提升會在其他方面花費很大的代價,每次預加載的文件發生改變時,框架需要重新啟動。 PHP 7.4 計劃在2019年11月21日發布,它主要新增了以下幾個特性: 短閉包函數(short closure) 預加載提交性能 屬性類型限定 Improved type variance(不會翻譯) 三元運算簡寫 數組展開運...
摘要:指針和引用假設動態庫中有函數如下第二個參數為結構體指針,第三個參數是一個引用。我這里選擇的是然后找到,下載替換掉重編譯和輸入版本號,這里實用的是為或者參考資料通過在中調用動態鏈接庫文件厚顏無恥加上自己的博客 0x01. 使用的 npm 包 首先要安裝 node-gyp, 用來重新編譯依賴包。 npm instal -g node-gyp 然后主要用到下面三個包: node-ffi -...
閱讀 1634·2023-04-26 02:11
閱讀 2979·2023-04-25 16:18
閱讀 3711·2021-09-06 15:00
閱讀 2630·2019-08-30 15:55
閱讀 1934·2019-08-30 13:20
閱讀 2051·2019-08-26 18:36
閱讀 3121·2019-08-26 11:40
閱讀 2538·2019-08-26 10:11