PHP7.4中FFI的介绍(代码示例)

PHP7.4中FFI的介绍(代码示例)

内容导读

收集整理的这篇技术教程文章主要介绍了PHP7.4中FFI的介绍(代码示例),小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含12336字,纯文字阅读大概需要18分钟

内容图文

本篇文章给大家带来的内容是关于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_LIBFFI_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:00

raylib

raylib是个特性丰富而且易用的游戏库,经过简单的封装就可以在PHP里使用。下面这个例子实现了一个跟随鼠标的圆:

<?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();

不足

  1. 性能
    C类库性能可能很高,但是FFI调用的消耗也非常大,通过FFI访问数据要比PHP访问对象和数组慢两倍,所以用FFI不一定能提高性能,RFC里给出的一个测试结果:

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

  2. 功能
    目前(20190301)FFI扩展还没实现的一些功能:

    1. 返回struct/union和数组
    2. 嵌套的struct(我写了个简单的补丁)

使用这些功能的时候,会抛出异常,提示功能未实现,所以只用等等或者马上贡献代码就好:)

参考

  • PHP RFC: FFI - Foreign Function Interface:RFC文档,有比较完整的API和设计目的







1 天前发布

PHP 7.4 前瞻:FFI
  • php
  • ffi
  • c

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_LIBFFI_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:00

raylib

raylib是个特性丰富而且易用的游戏库,经过简单的封装就可以在PHP里使用。下面这个例子实现了一个跟随鼠标的圆:

<?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();

不足

  1. 性能
    C类库性能可能很高,但是FFI调用的消耗也非常大,通过FFI访问数据要比PHP访问对象和数组慢两倍,所以用FFI不一定能提高性能,RFC里给出的一个测试结果:

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

  2. 功能
    目前(20190301)FFI扩展还没实现的一些功能:

    1. 返回struct/union和数组
    2. 嵌套的struct(我写了个简单的补丁)

使用这些功能的时候,会抛出异常,提示功能未实现,所以只用等等或者马上贡献代码就好:)

参考

  • PHP RFC: FFI - Foreign Function Interface:RFC文档,有比较完整的API和设计目的


你可能感兴趣的



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的介绍(代码示例)的详细内容,更多请关注Gxl网其它相关文章!

内容总结

以上是为您收集整理的PHP7.4中FFI的介绍(代码示例)全部内容,希望文章能够帮你解决PHP7.4中FFI的介绍(代码示例)所遇到的程序开发问题。 如果觉得技术教程内容还不错,欢迎将网站推荐给程序员好友。

内容备注

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。


本文关键词:

联系我们

在线咨询:点击这里给我发消息

邮件:w420220301@qq.com