————————————————————————————————————————————————————————————————————————————————————

init_logging()(\tor-0.3.1.8\src\common\log.c)内部逻辑如下图所示:

它的任务是初始化 tor 使用的全局日志设施;它首先检测并初始化用于保护日志信息和日志文件的互斥锁(log_mutex),它是一个 tor_mutex_t 对象

—— 请复习前一篇的相关讨论——具体方式为:

判断相同源文件内的全局变量 log_mutex_initialized 的值—— 0 代表日志互斥锁尚未初始化,那么它就调用 tor_mutex_init()

;显然,实际负责初始化 log_mutex 的是 pthread 库例程 pthread_mutex_init() 、并且把状态标记 log_mutex_initialized 置 1 。

涉及到的全局变量如下图:

接下来,它检查一枚全局的 smartlist_t 指针—— pending_cb_messages(基于 CallBack 的日志消息队列)——若该指针为 NULL 则

调用 smartlist_new()(\tor-0.3.1.8\src\common\container.c)来初始化它。

然后检查传入的实参——若为 1,表明禁用启动消息队列,它就把全局变量 queue_startup_messages 修改为 0,意味着在等待配置日志的过程中不保

存消息;事实上,tor_main() 中为该实参传入 0 指示消息应该在早期就记录下来,如下图:

这种情况下,它再次调用 smartlist_new()来分配构建一个 smartlist_t 结构指针并赋给全局变量 pending_startup_messages,用于

处理启动时刻日志消息队列。pending_startup_messages 指向的各类启动消息(待输出)会在日志系统初始化完毕后重新播放出来。

涉及到的全局变量如下图:

如果你已经头晕了,那么我们就梳理一下目前为止讨论到全局变量和用途吧:

log_mutex—— 保护日志文件和消息的互斥锁;

log_mutex_initialized—— log_mutex 是否初始化;

pending_cb_messages—— 一枚 smartlist_t 指针(smartlist_t*),存储基于 CallBack 的日志消息队列,可用 smartlist_new() 初始化它;

pending_startup_messages—— 一枚 smartlist_t 指针,存储启动时刻日志消息队列,可用 smartlist_new() 初始化它;

queue_startup_messages—— 启动时刻是否记录日志消息;

disable_startup_queue—— init_logging() 的形参,调用者借助它来开启或关闭启动时刻消息队列功能;

init_logging() 内部逻辑如下图所示:

tor 实现了一种容量可调整、可存储任意数据类型的智能链表——smartlist,它由结构体 smartlist_t

(\tor-0.3.1.8\src\common\container.h)来表示。“container”亦即容器,该模块抽象了许多数据结构来当成容器,

smartlist 只是其中之一。

smartlist_t 内有一个指针数组(list),每个元素(void*)可以指向任意类型的数据,且元素数量可调整,这就是它叫智能链表的原因

,如下图:

注释里写得很清楚:list 数组的大小可调整,调整后需要更新 capacity 字段(当前容量上限);

num_used 字段记录当前元素数,这些元素(void*)指向有效的数据。你需要意识到一点—— 在 num_used 小于等于 capacity 时,

list 的大小不变;num_used 大于 capacity 时,list 就会动态扩展,同时更新 num_used 和 capacity。

对 smartlist_t 的操作由一些精心编写的例程实施,它们都以 smartlist_ 为前缀,其它模块函数无需知晓智能链表的内部细节,

只需调用这些例程来保证安全、正确地使用智能链表即可,这再次体现出数据抽象和封装、以及接口暴露。。。等高级 C 编程技巧!

这些 smartlist 例程原型如下图所示,分析它们的内部逻辑有助于深入理解智能链表的设计思想:

smartlist_new() 在堆中分配一块内存用于 smartlist_t 结构,然后返回一枚指向该内存块的指针,如下图所示:

可以看到,实际的分配函数是 tor_malloc(),后者封装了系统库函数 malloc(),并实现 tor 自己的安全分配算法;分配的内存大小

亦即结构体 smartlist_t 的大小(应该是 12 字节),该值在编译阶段计算出来。在返回一枚 smartlist_t 指针前,它还会将 num_used 字段设置为零,

表示尚未加入元素;将 capacity 字段设置为 SMARTLIST_DEFAULT_CAPACITY 宏定义的常量值(16),表示初始的容量上限为 16 枚

void 指针(总大小为 16 * 4 = 64 字节);接下来它就会调用 tor_calloc() 实际分配另一块 64 字节大小的堆内存,用于

存储那些 void 指针,并让 list 字段持有该内存块的地址。smartlist_new() 返回后的堆内存布局如下图所示:

我在后面分析其它 smartlist_*() 辅助例程时,会将此图扩展以解释它们的作用。

smartlist_free() 销毁一个智能链表,但它并未实际释放堆中分配的 smartlist_t 结构与 void* 数组占据的内存,如下图:

smartlist_free() 通过调用 tor_free() -> raw_free() -> free() 把传入的 smartlist_t 指针置 NULL 后返回,因此它不会回收

相关的堆内存,如下图所示:

smartlist_add() 往智能链表(void* 数组)内加入新元素。

它首先调用 smartlist_ensure_capacity(),检查当前的容量上限是否允许加入,否则会先动态调大这个 void* 数组的容量后再把新元素

追加到尾部。如下图所示:

注意,它在调用 smartlist_ensure_capacity() 的同时就会通过一枚 smartlist_t 指针递增 num_used 字段值(寓意追加后的

元素数),然后在 smartlist_ensure_capacity() 内部会检查追加后的元素数是否超出当前容量上限,并采取相应措施!

smartlist_ensure_capacity() 处理完毕后,就可以确保作为数组下标的表达式 sl->list[sl->num_used++] 访问到的目标元素在

许可范围内,它被初始化为一枚新的 void 指针,语法解析如下图所示:

假设当前元素数已达初始上限(num_used = capacity = 16),相关的堆内存智能链表布局如下图:

执行 smartlist_add() 调用后的堆内存智能链表布局如下图:

由此可见,smartlist_ensure_capacity() 会按照 2 次幂来扩展 void* 数组的当前容量上限,而 smartlist_add() 在扩展部分

的起始地址处追加元素,扩展部分的其余元素为 NULL,留待后续使用。

现在让我们深入 smartlist_ensure_capacity() 内部研究它的堆内存分配算法,这种每次以 2 的幂扩展的机制在某种程度上类似于

OS 的内核内存分配算法简化版!如下图所示:

smartlist_ensure_capacity() 逻辑要点:

① 它是一个内联(inline)例程,这意味着在编译阶段,编译器会直接将其插入到调用者函数的内部,换言之,当我们反汇编

smartlist_add() 时,不会看到类似“call smartlist_ensure_capacity”这种指令,因为后者的逻辑已经被硬编码至前者内部,

这能够减少函数调用、返回时的栈帧创建、销毁等性能开销!

② 它的第二个参数类型为 size_t(亦即 unsigned int),这是一种安全表示长度、大小的类型(无负值),如前所述,

smartlist_add() 会为该参数传入递增 1 后的值,而 smartlist_ensure_capacity() 会判断这个更新的值是否超出了 void* 数组

的当前上限;

③ 开头部分的一些条件编译块计算 OS/硬件平台支持的 void* 数组最大上限—— SIZE_MAX 定义在平台相关的 limits.h 头文件内,

如下图所示,该头文件位于 Visual Studio 安装目录的 include 子路径下,它的值取决于编译器,比如这里的 0xFFFFFFFF,

就是十进制的 4,294,967,295 ;同理,INT_MAX 的值计算为 2,147,483,647 ;

SIZEOF_VOID_P 的值在 32 位平台上为 4。

经过一系列的预计算,最终得出 MAX_CAPACITY(表示 void* 数组的最大上限,以“元素数”为单位)的值为

1,073,741,823 个元素(SIZE_MAX / (sizeof(void*)),也就是说,void* 数组最大到 4 GB。

④ 用到了 tor_assert() 检查追加后的元素数,当超过最大容量上限时,调用库函数 abort() 终止程序运行;

tor_assert() 在前一篇讲过了。

⑤ 如果追加后的元素数在当前容量上限许可内,smartlist_ensure_capacity() 直接返回,不做任何事,它的调用者

可以安心执行后续操作;如果追加后的元素数超出当前上限,则每次按照 2 的幂为倍数增大当前上限,直到能够容纳

追加后的元素数,并且将扩展部分的内存初始化为零,以供后续使用、还要更新 capacity 字段为新的上限。

在分析源码的算法时,往往言词描述显得苍白无力,还是看看下面这张我绘制的 smartlist_ensure_capacity() 内部逻辑吧,

是不是有点 OS 内核内存分配器的影子?

我们最后再分析一下 smartlist_ensure_capacity() 内部实际负责扩展内存的 tor_reallocarray(),

以及负责清零内存的 memset() 函数调用,作为对本篇的收场,如下图所示:

由此我们推测出 init_logging() 以及相关的组件使用 smartlist 来集中管理日志消息。

下一篇将分析第二个条件编译块前的函数调用——monotime_init()。

——————————————————————————————————————————————————————

------- Tor 源码分析第三部分—— 日志设施与智能链表 --------的更多相关文章

  1. 一个普通的 Zepto 源码分析(三) - event 模块

    一个普通的 Zepto 源码分析(三) - event 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核心模块, ...

  2. Koa源码分析(三) -- middleware机制的实现

    Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: Koa源码分析(一) -- generator Koa源码分析(二) -- co的实现 Koa源码分 ...

  3. Spring MVC源码分析(三):SpringMVC的HandlerMapping和HandlerAdapter的体系结构设计与实现

    概述在我的上一篇文章:Spring源码分析(三):DispatcherServlet的设计与实现中提到,DispatcherServlet在接收到客户端请求时,会遍历DispatcherServlet ...

  4. Backbone源码分析(三)

    Backbone源码分析(一) Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨C ...

  5. 二维码扫描 zxing源码分析(三)result、history部分

    前两个部分的地址是:ZXING源码分析(一)CAMERA部分  . zxing源码分析(二)decode部分 下面我们来看第三部分 result包下面有很多的类,其中的核心类是 com.google. ...

  6. Java集合源码分析(三)Vevtor和Stack

    前言 前面写了一篇关于的是LinkedList的除了它的数据结构稍微有一点复杂之外,其他的都很好理解的.这一篇讲的可能大家在开发中很少去用到.但是有的时候也可能是会用到的! 注意在学习这一篇之前,需要 ...

  7. Spark RPC框架源码分析(三)Spark心跳机制分析

    一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...

  8. mybatis源码分析(三)------------映射文件的解析

    本篇文章主要讲解映射文件的解析过程 Mapper映射文件有哪几种配置方式呢?看下面的代码: <!-- 映射文件 --> <mappers> <!-- 通过resource ...

  9. Docker源码分析(三):Docker Daemon启动

    1 前言 Docker诞生以来,便引领了轻量级虚拟化容器领域的技术热潮.在这一潮流下,Google.IBM.Redhat等业界翘楚纷纷加入Docker阵营.虽然目前Docker仍然主要基于Linux平 ...

随机推荐

  1. B+索引、Hash索引、数据类型长度

    1.为什么在数据库中要用B树索引而不是Hash索引? Mysql Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这 ...

  2. ELK 经典用法—企业自定义日志手机切割和mysql模块

    本文收录在Linux运维企业架构实战系列 一.收集切割公司自定义的日志 很多公司的日志并不是和服务默认的日志格式一致,因此,就需要我们来进行切割了. 1.需切割的日志示例 2018-02-24 11: ...

  3. Windows平台的PHP之开启COM配置

    Windows平台的PHP如果未配置COM,调用COM组件,错误如下 Fatal error: Class 'COM' not found in XXXXXXXXX php 根目录的 ext 文件夹下 ...

  4. 图像采集系统的Camera Link标准接口设计

    高速数据采集系统可对相机采集得到的实时图像进行传输.实时处理,同时实现视频采集卡和计算机之间的通信.系统连接相机的接口用的是Camera Link接口,通过Camera Link接口把实时图像高速传输 ...

  5. Conditional Random Fields (CRF) 初理解

    1,Conditional Random Fields

  6. Java web切面编程

    在我们的 web开发中  我们在 对公用的 一些方法 我们需要抽取出来   这样达到 代码的冗余   今天 我利用项目上用的AOP的 实际 应用做了一个整理 首先  xml配置  扫描 <?xm ...

  7. 命令行工具osql.exe使用

    目标: 快速在21个库修改Test表的某条记录,这几个库都分别在不同的服务器上. 通常会想到,到每个库都执行一下语句不就好了吗?这个数据库切换来切换去,挺麻烦了,通过命令行工具osql.exe就可以快 ...

  8. Linux之shell编程

    一.Bash变量 1) Bash变量与变量分类 1. 定义:变量是计算机内存的单元,其中存放的值可以改变 2. 变量命令规则 #变量名必须以字母或下划线开头,名字中间只能由字母.数字和下划线组成 #变 ...

  9. 在ASP.NET 中检测手机浏览器(转)

    引言 之前做的项目中需要在浏览器查看PDF文件.在电脑端没有问题,但是手机端网页打开失败. 后来使用了pdf.js,个人认为pdf.js的页面不够清爽,就希望网站能自动检测登录设备,电脑端保持原样,手 ...

  10. webpacke踩坑-新手

    1.题叶-webpack入门指南 2.webpack入门系列 3.w3ctech的webpack入门及实践 4.Express结合Webpack的全栈自动刷新 5.webpack 单页面应用实战 6. ...