Redis 的 string

Redis 的字符串就是 SET 和 GET 操作对应的类型,算是 Redis 里最常用的类型了。 0x00 动态字符串 sds Redis 内部的字符串表示,没有直接使用 C 语言字符串,而是对其进行了一定的改造,改造后的字符串在内存管理和长度计算方面的性能都有所提升。 举个例子,假设要存储的是字符串”redis“。 +——–+——–+————-+ | len | alloc | |r|e|d|i|s| | +——–+——–++———–++ | | v v flag ‘\0’ 这个图就是 sds 的内存结构。sdshdr 分四个部分,从左往右一次是字符串长度、开辟的内存空间、sdshdr 的类型以及字符串本身。在字符串初始化好之后,会返回一个指针,指向字符串本身的首地址,也就是 r 的内存地址。这样,既能方便地享受 C 语言字符串带来的兼容性,又可以对内存管理了如指掌。 0x01 redisObj typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time).
Read more →

Redis 源码学习——浅谈 Redis 的字符串实现

Redis 源码学习——浅谈 Redis 的字符串实现 0x00 结构 Redis 的字符串,本质上还是沿用了 C 语言的字符串,但是还是有一些不同。Redis 的字符串附加了一些和字符串本身相关的信息。我觉得这个设计很巧妙(也许是一个常用的技巧,是我太菜了不知道)。 首先来看 sds 的定义 typedef char sds; 在目前的约定下,一个 char 是一个字节,这是开辟内存空间的最小的单位了,方便内存管理(我是这样认为的)。关于字符串的信息,定义在了几个结构体里面。 / Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. / struct attribute ((packed)) sdshdr5 { unsigned char flags; / 3 lsb of type, and 5 msb of string length / char buf[]; }; struct attribute ((packed)) sdshdr8 { uint8_t len; / used / uint8_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits / char buf[]; }; struct attribute ((packed)) sdshdr16 { uint16_t len; / used / uint16_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits / char buf[]; }; struct attribute ((packed)) sdshdr32 { uint32_t len; / used / uint32_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits / char buf[]; }; struct attribute ((packed)) sdshdr64 { uint64_t len; / used / uint64_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits */ char buf[]; }; 这几个结构体,其实是同一个作用,就是记录了字符串的各种信息,它们的区别就是用来表示不同长度的字符串,或者说用来开辟不同大小的空间。里面的几个成员变量,我认为是这样的作用:
Read more →

csapp-bomb-lab-phase-1

第一题比较简单,但本菜鸡也做了两个小时(╯‵□′)╯︵┻━┻。。。 首先打开事先已经反汇编的 bomb.s 文件,通过 bomb.c 已经知道每一关都是一个函数,它们的命名都是 phase_x,x 代表该关卡的数字,如果某个关卡输入的不正确,就会引爆炸弹 explode_bomb。首先看 main 函数的这几行 400e1e: bf 38 23 40 00 mov $0x402338,%edi 400e23: e8 e8 fc ff ff callq 400b10 <puts@plt> 400e28: bf 78 23 40 00 mov $0x402378,%edi 400e2d: e8 de fc ff ff callq 400b10 <puts@plt> 400e32: e8 67 06 00 00 callq 40149e <read_line> 400e37: 48 89 c7 mov %rax,%rdi 400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1> 400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused> 400e44: bf a8 23 40 00 mov $0x4023a8,%edi 打开 gdb,先给这一行打上断点 break *0x400e23,然后 run 起来。这里可以看到调用了 puts 这个函数,寄存器 %edi 存储的是函数的第一个参数,我们把它的结果打印出来 x/s 0x402338、x/s 0x402378,发现得到了运行 bomb 后输出的字符串。说明第一关就是从这里开始的。
Read more →

从零开始写 PHP 扩展(一)

PHP 是用 C 语言写的。对于每个 PHPer 来说,都有着内心的一种希望写扩展的冲动了吧。然而,缺乏一个很好的切入点。Google 上搜 PHP 扩展开发,大部分都是复制品文章,甚至有些人连操作都没有操作过就搬运在了自己的博客。不过也有几篇好教程,但是都是 PHP 5 时代的产物,隐藏着非常多的坑。我会将我自己慢慢踩坑的过程记录下来,也许这就成了其它人的“教程”了吧。 生成一个扩展 想必很多人已经看到很多网上的教程了。大多都是教我们执行这个命令:$ ./ext_skel –extname=extname。但是,当你 clone 了 PHP 源码后会发现,master 分支下并没有 ext/ext_skel 这个文件。所以,我总结了一下: 如果你是直接下载 PHP 的源码,或者在已经 release 的版本分之下,你可以执行这个命令 $ cd ext $ ./ext_skel –extname=extname 如果你是直接在 master 分支下,只有 ext_skel.php 文件,这个时候你就直接可以执行这个 PHP 文件 $ cd ext $ php ext_skel.php –ext extname 由于我是直接在 master 分支下开发的,所以后面的都是默认在 master 分之下的操作。 生成了扩展之后,我们会看到四个文件和一个文件夹。现在这个阶段,我们只需要用到两个文件,.c 文件和 .h 文件。 一个小坑 在我们生成好扩展之后,我们可以试着编译一下 $ phpize $ ./configure $ make && make test 我们会惊讶地发现,编译的时候会有一个 warning。
Read more →

浮点数那些事

本文为作者自己的总结的,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。 说起浮点数,大家都是又恨又爱的。爱呢,是因为,只有它可以方便地使用小数;恨呢,是因为它并不能精确地表示小数。 以 PHP 为例:floor((0.1 + 0.7) * 10) 这样一个函数调用,根据数学老师死得晚原理,大家都能得出 8 这个结果。可是事实上呢?它会返回 7。数学老师的棺材板。。。(╯‵□′)╯︵┻━┻ 可是为什么会出现这种情况呢?这就要从浮点数的特性说起了。 万物皆二进制 我们都知道,在计算机中,一切的一切都是二进制表示的。假设一个 4 字节整型的十进制数 8,在大端表示的机器中,表示成 00000000 00000000 00000000 00001000(0x0000008)。将十进制整数转换成二进制数,是非常容易的。可是,小数呢?比如,我们要表示 1.75,该怎么存储在计算机中呢?显然,不能像整数一样存储了。 小数的二进制 让我们回忆一下,在十进制中,小数是怎么计算的。上面的 1.75 我们是这么算的:1 × 10^0 + 7 × 10^-1 + 5 × 10^-2 。那么我们按照相同的规则,来用二进制计算一下小数部分:0.75 = 1⁄2 + 1/4,也就是 1 × 2^-1 + 1 × 2^-2 ,再加上前面的整数部分,那么整个式子就变成了 1 × 2^0 + 1 × 2^-1 + 1 × 2^-2 ,写成二进制形式就是 1.11。所以,1.75 的二进制表示是 1.11。 对于将小数转换为二进制,和整数部分除二取余相反的,是乘二取整。
Read more →

php 内核探秘之 PHP_FUNCTION 宏

本人也只是个初入门的菜鸟,因对技术有着向往,故在“无趣”的工作之余,尽自己所能提升自己。由于我的 C 语言功底也有限,故本文的深度也有限,如有幸得大牛阅读,还望指导一二,小弟感激不尽。 PHP 的函数 作为 PHPer,我们几乎每天都在写函数,我们一定会好奇,那些 PHP 内置的函数,是长什么样子的。如果写过 PHP 扩展的话,一定知道这个宏:PHP_FUNCTION。在定义一个函数的时候,这样来使用这个宏。例如 array_change_key_case,它的定义是这样的:PHP_FUNCTION(array_change_key_case)。没错,就是这么简单。但是,在这个简单的背后,却没有这么简单。 PHP_FUNCTION 追根溯源 宏 相信对这篇文章感兴趣的同学,一定多少对 C 语言以及它的宏定义有一定的了解。如果没有,也不要紧,我这里来简单解释一下,什么是宏。 C 语言中的宏,我认为,可以理解为一种简单的封装。通过宏定义,可以对开发者隐去一些细节,让开发者在使用简单的语法来完成重复的复杂的编码。当然,宏定义还有其它的用途,但是,我们在 PHP_FUNCTION 涉及到的就是这个作用。有下面的代码。 #define TEST(test) void test(int a) TEST(haha) 宏,就是完全的替换,即使用后面的语句替换前面的。那么对于下面的 TEST(haha) 就相当于下面的样子。 void haha(int a) PHP_FUNCTION 的定义 首先,我们要定义函数,这样使用这个宏。 PHP_FUNCTION(array_change_key_case) { // TODO } 我们在 php-src/main/php.h 中找到了下面的定义。 #define PHP_FUNCTION ZEND_FUNCTION 也就是说,这里用 ZEND_FUNCTION 替换了 PHP_FUNCTION 这个宏。所以,我们的定义就相当于变成了这样。 ZEND_FUNCTION(array_change_key_case) { // TODO } 我们继续往下找,因为,这里还是宏,我们并没有看到我们希望看到的代码。我们可以在 php-src/Zend/zend_API.h 中找到下面的定义。 #define ZENDFN(name) zif##name #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) 我们看到,在宏定义中,使用了另外的宏。不要怕,还是一个词,替换。我们按照这样的步骤来。(## 是一个连接符,它的作用是,是将它前面的与后面的,按照字符串的方式连接起来。
Read more →