下面要分析三个层次的代码逻辑。第一层是块设备,第二层是Device Mapper,第三层是作为Target Device的dm-verity。
1.块设备
在执行之前,代码逻辑先要在块设备架构中转上一大圈。首先看看块设备中的接口函数
函数generic_make_request的函数主体是一个循环,循环处理每一个bio,处理bio的工作由函数指针make_request_fn所指向的函数完成。函数指针make_request_fn的赋值是在函数blk_queue_make_request中完成的:
2.Device Mapper
上面是块设备的代码逻辑,下面看看在Device Mapper中如何赋值这个函数指针
Device Mapper要将make_request_fn指针赋值为dm_request。dm_request的定义如下:
代码逻辑从dm_request开始,经过一系列函数调用,最终会调用__map_bio:
从这里,代码逻辑就进入了具体的Target Device。
3.dm-verity
“map”是一个函数指针,对于dm-verity设备,它指向函数verity_map。代码还是很简单的:(www.xing528.com)
它主要做了两件事,第一件是将bio的bi_end_io函数指针和数据成员bi_private换掉,将原有值保存,以便以后恢复。第二件事是调用块设备的函数generic_make_request启动对dm-verity的data device的操作。按照块设备的代码逻辑,在io操作之后,bi_end_io函数指针会被调用,对于dm-verity来说就是verity_end_io。下面看看bi_end_io指针所指向的verity_end_io,就知道verity要干什么了:
如果读写出错,就调用verity_finish_io,此函数恢复原有的bi_end_io指针和bi_private指针:
重要的工作在verity_work函数中:
verity_work会调用verity_verify_io,此函数完成最重要的工作——校验。
verity_verify_io函数的定义比较长,这里省略了许多。函数的主体是一个循环,对io中所有的block进行校验。在循环体中又分为两部分,在标号test_block_hash之前的部分是对哈希设备进行校验,在标号test_block_hash之后的部分是对数据设备进行校验。回顾一下,dm-verity需要两个“底层”设备,一个是数据设备,用来存储数据,另一个是哈希设备,存储的是数据的校验值。
参考图13-4,在哈希设备中存储是分层的。最底层是0层,每个块中存储的是数据设备中对应块的数据的校验值;其上每层都存储着下一层的校验值。在此函数中,首先检验第0层数据的正确性。这里用了一个技巧,检验的结果是被缓存了的,如果以前检验的结果为正确,verity_verify_level(io,io->block+b,0,true)就会返回0,然后函数就直接到test_block_hash之后去执行校验数据设备的操作。如果之前没有做过校验,就要老老实实地验证哈希设备的完整性。0层由1层校验,1层由2层校验……顶层由root hash校验。函数又用了一个技巧,它是自顶向下做校验的,因为一开始“好的”校验值只有一个,就是root hash。经过root hash校验后levels-1层存储的校验值就成了“好的”校验值,又可以用它们来校验levels-2层了,以此类推直到第0层。校验操作无非是算出一个校验值,用此值和一个事先存储的校验值比较。函数中将好的校验值存储在缓冲区io_want_digest(v,io)之中。首先在本函数verity_verify_io中将root hash填入这个缓冲区,然后在函数verity_verify_level中做验证,验证通过后就会将新的校验值填入这个缓冲区。
在标号test_block_hash之前,第0层的哈希设备的数据一定是被存储在了缓冲区io_want_ digest(v,io)之中的。标号test_block_hash之后的部分所做的工作就是读出数据设备中的数据块内容,算出校验值,和io_want_digest(v,io)之中的内容进行比较,如果相同就通过了校验。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。