旗下导航:搜·么
当前位置:网站首页 > PHP教程 > 正文

PHP7.4中FFI的引见(代码示例)【php教程】

作者:搜搜PHP网发布时间:2019-11-26分类:PHP教程浏览:90


导读:本篇文章给人人带来的内容是关于PHP7.4中FFI的引见(代码示例),有肯定的参考价值,有须要的朋侪能够参考一下,愿望对你有所协助。FFI扩大已经由历程RFC,正式成为PH...
本篇文章给人人带来的内容是关于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的头文件获得:

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

不足

  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. 专栏
  2. Oraoto的一样平常
  3. 文章概况









oraoto 4.4k 宣布于 Oraoto的一样平常 关注专栏

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的头文件获得:

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

不足

  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和设想目标

    • 告发



| 6 珍藏 | 4

你能够感兴趣的



2 条批评

默许排序 时候排序






netstu · 16 小时前


我以为这是在瞎整,用zephir来编写C扩大已非常轻易了,能够防止许多题目,原本php就4不像的,如许搞只能把php搞的痴肥而且八不像的

我以为这是在瞎整,用zephir来编写C扩大已非常轻易了,能够防止许多题目,原本php就4不像的,如许搞只能把php搞的痴肥而且八不像的

+2 复兴 作废保留


0



已赞。

Zephir也好,PHP-X也好,都少不了一个编译历程,而FFI不必编译,改完剧本就可以革新实行,这就是一个疾速迭代和疾速试验的上风,就像这篇文章的一样玩玩种种C类库是非常轻易的。不过,由于机能缘由,我也不会在生产环境用FFI。

而且FFI只是个扩大,手艺上和其他PHP扩大没本质区别,只是有PHP官方保护罢了,对PHP中心基础没影响,谈不上让PHP更痴肥,不须要的大可不必。

已赞。 Zephir也好,PHP-X也好,都少不了一个编译历程,而FFI不必编译,改完剧本就可以革新实行,这就是一个疾速迭代和疾速试验的上风,就像这篇文章的一样玩玩种种C类库是非常轻易的。不过,由于机能缘由,我也不会在生产环境用FFI。 而且FFI只是个扩大,手艺上和其他PHP扩大没本质区别,只是有PHP官方保护罢了,对PHP中心基础没影响,谈不上让PHP更痴肥,不须要的大可不必。

oraoto 作者 · 15 小时前

增加复兴

载入中...

显现更多批评


宣布批评

以上就是PHP7.4中FFI的引见(代码示例)的细致内容,更多请关注ki4网别的相干文章!

标签:phpffic