想法

我以前对于 C 语言的印象是有很强的确定性,而 PHP 在执行的时候会被翻译为 C 语言执行,所以一直很好奇 PHP 怎么调用底层函数。

换句话说就是已知函数名字的情况下如何调用 C 语言中对应名字的函数?

解决这个问题前,首先根据过往的经验做出假设,然后再去验证。

之前在写《用 C 语言实现面向对象》的时候,就意识到使用 void 指针实现很多功能,包括指向任意的函数。接着在写《PHP 数组底层实现》的时候,了解了 HashTable 的实现,即在 C 语言层面通过字符串 key 找到任意类型值。

现在把两者结合起来,是否就能解决以上问题了?比如说把函数名作为 HashTable 的 key,函数指针作为 HashTable 的 value,这样就可以通过函数名获取函数指针来调用函数了。

接下来通过查看 PHP 的源码来看这个假设与真实情况有多少差距。

总体分为三个步骤:

  1. 从 PHP 层进入 C 语言层
  2. 找到字符串函数名与函数的关系
  3. 函数的调用

注:这篇博客的源码对应的版本是 PHP 7.4.4 。

https://github.com/php/php-src/tree/php-7.4.4

从 PHP 层进入 C 语言层

首先要找到 C 语言层调用函数的地方。怎么找?

经常使用 PHP 的同学看到前面的问题描述很容易联想到 PHP 中的一个传入函数名及其参数就可以调用函数的函数 call_user_func() 。可以从这里入手。

怎么找到 call_user_func() 在 PHP 源码中的位置?这就要根据 PHP 源码的规律来找了。

当然也可以直接全代码搜索,只是比较慢。

PHP 源码里面在定义一个 PHP 函数的时候会用 PHP_FUNCTION(函数名) ,所以只要找到 PHP_FUNCTION(call_user_func) 就可以了。

另外 call_user_func() 不像 array_column() 这种函数有特定前缀 array_ ,所以属于比较基础的函数,而 PHP 的基础函数会放在两个地方:

  • 内置函数,放在 Zend/zend_buildin_functions.c
  • 标准库函数,放在 ext/standard/

    举个例子: ext/standard/array.c 里有 array_column() 之类的函数。

在这两个地方搜索就能找到 PHP_FUNCTION(call_user_func) ,如下:

ext/standard/basic_functions.c

PHP_FUNCTION(call_user_func)
{
// ...
if (zend_call_function(&fci, &fci_cache) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
// ...
}
}

现在我们已经从 PHP 层面进入到 C 语言层面,接下去就是在 C 语言代码里面探索了。

找到字符串函数名与函数的关系

从上文展示位于 ext/standard/basic_functions.ccall_user_func() 函数定义可以找到关键点 zend_call_function() ,现在要找到这个函数。

这种以 zend_ 开头的函数都在 Zend/ 文件夹底下,所以我们要换个目录了。

Zend/ 文件夹里面随便搜索 zend_call_function ,从搜索结果里面随便挑一个跳转,然后通过 IDE 的功能(ctrl + 鼠标左键)跳转到它定义的地方就可以了。

如果 IDE 能直接跳转就不用在 Zend/ 文件夹搜索了,这里是因为 VS Code 没法直接跳转。

注:以下代码中的 // ... 都表示我省略了一部分代码,但我会尽量保持代码结构。

第一遍看代码的时候不需要掌握所有细节,只需要了解整体概念或者前后关系,否则会陷入细节无法自拔。

Zend/zend_execute_API.c

int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /* {{{ */
{
// ...
if (!fci_cache || !fci_cache->function_handler) {
// ...
if (!zend_is_callable_ex(&fci->function_name, fci->object, IS_CALLABLE_CHECK_SILENT, NULL, fci_cache, &error)) {
// ...
}
// ...
} func = fci_cache->function_handler;
// ...
call = zend_vm_stack_push_call_frame(call_info,
func, fci->param_count, object_or_called_scope);
// ...
if (func->type == ZEND_USER_FUNCTION) {
// ...
} else if (func->type == ZEND_INTERNAL_FUNCTION) {
// ...
func->internal_function.handler(call, fci->retval);
// ...
} else {
// ...
}
// ...
return SUCCESS;
}
/* }}} */

这里的关键点在于和函数名以及函数调用相关的词。关键词有:

  • function name
  • call
  • return value

上面的代码片段中,我把几个有可能的点抽出来了。从这几个点出发,往前追溯参数来源或者查看后面使用它的地方就行了。

如果被这个函数里面大量的 EG(...) 吸引而想知道其内部结构的话,就离结果非常近了。如果没有被其吸引,那也没关系,继续看。

优先深入看哪个呢?根据以前看数组源码的经验, “查找” 这个行为更容易获得信息,于是先看 zend_is_callable_check_func()

Zend/zend_API.c

static zend_always_inline int zend_is_callable_check_func(int check_flags, zval *callable, zend_fcall_info_cache *fcc, int strict_class, char **error) /* {{{ */
{
// ...
if (!ce_org) {
// ...
/* Check if function with given name exists.
* This may be a compound name that includes namespace name */
if (UNEXPECTED(Z_STRVAL_P(callable)[0] == '\\')) {
// ...
func = zend_fetch_function(lmname);
// ...
}
// ...
}
// ...
}

zend_fetch_function() 与我们想要的答案有很强的相关性,看它怎么实现的。

Zend/zend_execute.c

ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name)
{
zval *zv = zend_hash_find(EG(function_table), name);
// ...
}

来了来了!在这里就可以看到函数的确存在于 HashTable 里面。而这个 HashTable 通过 EG 获取。

Zend/zend_globals_macros.h

# define EG(v) (executor_globals.v)

再跳转一次。

Zend/zend_compile.c

ZEND_API zend_executor_globals executor_globals;

zend_executor_globals 是一个结构体。

PHP 的源码中,结构体的真实定义会以下划线开头。

于是找 _zend_executor_globals

Zend/zend_globals.h

struct _zend_executor_globals {
// ...
HashTable *function_table; /* function symbol table */
HashTable *class_table; /* class table */
HashTable *zend_constants; /* constants table */
// ...
}

到这里就找到存储函数的地方了。验证了函数名作为 key,函数指针作为 value 的可行性。

不过 PHP 并没有把函数指针直接作为 value,而是包装到了 zval 里面,以实现更多功能。从下面这一句就可以看出。

zval *zv = zend_hash_find(EG(function_table), name);

看看 zval 里面有什么。

Zend/zend_types.h

typedef struct _zval_struct     zval;

struct _zval_struct {
zend_value value; /* value */
// ...
};

继续:

Zend/zend_types.h

typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;

注:这个结构体很重要,我保留了全貌。

看到 zend_function 这个结构体,搜索 _zend_function

union _zend_function {
// ...
zend_internal_function internal_function;
};

在 zend_value 联合体中可以看到 zend_internal_function 这个内部函数专用结构体,调用内部函数时用到它。搜索 _zend_internal_function

Zend/zend_compile.h

/* zend_internal_function_handler */
typedef void (ZEND_FASTCALL *zif_handler)(INTERNAL_FUNCTION_PARAMETERS); typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string* function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_internal_arg_info *arg_info;
/* END of common elements */ zif_handler handler;
struct _zend_module_entry *module;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;

结构体 _zend_internal_function 里面的 handler 成员是 zif_handler 类型。 从前面的定义可以知道 zif_handler 是一个函数指针类型,这就是用来存函数指针的地方。

函数的调用

现在知道函数指针是存放在 handler 里面了,接着就是找到使用它的地方。

此时再回过头看 zend_call_function 这个函数。

Zend/zend_execute_API.c

int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /* {{{ */
{
// ...
if (func->type == ZEND_USER_FUNCTION) {
// ...
} else if (func->type == ZEND_INTERNAL_FUNCTION) {
// ...
func->internal_function.handler(call, fci->retval);
// ...
}
// ...
}
/* }}} */

可以看到调用函数的地方:

func->internal_function.handler(call, fci->retval);

handler 的参数固定是两个。这里要结合之前的 PHP_FUNCTION(call_user_func) 来看。

为了将 PHP_FUNCTION(call_user_func) 展开,以下连续列出三个定义:

main/php.h

#define PHP_FUNCTION            ZEND_FUNCTION

Zend/zend_API.h

#define ZEND_FN(name) zif_##name
#define ZEND_MN(name) zim_##name
#define ZEND_NAMED_FUNCTION(name) void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))

Zend/zend.h

#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value

根据这三个地方的代码展开 PHP_FUNCTION(call_user_func) 可以得到:

void ZEND_FASTCALL call_user_func(zend_execute_data *execute_data, zval *return_value)

再看一次 func->internal_function.handler(call, fci->retval); 。联系起来了!

函数调用真正的入口

上文以 PHP_FUNCTION(call_user_func) 作为入口只是其中一种思路。实际上 PHP 在调用函数的时候不是通过 call_user_func ,不然 call_user_func 本身又是如何被调用的呢?

PHP 执行的时候,会在 PHP 虚拟机里面去调用函数。PHP 虚拟机首先会读取 PHP 文件,然后解析为 OPCode (操作码)执行。这里就要借助调试器的力量了。

这里跳过 OPCode 的生成,因为与本次要探索的内容关系不是很大。

开启调试。然后不断往下走,可以找到一个比较接近答案的地方。

Zend/zend_vm_execute.h

ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
{
zend_execute_data *execute_data;
// ...
i_init_code_execute_data(execute_data, op_array, return_value);
zend_execute_ex(execute_data);
zend_vm_stack_free_call_frame(execute_data);
}

先看 zend_execute_ex

Zend/zend_vm_execute.h

// ...
# define OPLINE EX(opline)
// ...
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU execute_data
// ... ZEND_API void execute_ex(zend_execute_data *ex)
{
DCL_OPLINE
// ...
zend_execute_data *execute_data = ex;
// ...
LOAD_OPLINE();
ZEND_VM_LOOP_INTERRUPT_CHECK();
// ...
while (1) {
// ...
int ret;
// ...
if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
// ...
if (EXPECTED(ret > 0)) {
execute_data = EG(current_execute_data);
ZEND_VM_LOOP_INTERRUPT_CHECK();
} else {
// ...
return;
}
// ...
}
}
zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
}

又看到了 handler,这里难道就是真正执行函数的地方?

先找到 OPLINE 的真身,根据:

Zend/zend_compile.h

#define EX(element)             ((execute_data)->element)

对 OPLINE 展开后,得到 execute_data->opline

再根据 execute_ex() 前面的定义对整行展开得到:

if (UNEXPECTED((ret = ((opcode_handler_t)(execute_data->opline)->handler)(execute_data)) != 0))

现在出现四个新问题:

  • opline 的 handler 存在哪个结构体?
  • opline 的 handler 指向哪些函数?
  • opline 的 handler 在哪里被赋值?
  • 调用 opline 的 handler 就真的开始执行函数了吗?

opline 的 handler 存在哪个结构体?

要解决这个问题,得先找到 opline 是哪来的。

回到 Zend/zend_vm_execute.hzend_execute()

Zend/zend_vm_execute.h

ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
{
zend_execute_data *execute_data;
// ...
i_init_code_execute_data(execute_data, op_array, return_value);
zend_execute_ex(execute_data);
zend_vm_stack_free_call_frame(execute_data);
}

zend_execute_ex() 前面有个 i_init_code_execute_data()

Zend/zend_execute.c

static zend_always_inline void i_init_code_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value) /* {{{ */
{
// ...
EX(opline) = op_array->opcodes;
// ...
}

opline 来自于 zend_op_array 的 opcodes ,搜索 _zend_op_array

Zend/zend_compile.h

struct _zend_op_array {
// ...
zend_op *opcodes;
// ...
};

opcodes 是 zend_op 这种结构体,搜索 _zend_op

Zend/zend_compile.h

struct _zend_op {
const void *handler;
znode_op op1;
znode_op op2;
znode_op result;
uint32_t extended_value;
uint32_t lineno;
zend_uchar opcode;
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
};

到这里就找到了 handler 存储的位置。

注:在 Zend/zend_vm_opcodes.h 可以找到 OPCode 对应的整数,在 Zend/zend_vm_opcodes.c 可以找到这些整数和字符串的对应。

opline 的 handler 指向哪些函数?

由于 handler 是函数指针,可以指向任意函数,所以无法直接定位。于是通过调试执行下面这一句来找一些线索:

Zend/zend_vm_execute.h

ZEND_API void execute_ex(zend_execute_data *ex)
{
// ...
while (1) {
// ...
if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
// ...
}
}
// ...
}

在这一句的位置使用 “jump into”,会跳转到一个函数。这个函数就是 handler 指向的函数了。

由于每次跳到的函数都可能不一样,所以选其中一个来查。

Zend/zend_vm_execute.h

static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
// ...
}

搜索函数名 ZEND_INIT_FCALL_SPEC_CONST_HANDLER

Zend/zend_vm_execute.h

void zend_vm_init(void)
{
static const void * const labels[] = {
// ...
ZEND_INIT_FCALL_SPEC_CONST_HANDLER,
// ...
};
static const uint32_t specs[] = {
// ...
};
// ...
zend_opcode_handlers = labels;
zend_handlers_count = sizeof(labels) / sizeof(void*);
zend_spec_handlers = specs;
// ...
}

handler 可以指向 labels 里面包含的所有函数。

opline 的 handler 在哪里被赋值?

上一节列出的 zend_vm_init() 把所有函数都放到了 labels 数组里面,并赋值给了 zend_opcode_handlers ,找找用到它的地方。

Zend/zend_vm_execute.h

static const void* ZEND_FASTCALL zend_vm_get_opcode_handler_ex(uint32_t spec, const zend_op* op)
{
// ...
return zend_opcode_handlers[(spec & SPEC_START_MASK) + offset];
}

如果搜索调用 zend_vm_get_opcode_handler_ex 的代码,那么就很容易找到给 handler 赋值的地方了。

Zend/zend_vm_execute.h

ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler(zend_op* op)
{
// ...
op->handler = zend_vm_get_opcode_handler_ex(zend_spec_handlers[opcode], op);
}

调用 opline 的 handler 就真的开始执行函数了吗?

把上面举的例子 handler 指向的函数 ZEND_INIT_FCALL_SPEC_CONST_HANDLER 再拿出来。

为了更加明显,此处不省略代码。

Zend/zend_vm_execute.h

static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *fname;
zval *func;
zend_function *fbc;
zend_execute_data *call; fbc = CACHED_PTR(opline->result.num);
if (UNEXPECTED(fbc == NULL)) {
fname = (zval*)RT_CONSTANT(opline, opline->op2);
func = zend_hash_find_ex(EG(function_table), Z_STR_P(fname), 1);
if (UNEXPECTED(func == NULL)) {
ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
fbc = Z_FUNC_P(func);
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
init_func_run_time_cache(&fbc->op_array);
}
CACHE_PTR(opline->result.num, fbc);
} call = _zend_vm_stack_push_call_frame_ex(
opline->op1.num, ZEND_CALL_NESTED_FUNCTION,
fbc, opline->extended_value, NULL);
call->prev_execute_data = EX(call);
EX(call) = call; ZEND_VM_NEXT_OPCODE();
}

从中看不到执行的地方。找到的 func 也只是被放入 fcb,然后 push 到虚拟机调用栈里面。

注:这里另一个值得注意的地方是 ZEND_VM_NEXT_OPCODE(); 。因为最开始的 execute_ex 函数(下一节列出了代码)里面只是一个死循环,且没有修改 OPLINE 的指向,而是在这些 handler 函数里面修改。

那真正调用函数的地方在哪呢?

真正调用函数的地方

回到最开始的 execute_ex()

Zend/zend_vm_execute.h

ZEND_API void execute_ex(zend_execute_data *ex)
{
DCL_OPLINE
// ...
zend_execute_data *execute_data = ex;
// ...
LOAD_OPLINE();
ZEND_VM_LOOP_INTERRUPT_CHECK();
// ...
while (1) {
// ...
int ret;
// ...
if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
// ...
if (EXPECTED(ret > 0)) {
execute_data = EG(current_execute_data);
ZEND_VM_LOOP_INTERRUPT_CHECK();
} else {
// ...
return;
}
// ...
}
}
zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
}

通过调试可以知道,如果是一些简单的操作, handler 就会直接处理。比如加减法。但是像函数调用这种,就不会在 handler 这里处理。

那么只能看下面的代码。

只有当 ret 大于 0 的时候会有额外的操作。通过调试可以看到有以下几个大于 0 的情况。

Zend/zend_vm_execute.h

# define ZEND_VM_ENTER_EX()        return  1
# define ZEND_VM_ENTER() return 1
# define ZEND_VM_LEAVE() return 2

这个信息没有多大影响。

那么接下来就得看 ZEND_VM_LOOP_INTERRUPT_CHECK(); 了。

Zend/zend_execute.c

#define ZEND_VM_LOOP_INTERRUPT_CHECK() do { \
if (UNEXPECTED(EG(vm_interrupt))) { \
ZEND_VM_LOOP_INTERRUPT(); \
} \
} while (0)

继续:

Zend/zend_vm_execute.h

#define ZEND_VM_LOOP_INTERRUPT() zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);

继续:

Zend/zend_vm_execute.h

static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS)
{
EG(vm_interrupt) = 0;
if (EG(timed_out)) {
zend_timeout(0);
} else if (zend_interrupt_function) {
SAVE_OPLINE();
zend_interrupt_function(execute_data);
ZEND_VM_ENTER();
}
ZEND_VM_CONTINUE();
}

搜索 zend_interrupt_function 发现它是一个函数指针。那么转成搜索 zend_interrupt_function = ,看看哪个函数的指针传给了它。

这时搜索到了两条线。一条是 ext/pcntl/pcntl.c,另一条是 win32/signal.c

这里选 win32/signal.c

win32/signal.c

PHP_WINUTIL_API void php_win32_signal_ctrl_handler_init(void)
{/*{{{*/
// ...
zend_interrupt_function = php_win32_signal_ctrl_interrupt_function;
// ...
}/*}}}*/

接着找函数 php_win32_signal_ctrl_interrupt_function

win32/signal.c

static void php_win32_signal_ctrl_interrupt_function(zend_execute_data *execute_data)
{/*{{{*/
if (IS_UNDEF != Z_TYPE(ctrl_handler)) {
zval retval, params[1]; ZVAL_LONG(&params[0], ctrl_evt); /* If the function returns, */
call_user_function(NULL, NULL, &ctrl_handler, &retval, 1, params);
zval_ptr_dtor(&retval);
} if (orig_interrupt_function) {
orig_interrupt_function(execute_data);
}
}/*}}}*/

感觉很接近了。

call_user_function 传了两个 NULL,为了避免理解上有偏差,把它的定义列出来。

Zend/zend_API.h

#define call_user_function(function_table, object, function_name, retval_ptr, param_count, params) \
_call_user_function_ex(object, function_name, retval_ptr, param_count, params, 1)

继续:

Zend/zend_execute_API.c

int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], int no_separation) /* {{{ */
{
zend_fcall_info fci; fci.size = sizeof(fci);
fci.object = object ? Z_OBJ_P(object) : NULL;
ZVAL_COPY_VALUE(&fci.function_name, function_name);
fci.retval = retval_ptr;
fci.param_count = param_count;
fci.params = params;
fci.no_separation = (zend_bool) no_separation; return zend_call_function(&fci, NULL);
}

绕了一圈还是绕回来了。又一次见到 zend_call_function 。上文已经分析过这个函数了,不再重复。

小结

本文通过假设 PHP 函数调用方式和查询源码验证,得到了 PHP 底层将 C 语言函数存储到 HashTable 然后通过函数名字找到函数指针来调用这一结论。同时也了解了 PHP 函数执行的大致流程。

虽然了解了也没什么用的样子,但好奇心得到了满足 233

【PHP源码】PHP 函数调用的更多相关文章

  1. Ubuntu1804 源码阅读神器,egypt+graphviz 图形化显示函数调用关系(超详细+图文并茂)

    函数调用图可以让我们更加直观地了解到源码函数直接的调用和层次关系,提高阅读源码的效率,工欲善其事,必先利其器: 文章目录 1 前言 2 graphviz 安装 3 egypt 安装 4 测试 5 结论 ...

  2. React16源码解读:开篇带你搞懂几个面试考点

    引言 如今,主流的前端框架React,Vue和Angular在前端领域已成三足鼎立之势,基于前端技术栈的发展现状,大大小小的公司或多或少也会使用其中某一项或者多项技术栈,那么掌握并熟练使用其中至少一种 ...

  3. 源码分析:动态分析 Linux 内核函数调用关系

    源码分析:动态分析 Linux 内核函数调用关系 时间 2015-04-22 23:56:07  泰晓科技 原文  http://www.tinylab.org/source-code-analysi ...

  4. Golang源码学习:使用gdb调试探究Golang函数调用栈结构

    本文所使用的golang为1.14,gdb为8.1. 一直以来对于函数调用都仅限于函数调用栈这个概念上,但对于其中的详细结构却了解不多.所以用gdb调试一个简单的例子,一探究竟. 函数调用栈的结构(以 ...

  5. ClickHouse源码笔记3:函数调用的向量化实现

    分享一下笔者研读ClickHouse源码时分析函数调用的实现,重点在于分析Clickhouse查询层实现的接口,以及Clickhouse是如何利用这些接口更好的实现向量化的.本文的源码分析基于Clic ...

  6. 从Spark-Shell到SparkContext的函数调用路径过程分析(源码)

     不急,循序渐进,先打好基础 Spark shell的原理 首先,我们清晰定位找到这几个. 1.spark-shell 2. spark-submit 3.spark-class  4.SparkSu ...

  7. Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程

    在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们:module_init与module_exit,那会什么经过修饰后,内核就能狗调用我们编写的入口函数与出口函数呢?下面就来分析内核调用 ...

  8. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  9. iOS开发之Masonry框架源码深度解析

    Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...

随机推荐

  1. scroll-view组件bindscroll实例应用:自定义滚动条

    我们知道scroll-view组件作为滑动控件非常好用,而有时候我们想放置一个跟随滚动位置来跟进的滚动条,但又不想用滚动条api该怎么办呢?(当然是自己写一个呗还能怎么办[自黑冷漠脸])嗯,没错.自己 ...

  2. React的组件

    React的组件化思想尤为明显,一切皆组件,觉着比Vue的组件化思想更加凸显. const PacketBG = (props) =>( <div className="pack ...

  3. agent判断用户请求设备

  4. 第一篇:解析Linux是什么?能干什么?它的应用领域!

    不得不说的前言(不看完睡觉会尿床):饿货们~!你说你们上学都学了点啥?这不懂那也不懂,快毕业了啥也不会.专业课程不学好毕业了也找不到好工作.爸妈给你养大,投资了多少钱.你毕业后随便找了个什么鸡毛工作开 ...

  5. libra共识算法分析

    ​​ 核心算法说明 基于chained实现,整体上是当前轮推动下一轮共识继续下去, 如此来持续运转下去, 数据有效性证明基于(QC)实现 leader广播proposal消息{round, qc, p ...

  6. 01 UIPath抓取网页数据并导出Excel(非Table表单)

    上次转载了一篇<UIPath抓取网页数据并导出Excel>的文章,因为那个导出的是table标签中的数据,所以相对比较简单.现实的网页中,有许多不是通过table标签展示的,那又该如何处理 ...

  7. Visual Studio 安装中出现闪退

    问题描述:win7 系统下, 安装 Visual Studio Community 2017 过程中,安装界面闪退 原因:Visual Studio 的版本低了 解决方案:选择 Visual Stud ...

  8. Spring注解 - 组件的注册

    Spring Boot的出现极大的简化了我们的开发,让我们无需再写繁杂的配置文件,其正是利用了注解的便捷性,而Spring Boot又依赖于Spring,因此深入学习Spring的注解是十分必要的. ...

  9. 数字逻辑与EDA设计

    目录 第一章 数字逻辑基础 1.1数制与码制★★★ 数制 码制 1.2基本及常用的逻辑运算★★ 1.2逻辑函数表示方法★★ 1.3逻辑函数的化简★★★ 1.4常用74HC系列门电路芯片★ 第二章 组合 ...

  10. Cisco 综合配置(四)

    MSTP+HSRP 模式 为实现路由的备用.冗余: VLAN10,20 流量在CO-SW1上为active状态,在CO-SW2 上为standby状态, VLAN30,40 流量在CO-SW1上为st ...