archlinux下mongodb服务启动失败

问题:

sudo systemctl status mongodb.service显示如下

mongodb.service: Failed with result 'exit-code'.
  1. 查看mongodb的配置文件/etc/mongodb.conf

    dbpath = /var/lib/mongodb
    logpath = /var/log/mongodb/mongod.log

  2. 查看日志获取错误信息

    IMPORTANT: UPGRADE PROBLEM: The data files need to be fully upgraded to version 3.6 before attempting an upgrade to 4.0; see http://dochub.mongodb.org/core/4.0-upgrade-fcv for more details

  3. 原来是mongodb升级了之后,之前的数据库文件都是旧格式,所以现在启动不起来了

  4. 备份/var/lib/mongodb目录,重新创建一个一样的目录,把所属与权限设置正确,重启,问题解决

动感生活1

生活中总有太多的人需要感谢,太多的事值得纪念。遇见你们我觉得自己很幸运,非常幸运,感觉自己总能碰到贵人相助,衷心感谢你们。

年纪越大,这感觉越强烈。我会把这些恩情再传递给别人!

PHP7快速API提案(FAST ZPP)

注:

在查看PHP7源码的时候看到一个ZEND_PARSE_PARAMETERS_START()ZEND_PARSE_PARAMETERS_END()代码块,开始不太理解,找到官方RFC才明白。

文章主要内容翻译自PHP官方的RFC,欢迎指正查看原文

描述

PHP内部函数使用zend_parse_parameters()API接受参数,将输入参数转换成C变量,这个函数使用scanf()方法进行参数定义,所需数据的数量和类型由包含说明符列表的字符串定义(“s”-表示字符串,“l”表示长整型等),不幸的是,每次调用这个函数时都要对这个这个字符串进行解析,这会加重性能开销。

例如,在服务wordpress主页时,zend_parse_parameters()会占用CPU大约6%的运行时间。

对于一些非常简单的函数,如is_string()ord()zend_parse_parameters()的开销可能约为90%。

提案

我们提出了另一个API(fast zpp)以快速解析参数,它应该被用于最有用的函数里。API是基于C语言的宏,这可以将优化后的代码直接嵌入到内部函数体中。

我们不建议删除现有的API,并建议仅对最常用的函数使用快速API以提高性能。

我将在下面的示例中解释API:

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "al|zb", &input, &offset, &z_length, &preserve_keys) == FAILURE) {
    return;
}
ZEND_PARSE_PARAMETERS_START(2, 4)
    Z_PARAM_ARRAY(input)
    Z_PARAM_LONG(offset)
    Z_PARAM_OPTIONAL
    Z_PARAM_ZVAL(z_length)
    Z_PARAM_BOOL(preserve_keys)
ZEND_PARSE_PARAMETERS_END();

第一个代码片段只取自PHP_FUNCTION(array_slice),第二个是使用新API替换后的。根据代码实际上可以望文生义。

  • ZEND_PARSE_PARAMETERS_START()-接受两个参数,最少传入和最多传入的参数数量。
  • Z_PARAM_ARRAY()-将下一个参数作为数组
  • Z_PARAM_LONG-作为长整型参数
  • Z_PARAM_OPTIONAL-此项说明剩下的参数是可选的。

新的API涵盖了现有API的所有可能性。下表列出了旧说明符和新宏之间的对应关系。

specifier Fast ZPP API macro args
| Z_PARAM_OPTIONAL
a Z_PARAM_ARRAY(dest) dest - zval*
A Z_PARAM_ARRAY_OR_OBJECT(dest) dest - zval*
b Z_PARAM_BOOL(dest) dest - zend_bool
C Z_PARAM_CLASS(dest) dest - zend_class_entry*
d Z_PARAM_DOUBLE(dest) dest - double
f Z_PARAM_FUNC(fci, fcc) fci - zend_fcall_info, fcc - zend_fcall_info_cache
h Z_PARAM_ARRAY_HT(dest) dest - HashTable*
H Z_PARAM_ARRAY_OR_OBJECT_HT(dest) dest - HashTable*
l Z_PARAM_LONG(dest) dest - long
L Z_PARAM_STRICT_LONG(dest) dest - long
o Z_PARAM_OBJECT(dest) dest - zval*
O Z_PARAM_OBJECT_OF_CLASS(dest, ce) dest - zval*
p Z_PARAM_PATH(dest, dest_len) dest - char*, dest_len - int
P Z_PARAM_PATH_STR(dest) dest - zend_string*
r Z_PARAM_RESOURCE(dest) dest - zval*
s Z_PARAM_STRING(dest, dest_len) dest - char*, dest_len - int
S Z_PARAM_STR(dest) dest - zend_string*
z Z_PARAM_ZVAL(dest) dest - zval*
Z_PARAM_ZVAL_DEREF(dest) dest - zval*
+ Z_PARAM_VARIADIC('+', dest, num) dest - zval*, num int
* Z_PARAM_VARIADIC('*', dest, num) dest - zval*, num int

!的效果和关联修饰符可以使用相同宏的扩展版本来实现,例如Z_PARAM_ZVAL_EX(dest, check_null, separate)

PHP7变量的内部实现(二)

注:
转载文章翻译自Nikita的文章,欢迎指正查看原文

在第一部分中,讨论了PHP5和PHP7之间内部值表示的高级改变。注意,主要区别在于zvals不再单独分配,也不自行存储引用计数。整数或浮点数等简单值可以直接存储在zval中,而复杂数值则使用指向单独结构的指针表示。

复杂的zval值的额外结构使用公共头,它由zend_refcounted来定义(博主注:PHP7.2与7又不一样):

struct _zend_refcounted {
    uint32_t refcount;
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,
                uint16_t      gc_info)
        } v;
        uint32_t type_info;
    } u;
};

此头部含有refcount,值类型type与循环集合信息(gc_info),以及一个特定类型插槽的标志flags

下面将讨论各个复杂类型的详细信息并与PHP5里的实现进行对比。其中一个复杂的类型就是引用,这些引用在前一部分里已经介绍过了。另一种没涉及的类型是资源,因为我觉得它没啥意思。

字符串(Strings)

PHP7使用zend_string类型表示字符串,其定义如下:

struct _zend_string {
    zend_refcounted   gc;
    zend_ulong        h;        /* hash value */
    size_t            len;
    char              val[1];
};

除了refcounted标头之外,字符串还包含了哈希缓存h,长度len和值val。哈希缓存用于避免每次用于在哈希表中查找键时重新计算字符串的哈希值。首次使用时,它将初始化为(非零)哈希。

如果你不熟悉相当广泛的dirty C hacks(不懂什么是dirty hacks,请点击?)知识,那么val的定义可能看起来很奇怪:它被声明为一个带有单个元素的char数组 - 但我们确定要存储长于一个字符的字符串吗?这使用了一种称为“struct hack”的技术:数组只用一个元素声明,但是在创建zend_string时,我们将它分配给一个更大的字符串。我们将仍然可以通过val成员访问更大的字符串。

当然,这是技术上未定义的潜规则,因为我们最终会在单字符数组的末尾读取和写入,但是C编译器知道在执行此操作时不要搞乱代码。C99以“flexible array members(灵活的数组成员)”的形式明确支持这一点,但是感谢我们亲爱的微软朋友,在实际上使用C99时,没有人需要跨平台兼容性。

与使用普通C字符串相比,新字符串类型具有一些优势:首先,它直接嵌入字符串长度。这意味着不再需要在所有地方传递字符串的长度。其次,由于字符串具有refcounted标头,因此可以在不使用zvals的情况下在多个位置共享字符串。这对于共享哈希表键尤其重要。

新的字符串类型也有一个很大的缺点:虽然很容易从zend_string获取一个C字符串(仅使用str-> val),但是又不可能直接从C字符串中获取一个zend_string - 你需要实际复制字符串的值到新分配的zend_string中。在处理文字字符串(C源代码中出现的常量字符串)时,非常不方便。

字符串可以有许多标志(存储在GC标志字段中):

#define IS_STR_PERSISTENT           (1<<0) /* allocated using malloc */
#define IS_STR_INTERNED             (1<<1) /* interned string */
#define IS_STR_PERMANENT            (1<<2) /* interned string surviving request boundary */

持久化字符串,使用普通的系统分配器而不是Zend内存管理器(ZMM),因此可以保持超过一个请求的时间。将已使用的分配器指定为标志允许我们在zval中透明地使用持久化字符串,而之前在PHP5中需要事先将副本复制到ZMM中。

Interned字符串是在请求结束之前不会被销毁的字符串,因此不需要使用refcounting。它们也进行了重复数据删除,因此如果创建了新的interned字符串,引擎将先检查此内容的interned字符串是否已存在。字面上出现在PHP源代码中的所有字符串(包括字符串文字,变量和函数名称等)通常都是实例化的。永久字符串是在请求开始之前创建的实例化字符串。虽然正常的实例化字符串在请求关闭时被销毁,但永久字符串仍保持活动状态。

如果使用了opcache,则interned字符串将存储在共享内存(SHM)中,并在所有PHP工作进程中共享。在这种情况下,永久字符串的概念变得无关紧要,因为interned字符串永远不会被销毁。

数组(Arrays)

我不会在这里讨论新数组类型中实现的细节,因为这已在以前这篇文章中介绍过。由于最近的变化,它在某些细节上已不再准确,但所有概念仍然相同。

我在这里只提一个与数组相关的新概念,因为它没有在哈希表帖子中介绍:不可变数组。这些本质上是值相等的实例化字符串,因为它们不使用引用计数,并且一直存在到请求结束(或更久)。

由于某些内存管理问题,仅在启用opcache时才使用不可变数组。要了解这可以产生什么样的差异,请参考以下脚本:

for ($i = 0; $i < 1000000; ++$i) {
    $array[] = ['foo'];
}
var_dump(memory_get_usage());

使用opcache时,内存使用量为32MB,但没有opcache时使用量会增加到390MB,因为在这种情况下,$array的每个元素都将获得['foo']的新副本。在这里完成实际复制(而不是引用refcount)的原因是文字虚拟操作数不使用引用计数来避免SHM损坏。我希望我们能够改善这个目前的灾难性案例,以便将来在没有opcache的情况下也能更好地运行。

PHP5 里的对象(Objects in PHP5)

在参考PHP7中的对象实现之前,让我们先了解PHP5中的工作原理并强调一些低效的问题:zval本身用于存储zend_object_value,其定义如下:

typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;

句柄(handle)是对象的唯一ID,可用于查找对象数据。handlers是实现对象的各种行为的VTable函数指针。对于“普通”PHP对象,此handle表始终相同,但PHP扩展创建的对象可以使用一组自定义handlers来修改其行为方式(例如,通过重载)。

对象句柄用作“对象存储”的索引,“对象存储”是一个对象存储桶数组,定义如下:

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    zend_uchar apply_count;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;
} zend_object_store_bucket;

这里发生了很多事情。前三个成员只是一些元数据(是否调用了对象的析构函数,是否完全使用此存储桶以及某些递归算法访问此对象的次数)。下面的联合体区分了当前使用存储桶的情况或它是否是存储桶空闲列表的一部分。这个示例的重点是struct _store_object在哪被使用。

第一个成员对象是指向实际对象的指针(最终)。它没有直接嵌入到对象存储桶中,因为对象没有固定的大小。对象指针后面跟着三个处理销毁,释放和克隆的句柄。请注意,在PHP中,销毁和释放对象是不同的步骤,在某些情况下会跳过前者(“unclean shutdown”)。实际上克隆句柄几乎不会被用到。因为这些存储句柄不是普通对象句柄的一部分(无论出于何种原因),所以它们将为每个对象复制对象,而不是共享。

这些对象存储处理程序后跟一个指向普通对象处理程序的指针。如果在未知zval的情况下销毁对象,则这些对象会被存储起来(通常存储handlers)。

该存储桶还包含一个引用计数,考虑到在PHP5中zval已经存储了引用计数,这就有点奇怪了。为什么要弄两个呢?原因在于,虽然通常仅通过增加其引用来“复制”zval,但也存在发生硬拷贝的情况,即使用相同的zend_object_value分配全新的zval。在这种情况下,两个不同的zval最终使用相同的对象存储桶,因此它也需要重新计算。这种“双重引用计数”是PHP5 zval实现的固有问题之一。出于类似的原因,GC根缓冲区的缓冲指针也会复制。

现在让我们看一下对象存储所指向的实际对象。对于普通用户端对象,它定义如下:

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards;
} zend_object;

zend_class_entry是指向此对象实例的类的指针。下面两个成员是用于存储对象属性的两种不同方式。对于动态属性(即在运行时添加但未在类中声明的属性),使用属性哈希表,它只是属性的键值映射(混淆)而已。

但是对于声明的属性,使用了优化:在编译期间,为每个此类属性分配一个索引,并将该属性的值存储在properties_table中的该索引处。属性名称及其索引之间的映射存储在类实体的哈希表中。因此,对于各个对象,避免了哈希表的内存开销。此外,属性的索引在运行时以多态方式缓存。

guards哈希表用于实现像__get这样的魔术方法的递归行为,在这暂不讨论。

除了之前提到的双重引用计数问题之外,对单属性的小对象(不计算zval),内存使用占136个字节也太严重。此外还有很多其它问题,例如,要获取对象zval上的属性,首先必须获取对象存储桶,然后是zend对象,然后是属性表,再然后是它指向的zval。因此,至少已经有四个间接层次(实际上不少于七个)。

PHP7 里的对象(Objects in PHP7)

PHP7试图通过消除双重引用计数,减少一些内存膨胀和减少间接性来改进这些问题。下面是新的zend_object结构:

struct _zend_object {
    zend_refcounted   gc;
    uint32_t          handle;
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable        *properties;
    zval              properties_table[1];
};

请注意,此结构现在(几乎)是对象剩下的所有内容:zend_object_value已被替换为指向对象和对象存储的直接指针,虽然没有完全消失,但也不太重要了。

除了现在包括惯常的zend_refcounted标头之外,您还可以看到对象值的句柄(handle)和处理程序(handlers)已移动到zend_object中。此外,properties_table现在也使用"struct hack",因此zend_object和属性表将分配在一个块中。当然,属性表现在直接嵌入zvals,而不是包含指向它们的指针。

guards表不再直接存在于对象结构中。取而代之的是,如果需要,比如对象使用__get等,它将被存储在第一个properties_table槽中。但是如果不使用这些魔术方法,则省略了guards表。

先前存储在对象存储桶中的dtorfree_storageclone处理程序现已移动到处理程序表中,结构如下:

struct _zend_object_handlers {
    /* offset of real object header (usually zero) */
    int                                     offset;
    /* general object functions */
    zend_object_free_obj_t                  free_obj;
    zend_object_dtor_obj_t                  dtor_obj;
    zend_object_clone_obj_t                 clone_obj;
    /* individual object functions */
    // ... rest is about the same in PHP 5
};

在处理程序表的顶部是一个偏移成员,它显然不是一个句柄。此偏移量与内部对象的表示方式有关:内部对象始终嵌入标准zend_object,但通常还会添加许多其他成员。在PHP5中,是通过在标准对象之后添加它们来完成的:

struct custom_object {
    zend_object std;
    uint32_t something;
    // ...
};

这意味着如果你得到一个zend_object*,你可以简单地将它转换为你自定义的struct custom_object*。这是在C中实现结构继承的标准方法。但是在PHP7中这种特殊方法存在问题:因为zend_object使用struct hack来存储属性表,PHP存储的属性将超过zend_object尾部,从而覆盖其他内部成员。这就是为什么在PHP7中,其他成员存储在标准对象之前:

struct custom_object {
    uint32_t something;
    // ...
    zend_object std;
};

但是,这意味着不再可能使用简单的强制转换直接在zend_object*struct custom_object*之间进行转换,因为两者都由偏移量分隔。此偏移量是存储在对象处理程序表的第一个成员中的。在编译时,可以使用offsetof()宏来确定偏移量。

你可能想知道为什么PHP7对象仍然包含句柄。毕竟,我们现在直接存储一个指向zend_object的指针,因此我们不再需要用句柄来查找对象存储中的对象。

然而,仍然必需句柄,因为对象存储仍然存在,尽管形式明显减少。它现在是一个指向对象的简单数组。创建对象时,指向它的指针会在句柄索引处插入对象存储区,并在释放对象后删除。

为什么我们仍然需要对象存储?原因是,在请求关闭期间,运行用户端代码不安全,因为执行程序已经部分关闭。为避免这种情况,PHP将在关闭期间的开始运行所有对象的析构函数,并防止它们在以后的某个时间点运行。为此需要所有活动对象的列表。

此外,句柄对于调试很有用,因为它为每个对象提供了唯一的ID,因此很容易看出两个对象是否真的相同或只是具有一些相同的内容。尽管没有对象存储的概念,HHVM仍然存储对象句柄。

与PHP5实现相比,我们现在只有一个refcount(因为zval本身不再有)并且内存使用量要小得多:我们需要40个字节用于基础对象,16个字节用于每个声明的属性,而且已经包括它zval的了。间接使用量也显著减少,因为许多中间结构被丢弃或嵌入。因此,读取性情现在只是一个一级的连接,而不是四个。

Indirect zvals

此时我们已经涵盖了所有普通的zval类型,但是有一些特殊类型仅在某些情况下使用。PHP7中新添加了一个zval--IS_INDIRECT

间接zval表示其值存储在某个其他位置。注意这与IS_REFERENCE类型的不同之处在于它直接指向另一个zval,而不是嵌入zval的zend_reference结构。

为了理解在什么情况下这种方式可能是必要的,请参考PHP变量的实现(尽管同样适用于对象属性存储)。

编译时已知的所有变量都被赋予索引,它们的值将存储在编译变量(compiled variables CV)表中的该索引处。然而,PHP还允许你通过使用可变变量来动态引用变量,或者如果你在全局范围内,则通过$GLOBALS动态引用变量。如果发生这样的访问,PHP将为函数或者脚本创建一个符号表,其中包含健值映射。

这导致了一个问题:如何同时支持两种形式的访问?我们需要基于表的CV访问以进行常规变量提取和基于symtable的varvars访问。在PHP5中,CV表使用了双向间接的zval**指针。通常这些指针指向实际的zvals的第二个zval*指针表:

+------ CV_ptr_ptr[0]
| +---- CV_ptr_ptr[1]
| | +-- CV_ptr_ptr[2]
| | |
| | +-> CV_ptr[0] --> some zval
| +---> CV_ptr[1] --> some zval
+-----> CV_ptr[2] --> some zval

现在,一旦符号表开始使用,第二个包含单个zval*指针表就不再使用了,并且zval**指针被更新为指向哈希表桶。这里假设三个变量分别为$a$b$c

CV_ptr_ptr[0] --> SymbolTable["a"].pDataPtr --> some zval
CV_ptr_ptr[1] --> SymbolTable["b"].pDataPtr --> some zval
CV_ptr_ptr[2] --> SymbolTable["c"].pDataPtr --> some zval

在PHP7中,已经不可能使用相同的途径,因为当调整哈希表的大小时,指向哈希表桶的指针将失效。另外,PHP7使用了反向策略:对于存储在CV表中的变量,符号哈希表将包含指向CV条目的INDIRECT条目。CV表不会在符号表的生命周期内重新分配,所以无效指针就没有问题。

因此,如果你有一个包含CV变量$a$b$c以及一个动态创建的变量$d的函数,符号表可能看起来像这样:

SymbolTable["a"].value = INDIRECT --> CV[0] = LONG 42
SymbolTable["b"].value = INDIRECT --> CV[1] = DOUBLE 42.0
SymbolTable["c"].value = INDIRECT --> CV[2] = STRING --> zend_string("42")
SymbolTable["d"].value = ARRAY --> zend_array([4, 2])

间接zvals也可以指向IS_UNDEFzval,在这种情况下,它被视为哈希表不包含它关联的键。因此,如果unset($a)会把UNDEF类型写入CV[0],然后这就被看作符号表不再具有键“a”了。

Constands and ASTs

在PHP5和PHP7中还有两种特殊类型IS_CONSTANTIS_CONSTANT_AST值得一提。要了解这些操作,请参考以下示例:

function test($a = ANSWER,
              $b = ANSWER * ANSWER) {
    return $a + $b;
}

define('ANSWER', 42);
var_dump(test()); // int(42 + 42 * 42)

函数test()的参数的默认值是常量ANSWER-但是在声明函数时尚未定义此常量。只有在define()定义后,该常量才能使用。

因此,参数和属性默认值,常量和接受“静态表达式”的所有其他内容都能够推迟表达式的检测,直到首次使用的时候。

如果该值是常量(或类常量),这是后期检测的最常见情况,则使用具有常量名称的IS_CONSTANTzval来发信号通知。如果值是表达式,则使用指向抽象语法树(abstract syntax tree AST)的IS_CONSTANT_ASTzval。

什么是Dirty Hack?

什么是Dirty hack?

答案来自知乎,感觉生动有趣,所以摘抄过来,以做保留!


以不符合设计原理 / 不易维护 / 不易调整 / 不够健壮 / 不够美观的方式解决问题。
比如水管连接处生了锈开始漏——

  • 把水管系统整个重新布置成没有接头的管线,叫做 refactor
  • 按原样把锈掉的水管换新的,叫做 proper fix
  • 把水管拆下来用防渗胶带缠住螺丝纹再装回去,叫做 patch
  • 叫你女朋友先把漏水的地方捂住然后下面放个脸盆接漏水,叫做 monkey patch
  • 用电焊把接头焊起来,叫做 hack
  • 用口香糖塞住漏缝然后用水泥把接头浇筑起来,结果因为那一大坨太重,下面不得不放一根木棍撑着,叫做 dirty hack

Dirty hack 不一定总是坏事,如果你没有脸盆、电焊、管钳、女朋友、新水管和防渗胶带,而这套水管系统反正就快整个报废了的话。


巨硬(微软)的生动展示:
ms_dirty_hack


常规答案:

我觉得上面的例子非常生动,但有点脱离软件开发的场景了。
Dirty hack,首先是hack。
hack就是用非常规的手段搞定(不是解决)某个问题。
比如说有段代码是这样的:a/b
当b为0的时候,这段代码就会报错。
正确的fix是避免b为0。
hack就是这样:

try {
    a/b
}

dirty hack就是在这段代码下面写上: catch{}


:
前方高能,重口味答案:

假设一个女的得了直肠癌——

  • 移植一段正常直肠叫 fix
  • 在肚子上开个口再挂个袋子叫 workaround
  • 人工直肠叫 hack
  • 在阴道上打个洞叫大便从那里出去才叫真正的 DIRTY HACK ~~~
    -_-

修改FTP用户密码

记得ftp账号,但不记得密码怎么办?

由于需要在wordpress站点里安装插件,但是ftp密码忘记了,所以需要修改ftp密码。

  1. 切换到root用户下:sudo su
  2. 更新密码:passwd bobo(ftp用户名),可以先cat /etc/shadow查看一下此用户是否存在
  3. 提示:
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully

修改完成。

PHP7变量的内部实现(一)

注:
1. 转载自这,方便留存查看
2. 转载文章翻译自Nikita的文章,欢迎指正查看原文
3. 我准备翻译第二部分


受篇幅限制,这篇文章将分为两个部分。本部分会讲解PHP5和PHP7在zval结构体的差异,同时也会讨论引用的实现。第二部分会深入探究一些数据类型如string和对象的实现。

PHP5中的zval
PHP5中zval结构体的定义如下:

typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
} zval;

可以看到,zval由value、type和一些额外的__gc信息组成。__gc与垃圾回收相关,我们稍后讨论。value是一个共用体,可以存储一个zval各种可能的值。

typedef union _zvalue_value {
    long lval;                 // For booleans, integers and resources
    double dval;               // For floating point numbers
    struct {                   // For strings
        char *val;
        int len;
    } str;
    HashTable *ht;             // For arrays
    zend_object_value obj;     // For objects
    zend_ast *ast;             // For constant expressions
} zvalue_value;

C语言中,共用体的尺寸与它最大的成员尺寸相同,在某一时刻只能有一个成员处于活动状态。共用体所有的成员都存储在相同的内存,根据你访问的成员不同,内容会被解释成不同的类型。以上面的共用体为例,如果访问lval,值将被解释为一个有符号整型;而访问dval将被解释成双精度浮点型。以此类推。

为了弄清结构体中哪个成员处于活动状态,zval会存储一个整型type来标识具体的数据类型。

#define IS_NULL     0      /* Doesn't use value */
#define IS_LONG     1      /* Uses lval */
#define IS_DOUBLE   2      /* Uses dval */
#define IS_BOOL     3      /* Uses lval with values 0 and 1 */
#define IS_ARRAY    4      /* Uses ht */
#define IS_OBJECT   5      /* Uses obj */
#define IS_STRING   6      /* Uses str */
#define IS_RESOURCE 7      /* Uses lval, which is the resource ID */

/* Special types used for late-binding of constants */
#define IS_CONSTANT 8
#define IS_CONSTANT_AST 9

PHP5中的引用计数
除少数例外,在PHP5中zval都是分配在堆内存的,PHP需要通过某种方式跟踪哪些zval在被使用,哪些应该被释放。为达到这个目的,引用计数被使用。引用计数即在结构体中用refcount__gc成员来记录该结构体被“引用”了多少次。例如,在$a = $b = 42中,42被两个变量引用,所以它的引用计数为2。如果引用计数变成0,则意味着该值没被使用,可以被释放。

需要注意的是引用计数的“引用”(即一个值被引用的次数)与“PHP引用”($a=&$b)毫无关系。在接下来的内容里,我会始终使用“引用”和“PHP引用”这两个术语来释疑这两个概念。就当前来说,我们先把“PHP引用”放在一边。

与引用计数密切相关的一个概念是“写时复制”(copy on write):zval只能在其内容未被修改的时候才能在多个变量间共享。要实现修改,zval必选被复制(分离),而改动只能在复制出的zval上进行。

以下例子展示了写时复制和zval销毁。

$a = 42;   // $a         -> zval_1(type=IS_LONG, value=42, refcount=1)
$b = $a;   // $a, $b     -> zval_1(type=IS_LONG, value=42, refcount=2)
$c = $b;   // $a, $b, $c -> zval_1(type=IS_LONG, value=42, refcount=3)

// 下一行操作会导致zval分离
$a += 1;   // $b, $c -> zval_1(type=IS_LONG, value=42, refcount=2)
           // $a     -> zval_2(type=IS_LONG, value=43, refcount=1)

unset($b); // $c -> zval_1(type=IS_LONG, value=42, refcount=1)
           // $a -> zval_2(type=IS_LONG, value=43, refcount=1)

unset($c); // zval_1 被销毁,因为其refcount=0
           // $a -> zval_2(type=IS_LONG, value=43, refcount=1)

引用计数有一个致命缺陷:它不能检测和释放循环引用。为解决这个问题,PHP额外使用了环收集器。当一个zval的引用计数减少的时候,它就有一定几率是循环引用的一部分,该zval就被写入到“根缓冲区”。当根缓冲区满后,可能的引用环将被标记并收集,同时启动垃圾回收。

为了支持这个环收集器,实际使用了如下的zval结构体:

typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;
} zval_gc_info;

zval_gc_info结构体内置了普通zval和一个指针-注意u是一个共用体,也就是说实际上只有一个指针,它可能指向两种不同的类型。buffered指针用来存储zval在根缓冲区中的引用位置,如果zval在环收集器运行之前就被销毁(这是非常可能的),那么该指针将会从根缓冲区移除。next指针在收集器销毁值的时候会被用到,但是我不会深入讲解这一点。

改进的动机
先讨论一下基于64位系统的内存占用。首先,zvalue_value共用体占用16个字节,因为它的str和obj成员都那么大。整个zval结构体一共24个字节(由于内存对齐[padding]),而zval_gc_info是32字节。除此之外,在堆分配的过程中,又增加了16字节的分配开销。由此一个zval就占用48字节--尽管该zval可能在多个地方都被用到。

现在我们就可以分析下这种zval实现方式低效的地方。考虑用zval存储整数的情况,整数占用8个字节,另外类型标示是必需的,它本身占用一个字节,但是由于内存对齐,实际上就要加上8个字节。

这16字节是我们真正“需要”的空间(近似的),此外,为了处理引用计数和垃圾回收,我们增加了16字节;由于分配开销又增加了另外16字节。更不用提还要处理分配和后续的释放,这都是很昂贵的操作。

由此引发了一个问题:一个简单的整数真的需要存储为一个有引用计数、可垃圾回收,并且是堆分配的值吗?答案当然是不需要,这样做是没道理的。

以下概述了PHP5中zval实现方式的一些主要问题:

zval(几乎)总是需要堆分配。
zval总是会被引用计数且携带环收集信息,即使是在共享值不划算(比如整数)和不能形成引用环的情况下。
当处理对象和资源时,直接对zval进行引用计数会导致双重计数。原因会在下一部分讨论。
某些情况会引入很多的间接操作。比如为了访问一个对象,一共要进行4次指针跳转。这也将在下一篇中分析。
直接对zval进行引用计数意味着值只能在zval间共享。比如我们不能在zval和哈希表key之间共享一个字符串(不将哈希表key用zval变量存放)。
PHP7中的zval
通过以上讨论,我们引进了PHP7新的zval实现。最根本的改变是zval不再是堆分配且它自身不再存储引用计数。相反的,对zval指向的任何复杂类型值(如字符串、数组、对象),这些值将自己存储引用计数。这有以下优点:

简单值不需要分配且不用引用计数。
不再有双重引用计数。对对象来说,只有在对象本身存在引用计数。
由于引用计数保存在值中,这个可以独立于zval结构而被复用。同一个字符串能同时被zval和哈希表key引用。
间接操作少了很多,也就是说在获取一个值的时候需要跳转的指针数量变少了。
新的zval定义如下:

struct _zval_struct {
    zend_value 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;
    } u1;
    union {
        uint32_t var_flags;
        uint32_t next;                 // hash collision chain
        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
    } u2;
};

第一个成员跟之前类似,也是一个value共同体。第二个成员是个整数,用来存储类型信息,它被一个共用体分隔成独立的字节空间(可忽略ZEND_ENDIAN_LOHI_4宏,它是用来保证在不同字节序平台上布局的一致性)。这个子结构中type(它跟之前类似)和type_flags比较重要,我将稍后讨论他们。

此时有一个小问题:value成员占8字节空间,由于结构体内存对齐,即使增加一个字节也会让zval内存增长到16字节。然而很明显我们不需要8个字节来仅仅存放类型信息。这就是为什么此zval包含了一个额外的u2共用体,它默认情况下是没被占用的,但是却可以根据需要存储4字节的数据。这个共用体中不同的成员用来实现该额外数据片段不同的用途。

PHP7中的value共用体看起来略有不同:

typedef union _zend_value {
    zend_long         lval;
    double            dval;
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;

    // Ignore these for now, they are special
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        ZEND_ENDIAN_LOHI(
            uint32_t w1,
            uint32_t w2)
    } ww;
} zend_value;

首先要注意到这个共用体占用8字节而不是16字节。它仅仅会直接存储整数(lval)和双精度浮点数(dval),对其它类型它都会存储对应指针。所有的指针类型(除了什么代码中标记为特殊的)都会引用计数并且有一个通用的头部,定义为zend_refcounted:

struct _zend_refcounted {
    uint32_t refcount;
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,
                uint16_t      gc_info)
        } v;
        uint32_t type_info;
    } u;
};

不用说这个结构会包含引用计数。另外,它还包含type、flags和gc_info。type是复制的zval的type,它使得GC在不存储zval的情况下就能区分不同的引用计数结构。根据类型的不同,flags有不同的使用目的,这些会在下一部分按类型分别讨论。

gc_info等同于老zval中的buffered成员。不同的是它存储了在根缓冲区中的索引,来代替之前的指针。因为跟缓冲区尺寸固定(10000个元素),用16字节的数子而不是64位的指针就足够了。gc_info还含有该节点的“颜色”信息,这在垃圾回收中用来标记节点。

zval内存管理
我已经提到zval不再是单独的堆分配。然而很明显它仍然需要被存在某个地方,那么这是怎么实现的呢?尽管zval大多数时候仍是堆分配数据结构的一部分,不过它们是直接嵌入到这些数据结构中的。比如哈希表就会直接内置zval而不是存放一个指向另一zval的指针。函数的编译变量表或者对象的属性表会直接保存为一个拥有连续内存的zval数组,而不再存储指向散落各处zval的指针。因此当前的zval存储通常都会少了一层的间接引用,也就是说现在的zval相当于之前的zval*。

当一个zval在新的地方被引用时,按照之前的方式,就意味着要复制zavl*并增加它的引用计数。现在则需要复制zval的内容,同时如果该zval指向的值用到引用计数的话则还要增加该值的引用计数。

PHP是如何知道一个值是否用到引用计数的呢?这不能仅仅依靠类型来判断,因为有些类型比如字符串和数组并不总是引用计数的。相反的,会根据构成zval的type_info的一个字节来判断是否引用计数。另外还有其它几个字节编码了该类型的一些特征。

#define IS_TYPE_CONSTANT            (1<<0)   /* special */
#define IS_TYPE_IMMUTABLE           (1<<1)   /* special */
#define IS_TYPE_REFCOUNTED          (1<<2)
#define IS_TYPE_COLLECTABLE         (1<<3)
#define IS_TYPE_COPYABLE            (1<<4)
#define IS_TYPE_SYMBOLTABLE         (1<<5)   /* special */

一个类型能拥有的三个主要特征是引用计数、可回收和可复制。引用计数的含义已讨论过,可回收意味着该zval可能参与循环引用。举例来说,字符串(通常)是引用计数的,但是却没法用字符串构造一个引用环。

可复制性决定了在为一个变量创建“副本”的时候它的值是否需要执行拷贝。副本是硬拷贝,比如复制指向数组的zval时,就不是简单的增加数组的引用计数,而是要创建该数组的一个新的独立拷贝。然而对对象和资源这些类型来说,复制应该仅仅增加引用计数--这些类型就是所谓的不可复制。这与对象和资源在进行传递时的语义相符(当前不是引用传递)。

以下表格展示了不同类型和它们所用的标识。“简单类型”指整数和布尔值这类不需要用指针指向一个单独结构的类型。同时还用一列展示了“不可变”标记,它用来标记不可变数组,这将在下一部分详细讨论。

refcounted collectable copyable immutable
simple types
string x x
interned string
array x x x
immutable array x
object x x
resource x
reference x

来看一下在实际中zval管理是如何工作的。先基于上文PHP5的例子来讨论一下整型实现:

$a = 42;   // $a = zval_1(type=IS_LONG, value=42)

$b = $a;   // $a = zval_1(type=IS_LONG, value=42)
           // $b = zval_2(type=IS_LONG, value=42)

$a += 1;   // $a = zval_1(type=IS_LONG, value=43)
           // $b = zval_2(type=IS_LONG, value=42)

unset($a); // $a = zval_1(type=IS_UNDEF)
           // $b = zval_2(type=IS_LONG, value=42)

这个例子挺无趣的。简单来说就是整型不会再被共用,这些变量都有单独的zval。不要忘了zval不再需要单独分配,它们是内嵌的,我通过把->换成=来表示这种变化。unset一个变量会把对应zval的type设置为IS_UNDEF。现在来考虑一下当涉及复杂类型时的情况,这种案例有趣的多。

$a = [];   // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])

$b = $a;   // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=2, value=[])
           // $b = zval_2(type=IS_ARRAY) ---^

// zval在这里发生了分离
$a[] = 1   // $a = zval_1(type=IS_ARRAY) -> zend_array_2(refcount=1, value=[1])
           // $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])

unset($a); // $a = zval_1(type=IS_UNDEF) and zend_array_2 is destroyed
           // $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])

本例中每个变量依然有单独的zval(内嵌的),但是这些zval都指向了同一个zend_array(引用计数的)结构。同PHP5一样,当发生修改时,数组需要被复制。

类型
看一下PHP7是如何支持各种数据类型的:

// 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

// internal types
#define IS_INDIRECT                 15
#define IS_PTR                      17

这个列表跟PHP5类似,但有一些内容增加:

IS_UNDEF类型替代了之前的NULL zval指针(注意与IS_NULL zval区分),比如在上面引用计数的例子中,变量被unset时,zval的类型就被置为IS_UNDEF。
IS_BOOL类型被细分成了IS_FALSE和IS_TRUE。由此布尔变量的值就被编码在类型中,这就使得一些基于类型检查的优化成为可能。这个改变对用户层是透明的,仍然有一个“布尔”类型。
在zval上,PHP引用不再使用is_ref标识,而是用IS_REFERENCE类型。下一部分将会讨论。
IS_INDIRECT和IS_PTR是特殊的内部类型。
IS_LONG目前存储的是zend_long类型的值,而不是一个普通的C语言long整数。原因是在64位windows(LLP64)上,long型只有32位,于是在windows上PHP5的IS_LONG总是32位的。在64位操作系统上,即使你使用的是windows,PHP7都允许你使用64位的数字。

zend_refcounted类型相关的细节将在下一部分讨论,现在我们先看一下PHP引用的实现。

引用
PHP7处理PHP引用(&)的方式与PHP5完全不同(我可以告诉你这个改变是PHP7最大的bug来源之一)。PHP5中引用的实现如下:

通常,写时复制(COW)机制意味着在修改之前,zval要先进行分离,以保证不会把其它共用该zval的变量给一起修改了。这与值传递的语义相符。

对PHP引用来说,就不是这种情况了。如果一个值是引用,那么修改的时候就希望其它变量也同步被修改。PHP5用is_ref来判断一个值是不是PHP引用,以及在修改的时候是否要执行分离操作。看一个例子:

$a = [];  // $a     -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[])
$b =& $a; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[])

$b[] = 1; // $a = $b = zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[1])

这种设计一个很重大的问题就是不能在普通变量和PHP引用之前共享一个值。考虑如下情形:

$a = [];  // $a         -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[])
$b = $a;  // $a, $b     -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])
$c = $b   // $a, $b, $c -> zval_1(type=IS_ARRAY, refcount=3, is_ref=0) -> HashTable_1(value=[])

$d =& $c; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])
          // $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[])
          // $d是$c的引用, 但不是$a和$b的引用,所以zval要复制。
          //现在就有了相同的zval,一个is_ref=0,一个is_ref=1

$d[] = 1; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])
          // $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[1])
          // 由于有两个独立的zval $d[] = 1 不会修改到$a和$b.

这种行为就导致使用PHP引用通常比普通变量更慢。下面的例子就有这个问题:

$array = range(0, 1000000);
$ref =& $array;
var_dump(count($array)); // <-- 这里发生zval分离

因为count()的参数是按值传递的,而$array是一个引用变量,在把它传递给count()时,会对该数组执行完整的复制。如果$array不是引用,它的值就可以共用,在传递的时候就不会发生复制。

现在来看下PHP7中引用的实现。由于zval不再是独立分配,不再可能使用PHP5一样的方式。转而增加了IS_REFERENCEl类型,它的值是如下的zend_reference结构:

struct _zend_reference {
    zend_refcounted   gc;
    zval              val;
};

所以zend_reference本质上只是一个有引用计数的zval。在一个引用集合中所有的变量都会保存一份IS_REFERENCEl类型的zval,并且指向同一个zend_reference实例。val跟其他zval类似,特别是它可以共享其指向的复杂值。比如数组可以在普通变量和引用变量之间共享。

还是上面的示例代码,来看一下在PHP7下的情形。为了简洁性,我不会再写变量的zval,只展示它们指向的值。

$a = [];  // $a                                     -> zend_array_1(refcount=1, value=[])
$b =& $a; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[])

$b[] = 1; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[1])

引用赋值会创建一个zend_reference,该引用的引用计数是2(有两个变量用到了这个引用),但是值本身的引用计数是1(只有一个zend_reference指向了该值)。再考虑下引用变量和普通变量混合的情况:

$a = [];  // $a         -> zend_array_1(refcount=1, value=[])
$b = $a;  // $a, $b,    -> zend_array_1(refcount=2, value=[])
$c = $b   // $a, $b, $c -> zend_array_1(refcount=3, value=[])

$d =& $c; // $a, $b                                 -> zend_array_1(refcount=3, value=[])
          // $c, $d -> zend_reference_1(refcount=2) ---^
          // 注意所有的变量共享同一个zend_array, 即使有的是引用,有的不是。

$d[] = 1; // $a, $b                                 -> zend_array_1(refcount=2, value=[])
          // $c, $d -> zend_reference_1(refcount=2) -> zend_array_2(refcount=1, value=[1])
          // 只有当赋值发生的时候,zend_array才会复制,即写时分离。

与PHP5一个重要的不同是所有的变量都能共享同一个数组,即使有的是引用变量有的不是。只有当进行修改的时候才会发生分离。这意味着在PHP7中把一个很大的引用数组传递给count()是安全的,因为不会复制。但是引用仍然会比普通变量慢,因为需要分配zend_reference结构(以及由此产生的间接操作),而且机器码处理起来也不会很快。

总结

总的来说,PHP7主要的改变是zval不再是独立的堆分配且其本身不再存储引用计数。转而是它们指向的复杂类型的值(如字符串、数组、对象)会存储引用计数。这通常会带来更少的内存分配、间接操作和内存使用。

下一部分将会讨论其它复杂类型。

为什么有的地方拍卖价格是从高到低?

在我们常见的拍卖会上,一件物品的价格往往是由低向高叫价,但有些地方也会使用另一种方式,拍卖价由高往低叫,那你知道他们为什么要这么做吗?

要弄明白这个,就需要理解什么叫消费者剩余了。

假设你现在有一张纪念邮票,因为你不是邮票爱好者,所以你打算把邮票卖出,卖出的一种方法是举行一场拍卖会。

现在有四个集邮爱好者出现在拍卖会上:张三,李四,王五,赵六。

他们的支付意愿为:

买者 支付意愿(人民币)
张三 100
李四 80
王五 70
赵六 60

支付意愿:买者愿意为某种物品支付的最高价。

为了卖出你的纪念邮票,你从一个低价格开始叫价,比如30元。当价叫到80元(或略高一点)时,李四、王五、赵六都不愿意出更高的价格,这时张三80元获得了这张邮票。

在某种意义上来说,张三做了一笔划算的生意,本来他愿意为这张邮票出100元,而现在他只出了80元。对此我们说,张三获得了20元的消费者剩余

消费者剩余是指买者愿意为一种物品支付的价格减去其为此实际支付的价格。

在上个例子中,张三获得了20元的消费者剩余,而其他三人没有获得消费者剩余,因为他们没有得到邮票,也没有花一分钱。

现在我们把上面的情况修改一下,假如你有两张邮票,那么当张三与李四喊出70元(或略高一点)时,王五与赵六放弃,这时张三与李四花70元获得了邮票,那么张三的消费者剩余是30元,李四也获得了10元的消费者剩余

消费者剩余增加了,但你获得的利益减少了。理解了这个,我们现在看另一种拍卖方式,价格从高往低叫价:

你现在从200元开始叫价,当减少到100元时,张三愿意支付100元,交易成立,张三没有获得消费者剩余,而你获得的利益增多。这就是为什么很多地方会使用这种方式拍卖。

这种情况适用于拍卖物具有独特性,而且这也是一种理想情况,现实情况往往复杂的多。

读《经济学原理(微观)》第7章有感。

debugfs使用简述

知道具体文件,查看文件所存放的block号:
  1. 比如我现在要查看/home/sting/.Xauthority文件所占的block号,df -h 查看分区对应的系统目录
    Filesystem      Size  Used Avail Use% Mounted on
    udev            3.9G     0  3.9G   0% /dev
    tmpfs           789M   42M  748M   6% /run
    /dev/sda1        47G   32G   13G  72% /
    tmpfs           3.9G  423M  3.5G  11% /dev/shm
    tmpfs           5.0M  4.0K  5.0M   1% /run/lock
    tmpfs           3.9G     0  3.9G   0% /sys/fs/cgroup
    /dev/loop2      207M  207M     0 100% /snap/ubuntu-app-platform/34
    /dev/loop1       82M   82M     0 100% /snap/core/4206
    /dev/loop0       82M   82M     0 100% /snap/core/4017
    /dev/loop4       62M   62M     0 100% /snap/go/1473
    /dev/loop3       82M   82M     0 100% /snap/core/4110
    /dev/loop5       11M   11M     0 100% /snap/ipfs/335
    /dev/sda2       465M  396M   41M  91% /boot
    /dev/sda5       179G   59G  112G  35% /home
    /dev/sda4       976M  3.5M  972M   1% /boot/efi
    tmpfs           789M  140K  789M   1% /run/user/1000
    
  2. sudo debugfs /dev/sda5
    [sting@sting-snds]
    [:~]$ sudo debugfs /dev/sda5
    [sudo] password for sting:
    debugfs 1.42.13 (17-May-2015)
    debugfs:  pwd
    [pwd]   INODE:      2  PATH: /    # 由上一步所知,这时你所在的目录其实在/home下
    [root]  INODE:      2  PATH: /
    debugfs:  stat /sting/.Xauthority # 查看这个文件的信息,它的实际路径其实是 /home/sting/.Xauthority
        # 显示如下:
        Inode: 10485770   Type: regular    Mode:  0600   Flags: 0x80000
        Generation: 2100926769    Version: 0x00000000:00000001
        User:  1000   Group:  1000   Size: 55
        File ACL: 0    Directory ACL: 0
        Links: 1   Blockcount: 8
        Fragment:  Address: 0    Number: 0    Size: 0
         ctime: 0x5ab84844:5b09ecdc -- Mon Mar 26 09:09:24 2018
         atime: 0x5ac2db93:a9a86890 -- Tue Apr  3 09:40:35 2018
         mtime: 0x5ab84844:5b09ecdc -- Mon Mar 26 09:09:24 2018
        crtime: 0x599bcd3f:1183b45c -- Tue Aug 22 14:20:47 2017
        Size of extra inode fields: 32
        EXTENTS:
        (0):43769344  # 文件对应的块号
        (END)
    
知道block号,查看block对应的具体文件:
  1. 根据块号查找分区。查看分区情况,sudo fdisk -lu
    Device         Start       End   Sectors   Size Type
    /dev/sda1       2048  99999743  99997696  47.7G Linux filesystem
    /dev/sda2   99999744 100999167    999424   488M Linux filesystem
    /dev/sda3  100999168 117000191  16001024   7.6G Linux swap
    /dev/sda4  117000192 119001087   2000896   977M EFI System
    /dev/sda5  119001088 500117503 381116416 181.7G Linux filesystem  # End - Start = Sectors
    

    Block(4KB) = 8 * Sectors(0.5KB)b = (int)((L-S)*512/B)

    where:
    b = File System block number
    L = LBA(Logical Block Address)
    S = Starting sector of partition as shown by fdisk -lu and (int) denotes the integer part
    B = File system block size in bytes

    可知: L-S = b / (512 / B) = 43769344 / (512 / 4096) = 350154752。所以, L = 350154752 + 119001088('注:此值为/dev/sda5 Start位置') = 469155840,判断这个块号应该是在/dev/sda5里,在Start到End区间内(如果判断不了,使用下一步试一下也能知道)。

  2. 使用debugfs获取对应具体文件信息,sudo debugfs /dev/sda5

    [sting@sting-snds]
    [:~]$ sudo debugfs /dev/sda5
    [sudo] password for sting:
    debugfs 1.42.13 (17-May-2015)
    debugfs:  icheck 43769344  #如果不在这个分区,会提示 <block not found>
    Block   Inode number
    43769344    10485770
    debugfs:  ncheck 10485770
    Inode   Pathname
    10485770    /sting/.Xauthority # 文件路径
    debugfs:  q
    

archlinux下i3-wm突然登录不进去问题

今天i3-wm突然登录不进去问题(2018-03-31)

  1. 每次输入密码登录之后又回到登录界面,完全摸不着头脑,也没有看到错误信息。

  2. 后来发现是有错误提示信息,在我ctrl+alt+fx(f1~f6)切入到别的字符界面控制台之后,但是没有仔细研究这个提示,结合dmesg命令之后判断应该是磁盘有坏道造成的。

  3. 关键错误提示:

    Buffer I/O error on dev sda10, logical block 8421888print_req_error: I/O error, dev sda, sector 1449822208 这两点。

  4. 第一条信息是最关键的,可惜我一直没有重视,只看到第二条了。

  5. 看到网上有一篇文章是关于磁盘坏道修复的,使用到的工具为smartctl,安装yaourt -S gsmartcontrol

  6. 根据文章写的步骤,第一步我发现1449822208属于/dev/sda10,这时我发现了我这第3步关键错误信息提示里的与这里的/dev/sda10提示的是一样的,然后结合这篇文章,我有些明白了block 8421888的含义了。

  7. 根据上面文章里的第四步,使用工具debugfs,详见debugfs使用简述,我找到坏道这里损坏的文件是/home/sting/.Xauthority,这时突然就明白了为什么会登录不进去了

  8. Xauthority,是startx脚本记录文件。Xserver启动时,读文件~/.Xauthority,读入对应其display的记录。当一个需要显示的客户程序启动调用XOpenDisplay()也读这个文件,并把找到的magic code 发送给Xserver。当Xserver验证这个magic code正确以后,就同意连接啦。观察startx脚本也可以看到,每次startx运行,都在调用xinit以前使用了xauth的add命令添加了一个新的记录到~/.Xauthority,用来这次运行X使用认证

  9. 知道问题所在了就好办,使用startx自动生成.Xauthority文件

  10. crtl+alt+f7,重新登录果然进来了,问题解决。

总结:

  • 在登录界面登录不进来,不一定是这个问题,但这个问题会导致登录不了,又多了一个解题思路 🙂
  • 要仔细研究系统给出的错误提示信息