科技知識動態:PHP7.4中FFI的介紹(代碼示例)

導讀 跟大家講解下有關PHP7 4中FFI的介紹(代碼示例),相信小伙伴們對這個話題應該也很關注吧,現在就為小伙伴們說說PHP7 4中FFI的介紹(代碼示

跟大家講解下有關PHP7.4中FFI的介紹(代碼示例),相信小伙伴們對這個話題應該也很關注吧,現在就為小伙伴們說說PHP7.4中FFI的介紹(代碼示例),小編也收集到了有關PHP7.4中FFI的介紹(代碼示例)的相關資料,希望大家看到了會喜歡。

本篇文章給大家帶來的內容是關于PHP7.4中FFI的介紹(代碼示例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

FFI擴展已經通過RFC,正式成為PHP 7.4核心擴展。

什么是FFI

FFI(Foreign Function Interface),即外部函數接口,是指在一種語言里調用另一種語言代碼的技術。PHP的FFI擴展就是一個讓你在PHP里調用C代碼的技術。

FFI的使用非常簡單,只用聲明和調用兩步就可以,對于有C語言經驗,但是不了解Zend引擎的程序員來說,這簡直是打開了新世界的大門,可以快速地使用C類庫進行原型試驗。

(此處有圖:溜了溜了,要懂C的……)

下面通過3個例子,看一下FFI是怎樣使用的。

Libbloom

libbloom是一個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的頭文件得到:

// cloneconst CLONE_NEWNS = 0x00020000; // mount namespaceconst CLONE_NEWCGROUP = 0x02000000; // cgroup namespaceconst CLONE_NEWUTS = 0x04000000; // utsname namespaceconst CLONE_NEWIPC = 0x08000000; // ipc namespaceconst CLONE_NEWUSER = 0x10000000; // user namespaceconst CLONE_NEWPID = 0x20000000; // pid namespaceconst CLONE_NEWNET = 0x40000000; // network namespace// mountconst 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.phpsh-5.0# id # 在容器內是rootuid=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 COMMANDroot 1 0.0 0.1 10524 4124 pts/1 S 10:19 0:00 /bin/shroot 3 0.0 0.0 15864 3076 pts/1 R+ 10:19 0:00 ps auxsh-5.0# ip a # 獨立的網絡命名空間1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00raylib

raylib是個特性豐富而且易用的游戲庫,經過簡單的封裝就可以在PHP里使用。下面這個例子實現了一個跟隨鼠標的圓:

clipboard.png

<?phpinclude __DIR__ . "/../../RayLib.php";// 初始化RayLib::init(); // 初始化FFI和“常量”RayLib::InitWindow(400, 300, "raylib example");// 狀態:球的位置$ballPosition = RayLib::Vector2(-100.0, 100.0);// 主循環while (!RayLib::WindowShouldClose()){ // 狀態更新 $ballPosition = RayLib::GetMousePosition(); // 獲取鼠標位置 // 渲染 RayLib::BeginDrawing(); RayLib::ClearBackground(RayLib::$RAYWHITE); // 清除背景顏色 RayLib::DrawCircleV($ballPosition, 40, RayLib::$RED); // 畫個圈圈 RayLib::DrawFPS(10, 10); // 顯示FPS RayLib::EndDrawing();}// 釋放RayLib::CloseWindow();不足性能C類庫性能可能很高,但是FFI調用的消耗也非常大,通過FFI訪問數據要比PHP訪問對象和數組慢兩倍,所以用FFI不一定能提高性能,RFC里給出的一個測試結果:

clipboard.png

就算用了JIT,還是比不上不用JIT的PHP。

功能目前(20190301)FFI擴展還沒實現的一些功能:

返回struct/union和數組嵌套的struct(我寫了個簡單的補丁)

使用這些功能的時候,會拋出異常,提示功能未實現,所以只用等等或者馬上貢獻代碼就好:)

參考PHP RFC: FFI - Foreign Function Interface:RFC文檔,有比較完整的API和設計目的專欄Oraoto的日常文章詳情

1 天前發布

PHP 7.4 前瞻:FFIphpffic

212 次閱讀 · 讀完需要 19 分鐘

6

FFI擴展已經通過RFC,正式成為PHP 7.4核心擴展。

什么是FFI

FFI(Foreign Function Interface),即外部函數接口,是指在一種語言里調用另一種語言代碼的技術。PHP的FFI擴展就是一個讓你在PHP里調用C代碼的技術。

FFI的使用非常簡單,只用聲明和調用兩步就可以,對于有C語言經驗,但是不了解Zend引擎的程序員來說,這簡直是打開了新世界的大門,可以快速地使用C類庫進行原型試驗。

(此處有圖:溜了溜了,要懂C的……)

下面通過3個例子,看一下FFI是怎樣使用的。

Libbloom

libbloom是一個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的頭文件得到:

// cloneconst CLONE_NEWNS = 0x00020000; // mount namespaceconst CLONE_NEWCGROUP = 0x02000000; // cgroup namespaceconst CLONE_NEWUTS = 0x04000000; // utsname namespaceconst CLONE_NEWIPC = 0x08000000; // ipc namespaceconst CLONE_NEWUSER = 0x10000000; // user namespaceconst CLONE_NEWPID = 0x20000000; // pid namespaceconst CLONE_NEWNET = 0x40000000; // network namespace// mountconst 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.phpsh-5.0# id # 在容器內是rootuid=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 COMMANDroot 1 0.0 0.1 10524 4124 pts/1 S 10:19 0:00 /bin/shroot 3 0.0 0.0 15864 3076 pts/1 R+ 10:19 0:00 ps auxsh-5.0# ip a # 獨立的網絡命名空間1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00raylib

raylib是個特性豐富而且易用的游戲庫,經過簡單的封裝就可以在PHP里使用。下面這個例子實現了一個跟隨鼠標的圓:

clipboard.png

<?phpinclude __DIR__ . "/../../RayLib.php";// 初始化RayLib::init(); // 初始化FFI和“常量”RayLib::InitWindow(400, 300, "raylib example");// 狀態:球的位置$ballPosition = RayLib::Vector2(-100.0, 100.0);// 主循環while (!RayLib::WindowShouldClose()){ // 狀態更新 $ballPosition = RayLib::GetMousePosition(); // 獲取鼠標位置 // 渲染 RayLib::BeginDrawing(); RayLib::ClearBackground(RayLib::$RAYWHITE); // 清除背景顏色 RayLib::DrawCircleV($ballPosition, 40, RayLib::$RED); // 畫個圈圈 RayLib::DrawFPS(10, 10); // 顯示FPS RayLib::EndDrawing();}// 釋放RayLib::CloseWindow();不足性能C類庫性能可能很高,但是FFI調用的消耗也非常大,通過FFI訪問數據要比PHP訪問對象和數組慢兩倍,所以用FFI不一定能提高性能,RFC里給出的一個測試結果:

clipboard.png

就算用了JIT,還是比不上不用JIT的PHP。

功能目前(20190301)FFI擴展還沒實現的一些功能:

返回struct/union和數組嵌套的struct(我寫了個簡單的補丁)

使用這些功能的時候,會拋出異常,提示功能未實現,所以只用等等或者馬上貢獻代碼就好:)

參考PHP RFC: FFI - Foreign Function Interface:RFC文檔,有比較完整的API和設計目的

舉報

贊 | 6 收藏 | 4

你可能感興趣的

2 條評論

netstu · 16 小時前

我覺得這是在瞎整,用zephir來編寫C擴展已經非常方便了,可以避免很多問題,本來php就4不像的,這樣搞只能把php搞的臃腫而且八不像的

贊 +2 回復 取消保存

0

已贊。

Zephir也好,PHP-X也好,都少不了一個編譯過程,而FFI不用編譯,改完腳本就能刷新執行,這就是一個快速迭代和快速實驗的優勢,就像這篇文章的一樣玩玩各種C類庫是非常方便的。不過,因為性能原因,我也不會在生產環境用FFI。

而且FFI只是個擴展,技術上和其他PHP擴展沒本質區別,只是有PHP官方維護而已,對PHP核心根本沒影響,談不上讓PHP更臃腫,不需要的大可不用。

— oraoto 作者 · 15 小時前

添加回復

載入中...

顯示更多評論

以上就是PHP7.4中FFI的介紹(代碼示例)的詳細內容,更多請關注php中文網其它相關文章!

來源:php中文網

免責聲明:本文由用戶上傳,如有侵權請聯系刪除!