一套非常不错的计算机系统概论视频,看完有一种醍醐灌顶的感觉,从计算机基本组成,各种基本数据的表示到ISA,指令再到虚拟内存,寻址等。
复习了很多之前的 知识,解决了不少疑惑。
https://www.coursera.org/learn/jisuanji-xitong/home/welcome
(需要科学上网)
常怀敬畏之心
一套非常不错的计算机系统概论视频,看完有一种醍醐灌顶的感觉,从计算机基本组成,各种基本数据的表示到ISA,指令再到虚拟内存,寻址等。
复习了很多之前的 知识,解决了不少疑惑。
https://www.coursera.org/learn/jisuanji-xitong/home/welcome
(需要科学上网)
undo tablespace 中包含undo log,是一种独立的表空间。在mysql启动时会默认创建两个undo表空间,且系统要求起码有两个活跃的undo表空间以支持自动truncate功能。(这样可以保证一个离线truncate另一个继续提供服务)。undo表空间文件创建在–innodb_undo_derectory变量指定的位置,默认在data目录下,文件名undo_N格式。每个文件的初始大小基于–innodb_page_size参数(默认16K), 默认大小为10M。
8.0.14版本以前,可以在启动时设置–innodb_undo_tablespaces来指定创建的undo表空间,后续版本废弃改参数,创建可以使用
create undo tablespace tablespace_name add datafile "filename";
使用该语法时,指定的filename可以是绝对路径,但是该路径必须是innodb可以识别的,–innodb_directoried变量定义了这些路径,而且不管该变量如何显示定义,都会包含三个默认路径,分别是–innodb_undo_directory, –data_dir,–innodb_data_home_dir,mysql启动时会在这些路径里扫描寻找undo tablespaces。
如果没有指定绝对路径,那么undo tablespace会被创建在innodb_undo_directory下,如果该变量为空,会创建在mysql的data(/var/lib/mysql)目录下.
用create undo tablespace语法创建的undo表空间可以使用drop undo tablespace语法来删除(默认创建的表空间不可用),在删除前必须保证该空间是empty状态。
在清空一个表空间前必须把它的状态设置为inactive,确保该表空间的rollback segment的undo segment不会分配给事务使用。
alter undo tablespace tablespace_name set inactive;
设置后会等待已经使用该表空间的事务提交,待所有的事务提交后,purge线程开始truncate表空间至初始大小,这时候表空间的状态为empty,即可以被drop。
drop undo tablespace tablespace_name;
可以在INNODB_TABLESPACES 表里查询到每个表空间的状态,包括undo表空间
SELECT NAME, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES
WHERE NAME LIKE tablepace_name;
默认的undo表空间不可以被drop但是可以设置成inactive状态,但是必须保证起码有两个活跃的表空间以支持表空间的自动truncate,否则会报错。
用create undo tablespace语法创建的undo表空间文件可以移动到任何innndb可以识别的路径,mysql在启动时会找到他们,但是默认创建的undo表空间不可移动,如果默认undo表空间被移动了,那么必须在启动时更改–innodb_undo_directory参数为移动后的路径。
一个表空间文件默认最大为1G,由–innodb_max_undo_log_size参数控制,innodb提供了自动和手动两种方式清除undo log
自动清除由purge线程(垃圾回收线程,可能有多个,后台独立运行,参数–innodb_purge_threads指定)完成,需要开启–innodb_undo_log_truncate参数,默认是开启的。
由于自动truncate必须保证有两个活动的undo表空间,那么手动就必须有三个获得undo表空间,因为可能有一个正在truncate而处于offline。
两种方式清除过程如上所述,等undo表空间的状态时empty后,状态再设置成active
对于自动清除可以使用–innodb_purge_rseg_truncate_frequency参数来提高清除频率,默认是purge被触发(undo tablespace大小超过–innodb_max_undo_log_size参数)了128次后,执行一次truncate,可以设置更小的值加速truncate。
mvcc可以保证RR隔离级别下一致性读(consistent read)的问题,也解决了读写冲突的问题。
没有mvcc控制的情况下,假如我们的场景是读远大于写的场景,那么事务A对某读频繁的表id为1的行进行更新,根据两阶段锁协议,那么该行是会加一个X锁,且没有释放,这样其他事务在读取的时候会被锁上,导致并发能力降低。
而mvcc允许行被加锁的情况下,读取旧版本数据。
innodb的一致性读通过一致性读视图及undo log实现。
Undo Log
数据库中的每一行都保存这不同的版本,旧版本保存在undo log中。
当一行数据被创建时,会有一些额外的数据,如下图(其中rowid只会在没有主键情况下生成,一般某个表没有指定主键innodb会选择第一个唯一索引作为主键,如果在没有会隐式的生成一列rowid):
当改行数据被修改时,
为该行记录生成undo log, 新纪录的db_roll_pt指针指向该条记录。
undo logs会有一个单独的purge线程负责清除,保证undo logs不会无限增长。
undo logs会有一些参数控制:
innodb_undo_directory:将undo log存储位置独立出来,默认位置为安装目录。
inndb_undo_tablespace:mysql初始化时分配的表空间个数,默认为128,之后会在data目录下创建制定个数的undo文件,文件名以undo_000,undo_001递增。(8.0.14版本后不再支持,具体undo tablespace的介绍,看详情)
(之所以mysql可以把undo log存储独立出来,原因是可以把日志部署到ssd上,提高事务的并发读。)
undo log都是保存在undo log segment中,undo log segment 保存在rollback segment中。
每个回滚段包含1024个undo log segment, 而mysql最大支持128个回滚段(innodb_rollback_segments参数可以显示控制)。因为每个事务在读写undo log时都要独占一块undo log segment,所以理论上最大支持1024 * 128个并发事务操作。
一致性读的实现
RR级别下,数据可见性的规则就是,事务启动前已经提交的事务的更改可见,事务启动后的事务提交不可见。(begin/start transaction并不是一个事务的起点,在该指令后的第一个操作标的语句才标识着事务启动。)
每个事务在启动时会获得一个递增的事务id,记trx_id,并且会创建一个read-view的struct,该结构体包含一个当前所有活动事务的id集合(包括当前事务),一个最小活动事务id和一个最大活动事务id,之前提到过,每一行数据都有一个额外的db_trx_id, 读取到该行时,如果该db_trx_id与当前事务id不一致,就开始读取undo log,然后利用undo log和活动事务集合判断出可以读取的值,判断逻辑图如下:
以上概念称为一致性读,与之对应的有一个”当前读”, 即:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)
举个例子:
k初始为1。
那么B再去查询时结果为3,这就是当前读。
输入的大小超出了分配的缓冲区内存大小,导致溢出。
char string[5];
gets(string); // 输入helloworld
程序相关的数据一般保存在内存的以下区域:
当调用函数时,逻辑堆栈帧被压入栈中,堆栈帧包括函数的参数、返回地址、EBP(EBP是当前函数的存取指针,即存储或者读取数时的指针基地址,可以看成一个标准的函数起始代码)和局部变量(如果函数有局部变量)。程序执行结束后,局部变量的内容将会消失,但是不会被清除。
当函数返回时,逻辑堆栈帧从栈中被弹出,然后弹出EBP,恢复堆栈到调用函数时的地址,最后弹出返回地址到EIP(寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。),从而继续运行程序。所以假如有下图的情况:
由于栈是低地址方向增长的,因此局部数组buffer的指针在缓冲区的下方。当把data的数据拷贝到buffer内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据,根据淹没数据的内容不同,可能会有产生以下情况:
2. 淹没了ebp的值。修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡。
3. 淹没了返回地址。这是栈溢出原理的核心所在,通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程!
4. 淹没参数变量。修改函数的参数变量也可能改变当前函数的执行结果和流程。
5. 淹没上级函数的栈帧,情况与上述4点类似,只不过影响的是上级函数的执行。当然这里的前提是保证函数能正常返回,即函数地址不能被随意修改(这可能很麻烦!)。
而缓冲区溢出攻击就是利用了第三点,改变了cpu要执行的下一条指令的地址。
但是从实现上来说很难,因为没法预估返回地址在栈区的什么位置,也就无法确定溢出多少字节才能覆盖到返回地址。