测试脚本如下:
1 |
|
并且开启opcache
。
然后,我们编写如下代码:
1 | PHP_RSHUTDOWN_FUNCTION(yasd) { |
接着,使用php-cgi
来启动服务:
1 | php-cgi -b 0.0.0.0:8000 |
然后,请求两次我们的脚本。第一次是正常的,第二次就会出现zend_mm_heap corrupted
的问题。
并且,我发现,关闭opcache
之后,这个错误就会消失。当然,我们还是不要在RSHUTDOWN
阶段去调用zend_bailout
。
测试脚本如下:
1 |
|
并且开启opcache
。
然后,我们编写如下代码:
1 | PHP_RSHUTDOWN_FUNCTION(yasd) { |
接着,使用php-cgi
来启动服务:
1 | php-cgi -b 0.0.0.0:8000 |
然后,请求两次我们的脚本。第一次是正常的,第二次就会出现zend_mm_heap corrupted
的问题。
并且,我发现,关闭opcache
之后,这个错误就会消失。当然,我们还是不要在RSHUTDOWN
阶段去调用zend_bailout
。
本篇文章基于PHP7.4.10
我们的测试脚本如下:
1 |
|
其中printAllAttributeKeys
是我们要编写的一个扩展函数,用来打印对象所有的属性名字。
因为PHP
属性是存在一个哈希表里面的,所以我们可以进行如下操作:
1 | static PHP_FUNCTION(printAllAttributeKeys) { |
执行结果如下:
1 | php test.php |
可以发现,只打印出了a
。实际上,对于b
这个属性,它在zend_string
里的存储内容为:
1 | \0Foo\0b |
如果我们要打印出私有属性,我们可以作如下操作:
1 | const char *get_property_name(zend_string *property_name) { |
对属性key
这个zend_string
调用zend_unmangle_property_name_ex
即可获取到私有属性的名字。
执行结果如下:
1 | php test.php |
找了一圈,发现PHP
没有去实现这样的一个宏来进行检测。并且,发现所有C++ wrapper
扩展都没有去实现这个功能,只是在文档里面说了一下依赖了这个C++
库。这样不太好,容易让开发者在编译的途中,发现漏了一个依赖库。
所以,我就写了一个简单的宏来实现这个功能:
1 | AC_DEFUN([YASD_CHECK_CXX_LIB], [ |
那么怎么去用呢?我们只需要随便去找一个库的头文件就好了,例如:
1 | YASD_CHECK_CXX_LIB([boost], [<boost/algorithm/string/constants.hpp>]) |
第一个参数填写依赖库的名字,第二个参数填头文件。
这个还是有点复杂的,记录一下:
1 | ┌──────────────────────────┐ |
在编译器进行后端优化的时候,会使用支配树来构造SSA
。支配树的生成算法有好几种,这里我们介绍一下最暴力的方法。(Opcache
则是使用其他算法来实现,我们可以搜索论文A Simple, Fast Dominance Algorithm
找到)
在一个图里面,有两个点u
和w
,如果从图的源顶点出发,必须经过u
才能到达w
,那么我们称u
支配w
。
如下图:
1 | ┌────────────┐ |
那么有如下支配关系:
1 | 1支配2,1支配3,1支配4,1支配5 |
因为
3
和4
都可以到达5
,所以3
和4
不支配5
。
如果u
支配w
,而w
的其他支配者支配u
,则节点u
被认为是节点w
的直接支配者,表示为idom (w)
。
如下图:
1 | ┌────────────┐ |
那么有如下直接支配关系:
1 | 1直接支配2 |
我们发现,1
和2
都支配着3
、4
、5
。但是,因为1
支配了2
,所以,按照直接支配的定义,2
才是3
、4
、5
的直接支配。
1.除了图的源点外,其他点至少有一个点支配着它。
我们从上面的直接支配点可以看出,2
、3
、4
、5
都被支配着。
2.除了图的源点外,其他点只有一个点直接支配着它。(我们可以结合上面的例子来理解)
我们可以通过edges {(idom(w),w)}
来得到支配树。其中,有向图的源点就是支配树的根。
可以通过DFS
来遍历有向图:
1 | public function domDFS(Vertex $vertex) |
实际上,这个算法很好理解,非常的直观暴力,但是我们还是来解释下。
前提,对图进行深度优先遍历,得到一组序列。
从第一个序列开始,每次取出一个序列,我们记作s
。然后重新对图进行深度优先遍历,但是,如果遇到了当前这个点s
,就停止往s
这个点后面的点深度遍历了,开始回退,深度遍历其他的点。我们把每一个遍历到的点保存下来,比如放在一个visitedMap
里面。最后,我们和所有的点进行对比,不在visitedMap
里面的点,就是被当前s
这个点支配的。一直重复下去,可以得到每一个点的直接支配点。最终,得到支配树。
在演算的过程中我们发现,对于这个算法,如果有两个点(记作v1
、v2
)可以到达第三个点(记作v3
)。那么,当选取v1
或者v2
进行深度遍历的时候,visitedMap
会保存所有的顶点。也就意味着,v1
和v2
不支配任何点。
Swoole
在实现一些不好Hook
的函数的时候,采用了AIO
线程池来完成协程化的工作。
它的基本工作思路如下:
1 | ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
大概讲一讲这个流程:
1.当一个协程执行一个不好协程化的任务的时候,就会创建一个任务,投递到线程池的queue
里面,对应代码:
1 | AsyncEvent *dispatch(const AsyncEvent *request) { |
2.投递完任务之后,挂起当前协程:
1 | bool async(const std::function<void(void)> &fn, double timeout) { |
其他,async_lambda_handler
会被AIO
线程使用,async_lambda_callback
被主线程的调度器使用。
3.当AIO
线程抢到一个任务的时候,会调用async_lambda_handler
,而async_lambda_handler
就会去执行协程投递任务时设置的那个不好协程化的函数。
4.AIO
线程执行完任务之后,通过unix socket
通知主线程。此时,主线程就会执行async_lambda_callback
,这个函数会resume
这个任务对应的协程。然后,该协程继续往下运行。
这种方式很好用,但是,我们使用这种方式协程化的时候,需要注意一个问题,不要在任务里面去调用PHP
的函数,因为这样就会让AIO
线程操作ZendVM
。因为主线程和AIO
线程同时在修改同一个ZendVM
上的数据,会导致一些内存错误。
用一个图来总结一下:
1 | ┌───────────────────────────────┐ |
摘抄自虎书
抽象语法中的复杂部分并不总是能正好与机器可以执行的复杂指令相对应。因此,中间表示中的个体成分应该只描述特别简单的事情:如单个取、存、加法或指令跳转等操作。这样,抽象语法中的任何复杂部分都可以用一组恰当的抽象机器指令来表示,而这些抽象机器指令则能形成“真正的”机器指令。
这个问题来自Swoole
的一个issue
。
有如下代码:
1 |
|
此时,test.txt
文件里面的内容是:
1 | third line |
我们发现,这实际上没有追加,而是覆盖了之前写入的内容。也就意味着pwrite
的offset
和O_APPEND
没有一起起到作用。
我们换成write
来测试追加:
1 |
|
此时,test.txt
文件里面的内容是:
1 | first line |
追加成功了。
Swoole Table
内部结构还是比较复杂的,这里做一个记录:
1 | ┌───────────────────┐ |