首页 文章 精选 留言 我的

精选列表

搜索[内核],共10002篇文章
优秀的个人博客,低调大师

php内核分析(五)-zval

这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux 实际上,从这个函数开始,就已经进入到了zend引擎的范围了。 zend_eval_string_ex(exec_direct, NULL, "Command line code", 1) 实际上是调用Zend/zend_execute_API.c zend_eval_stringl_ex(str, strlen(str), retval_ptr, string_name, handle_exceptions); 再进去是调用 result = zend_eval_stringl(str, str_len, retval_ptr, string_name); 这里的retval_ptr为NULL,string_name为"Command line code", str为"echo 12;" zend_eval_stringl 其实这个函数主流程并不复杂。简化下来就如下 ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char *string_name) /* {{{ */ { ... new_op_array = zend_compile_string(&pv, string_name); // 这个是把php代码编译成为opcode的过程 ... zend_execute(new_op_array, &local_retval); // 这个是具体的执行过程,执行opcode,把结果存储到local_retval中 ... retval = SUCCESS; return retval; } 先把php编译为opcode,然后执行这个opcode。只是这个函数有一些关键的结构需要理一下。 zval 我们会看到 zval local_retval; 这样的变量,然后会对这个变量进行如下操作: ZVAL_UNDEF(&local_retval); ZVAL_NULL(z) ZVAL_FALSE(z) ZVAL_TRUE(z) ZVAL_BOOL(z, b) ZVAL_LONG(z, l) ZVAL_DOUBLE(z, d) ZVAL_STR(z, s) ZVAL_INTERNED_STR(z, s) ZVAL_NEW_STR(z, s) ZVAL_STR_COPY(z, s) ZVAL_ARR(z, a) ZVAL_NEW_ARR(z) ZVAL_NEW_PERSISTENT_ARR(z) ZVAL_OBJ(z, o) ZVAL_RES(z, r) ZVAL_NEW_RES(z, h, p, t) ZVAL_NEW_PERSISTENT_RES(z, h, p, t) ZVAL_REF(z, r) ZVAL_NEW_EMPTY_REF(z) ZVAL_NEW_REF(z, r) ZVAL_NEW_PERSISTENT_REF(z, r) ZVAL_NEW_AST(z, a) ZVAL_INDIRECT(z, v) ZVAL_PTR(z, p) ZVAL_FUNC(z, f) ZVAL_CE(z, c) ZVAL_ERROR(z) php是一个弱类型的语言,它可以用一个$var来代表string,int,array,object等。这个就是归功于zval_struct结构 // zval的结构 struct _zval_struct { zend_value value; // 存储具体值,它的结构根据类型不同而不同 union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, // 这个位置标记了这个val是什么类型的(IS_STRING/IS_INT) zend_uchar type_flags, // 这个位置标记了这个val是什么属性 (IS_CALLABLE等) zend_uchar const_flags, // 常量的一些属性 (IS_CONSTANT_CLASS) zend_uchar reserved) // 保留的一些字段 } v; uint32_t type_info; // 类型的一些额外信息 } u1; // 保存类型的一些关键信息 union { uint32_t next; // 如果是在hash链表中,这个指针代表下一个元素的index uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ } u2; // 一些附属字段 }; 这个接口最重要的两个字段是 value,存储变量的值。另一个是u1.v.type 存储变量的类型。这里,value也是一个结构 typedef union _zend_value { zend_long lval; /* long value */ double dval; /* double value */ zend_refcounted *counted; zend_string *str; // string zend_array *arr; // array zend_object *obj; // object zend_resource *res; // resource zend_reference *ref; // 指针 zend_ast_ref *ast; // ast指针 zval *zv; void *ptr; zend_class_entry *ce; // class实体 zend_function *func; // 函数实体 struct { uint32_t w1; uint32_t w2; } ww; } zend_value; 如果u1.v.type == IS_STRING, 那么value.str就是指向了zend_string结构。好了,php的垃圾回收是通过引用计数来进行的,这个引用计数的计数器就放在zval.value.counted里面。 我们对zval设置的时候设置了一些宏来进行设置,比如:ZVAL_STRINGL是设置string,我们仔细看下调用堆栈: ZVAL_STRINGL(&pv, str, str_len); // 把pv设置为string类型,值为str 这个函数就是把pv设置为zend_string类型 // 带字符串长度的设置zend_sting类型的zval #define ZVAL_STRINGL(z, s, l) do { \ ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); \ } while (0) 注意到,这里使用了一个写法,do {} while(0) 来设置一个宏,这个是C里面比较好的写法,这样写,能保证宏中定义的东西在for,if,等各种流程语句中不会出现语法错误。不过其实我们学习代码的时候,可以忽略掉这个框框写法。 zend_string_init(s, l, 0) ... // 从char* + 长度 + 是否是临时变量(persistent为0表示最迟这个申请的空间在请求结束的时候就进行释放),转变为zend_string* static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) { zend_string *ret = zend_string_alloc(len, persistent); // 申请空间,申请的大小为zend_string结构大小(除了val)+ len + 1 memcpy(ZSTR_VAL(ret), str, len); ZSTR_VAL(ret)[len] = '\0'; return ret; } 这个函数可以看的点有几个: persistent 这个参数是用来代表申请的空间是不是“临时”的。这里说的临时是zend提供的一种内存管理器,相关请求数据只服务于单个请求,最迟会在请求结束的时候释放。 临时内存申请对应的函数为: void *emalloc(size_t size) 而永久内存申请对应的函数为: malloc zend_string_alloc static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent) { zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); GC_REFCOUNT(ret) = 1; GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << 8); zend_string_forget_hash_val(ret); ZSTR_LEN(ret) = len; return ret; } 我们先看看zend_string的结构: // 字符串 struct _zend_string { zend_refcounted_h gc; // gc使用的被引用的次数 zend_ulong h; // 如果这个字符串作为hashtable的key在查找时候需要重复计算它的hash值,所以保存一份在这里 size_t len; // 字符串长度 char val[1]; // 柔性数组,虽然我们定义了数组只有一个元素,但是在实际分配内存的时候,会分配足够的内存 }; _ZSTR_STRUCT_SIZE(len) gc+h+len的空间,最后给了val留了len+1的长度 #define _ZSTR_STRUCT_SIZE(len) (_ZSTR_HEADER_SIZE + len + 1) ## GC_REFCOUNT(ret) = 1; #define GC_REFCOUNT(p) (p)->gc.refcount 这里就看到一个结构zend_refcounted_h typedef struct _zend_refcounted_h { uint32_t refcount; // 真正的计数 union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, // 冗余了zval中的类型值 zend_uchar flags, // used for strings & objects中有特定作用 uint16_t gc_info) // 在GC缓冲区中的索引位置 } v; uint32_t type_info; // 冗余zval中的type_info } u; // 类型信息 } zend_refcounted_h; 回到我们的实例,我们调用的是 zend_string_init(s, l, 0) // s=char*(echo 12;) l=8 返回的zend_string实际值为: struct _zend_string { struct { uint32_t refcount; // 1 union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, // IS_STRING zend_uchar flags, uint16_t gc_info) } v; uint32_t type_info; //IS_STRING | 0 => IS_STRING } u; } gc; zend_ulong h; // 0 size_t len; // 8 char val[1]; // echo 12;\0 }; 结合到zval里面,那么ZVAL_STRINGL(&pv, str, str_len);返回的zval为 // zval的结构 struct _zval_struct { union _zend_value { zend_long lval; double dval; zend_refcounted *counted; zend_string *str; // 指向到上面定义的那个zend_string中 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; } value; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) } v; uint32_t type_info; // IS_STRING_EX } u1; union { uint32_t next; uint32_t cache_slot; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t access_flags; uint32_t property_guard; } u2; }; 这里,就对zval结构有初步了解了。 另外建议记住几个常用的类型,后续调试的时候会很有用 /* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/p/6108444.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

php内核分析(六)-opcode

这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux 查看opcode php是先把源码解析成opcode,然后再把opcode传递给zend_vm进行执行的。 // 一个opcode的结构 struct _zend_op { const void *handler; // opcode对应的执行函数,每个opcode都有一个对应的执行函数 znode_op op1; // 执行参数的第一个元素 znode_op op2; // 执行参数的第二个元素 znode_op result; // 执行结果 uint32_t extended_value; // 额外扩展的字段和值 uint32_t lineno; // 行数 zend_uchar opcode; // 操作码,具体操作码列表见 http://cn.php.net/manual/zh/internals2.opcodes.php zend_uchar op1_type; // 第一个元素的类型 zend_uchar op2_type; // 第二个元素的类型 zend_uchar result_type; // 结果的类型 }; 在php7中,我们能很方便用phpdbg来查看一个文件或者一个函数的opcode了。至于phpdbg的使用,现在网上介绍不多,不过好在有很详细的help文档。下面是一个最简单的opcode代码: $ bin/phpdbg -f /home/xiaoju/software/php7/demo/echo.php prompt> list 100 00001: <?php 00002: 00003: $a = 1; 00004: $b = $a; 00005: $b = $b + 1; 00006: echo $b; 00007: prompt> print exec [Context /home/xiaoju/software/php7/demo/echo.php (6 ops)] L1-7 {main}() /home/xiaoju/software/php7/demo/echo.php - 0x7fe3fae63300 + 6 ops L3 #0 ASSIGN $a 1 L4 #1 ASSIGN $b $a L5 #2 ADD $b 1 ~2 L5 #3 ASSIGN $b ~2 L6 #4 ECHO $b L7 #5 RETURN 1 这个php文件就做了一个最简单的加法操作。生成了6个_zend_op。所展示的每一行代表一个_zend_op _zendop.lineno op号 _zend_op.opcode _zend_op.op1 _zend_op.op2 _zend_op.result L5 #2 ADD $b 1 ~2 这里_zend_op.opcode对应的操作在官网有文档和详细的例子可以查看:http://cn.php.net/manual/zh/internals2.opcodes.php 值得一说的是,phpdbg还有一个远端UI版本,能让我们在近端诊断服务端的php信息 gdb 但是我们的目标还是在于研究php源码,phpdbg只能分析到opcode这层,还是不够的,gdb可能是更好的选择。 gdb的使用和平时使用差不多 比如我现在有个脚本echo.php: 1 <?php 2 3 $a = 1; 4 $b = $a; 5 $b = $b + 1; 6 echo $b; 我的php安装路径在: /home/xiaoju/software/php7/bin/php php源码路径在: /home/xiaoju/webroot/php-src/php-src-master/ 运行gdb $ gdb /home/xiaoju/software/php7/bin/php 加载gdbinit: (gdb) source /home/xiaoju/webroot/php-src/php-src-master/.gdbinit 设置断点: (gdb) b zend_execute_scripts 运行: (gdb) run -f /home/xiaoju/software/php7/demo/echo.php 我想在1459这行设置个断点: 1452 for (i = 0; i < file_count; i++) { 1453 file_handle = va_arg(files, zend_file_handle *); 1454 if (!file_handle) { 1455 continue; 1456 } 1457 1458 op_array = zend_compile_file(file_handle, type); 1459 if (file_handle->opened_path) { 1460 zend_hash_add_empty_element(&EG(included_files), file_handle->opened_path); 1461 } (gdb) b 1459 继续跑 (gdb) continue (gdb) s (gdb) s 打印出这个时候的op_array (gdb) p *op_array $4 = {type = 2 '\002', arg_flags = "\000\000", fn_flags = 134217728, function_name = 0x0, scope = 0x0, prototype = 0x0, num_args = 0, required_num_args = 0, arg_info = 0x0, refcount = 0x7ffff6002000, last = 6, opcodes = 0x7ffff6076240, last_var = 2, T = 4, vars = 0x7ffff6079030, last_live_range = 0, last_try_catch = 0, live_range = 0x0, try_catch_array = 0x0, static_variables = 0x0, filename = 0x7ffff605c2d0, line_start = 1, line_end = 7, doc_comment = 0x0, early_binding = 4294967295, last_literal = 3, literals = 0x7ffff60030c0, cache_size = 0, run_time_cache = 0x0, reserved = {0x0, 0x0, 0x0, 0x0}} 我可以优化输出: (gdb) set print pretty on (gdb) p *op_array $5 = { type = 2 '\002', arg_flags = "\000\000", fn_flags = 134217728, function_name = 0x0, scope = 0x0, prototype = 0x0, num_args = 0, required_num_args = 0, arg_info = 0x0, refcount = 0x7ffff6002000, last = 6, opcodes = 0x7ffff6076240, last_var = 2, T = 4, vars = 0x7ffff6079030, last_live_range = 0, last_try_catch = 0, live_range = 0x0, try_catch_array = 0x0, static_variables = 0x0, filename = 0x7ffff605c2d0, line_start = 1, line_end = 7, doc_comment = 0x0, early_binding = 4294967295, last_literal = 3, literals = 0x7ffff60030c0, cache_size = 0, run_time_cache = 0x0, reserved = {0x0, 0x0, 0x0, 0x0} } 我想打出op_array.filename.val的具体值 (gdb) p (op_array.filename.len) $12 = 40 (gdb) p *(op_array.filename.val)@40 $13 = "/home/xiaoju/software/php7/demo/echo.php" 好了,我们可以顺便研究下_zend_op_array这个结构: // opcode组成的数组,编译的时候就是生成这个结构 struct _zend_op_array { zend_uchar type; // op array的类型,比如 ZEND_EVAL_CODE 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_arg_info *arg_info; /* END of common elements */ uint32_t *refcount; // 这个结构的引用次数 uint32_t last; // opcode的个数 zend_op *opcodes; // 存储所有的opcode int last_var; // php变量的个数 uint32_t T; zend_string **vars; // 被编译的php变量的个数 int last_live_range; int last_try_catch; // try_catch的个数 zend_live_range *live_range; zend_try_catch_element *try_catch_array; // /* static variables support */ HashTable *static_variables; // 静态变量 zend_string *filename; // 执行的脚本的文件 uint32_t line_start; // 开始于第几行 uint32_t line_end; // 结束于第几行 zend_string *doc_comment; // 文档的注释 uint32_t early_binding; /* the linked list of delayed declarations */ int last_literal; zval *literals; int cache_size; void **run_time_cache; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; // 保留字段 }; 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/p/6112634.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

php内核分析(七)-扩展

这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux。 我们研究下反射这个扩展。 反射这个扩展目录是存在在:ext/reflection。其实里面的代码很简单。一个.h文件,一个 .c文件。 我们先看下.c文件中,会看到很多ZEND_METHOD ZEND_METHOD(reflection_function, getReturnType) { ... } 对应的宏: #define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name)) #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) #define ZEND_MN(name) zim_##name #define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value 这里的##代表的是连接,展开实际上就是: void zim_reflection_function_getReturnType(zend_execute_data *execute_data, zval *return_value) 总而言之,我们这里是使用ZEND_METHOD定义了一个函数zim_reflection_function_getReturnType,那从执行代码是怎么调用到这里的呢? 好吧,所以我们这里是看不到扩展的调用堆栈的。那我们用gdb看下调用堆栈。 写个使用反射扩展的脚本: 1 <?php 2 3 class B 4 { 5 public function test(): B 6 { 7 8 } 9 } 10 11 function getB(): B 12 { 13 14 } 15 16 $rc = new ReflectionMethod('B', 'test'); 17 var_dump((string)$rc->getReturnType(), $rc->getReturnType()); 18 19 $rc = new ReflectionFunction('getB'); 20 var_dump((string)$rc->getReturnType(), $rc->getReturnType()); 使用gdb进行打点,我们看了下getReturnType的扩展定义,里面有个在扩展代码中的函数reflection_type_factory,就使用这个打点了。 (gdb) b reflection_type_factory (gdb) run -f /home/xiaoju/software/php7/demo/echo.php (gdb) s (gdb) bt #0 reflection_type_factory (fptr=0x7ffff6004210, closure_object=0x0, arg_info=0x7ffff6079048, object=0x7ffff60140d0) at /home/xiaoju/webroot/php-src/php-src-master/ext/reflection/php_reflection.c:1280 #1 0x0000000000760d23 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER (execute_data=0x7ffff6014030) at /home/xiaoju/webroot/php-src/php-src-master/Zend/zend_vm_execute.h:1097 #2 0x000000000073fc88 in execute_ex (ex=<value optimized out>) at /home/xiaoju/webroot/php-src/php-src-master/Zend/zend_vm_execute.h:432 #3 0x000000000078b670 in zend_execute (op_array=0x7ffff60782a0, return_value=<value optimized out>) at /home/xiaoju/webroot/php-src/php-src-master/Zend/zend_vm_execute.h:474 #4 0x00000000006e48a3 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/xiaoju/webroot/php-src/php-src-master/Zend/zend.c:1464 #5 0x0000000000684870 in php_execute_script (primary_file=0x7fffffffe090) at /home/xiaoju/webroot/php-src/php-src-master/main/main.c:2541 #6 0x000000000078e9ea in do_cli (argc=3, argv=0xee1bc0) at /home/xiaoju/webroot/php-src/php-src-master/sapi/cli/php_cli.c:994 #7 0x000000000078f1ea in main (argc=3, argv=0xee1bc0) at /home/xiaoju/webroot/php-src/php-src-master/sapi/cli/php_cli.c:1387 (gdb) 好了,很清晰可以看到这个脉络: main->do_cli->php_execute_scripts->zend_execute->execute_ex->ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER->reflection_type_factory 对于main, do_cli, php_execute_scripts, zend_execute, execute_ex 根据前面的main函数分析,我们很容易能够理解各个函数的作用。换句话说,execute_ex才是实际上调用opcode最终最重要的函数。 对照这个脚本的opcode: L1-21 {main}() /home/xiaoju/software/php7/demo/echo.php - 0x7fd6a127f000 + 30 ops L3 #0 NOP L11 #1 NOP L16 #2 NEW "ReflectionMethod" @1 L16 #3 SEND_VAL_EX "B" 1 L16 #4 SEND_VAL_EX "test" 2 L16 #5 DO_FCALL L16 #6 ASSIGN $rc @1 L17 #7 INIT_FCALL 112 "var_dump" L17 #8 INIT_METHOD_CALL $rc "getReturnType" L17 #9 DO_FCALL @4 L17 #10 CAST @4 ~5 L17 #11 SEND_VAL ~5 1 L17 #12 INIT_METHOD_CALL $rc "getReturnType" L17 #13 DO_FCALL @6 L17 #14 SEND_VAR @6 2 L17 #15 DO_ICALL L19 #16 NEW "ReflectionFunction" @8 L19 #17 SEND_VAL_EX "getB" 1 L19 #18 DO_FCALL L19 #19 ASSIGN $rc @8 L20 #20 INIT_FCALL 112 "var_dump" L20 #21 INIT_METHOD_CALL $rc "getReturnType" L20 #22 DO_FCALL @11 L20 #23 CAST @11 ~12 L20 #24 SEND_VAL ~12 1 L20 #25 INIT_METHOD_CALL $rc "getReturnType" L20 #26 DO_FCALL @13 L20 #27 SEND_VAR @13 2 L20 #28 DO_ICALL L21 #29 RETURN 1 可以看到这个$rc->getReturnType()相对应的opcode是在#9 DO_FCALL 好了,我们从execute_ex开始跟,可以简化成: // 最核心的执行opcode的函数 ZEND_API void execute_ex(zend_execute_data *ex) { ... while (1) { int ret; if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) { ... } } ... } 这里的handler每个opcode的op对应一个handler,比如 DO_FCALL对应的handler就是ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER(和刚才的bt现显示的堆栈一样) 简化下伪代码如下: // DO_FCALL这个opcode对应的处理函数 static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { ... if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) { // 如果是用户定义的函数 ... zend_execute_ex(call); ... } else if (EXPECTED(fbc->type < ZEND_USER_FUNCTION)) { // 如果是内部函数 ... if (!zend_execute_internal) { fbc->internal_function.handler(call, ret); // 执行这个internal_function所定义的handler函数,这个就是实际的调用方法了,命名为:zim_[class]_function_[function] } else { zend_execute_internal(call, ret); } ... } else { /* ZEND_OVERLOADED_FUNCTION */ ... if (UNEXPECTED(!zend_do_fcall_overloaded(fbc, call, ret))) { HANDLE_EXCEPTION(); } ... } fcall_end: ... ZEND_VM_SET_OPCODE(opline + 1); ZEND_VM_CONTINUE(); // 下一条op } 可以看到,这个函数里面就有一个fbc->internal_function.handler,这里的internal_function对应的函数名就是zim_reflection_function_getReturnType,和我们扩展模块里面定义的函数对应上了。可以说,这里就进入了扩展里面了。 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/p/6120856.html,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册