# 16.8 为什么新transaction需要等前一个transaction中系统调用执行完成

以上就是ext3中相对来说直观的部分。实际上还有一些棘手的细节我想讨论一下。之前我提到过，ext3中存在一个open transaction，但是当ext3决定要关闭该transaction时，它需要等待该transaction中的所有系统调用都结束，之后才能开始新的transaction。假设我们现在有transaction T1，其中包含了多个系统调用。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MTJ6xt03A2w51ESKqKL%2F-MTJx3qwRZl3joIJEm-g%2Fimage.png?alt=media\&token=81aaee0f-17d1-40fa-b83c-b2d9696937c7)

如果我们想要关闭T1，我们需要停止接收新的系统调用，因为我们想要等待现有的系统调用结束，这样才能commit transaction。所以直到这些系统调用都结束了，在ext3中不能允许开始任何新的系统调用。所以只有在T1中的系统调用完成之后，才能开始在接下来的transaction T2中接收系统调用。在这之间有一段时间，新的系统调用是被拦截的，这降低了性能，因为我们本来应该执行系统调用的但是又不被允许。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MTJyQCwBXEnGlZk1NmK%2F-MTKQRv6BthCVzSM2VTr%2Fimage.png?alt=media\&token=ee1a9484-c4a7-45c2-8672-b392a9ac09ab)

这里的问题是，直到T1中所有的系统调用都结束之前，ext3为什么不让T2中的系统调用开始执行呢？让我们来看一下没有这个限制条件可能会带来的错误的场景。我们假设T1只包含了一个系统调用，这是一个create系统调用用来创建文件x。在create系统调用结束之前，文件系统决定开始一个新的transaction T2用来接收create之后的所有系统调用。我们假设T2在T1结束之前就开始了，T2对另一个文件y调用了unlink系统调用。unlink会释放与y关联的inode。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MTJyQCwBXEnGlZk1NmK%2F-MTKRmMvNfLH_kK6rLKP%2Fimage.png?alt=media\&token=cf811cbb-7808-4125-8feb-69c49c20ac8f)

假设在下面的时间点T2将inode标记为空闲的，create会为x分配inode，或许它在之后的一个时间点分配了inode。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MTJyQCwBXEnGlZk1NmK%2F-MTKSGJNfcYzr0szR3si%2Fimage.png?alt=media\&token=10d2f211-ad5c-4ea5-9075-4e82bc017f7b)

因为create在unlink释放inode之后分配的inode，它可能会重用同一个inode，所以x可能会获得y的inode，假设是inode 17。目前为止没有问题，因为unlink本来就是释放inode。当T1中的create结束之后，我们会关闭T1，在最后我们会将T1的所有更新都写入到磁盘的log中。之后unlink还要花点时间才能结束，但是在它结束之前计算机crash了。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MTJyQCwBXEnGlZk1NmK%2F-MTKTY85mNRMb-syFraU%2Fimage.png?alt=media\&token=e25dfea4-096d-45f3-bb94-273c734aa98f)

在重启并运行恢复软件时，可以发现T1已经commit了，而T2没有。所以恢复软件会完全忽略T2，这意味着T2中的unlink就跟没有发生过一样，恢复软件不会执行T2中的unlink，也就不会删除文件y。所以crash并重启之后y文件仍然存在，并还在使用inode 17。然而T1又完成了，x文件使用的也是inode 17，所以现在我们错误的有了两个文件都使用了相同的inode，这意味着它们共享了文件内容，向一个文件写数据会神奇的出现在另一个文件中。这完全是错误的，因为我们本来想的是删除y，并为x分配一个空闲的inode，而不是一个已经在使用中的inode。这里可以这么想，T2中的unlink修改了一个block，最终这个修改过的block被前一个transaction所使用。T2中修改的信息，被T1所使用了，这意味着我们丢失了T2的原子性。因为T2的目标是unlink的效果要么是全发生，要么是完全不发生。但是刚刚的例子中，因为T1使用了T2中释放的inode，这意味着T2中部分修改已经生效了，但是其他的修改随着crash又丢失了。

或许你可以想到一些修复这里问题的方法，或许T1可以发现inode是由后一个transaction释放的而不去使用它。而ext3采用了一个非常简单的方法，在前一个transaction中所有系统调用都结束之前，它不允许任何新的系统调用执行。所以transaction T1也就不可能看到之后的transaction包含的更新。因为直到T1 commit了，整个unlink都不被允许执行。

> 学生提问：当你关闭一个open transaction时，具体会发生什么呢？会对当前的缓存做一个快照吗？
>
> Robert教授：会的，当我们关闭一个transaction，文件系统会拷贝被transaction中的系统调用所修改的所有block，之后transaction才会commit这些block。后面的transaction会在真正的block cache上运行。当将block都commit到log之后，对于block cache的拷贝就可以丢弃了。

以上是众多ext3需要处理的小细节之一，因为为了支持并发，ext3需要处理各种各样的特殊细节，但是我们没有时间讨论所有的细节。
