内容简介:FFI扩展已经通过RFC,正式成为PHP 7.4核心扩展。FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。FFI的使用非常简单,只用声明和调用两步就可以,对于有C语言经验,但是不了解Zend引擎的程序员来说,这简直是打开了新世界的大门,可以快速地使用C类库进行原型试验。
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 的头文件得到:
// 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: <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:00
raylib
raylib 是个特性丰富而且易用的游戏库,经过简单的封装就可以在PHP里使用。下面这个例子实现了一个跟随鼠标的圆:
<?php include __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里给出的一个测试结果:
就算用了JIT,还是比不上不用JIT的PHP。
-
功能
目前(20190301)FFI扩展还没实现的一些功能:
- 返回struct/union和数组
- 嵌套的struct(我写了个简单的补丁)
使用这些功能的时候,会抛出异常,提示功能未实现,所以只用等等或者马上贡献代码就好:)
参考
- PHP RFC: FFI - Foreign Function Interface :RFC文档,有比较完整的API和设计目的
以上所述就是小编给大家介绍的《PHP 7.4 前瞻:FFI》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Laravel 5.8 前瞻
- 2019年软件安全趋势前瞻
- Go2 Error Inspection前瞻
- 年中干货:Gartner 2019十大安全项目前瞻
- TiDB 4.0 新特性前瞻:白话“悲观锁”
- zookeeper学习04 zookeeper收尾+dubbo前瞻
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。