------ 开源软件 Tor(洋葱路由器,构建匿名网络的方案之一)源码分析——主程序入口点(二)------
——————————————————————————————————————————————————————————
第二部分仅考察下图所示的代码片段——configure_backtrace_handler() 后面的五条函数调用序列;在这些看似简洁的
逻辑背后其实蕴涵乐许多“类 UNIX”系统相关的概念,因此或需要用到整个篇幅来讲解。首先,从这些自注释的函数名称来看,
无非就是更新系统时间的估计值、tor 的线程和压缩功能初始化、日志系统初始化,以及初始化单调定时器子系统(monotime_init):

调用 UNIX 库函数 time(),把返回的当前时间(一个 time_t 结构实例,一般定义在类 UNIX 文件系统的 /user/include/time.h 头文件中)传入
update_approx_time()(\tor-0.3.1.8\src\common\util.c),后者用它来初始化全局(静态)的 cached_approx_time 变量,代表缓存当前时间的估计值,相关代码片段如下:

值得一提,tor 源码树中的“common”路径下包含一些各组件都会利用到的公共设施,比如对类 UNIX 系统机制——pthread——的
封装例程、对 OpenSSL 库的封装例程、对开源事件通知库 libevent 的封装例程。。。。等等。
由于 update_approx_time() 每秒被调用一次,cached_approx_time 也就每秒更新一次,此后就可以随时利用 approx_time() 查询最近一次 update_approx_time() 调用所
更新的 cached_approx_time。
tor 用上述逻辑实现了自己的计时体系,避免在关键路径上直接调用库函数 time(),这是一种编程诀窍(hack)!
除了前述的 Win32 平台相关代码和注册崩溃回调外,update_approx_time() 在任何 tor 组件初始化前就被调用,体现出时间参照的重要性。
——————————————————————————————————————————————————————————————————————————————————————————————
tor_threads_init()(\tor-0.3.1.8\src\common\compat_pthreads.c)设立线程使用的公共结构,然后调用
set_main_thread() -> tor_get_thread_id() -> pthread_self() 把当前线程标记为主线程(存储在全局变量 main_thread_id 内)。
事实上,compat_pthreads.c 中所有的多线程支持函数都依赖于 UNIX/Linux 平台上的 pthread 机制。
而由于本系列博文的目的是考察 tor 的原理和架构,所以我不想试图陷入系统库函数底层代码中分析,相关的代码片段如下:

上图中的 threads_initialized 是一个全局变量,它扮演着一种状态标记的角色,综观 tor 源码随处可见此类编程技巧——
这些状态起初被设置为 0,然后一些相关的例程(名称中带有 init )会通过检测这些标记来判断是否执行过初始化任务,比如
上图中,若 threads_initialized = 0 则调用 pthread 系列的函数来初始化线程用到的一些公共设施(attr_recursive、attr_detached),它们都是一些全局的结构实例,
相关的代码片段如下:

源码里的注释已经写得很清楚了:结构体 pthread_attr_t 表示一个使线程开始分离的 pthread 属性;
结构体 pthread_mutexattr_t 代表一个互斥锁属性,它被指定为“递归”性质的——在已经持有该锁的情况下还可以重新上锁。
如前所述,碰到平台相关的库函数时,如非特别重要,我一般会忽略分析,把精力集中在 tor 程序的逻辑上。
谈到 tor_threads_init() 结尾处的 set_main_thread() -> tor_get_thread_id() -> pthread_self() 调用逻辑,相关的代码片段如下:


你可以在上图看见 tor_get_thread_id() 在内部定义了一个联合(union),其中具备一个“id”字段,它最终被返回并赋给全局变量 main_thread_id 。
值得关注的另一个焦点是 tor_assert 宏(\tor-0.3.1.8\src\common\util_bug.h),它根据表达式的求值结果采取相应的措施,相关的代码片段如下:

注释中提到,tor_assert 在断言失败的情况下会发送错误消息到日志、标准错误(stderr),然后调用系统服务 abort(),终止程序,
因此用到 tor_assert 的地方想必都是一些攸关 tor 能否正常运行的检查逻辑!
——————————————————————————————————————————————————————————————————
\tor-0.3.1.8\src\common\compress.h 中定义了一些数据压缩办法、压缩级别(程度)。
其中仅有 gzip 与 zlib 明确被 tor_compress()(压缩)和 tor_uncompress()(解压)支持,相关的代码片段如下:

tor_compress_init() 函数体位于相同路径下的 compress.c 文件内:它首先调用 atomic_counter_init() 初始化一个全局变量
total_compress_allocation(一个 atomic_counter_t 结构实例),描述为压缩状态分配的总字节开销;
atomic_counter_init() 执行 memset() 将整个结构内容初始化为 0,接着调用 tor_mutex_init_nonrecursive() 处理该结构的 mutex 字段。
tor_compress_init() 还初始化了所有的压缩模块,包括 zlib,以及不常见的 lzma、zstd 等压缩办法。而实际的初始化逻辑仅仅是构建并归零为各模块状态分配的字节计数器
(都是些 atomic_counter_t 对象),相关的代码片段如下:


在分析 atomic_counter_init() 内部逻辑之前,有必要先来讨论一下 tor 首创的 atomic_counter_t 结构
(\tor-0.3.1.8\src\common\compat_threads.h)——后文简称“原子计数器”! 相关的代码片段如下:


由此可知,atomic_counter_t 是一个复合结构,其内包含了 tor_mutex_t 结构,而后者在 Win32 平台下,是对临界区(CRITICAL SECTION)的封装;
在支持 pthread 的类 UNIX 平台上,则是对 pthread_mutex_t 结构的封装。示意图如下,这种兼容各平台的设计思想确实值得学习:

而对于 Windows 特有的 CRITICAL SECTION 原生支持则通过下面的条件编译块实现:


可以从上图看到,在 Windows 平台上,tor_mutex_* 系列的函数都封装了 Windows 特有的临界区相关 API——
tor_mutex_init_nonrecursive() -> InitializeCriticalSection()
tor_mutex_uninit() -> DeleteCriticalSection()
tor_mutex_acquire() -> EnterCriticalSection()
tor_mutex_release() -> LeaveCriticalSection()
原子计数器结构内的“mutex”成员封装了系统底层的互斥锁,用来保护对母结构(亦即 atomic_counter_t)的同步/互斥访问;
而真正要保护的对象则是其内的“val”成员,它实现了计数器的功能,像 tor_compress_init() 就用该字段来记录为压缩状态分配的总字节开销;
实际上,tor 提供了专门的例程来操纵原子计数器,并且源码注释也建议程序员通过此类例程来访问它,而不要直接修改此数据结构,避免预料之外的错误操作!
这体现了面向对象编程中的“对象方法”思维,相关的代码片段如下:

如您所见,访问“val”字段前(无论是增加还是删减)都需要先获取互斥锁,修改完后再释放互斥锁。注意,此类支持例程都接收一枚原子计数器指针,
所以调用它们时,需要传入一个原子计数器结构的地址,就像在 tor_compress_init() 内那样:
atomic_counter_init(&total_compress_allocation);
同理,像 &counter->mutex 此类运算,是先通过形参 counter(原子计数器指针)引用到 mutex 成员,然后取它的地址,这样才
与它们的参数类型匹配(通过指针选择结构成员 -> 的优先级,高于取地址操作符 & 的优先级),相关的代码片段如下:

从上图可知,tor_mutex_init_nonrecursive() 的形参类型为一枚 tor_mutex_t 指针,所以 atomic_counter_init() 在调用它
时,传入了 &counter->mutex ,再次体现指针与地址的等价性。
另外我们从上图了解到:归根究底,还是要通过 pthread_mutex_init() 来初始化原子计数器内的互斥锁。。。。。
tor_compress_init() 内部的流程可以粗略总结如下图:

——————————————————————————————————————————————————————
限于篇幅,第三部分将分析剩余的两条函数调用—— init_logging() 与 monotime_init()。
------ 开源软件 Tor(洋葱路由器,构建匿名网络的方案之一)源码分析——主程序入口点(二)------的更多相关文章
- ------ Tor(洋葱路由器)匿名网络源码分析——主程序入口点(一)------
--------------------------------------------------------<概览> tor 的源码包可以从官网下载,可能需要预先利用其它FQ软件才能访 ...
- 开源网站流量统计系统Piwik源码分析——后台处理(二)
在第一篇文章中,重点介绍了脚本需要搜集的数据,而本篇主要介绍的是服务器端如何处理客户端发送过来的请求和参数. 一.设备信息检测 通过分析User-Agent请求首部(如下图红线框出的部分),可以得到相 ...
- 40 网络相关函数(八)——live555源码阅读(四)网络
40 网络相关函数(八)——live555源码阅读(四)网络 40 网络相关函数(八)——live555源码阅读(四)网络 简介 15)writeSocket向套接口写数据 TTL的概念 函数send ...
- 37 网络相关函数(五)——live555源码阅读(四)网络
37 网络相关函数(五)——live555源码阅读(四)网络 37 网络相关函数(五)——live555源码阅读(四)网络 简介 10)MAKE_SOCKADDR_IN构建sockaddr_in结构体 ...
- 33 网络相关函数(一)——live555源码阅读(四)网络
33 网络相关函数(一)——live555源码阅读(四)网络 33 网络相关函数(一)——live555源码阅读(四)网络 简介 1)IsMulticastAddress多播(组播)地址判断函数 多播 ...
- Android网络框架源码分析一---Volley
转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...
- Java开源生鲜电商平台-购物车模块的设计与架构(源码可下载)
ava开源生鲜电商平台-购物车模块的设计与架构(源码可下载) 说明:任何一个电商无论是B2C还是B2B都有一个购物车模块,其中最重要的原因就是客户需要的东西放在一起,形成一个购物清单,确认是否有问题, ...
- Flink源码分析 - 源码构建
原文地址:https://mp.weixin.qq.com/s?__biz=MzU2Njg5Nzk0NQ==&mid=2247483692&idx=1&sn=18cddc1ee ...
- Elasticsearch源码分析 - 源码构建
原文地址:https://mp.weixin.qq.com/s?__biz=MzU2Njg5Nzk0NQ==&mid=2247483694&idx=1&sn=bd03afe5a ...
随机推荐
- 【深度学习系列】用PaddlePaddle进行车牌识别(一)
小伙伴们,终于到了实战部分了!今天给大家带来的项目是用PaddlePaddle进行车牌识别.车牌识别其实属于比较常见的图像识别的项目了,目前也属于比较成熟的应用,大多数老牌厂家能做到准确率99%+.传 ...
- 【Unity3D技术文档翻译】第1.8篇 AssetBundles 问题及解决方法
上一章:[Unity3D技术文档翻译]第1.7篇 AssetBundles 补丁更新 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced Deve ...
- iOS 添加WKWebView导致控制器无法释放的问题
在WkWebView与JavaScript交互中,经常会在原生中注入MessageHandler,app中注入MessageHandler的方法 WKWebViewConfiguration *con ...
- Python基于Flask框架配置依赖包信息的项目迁移部署小技巧
一般在本机上完成基于Flask框架的代码编写后,如果有接口或者数据操作方面需求需要把代码部署到指定服务器上. 一般情况下,使用Flask框架开发者大多数都是选择Python虚拟环境来运行项目,不同的虚 ...
- php-fpm问题
这个问题怎么说呢?之前遇到这个问题内心是奔溃的.因为我压根不知道是哪里出问题啦.不过,在我努力探索下,最终还是解决了问题. so请记住,坚持不一定成功,但放弃一定失败. 简单描述一下问题: 1.本地的 ...
- SpringCloud @FeignClient的类注解@ReqestMapping无效报错:No message available","path":"/xxxx
最近在使用Feign组合微服务的时候发现在@FeignClient接口类上使用@ReqestMapping无效. 像下面的这个代码: @FeignClient("xxx") @Re ...
- 微软Surface Book推送Windows 10新固件更新:增强系统和电池
微软公司最近为Surface Book推出了新的Windows 10固件系统和驱动的更新,并且以MSI的文件格式上传到了微软的下载中心里面.此次更新的内容主要是改进了Surface Book的系统的稳 ...
- 做一个合格的Team Leader -- 基本概念
1.领导和管理 人们乐于被领导:他们不喜欢被管理,不喜欢像牛一样被驱赶或指挥. 管理者强迫人们服从他们的命令,而领导者则会带领他们一起工作. 管理是客观的,没有个人感情因素,它假定被管理者没有思想和感 ...
- Linux以百万兆字节显示内存大小
Linux以百万兆字节显示内存大小 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ free --tera total used free shared buf ...
- Linux显示2015年日历表
Linux显示2015年日历表 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ cal 2015 2015 一月 二月 三月 日 一 二 三 四 五 六 日 一 ...