编译原理栈缓冲区与句型的关系
㈠ Linux内核设计与实现的目录
译者序
序言
前言
作者简介
第1章Linux内核简介1
1.1Unix的历史1
1.2追寻Linus足迹:Linux简介2
1.3操作系统和内核简介3
1.4Linux内核和传统Unix内核的比较5
1.5Linux内核版本7
1.6Linux内核开发者社区8
1.7小结8
第2章从内核出发10
2.1获取内核源码10
2.1.1使用Git10
2.1.1安装内核源代码10
2.1.3使用补丁11
2.2内核源码树11
2.3编译内核12
2.3.1配置内核12
2.3.2减少编译的垃圾信息14
2.3.3衍生多个编译作业 14
2.3.4安装新内核14
2.4内核开发的特点15
2.4.1无libc库抑或无标准头文件15
2.4.2GNU C16
2.4.3没有内存保护机制18
2.4.4不要轻易在内核中使用浮点数18
2.4.5容积小而固定的栈18
2.4.6同步和并发18
2.4.7可移植性的重要性19
2.5小结19
第3章进程管理20
3.1进程20
3.2进程描述符及任务结构 21
3.2.1分配进程描述符22
3.2.2进程描述符的存放23
3.2.3进程状态23
3.2.4设置当前进程状态25
3.2.5进程上下文25
3.2.6进程家族树25
3.3进程创建26
3.3.1写时拷贝27
3.3.2fork()27
3.3.3vfork()28
3.4线程在Linux中的实现28
3.4.1创建线程29
3.4.2内核线程30
3.5进程终结31
3.5.1删除进程描述符32
3.5.2孤儿进程造成的进退维谷32
3.6小结34
第4章进程调度35
4.1多任务35
4.2Linux 的进程调度36
4.3策略36
4.3.1I/O消耗型和处理器消耗型的进程36
4.3.2进程优先级37
4.3.3时间片38
4.3.4调度策略的活动38
4.4Linux调度算法39
4.4.1调度器类39
4.4.2Unix 系统中的进程调度40
4.4.3公平调度41
4.5Linux调度的实现42
4.5.1时间记账42
4.5.2进程选择44
4.5.3调度器入口48
4.5.4睡眠和唤醒49
4.6抢占和上下文切换51
4.6.1用户抢占53
4.6.2内核抢占53
4.7实时调度策略54
4.8与调度相关的系统调用54
4.8.1与调度策略和优先级相关的系统调用55
4.8.2与处理器绑定有关的系统调用55
4.8.3放弃处理器时间56
4.9小结56
第5章系统调用57
5.1与内核通信57
5.2API、POSIX和C库57
5.3系统调用58
5.3.1系统调用号59
5.3.2系统调用的性能59
5.4系统调用处理程序60
5.4.1指定恰当的系统调用60
5.4.2参数传递60
5.5系统调用的实现61
5.5.1实现系统调用61
5.5.2参数验证62
5.6系统调用上下文64
5.6.1绑定一个系统调用的最后步骤65
5.6.2从用户空间访问系统调用67
5.6.3为什么不通过系统调用的方式实现68
5.7小结68
第6章内核数据结构69
6.1链表69
6.1.1单向链表和双向链表69
6.1.2环形链表70
6.1.3沿链表移动71
6.1.4Linux 内核中的实现71
6.1.5操作链表73
6.1.6遍历链表75
6.2队列78
6.2.1kfifo79
6.2.2创建队列79
6.2.3推入队列数据79
6.2.4摘取队列数据80
6.2.5获取队列长度80
6.2.6重置和撤销队列80
6.2.7队列使用举例 81
6.3映射 81
6.3.1初始化一个idr82
6.3.2分配一个新的UID82
6.3.3查找UID83
6.3.4删除UID84
6.3.5撤销idr84
6.4二叉树84
6.4.1二叉搜索树84
6.4.2自平衡二叉搜索树 85
6.5数据结构以及选择 87
6.6算法复杂度88
6.6.1算法88
6.6.2大o 符号88
6.6.3大θ符号89
6.6.4时间复杂度89
6.7小结 90
第7章中断和中断处理91
7.1中断91
7.2中断处理程序92
7.3上半部与下半部的对比93
7.4注册中断处理程序93
7.4.1中断处理程序标志94
7.4.2一个中断例子95
7.4.3释放中断处理程序95
7.5编写中断处理程序96
7.5.1共享的中断处理程序97
7.5.2中断处理程序实例97
7.6中断上下文99
7.7中断处理机制的实现100
7.8/proc/interrupts102
7.9中断控制103
7.9.1禁止和激活中断103
7.9.2禁止指定中断线105
7.9.3中断系统的状态105
7.10小结106
第8章下半部和推后执行的工作107
8.1下半部107
8.1.1为什么要用下半部108
8.1.2下半部的环境108
8.2软中断110
8.2.1软中断的实现111
8.2.2使用软中断113
8.3tasklet114
8.3.1tasklet的实现114
8.3.2使用tasklet116
8.3.3老的BH机制119
8.4工作队列120
8.4.1工作队列的实现121
8.4.2使用工作队列124
8.4.3老的任务队列机制126
8.5下半部机制的选择127
8.6在下半部之间加锁128
8.7禁止下半部128
8.8小结129
第9章内核同步介绍131
9.1临界区和竞争条件131
9.1.1为什么我们需要保护132
9.1.2单个变量133
9.2加锁134
9.2.1造成并发执行的原因135
9.2.2了解要保护些什么136
9.3死锁137
9.4争用和扩展性138
9.5小结140
第10章内核同步方法141
10.1原子操作141
10.1.1原子整数操作142
10.1.264位原子操作144
10.1.3原子位操作145
10.2自旋锁147
10.2.1自旋锁方法148
10.2.2其他针对自旋锁的操作149
10.2.3自旋锁和下半部150
10.3读-写自旋锁150
10.4信号量152
10.4.1计数信号量和二值信号量153
10.4.2创建和初始化信号量154
10.4.3使用信号量154
10.5读-写信号量155
10.6互斥体156
10.6.1信号量和互斥体158
10.6.2自旋锁和互斥体158
10.7完成变量158
10.8BLK:大内核锁159
10.9顺序锁160
10.10禁止抢占161
10.11顺序和屏障162
10.12小结165
第11章定时器和时间管理166
11.1内核中的时间概念166
11.2节拍率:HZ167
11.2.1理想的HZ值168
11.2.2高HZ的优势169
11.2.3高HZ的劣势169
11.3jiffies170
11.3.1jiffies的内部表示171
11.3.2jiffies 的回绕172
11.3.3用户空间和HZ173
11.4硬时钟和定时器174
11.4.1实时时钟174
11.4.2系统定时器174
11.5时钟中断处理程序174
11.6实际时间176
11.7定时器178
11.7.1使用定时器178
11.7.2定时器竞争条件180
11.7.3实现定时器180
11.8延迟执行181
11.8.1忙等待181
11.8.2短延迟182
11.8.3schele_timeout()183
11.9小结185
第12章内存管理186
12.1页186
12.2区187
12.3获得页189
12.3.1获得填充为0的页190
12.3.2释放页191
12.4kmalloc()191
12.4.1gfp_mask标志192
12.4.2kfree()195
12.5vmalloc()196
12.6slab层197
12.6.1slab层的设计198
12.6.2slab分配器的接口200
12.7在栈上的静态分配203
12.7.1单页内核栈203
12.7.2在栈上光明正大地工作203
12.8高端内存的映射204
12.8.1永久映射204
12.8.2临时映射204
12.9每个CPU的分配20512.10新的每个CPU接口206
12.10.1编译时的每个CPU数据206
12.10.2运行时的每个CPU数据207
12.11使用每个CPU数据的原因208
12.12分配函数的选择209
12.13小结209
第13章虚拟文件系统210
13.1通用文件系统接口210
13.2文件系统抽象层211
13.3Unix文件系统212
13.4VFS 对象及其数据结构213
13.5超级块对象214
13.6超级块操作215
13.7索引节点对象217
13.8索引节点操作219
13.9目录项对象222
13.9.1目录项状态222
13.9.2目录项缓存223
13.10目录项操作224
13.11文件对象225
13.12文件操作226
13.13和文件系统相关的数据结构230
13.14和进程相关的数据结构232
13.15小结233
第14章块I/O层234
14.1剖析一个块设备234
14.2缓冲区和缓冲区头235
14.3bio结构体237
14.3.1I/O向量238
14.3.2新老方法对比239
14.4请求队列240
14.5I/O调度程序240
14.5.1I/O调度程序的工作241
14.5.2Linus 电梯241
14.5.3最终期限I/O调度程序242
14.5.4预测I/O调度程序244
14.5.5完全公正的排队I/O调度程序244
14.5.6空操作的I/O调度程序245
14.5.7I/O调度程序的选择245
14.6小结246
第15章进程地址空间247
15.1地址空间247
15.2内存描述符248
15.2.1分配内存描述符249
15.2.2撤销内存描述符250
15.2.3mm_struct 与内核线程250
15.3虚拟内存区域251
15.3.1VMA标志251
15.3.2VMA 操作253
15.3.3内存区域的树型结构和内存区域的链表结构254
15.3.4实际使用中的内存区域254
15.4操作内存区域255
15.4.1find_vma()256
15.4.2find_vma_prev()257
15.4.3find_vma_intersection()257
15.5mmap()和do_mmap():创建地址区间258
15.6mummap()和do_mummap():删除地址区间259
15.7页表260
15.8小结261
第16章页高速缓存和页回写262
16.1缓存手段262
16.1.1写缓存262
16.1.2缓存回收263
16.2Linux 页高速缓存264
16.2.1address_space对象264
16.2.2address_space 操作266
16.2.3基树267
16.2.4以前的页散列表268
16.3缓冲区高速缓存268
16.4flusher线程268
16.4.1膝上型计算机模式270
16.4.2历史上的bdflush、kupdated 和pdflush270
16.4.3避免拥塞的方法:使用多线程271
16.5小结271
第17章设备与模块273
17.1设备类型273
17.2模块274
17.2.1Hello,World274
17.2.2构建模块275
17.2.3安装模块277
17.2.4产生模块依赖性277
17.2.5载入模块278
17.2.6管理配置选项279
17.2.7模块参数280
17.2.8导出符号表282
17.3设备模型283
17.3.1kobject283
17.3.2ktype284
17.3.3kset285
17.3.4kobject、ktype和kset的相互关系285
17.3.5管理和操作kobject286
17.3.6引用计数287
17.4sysfs288
17.4.1sysfs中添加和删除kobject 290
17.4.2向sysfs中添加文件291
17.4.3内核事件层293
17.5小结294
第18章调试295
18.1准备开始295
18.2内核中的bug296
18.3通过打印来调试296
18.3.1健壮性296
18.3.2日志等级297
18.3.3记录缓冲区298
18.3.4syslogd和klogd298
18.3.5从printf()到printk()的转换298
18.4oops298
18.4.1ksymoops300
18.4.2kallsyms300
18.5内核调试配置选项301
18.6引发bug并打印信息301
18.7神奇的系统请求键302
18.8内核调试器的传奇303
18.8.1gdb303
18.8.2kgdb304
18.9探测系统304
18.9.1用UID作为选择条件304
18.9.2使用条件变量305
18.9.3使用统计量305
18.9.4重复频率限制305
18.10用二分查找法找出引发罪恶的变更306
18.11使用Git进行二分搜索307
18.12当所有的努力都失败时:社区308
18.13小结308
第19章可移植性309
19.1可移植操作系统309
19.2Linux移植史310
19.3字长和数据类型311
19.3.1不透明类型313
19.3.2指定数据类型314
19.3.3长度明确的类型314
19.3.4char型的符号问题315
19.4数据对齐315
19.4.1避免对齐引发的问题316
19.4.2非标准类型的对齐316
19.4.3结构体填补316
19.5字节顺序318
19.6时间319
19.7页长度320
19.8处理器排序320
19.9SMP、内核抢占、高端内存321
19.10小结321
第20章补丁、开发和社区322
20.1社区322
20.2Linux编码风格322
20.2.1缩进323
20.2.2switch 语句323
20.2.3空格324
20.2.4花括号325
20.2.5每行代码的长度326
20.2.6命名规范326
20.2.7函数326
20.2.8注释326
20.2.9typedef327
20.2.10多用现成的东西328
20.2.11在源码中减少使用ifdef328
20.2.12结构初始化328
20.2.13代码的事后修正329
20.3管理系统329
20.4提交错误报告329
20.5补丁330
20.5.1创建补丁330
20.5.2用Git创建补丁331
20.5.3提交补丁331
20.6小结332
参考资料333
㈡ 如何用PyTorch实现递归神经网络
从 Siri 到谷歌翻译,深度神经网络已经在机器理解自然语言方面取得了巨大突破。这些模型大多数将语言视为单调的单词或字符序列,并使用一种称为循环神经网络(recurrent neural network/RNN)的模型来处理该序列。但是许多语言学家认为语言最好被理解为具有树形结构的层次化词组,一种被称为递归神经网络(recursive neural network)的深度学习模型考虑到了这种结构,这方面已经有大量的研究。虽然这些模型非常难以实现且效率很低,但是一个全新的深度学习框架 PyTorch 能使它们和其它复杂的自然语言处理模型变得更加容易。
虽然递归神经网络很好地显示了 PyTorch 的灵活性,但它也广泛支持其它的各种深度学习框架,特别的是,它能够对计算机视觉(computer vision)计算提供强大的支撑。PyTorch 是 Facebook AI Research 和其它几个实验室的开发人员的成果,该框架结合了 Torch7 高效灵活的 GPU 加速后端库与直观的 Python 前端,它的特点是快速成形、代码可读和支持最广泛的深度学习模型。
开始 SPINN
链接中的文章(https://github.com/jekbradbury/examples/tree/spinn/snli)详细介绍了一个递归神经网络的 PyTorch 实现,它具有一个循环跟踪器(recurrent tracker)和 TreeLSTM 节点,也称为 SPINN——SPINN 是深度学习模型用于自然语言处理的一个例子,它很难通过许多流行的框架构建。这里的模型实现部分运用了批处理(batch),所以它可以利用 GPU 加速,使得运行速度明显快于不使用批处理的版本。
SPINN 的意思是堆栈增强的解析器-解释器神经网络(Stack-augmented Parser-Interpreter Neural Network),由 Bowman 等人于 2016 年作为解决自然语言推理任务的一种方法引入,该论文中使用了斯坦福大学的 SNLI 数据集。
该任务是将语句对分为三类:假设语句 1 是一幅看不见的图像的准确标题,那么语句 2(a)肯定(b)可能还是(c)绝对不是一个准确的标题?(这些类分别被称为蕴含(entailment)、中立(neutral)和矛盾(contradiction))。例如,假设一句话是“两只狗正跑过一片场地”,蕴含可能会使这个语句对变成“户外的动物”,中立可能会使这个语句对变成“一些小狗正在跑并试图抓住一根棍子”,矛盾能会使这个语句对变成“宠物正坐在沙发上”。
特别地,研究 SPINN 的初始目标是在确定语句的关系之前将每个句子编码(encoding)成固定长度的向量表示(也有其它方式,例如注意模型(attention model)中将每个句子的每个部分用一种柔焦(soft focus)的方法相互比较)。
数据集是用句法解析树(syntactic parse tree)方法由机器生成的,句法解析树将每个句子中的单词分组成具有独立意义的短语和子句,每个短语由两个词或子短语组成。许多语言学家认为,人类通过如上面所说的树的分层方式来组合词意并理解语言,所以用相同的方式尝试构建一个神经网络是值得的。下面的例子是数据集中的一个句子,其解析树由嵌套括号表示:
( ( The church ) ( ( has ( cracks ( in ( the ceiling ) ) ) ) . ) )
这个句子进行编码的一种方式是使用含有解析树的神经网络构建一个神经网络层 Rece,这个神经网络层能够组合词语对(用词嵌入(word embedding)表示,如 GloVe)、 和/或短语,然后递归地应用此层(函数),将最后一个 Rece 产生的结果作为句子的编码:
X = Rece(“the”, “ceiling”)
Y = Rece(“in”, X)
... etc.
但是,如果我希望网络以更类似人类的方式工作,从左到右阅读并保留句子的语境,同时仍然使用解析树组合短语?或者,如果我想训练一个网络来构建自己的解析树,让解析树根据它看到的单词读取句子?这是一个同样的但方式略有不同的解析树的写法:
The church ) has cracks in the ceiling ) ) ) ) . ) )
或者用第 3 种方式表示,如下:
WORDS: The church has cracks in the ceiling .
PARSES: S S R S S S S S R R R R S R R
我所做的只是删除开括号,然后用“S”标记“shift”,并用“R”替换闭括号用于“rece”。但是现在可以从左到右读取信息作为一组指令来操作一个堆栈(stack)和一个类似堆栈的缓冲区(buffer),能得到与上述递归方法完全相同的结果:
1. 将单词放入缓冲区。
2. 从缓冲区的前部弹出“The”,将其推送(push)到堆栈上层,紧接着是“church”。
3. 弹出前 2 个堆栈值,应用于 Rece,然后将结果推送回堆栈。
4. 从缓冲区弹出“has”,然后推送到堆栈,然后是“cracks”,然后是“in”,然后是“the”,然后是“ceiling”。
5. 重复四次:弹出 2 个堆栈值,应用于 Rece,然后推送结果。
6. 从缓冲区弹出“.”,然后推送到堆栈上层。
7. 重复两次:弹出 2 个堆栈值,应用于 Rece,然后推送结果。
8. 弹出剩余的堆栈值,并将其作为句子编码返回。
我还想保留句子的语境,以便在对句子的后半部分应用 Rece 层时考虑系统已经读取的句子部分的信息。所以我将用一个三参数函数替换双参数的 Rece 函数,该函数的输入值为一个左子句、一个右子句和当前句的上下文状态。该状态由神经网络的第二层(称为循环跟踪器(Tracker)的单元)创建。Tracker 在给定当前句子上下文状态、缓冲区中的顶部条目 b 和堆栈中前两个条目 s1\s2 时,在堆栈操作的每个步骤(即,读取每个单词或闭括号)后生成一个新状态:
context[t+1] = Tracker(context[t], b, s1, s2)
容易设想用你最喜欢的编程语言来编写代码做这些事情。对于要处理的每个句子,它将从缓冲区加载下一个单词,运行跟踪器,检查是否将单词推送入堆栈或执行 Rece 函数,执行该操作;然后重复,直到对整个句子完成处理。通过对单个句子的应用,该过程构成了一个大而复杂的深度神经网络,通过堆栈操作的方式一遍又一遍地应用它的两个可训练层。但是,如果你熟悉 TensorFlow 或 Theano 等传统的深度学习框架,就知道它们很难实现这样的动态过程。你值得花点时间回顾一下,探索为什么 PyTorch 能有所不同。
图论
图 1:一个函数的图结构表示
深度神经网络本质上是有大量参数的复杂函数。深度学习的目的是通过计算以损失函数(loss)度量的偏导数(梯度)来优化这些参数。如果函数表示为计算图结构(图 1),则向后遍历该图可实现这些梯度的计算,而无需冗余工作。每个现代深度学习框架都是基于此反向传播(backpropagation)的概念,因此每个框架都需要一个表示计算图的方式。
在许多流行的框架中,包括 TensorFlow、Theano 和 Keras 以及 Torch7 的 nngraph 库,计算图是一个提前构建的静态对象。该图是用像数学表达式的代码定义的,但其变量实际上是尚未保存任何数值的占位符(placeholder)。图中的占位符变量被编译进函数,然后可以在训练集的批处理上重复运行该函数来产生输出和梯度值。
这种静态计算图(static computation graph)方法对于固定结构的卷积神经网络效果很好。但是在许多其它应用中,有用的做法是令神经网络的图结构根据数据而有所不同。在自然语言处理中,研究人员通常希望通过每个时间步骤中输入的单词来展开(确定)循环神经网络。上述 SPINN 模型中的堆栈操作很大程度上依赖于控制流程(如 for 和 if 语句)来定义特定句子的计算图结构。在更复杂的情况下,你可能需要构建结构依赖于模型自身的子网络输出的模型。
这些想法中的一些(虽然不是全部)可以被生搬硬套到静态图系统中,但几乎总是以降低透明度和增加代码的困惑度为代价。该框架必须在其计算图中添加特殊的节点,这些节点代表如循环和条件的编程原语(programming primitive),而用户必须学习和使用这些节点,而不仅仅是编程代码语言中的 for 和 if 语句。这是因为程序员使用的任何控制流程语句将仅运行一次,当构建图时程序员需要硬编码(hard coding)单个计算路径。
例如,通过词向量(从初始状态 h0 开始)运行循环神经网络单元(rnn_unit)需要 TensorFlow 中的特殊控制流节点 tf.while_loop。需要一个额外的特殊节点来获取运行时的词长度,因为在运行代码时它只是一个占位符。
# TensorFlow
# (this code runs once, ring model initialization)
# “words” is not a real list (it’s a placeholder variable) so
# I can’t use “len”
cond = lambda i, h: i < tf.shape(words)[0]
cell = lambda i, h: rnn_unit(words[i], h)
i = 0
_, h = tf.while_loop(cond, cell, (i, h0))
基于动态计算图(dynamic computation graph)的方法与之前的方法有根本性不同,它有几十年的学术研究历史,其中包括了哈佛的 Kayak、自动微分库(autograd)以及以研究为中心的框架 Chainer和 DyNet。在这样的框架(也称为运行时定义(define-by-run))中,计算图在运行时被建立和重建,使用相同的代码为前向通过(forward pass)执行计算,同时也为反向传播(backpropagation)建立所需的数据结构。这种方法能产生更直接的代码,因为控制流程的编写可以使用标准的 for 和 if。它还使调试更容易,因为运行时断点(run-time breakpoint)或堆栈跟踪(stack trace)将追踪到实际编写的代码,而不是执行引擎中的编译函数。可以在动态框架中使用简单的 Python 的 for 循环来实现有相同变量长度的循环神经网络。
# PyTorch (also works in Chainer)
# (this code runs on every forward pass of the model)
# “words” is a Python list with actual values in it
h = h0
for word in words:
h = rnn_unit(word, h)
PyTorch 是第一个 define-by-run 的深度学习框架,它与静态图框架(如 TensorFlow)的功能和性能相匹配,使其能很好地适合从标准卷积神经网络(convolutional network)到最疯狂的强化学习(reinforcement learning)等思想。所以让我们来看看 SPINN 的实现。
代码
在开始构建网络之前,我需要设置一个数据加载器(data loader)。通过深度学习,模型可以通过数据样本的批处理进行操作,通过并行化(parallelism)加快训练,并在每一步都有一个更平滑的梯度变化。我想在这里可以做到这一点(稍后我将解释上述堆栈操作过程如何进行批处理)。以下 Python 代码使用内置于 PyTorch 的文本库的系统来加载数据,它可以通过连接相似长度的数据样本自动生成批处理。运行此代码之后,train_iter、dev_iter 和 test_itercontain 循环遍历训练集、验证集和测试集分块 SNLI 的批处理。
from torchtext import data, datasets
TEXT = datasets.snli.ParsedTextField(lower=True)
TRANSITIONS = datasets.snli.ShiftReceField()
LABELS = data.Field(sequential=False)train, dev, test = datasets.SNLI.splits(
TEXT, TRANSITIONS, LABELS, wv_type='glove.42B')TEXT.build_vocab(train, dev, test)
train_iter, dev_iter, test_iter = data.BucketIterator.splits(
(train, dev, test), batch_size=64)
你可以在 train.py中找到设置训练循环和准确性(accuracy)测量的其余代码。让我们继续。如上所述,SPINN 编码器包含参数化的 Rece 层和可选的循环跟踪器来跟踪句子上下文,以便在每次网络读取单词或应用 Rece 时更新隐藏状态;以下代码代表的是,创建一个 SPINN 只是意味着创建这两个子模块(我们将很快看到它们的代码),并将它们放在一个容器中以供稍后使用。
import torchfrom torch import nn
# subclass the Mole class from PyTorch’s neural network package
class SPINN(nn.Mole):
def __init__(self, config):
super(SPINN, self).__init__()
self.config = config self.rece = Rece(config.d_hidden, config.d_tracker)
if config.d_tracker is not None:
self.tracker = Tracker(config.d_hidden, config.d_tracker)
当创建模型时,SPINN.__init__ 被调用了一次;它分配和初始化参数,但不执行任何神经网络操作或构建任何类型的计算图。在每个新的批处理数据上运行的代码由 SPINN.forward 方法定义,它是用户实现的方法中用于定义模型向前过程的标准 PyTorch 名称。上面描述的是堆栈操作算法的一个有效实现,即在一般 Python 中,在一批缓冲区和堆栈上运行,每一个例子都对应一个缓冲区和堆栈。我使用转移矩阵(transition)包含的“shift”和“rece”操作集合进行迭代,运行 Tracker(如果存在),并遍历批处理中的每个样本来应用“shift”操作(如果请求),或将其添加到需要“rece”操作的样本列表中。然后在该列表中的所有样本上运行 Rece 层,并将结果推送回到它们各自的堆栈。
def forward(self, buffers, transitions):
# The input comes in as a single tensor of word embeddings;
# I need it to be a list of stacks, one for each example in
# the batch, that we can pop from independently. The words in
# each example have already been reversed, so that they can
# be read from left to right by popping from the end of each
# list; they have also been prefixed with a null value.
buffers = [list(torch.split(b.squeeze(1), 1, 0))
for b in torch.split(buffers, 1, 1)]
# we also need two null values at the bottom of each stack,
# so we can from the nulls in the input; these nulls
# are all needed so that the tracker can run even if the
# buffer or stack is empty
stacks = [[buf[0], buf[0]] for buf in buffers]
if hasattr(self, 'tracker'):
self.tracker.reset_state()
for trans_batch in transitions:
if hasattr(self, 'tracker'):
# I described the Tracker earlier as taking 4
# arguments (context_t, b, s1, s2), but here I
# provide the stack contents as a single argument
# while storing the context inside the Tracker
# object itself.
tracker_states, _ = self.tracker(buffers, stacks)
else:
tracker_states = itertools.repeat(None)
lefts, rights, trackings = [], [], []
batch = zip(trans_batch, buffers, stacks, tracker_states)
for transition, buf, stack, tracking in batch:
if transition == SHIFT:
stack.append(buf.pop())
elif transition == REDUCE:
rights.append(stack.pop())
lefts.append(stack.pop())
trackings.append(tracking)
if rights:
reced = iter(self.rece(lefts, rights, trackings))
for transition, stack in zip(trans_batch, stacks):
if transition == REDUCE:
stack.append(next(reced))
return [stack.pop() for stack in stacks]
在调用 self.tracker 或 self.rece 时分别运行 Tracker 或 Rece 子模块的向前方法,该方法需要在样本列表上应用前向操作。在主函数的向前方法中,在不同的样本上进行独立的操作是有意义的,即为批处理中每个样本提供分离的缓冲区和堆栈,因为所有受益于批处理执行的重度使用数学和需要 GPU 加速的操作都在 Tracker 和 Rece 中进行。为了更干净地编写这些函数,我将使用一些 helper(稍后将定义)将这些样本列表转化成批处理张量(tensor),反之亦然。
我希望 Rece 模块自动批处理其参数以加速计算,然后解批处理(unbatch)它们,以便可以单独推送和弹出。用于将每对左、右子短语表达组合成父短语(parent phrase)的实际组合函数是 TreeLSTM,它是普通循环神经网络单元 LSTM 的变型。该组合函数要求每个子短语的状态实际上由两个张量组成,一个隐藏状态 h 和一个存储单元(memory cell)状态 c,而函数是使用在子短语的隐藏状态操作的两个线性层(nn.Linear)和将线性层的结果与子短语的存储单元状态相结合的非线性组合函数 tree_lstm。在 SPINN 中,这种方式通过添加在 Tracker 的隐藏状态下运行的第 3 个线性层进行扩展。
图 2:TreeLSTM 组合函数增加了第 3 个输入(x,在这种情况下为 Tracker 状态)。在下面所示的 PyTorch 实现中,5 组的三种线性变换(由蓝色、黑色和红色箭头的三元组表示)组合为三个 nn.Linear 模块,而 tree_lstm 函数执行位于框内的所有计算。图来自 Chen et al. (2016)。
㈢ C语言缓冲区在哪里
缓冲区具体在哪里是与操作系统、编译器相关的
以VC++为例。察看getchar的源代码(src\fgetchar.c),有:
int __cdecl _fgetchar (void){
return(getc(stdin));
}
#undef getchar
int __cdecl getchar (void){
return _fgetchar();
}
可见getchar()相当于getc(stdin)
继续察看getc(src\fgetc.c),有一段(为便于阅读,有删减):
int __cdecl getc (FILE *stream){
int retval;
_ASSERTE(stream != NULL);
_lock_str(stream);
__try {
retval = _getc_lk(stream);
}
__finally {
_unlock_str(stream);
}
return(retval);
}
这段代码里_lock_str其实是通过Win32 API提供的临街区来锁住文件
接收用户输入发生在_getc_lk,_getc_lk宏调用_filbuf。_filbuf在_filbuf.c中可以查看,这段代码比较长,就不贴出来了
_filbuf主要是调用了_read(_fileno(stream), stream->_base, stream->_bufsiz)
而_read最终则是调用了Win32API ReadFile,以下是用WinDbg输出的getchar的调用栈:
# ChildEBP RetAddr
00 0012fe6c 0040a4e7 kernel32!ReadFile
01 0012fea8 0040a3b9 TestStruct!_read_lk+0x107 [read.c @ 146]
02 0012fec0 00403140 TestStruct!_read+0x69 [read.c @ 75]
03 0012fee8 00401290 TestStruct!_filbuf+0xd0 [_filbuf.c @ 127]
04 0012ff08 004012cc TestStruct!fgetc+0x80 [fgetc.c @ 44]
05 0012ff14 0040103d TestStruct!getc+0xc [fgetc.c @ 56]
06 0012ff20 00401058 TestStruct!_fgetchar+0xd [fgetchar.c @ 37]
07 0012ff28 0040101e TestStruct!getchar+0x8 [fgetchar.c @ 47]
08 0012ff80 0040115c TestStruct!main+0xe [d:\my programs\teststruct\ts.cpp @ 4]
09 0012ffc0 7c816fe7 TestStruct!mainCRTStartup+0xfc [crt0.c @ 206]
0a 0012fff0 00000000 kernel32!BaseProcessStart+0x23
可见,getchar最终调用了ReadFile。关于ReadFile的原理以及缓冲区在哪里,请你再提一个问我再回答
㈣ 编译原理什么是素短语
编译原理中,素短语是至少含义一个终结符,并且自身不包含任何更小素短语的一种短语。
素短语是一种特殊的短语,它是一个递归的定义,至少含有一个终结符,并且除它自身之外不再含任何更小的素短语,所谓最左素短语就是处于句型最左边的素短语的短语。
一个算符优先文法G的任何句型的最左素短语是满足以下条件的最左子串NaNb…NcNdN(N是非终结符,a,b,c,d是终结符)。例如:句型T+T*F+id,T*F是最左素短语,id是素短语。
(4)编译原理栈缓冲区与句型的关系扩展阅读:
通过语法树可以得知素短语:
1、每个句型对应一棵语法树
2、每棵语法树的叶子结点从左到右排列构成一个句型
3、每棵语法树的子树的叶子结点从左到右排列构成一个短语
4、每棵语法树的简单子树(只有父子两层结点)的叶子结点从左到右排列构成一个简单(直接)短语。
5、素短语是至少包含一个终结符的短语,但它不能包含其它素短语。
㈤ 漏洞分析的内容导读
本书分为5篇,共33章。
第1篇 漏洞利用原理(初级)
第1章 基础知识
本章着重对漏洞挖掘中的一些基础知识进行介绍。首先是漏洞研究中的一些基本概念和原理;然后是对Windows平台下可执行文件的结构和内存方面的一些基础知识的介绍;最后介绍了一些漏洞分析中经常使用的软件工具。包括调试工具、反汇编工具、二进制编辑工具等。您会在后面的调试实验中反复见到这些工具的身影。在这章的最后一节,我们设计了一个非常简单的破解小实验,用于实践工具的应用,消除您对二进制的恐惧感,希望能够给您带来一些乐趣。
第2章 栈溢出原理与实践
基于栈的溢出是最基础的漏洞利用方法。本章首先用大量的示意图,深入浅出地讲述了操作系统中函数调用、系统栈操作等概念和原理;随后通过三个调试实验逐步讲解如何通过栈溢出,一步一步地劫持进程并植入可执行的机器代码。即使您没有任何汇编语言基础,从未进行过二进制级别的调试,在本章详细的实验指导下也能轻松完成实验,体会到exploit的乐趣。
第3章 开发shellcode的艺术
本章紧接第2章的讨论,比较系统地介绍了溢出发生后,如何布置缓冲区、如何定位shellcode、如何编写和调试shellcode等实际的问题。最后两小节还给出了一些编写shellcode的高级技术,供有一定汇编基础的朋友做参考。
第4章 用MetaSploit开发Exploit
MetaSploit是软件工程中的Frame Work(架构)在安全技术中的完美实现,它把模块化、继承性、封装等面向对象的特点在漏洞利用程序的开发中发挥得淋漓尽致。使用这个架构开发Exploit要比直接使用C语言写出的Exploit简单得多。本章将集中介绍如何使用这个架构进行Exploit开发。
第5章 堆溢出利用
在很长一段时间内,Windows下的堆溢出被认为是不可利用的,然而事实并非如此。本章将用精辟的论述点破堆溢出利用的原理,让您轻松领会堆溢出的精髓。此外,这章的一系列调试实验将加深您对概念和原理的理解。用通俗易懂的方式论述复杂的技术是本书始终坚持的原则。
第6章 形形色色的内存攻击技术
在了解基本的堆栈溢出后,本章将为大家展示更为高级的内存攻击技术。本章集中介绍了一些曾发表于Black Hat上的着名论文中所提出的高级利用技术,如狙击Windows异常处理机制、攻击虚函数、off by one、 Heap Spray等利用技巧。对于安全专家,了解这些技巧和手法不至于在分析漏洞时错把可以利用的漏洞误判为低风险类型;对于黑客技术爱好者,这些知识很可能成为激发技术灵感的火花。
第7章 手机里的缓冲区溢出
在PC机上的溢出攻击进行的如火如荼的时候,您是否也想了解手机平台上的缓冲区溢出问题?那就不要错过本章!本章以ARM和Windows Mobile为例,介绍手机平台上编程和调试技巧。并在最后以一个手机上的exploit me为大家揭开手机里缓冲区溢出的神秘面纱。
第8章 其他类型的软件漏洞
缓冲区溢出漏洞只是软件漏洞的一个方面,我们来看看其他一些流行的安全漏洞。如格式化串漏洞、SQL注入、XPath注入、XSS等安全漏洞产生的原因、利用技巧及防范措施。
第2篇 漏洞利用原理(高级)
第9章 Windows安全机制概述
微软在Windows XP SP2和Windows 2003之后,向操作系统中加入了许多安全机制。本章将集中讨论这些安全机制对漏洞利用的影响。
第10章 栈中的守护天使:GS
针对缓冲区溢出时覆盖函数返回地址这一特征,微软在编译程序时使用了一个很酷的安全编译选项——GS。本章将对GS编译选项的原理进行详细介绍,并介绍几种绕过GS的溢出技巧。
第11章 亡羊补牢:SafeSEH
攻击S.E.H已经成为windows平台下漏洞利用的经典手法。为了遏制日益疯狂的攻击,微软在Windows XP SP2及后续版本的操作系统中引入了着名的S.E.H校验机制SafeSEH。本章将会对这一安全机制进行详细的分析,并介绍其中的不足和绕过方法。
第12章 数据与程序的分水岭:DEP
溢出攻击的根源在于现代计算机对数据和代码没有明确区分这一先天缺陷, 而DEP这种看似釜底抽薪式的防护措施是否真的可以杜绝溢出攻击呢?答案马上揭晓。
第13章 在内存中躲猫猫:ASLR
程序加载时不再使用固定的基址加载,ASLR技术将溢出时使用的跳板在内存中隐藏了起来,没有了跳板我们如何溢出呢?本章将带领您在黑暗中寻找溢出的出口。
第14章 S.E.H终极防护:SEHOP
SafeSEH的败北,让微软推出一种更为严厉的S.E.H保护机制SEHOP。这里将为您展示这种保护机制的犀利之处。
第15章 重重保护下的堆
当堆溢出变成可能后,微软不能再无视堆中的保护机制了,让我们一览堆中的保护机制,并分析其漏洞。
第3篇 漏洞挖掘技术
第16章 漏洞挖掘技术简介
不论从工程上讲还是从学术上讲,漏洞挖掘都是一个相当前沿的领域。本章将从动态测试和静态审计两方面对漏洞挖掘技术的基础知识进行简单的介绍。
第17章 文件类型漏洞挖掘与Smart Fuzz
文件类型的漏洞层出不穷,持续威胁着互联网的安全。如何系统的测试文件格式,产生精确有效的畸形测试用例用以发掘文件解析器的安全漏洞,并不是一件容易的事情。本章将从理论和实践两个方面向您讲述灰盒测试技术。
第18章 FTP的漏洞挖掘
本章将简述FTP协议,并手把手地带领您完成几个初级的漏洞测试案例,让您亲身体会下真实的漏洞长什么模样。
第19章 E-mail的漏洞挖掘
E-mail系统涉及的安全问题不光只有缓冲区溢出,在本章的挖掘案例中,您会发现除了工具和常用方法外,威力最为强大的武器还是您的大脑。Evil thinking是安全测试中最重要的思维方式之一。
第20章 ActiveX控件的漏洞挖掘
控件类漏洞曾经是大量网马的栖身之地。本章将结合若干个曾经的0 day向您比较系统的介绍这类漏洞的测试、调试的相关工具和方法。
第4篇 操作系统内核安全
第21章 探索ring0
研究内核漏洞,需要首先掌握一些内核基础知识,例如内核驱动程序的开发、编译、运行和调试,内核中重要的数据结构等,本章将为读者开启探索ring0之门,逐步掌握一些内核基础知识。
第22章 内核漏洞利用技术
本章将带领读者从一个简单的内核漏洞程序exploitme.sys的编写开始,展示内核漏洞利用的思路、方法,以及利用程序和Ring0 Shellcode的编写和设计。
第23章 FUZZ驱动程序
掌握了内核漏洞的原理和利用方法,本章将进入内核漏洞挖掘阶段,学习较为高级的内核漏洞挖掘技术,最后实践该漏洞挖掘技术,分析挖掘出内核漏洞。
第24章 内核漏洞案例分析
本章对几种典型的内核漏洞,用几个真实的内核漏洞案例来详细分析,分析漏洞造成的具体原因和细节,并构造漏洞成功利用的方法。
第5篇 漏洞分析案例
第25章 漏洞分析技术概述
本章纵览了漏洞分析与调试的思路,并介绍了一些辅助漏洞调试分析的高级逆向工具。
第26章 RPC入侵:MS06-040 与MS08-067
由于可以做到主动式远程入侵,RPC级别的漏洞被誉为漏洞中的王者,此类漏洞也极其稀有,每一个都有一段曲折的故事。值得一提的是最近的两个RPC系统漏洞竟然出自同一个函数。本章将对这个缝来补去没有修好的函数进行详细分析,让您从攻防两方面深刻理解漏洞的起因和修复策略的重要性。
第27章 MS06-055分析:实战Heap Spray
通过网页“挂马”是近年来攻击者惯用的手法。本章通过分析微软IE浏览器中真实的缓冲区溢出漏洞,告诉您为什么不能随便点击来历不明的URL链接,并在实战中为大家演示Heap Spray技术。
第28章 MS09-032分析:一个“&”引发的血案
一个视频网页的背后可能是一只凶狠的木马,这就是着名的Microsoft DirectShow MPEG-2视频ActiveX控件远程代码执行漏洞。本章将为您分析该漏洞产生的原因及分析技巧。
第29章 Yahoo!Messenger栈溢出漏洞
在波涛汹涌的溢出大潮中Yahoo也没能幸免,作为国外非常流行的Yahoo!Messenger也存在过非常严重的漏洞。本章将重现当时的场景,并分析漏洞产生的原因。
第30章 CVE-2009-0927:PDF中的JS
您可能不会随便运行一个可执行文件,但是您会想到别人发过来的PDF文档中也有可能隐藏着一些东西吗?本章将以PDF文档为例,带您领略文件类型溢出漏洞的风采。
第31章 坝之蚁穴:超长URL溢出漏洞
安全软件不一定安全,即便是这款保护未成年人健康上网的计算机终端过滤软件,也有可能成为黑客攻击的窗口。本章将介绍绿坝软件中一个已经被修复了的安全漏洞。
第32章 暴风影音M3U文件解析漏洞
晚上回家后用暴风影音打开别人发过来的M3U列表文件,在你陶醉于其内容之时,一只精干的小马已悄然在后台运行。想要了解这只小马是如何进入你的电脑的?请阅读本章。
第33章 LNK快捷方式文件漏洞
是否我不去运行任何可疑文件,不去打开陌生的网址就安全了呢?答案是否定。LNK快捷方式漏洞无需打开文件,只要浏览恶意文件,所在文件夹就会中毒,俗称“看一眼就挂”。本章将带您分析这一神奇的漏洞。
Failwest