摘要:擴(kuò)展已經(jīng)通過,正式成為的捆綁擴(kuò)展庫(kù)。第一步,從頭文件把主要的數(shù)據(jù)結(jié)構(gòu)和函數(shù)聲明復(fù)制出來(lái)目前不支持預(yù)處理器除了和,所以宏定義要自己展開。
FFI擴(kuò)展已經(jīng)通過RFC,正式成為PHP 7.4的捆綁擴(kuò)展庫(kù)(Bundled Extensions)。
什么是FFIFFI(Foreign Function Interface),即外部函數(shù)接口,是指在一種語(yǔ)言里調(diào)用另一種語(yǔ)言代碼的技術(shù)。PHP的FFI擴(kuò)展就是一個(gè)讓你在PHP里調(diào)用C代碼的技術(shù)。
FFI的使用非常簡(jiǎn)單,只用聲明和調(diào)用兩步就可以,對(duì)于有C語(yǔ)言經(jīng)驗(yàn),但是不了解Zend引擎的程序員來(lái)說,這簡(jiǎn)直是打開了新世界的大門,可以快速地使用C類庫(kù)進(jìn)行原型試驗(yàn)。
(此處有圖:溜了溜了,要懂C的……)
下面通過3個(gè)例子,看一下FFI是怎樣使用的。
Libbloomlibbloom是一個(gè)C實(shí)現(xiàn)的bloom filter,比較知名的用戶有Shadowsocks-libev,下面看一下怎樣通過FFI在PHP里調(diào)用libbloom。
第一步,從頭文件bloom.h把主要的數(shù)據(jù)結(jié)構(gòu)和函數(shù)聲明復(fù)制出來(lái):
$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目前不支持預(yù)處理器(除了FFI_LIB和FFI_SCOPE),所以宏定義要自己展開。
之后就可以通過$ffi創(chuàng)建已聲明的數(shù)據(jù)結(jié)構(gòu)和調(diào)用函數(shù):
// 創(chuàng)建一個(gè)bloom結(jié)構(gòu)體,然后用FFI::addr取地址 // libbloom的函數(shù)都是使用bloom結(jié)構(gòu)體的指針 $bloom = FFI::addr($ffi->new("struct bloom")); // 調(diào)用libbloom的初始化函數(shù) $ffi->bloom_init($bloom, 10000, 0.01); // 添加數(shù)據(jù) $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命名空間是容器技術(shù)的基石之一,通過FFI可以直接調(diào)用glibc的對(duì)應(yīng)系統(tǒng)調(diào)用封裝,從而通過PHP實(shí)現(xiàn)容器。下面是一個(gè)讓bash在一個(gè)新的命名空間里運(yùn)行的例子。
首先是一些常量,可以從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;
接著時(shí)我們要用到的函數(shù)聲明:
$cdef=" // fork進(jìn)程 int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); // 掛載文件系統(tǒng) int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data); // 設(shè)置gid int setgid(int gid); // 設(shè)置uid int setuid(int uid); // 設(shè)置hostname int sethostname(char *name, unsigned int len); "; $libc = FFI::cdef($cdef, "libc.so.6");
定義我們的子進(jìn)程:
// 生成一個(gè)容器ID $containerId = sha1(random_bytes(8)); // 定義子進(jìn)程 $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"); };
在子進(jìn)程里,我們重新掛載了/proc,設(shè)置了uid、gid和hostname,然后啟動(dòng)/bin/sh。
父進(jìn)程通過clone函數(shù),創(chuàng)建子進(jìn)程:
// 分配子進(jìn)程的棧 $child_stack = FFI::new("char[1024 * 4]"); $child_stack = FFI::cast("void *", FFI::addr($child_stack)) - 1024 * 4; // fork子進(jìn)程 $pid = $libc->clone($childfn, $child_stack, CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWCGROUP | SIGCHLD, null); // 設(shè)置UID、GID映射,把容器內(nèi)的root映射到當(dāng)前用戶 $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"); // 等待子進(jìn)程 pcntl_wait($pid);
glibc的clone函數(shù)是clone系統(tǒng)調(diào)用的封裝,它需要一個(gè)函數(shù)指針作為子進(jìn)程/線程的執(zhí)行體,我們可以直接把PHP的閉包和匿名函數(shù)當(dāng)作函數(shù)指針使用。
運(yùn)行效果:
$ php container.php sh-5.0# id # 在容器內(nèi)是root uid=0(root) gid=0(root) groups=0(root),65534(nobody) sh-5.0# ps aux # 獨(dú)立的PID進(jìn)程空間 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 # 獨(dú)立的網(wǎng)絡(luò)命名空間 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是個(gè)特性豐富而且易用的游戲庫(kù),經(jīng)過簡(jiǎn)單的封裝就可以在PHP里使用。下面這個(gè)例子實(shí)現(xiàn)了一個(gè)跟隨鼠標(biāo)的圓:
不足性能
C類庫(kù)性能可能很高,但是FFI調(diào)用的消耗也非常大,通過FFI訪問數(shù)據(jù)要比PHP訪問對(duì)象和數(shù)組慢兩倍,所以用FFI不一定能提高性能,RFC里給出的一個(gè)測(cè)試結(jié)果:就算用了JIT,還是比不上不用JIT的PHP。
功能
目前(20190301)FFI擴(kuò)展還沒實(shí)現(xiàn)的一些功能:返回struct/union和數(shù)組
嵌套的struct(我寫了個(gè)簡(jiǎn)單的補(bǔ)丁)
使用這些功能的時(shí)候,會(huì)拋出異常,提示功能未實(shí)現(xiàn),所以只用等等或者馬上貢獻(xiàn)代碼就好:)
參考PHP RFC: FFI - Foreign Function Interface:RFC文檔,有比較完整的API和設(shè)計(jì)目的
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/30912.html
摘要:性能提升當(dāng)然需要付出代價(jià)如果預(yù)加載文件的來(lái)源發(fā)生變化,則必須重新啟動(dòng)服務(wù)器。應(yīng)該指出,這是一個(gè)復(fù)雜的主題。默認(rèn)情況下不啟用由于不再維護(hù),核心團(tuán)隊(duì)決定使用刪除其默認(rèn)安裝。將在錯(cuò)誤情況下拋出異常。請(qǐng)注意,強(qiáng)制轉(zhuǎn)換不受影響。 新特性 預(yù)加載預(yù)加載是PHP核心的一個(gè)驚人的補(bǔ)充,可以帶來(lái)一些重大的性能改進(jìn)。簡(jiǎn)而言之:如果您今天使用的是框架,則必須在每次請(qǐng)求時(shí)加載和重新編譯其文件。 預(yù)加載允許服務(wù)...
摘要:原文來(lái)源預(yù)計(jì)在年年末就會(huì)正式發(fā)布了,本文先來(lái)看看一下的新特性。預(yù)加載預(yù)加載的實(shí)現(xiàn)理論上是可以為帶來(lái)很大的性能提升的。最后,你需要注意的向后不兼容特性,可以通過此鏈接來(lái)查看詳細(xì)內(nèi)容 原文來(lái)源:https://geixue.com/blogs/chan... PHP 7.4 預(yù)計(jì)在 2019 年年末就會(huì)正式發(fā)布了,本文先來(lái)看看一下 PHP 7.4 的新特性。 1.預(yù)加載預(yù)加載的實(shí)現(xiàn)理論上是...
摘要:預(yù)加載在框架啟動(dòng)時(shí)在內(nèi)存中加載文件,而且在后續(xù)請(qǐng)求中永久有效。缺點(diǎn)性能的提升會(huì)在其他方面花費(fèi)很大的代價(jià),每次預(yù)加載的文件發(fā)生改變時(shí),框架需要重新啟動(dòng)。 PHP 7.4 計(jì)劃在2019年11月21日發(fā)布,它主要新增了以下幾個(gè)特性: 短閉包函數(shù)(short closure) 預(yù)加載提交性能 屬性類型限定 Improved type variance(不會(huì)翻譯) 三元運(yùn)算簡(jiǎn)寫 數(shù)組展開運(yùn)...
摘要:指針和引用假設(shè)動(dòng)態(tài)庫(kù)中有函數(shù)如下第二個(gè)參數(shù)為結(jié)構(gòu)體指針,第三個(gè)參數(shù)是一個(gè)引用。我這里選擇的是然后找到,下載替換掉重編譯和輸入版本號(hào),這里實(shí)用的是為或者參考資料通過在中調(diào)用動(dòng)態(tài)鏈接庫(kù)文件厚顏無(wú)恥加上自己的博客 0x01. 使用的 npm 包 首先要安裝 node-gyp, 用來(lái)重新編譯依賴包。 npm instal -g node-gyp 然后主要用到下面三個(gè)包: node-ffi -...
閱讀 1646·2023-04-26 02:11
閱讀 2990·2023-04-25 16:18
閱讀 3720·2021-09-06 15:00
閱讀 2636·2019-08-30 15:55
閱讀 1942·2019-08-30 13:20
閱讀 2058·2019-08-26 18:36
閱讀 3131·2019-08-26 11:40
閱讀 2549·2019-08-26 10:11