8

我正在研究将执行程序的状态存储到磁盘并再次将其带回的基本原则。在我们目前的设计中,每个对象(这是一个带有函数指针列表的 C 级事物,一种低级的自制面向对象——这样做有很好的理由)将是调用以将其显式状态导出为可写和可恢复的格式。使这项工作的关键属性是与对象相关的所有状态确实封装在对象数据结构中。

还有其他使用活动对象的解决方案,其中有一个用户级线程附加到某些对象。因此,程序计数器、寄存器内容和堆栈内容突然成为程序状态的一部分。据我所知,没有好的方法可以在任意时间点将这些东西序列化到磁盘上。线程必须将自己停放在程序计数器等不表示任何内容的特殊状态下,因此基本上将它们的执行状态机状态“保存”到显式对象状态。

我查看了一系列序列化库,据我所知,这是一个通用属性。

核心问题是:或者事实并非如此?就线程在其代码中执行的位置而言,是否有可以包括线程状态的保存/恢复解决方案?

请注意,在虚拟机中保存整个系统状态不算数,这并不是真正序列化状态,而只是冻结机器并移动它。这是一个明显的解决方案,但大多数时候有点重量级。

有些问题清楚地表明,我在解释我们如何做事的想法时不够清楚。我们正在开发一个模拟器系统,对于在其中运行的代码有非常严格的规则是允许编写的。特别是,我们完全区分了对象构造和对象状态。每次设置系统时都会重新创建接口函数指针,而不是状态的一部分。状态仅由特定指定的“属性”组成,每个“属性”都具有定义的获取/设置函数,该函数在内部运行时表示和存储表示之间进行转换。对于对象之间的指针,它们都被转换为名称。所以在我们的设计中,一个对象可能会像这样在存储中出现:

Object foo {
  value1: 0xff00ff00;
  value2: 0x00ffeedd;
  next_guy_in_chain: bar;
}

Object bar {
  next_guy_in_chain: null;
}

链表从未真正存在于模拟结构中,每个对象代表某种硬件单元。

问题是有些人想这样做,但也有线程作为编码行为的一种方式。这里的“行为”实际上是模拟单元状态的突变。基本上,我们的设计说所有这些更改都必须在原子完整操作中进行,这些操作被调用、完成它们的工作并返回。所有状态都存储在对象中。你有一个反应模型,或者它可以被称为“运行到完成”或“事件驱动”。

另一种思考方式是让对象有活动的线程在它们上面工作,它们像经典的 Unix 线程一样处于一个永恒的循环中,并且永远不会终止。这是我试图查看它是否可以合理地存储到磁盘的情况,但是如果不在下面插入 VM,这似乎是不可行的。

2009 年 10 月更新:与此相关的论文发表在 2009 年的 FDL 会议上,请参阅这篇关于检查点和 SystemC 的论文。

4

7 回答 7

2

我不认为仅序列化程序的“某些线程”可以工作,因为您会遇到同步问题(这里描述了一些问题http://java.sun.com/j2se/1.3/docs/guide /misc/threadPrimitiveDeprecation.html)。因此,坚持整个程序是获得一致状态的唯一可行方法。

您可能会研究的是正交持久性。有一些原型实现:

http://research.sun.com/forest/COM.Sun.Labs.Forest.doc.external_www.PJava.main.html

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.17.7429

但是它们都不再被维护或获得很多吸引力(afaik)。我想检查点毕竟不是最好的解决方案。在我自己的项目http://www.siebengeisslein.org我正在尝试使用轻量级事务来调度事件的方法,因此不必维护线程状态(因为在事务结束时,线程调用堆栈再次为空, 如果一个操作在事务中间停止,一切都会回滚,所以线程调用堆栈也很重要)。您可能可以使用任何 OODBMS 实现类似的东西。

另一种看待事物的方式是延续http://en.wikipedia.org/wiki/Continuation,http://jauvm.blogspot.com/)。它们是一种在定义的代码位置暂停执行的方法(但它们不一定保持线程状态)。

我希望这能给你一些起点(但没有现成的解决方案来解决这个问题)。

编辑:阅读您的说明后:您绝对应该研究 OODBMS。在自己的事务中调度每个事件,而不关心线程。

于 2008-10-08T18:48:22.680 回答
1

这听起来真的就像保存虚拟机的状态并能够以完全相同的方式恢复它正是您想要的。

如果您只需要能够使用先前执行所使用的相同数据来启动程序运行,那么您只需要保存并恢复持久数据,每个线程的确切状态并不重要,因为它无论如何都会变化得如此之快-下次事物的实际地址会有所不同。无论如何,使用数据库应该会给你这种能力。

于 2008-10-08T18:29:33.077 回答
1

比尝试序列化程序状态更好的方法是实现带有数据检查点的Crash Only Software 。您如何进行数据检查点将取决于您的实施和问题域。

于 2008-10-08T18:58:11.053 回答
0

JSR 323 中实际上为 Java 提出了这样的建议:

http://tech.puredanger.com/2008/01/09/strong-mobility-for-java/

但不被接受为过于理论化:

http://tech.puredanger.com/2008/01/24/jcp-votes-down-jsr-323/

如果您点击链接,您可以找到一些关于这个问题的有趣研究。

于 2008-10-08T21:47:51.133 回答
0

您不应该尝试序列化您的程序必须存储的状态。因为除非操作系统允许,否则您的程序将永远无法完全控制其状态,在这种情况下......它是操作系统的一部分。

不能保证指向某个虚拟内存位置的指针将再次指向相同的虚拟内存位置(除了 heap-begin/end、stack-begin 等属性),因为对于程序而言,操作系统对虚拟内存的选择是不确定的。您通过 sbrk 或更高级别的接口(例如 malloc)从操作系统请求的页面将在任何地方开始。

更好的:

  • 清理代码并检查您的设计:其中包含哪些状态属性?
  • 不要使用这种低级语言,因为创建您尝试做的事情的开销不值得结果。
  • 如果您必须使用 C,请考虑使您的生活尽可能轻松的方法(考虑 offsetof 运算符和属性结构具有从偏移量 0 开始的第一个成员)。

我怀疑您想缩短序列化/反序列化特定数据结构(例如链表)所需的开发时间。请放心,您尝试做的事情并非微不足道,而且需要做更多的工作。如果您坚持这样做,请考虑查看操作系统的内存管理代码和操作系统的分页机制。;-)

由于附加问题而进行编辑:您陈述的设计听起来像是某种状态机;对象属性设置为可序列化,函数指针可以恢复。

首先,关于对象中的线程状态:这些仅在可能存在典型的并发编程问题(例如竞争条件等)时才重要。如果是这种情况,您需要线程同步功能,例如互斥锁、信号量等。然后您可以随时访问属性以进行序列化/反序列化并确保安全。

其次,关于对象设置:看起来很酷,不确定您是否有二进制或其他对象表示。假设二进制:如果您可以表示内存中的实际结构(这是一些编码开销),您可以轻松地序列化它们。在对象的开头插入某种类 ID 值,并有一个指向实际装备的查找表。查看第一个 sizeof(id) 字节,您就知道您拥有哪种结构。然后你就会知道哪个结构在那里铺设。

在序列化/反序列化时,请像这样处理问题:您可以查找假设打包(成员之间没有间距)结构的长度,分配该大小并一个接一个地读/写成员。考虑 offsetof,或者,如果您的编译器支持它,则只需使用打包结构。

编辑由于大胆的核心问题:-)不,没有;不适合 C。

于 2008-10-08T18:22:22.717 回答
0

看起来你想在 C++ 中有一个闭包。正如您所指出的,语言中没有内置机制可以让您这样做。据我所知,这基本上不可能以完全通用的方式进行。一般来说,用没有 VM 的语言很难做到。你可以通过做一些你建议的事情来伪造它,基本上创建一个维护执行环境/状态的闭包对象。然后在它处于已知状态时让它自己序列化。

您还将在函数指针方面遇到麻烦。这些函数可以在每次加载时加载到不同的内存地址。

于 2008-10-08T18:22:53.450 回答
0

我认为线程状态是一个可能不适合序列化的实现细节。你想保存你的对象的状态——不一定是它们是如何变成现在这样的状态的。

作为为什么要采用这种方法的示例,请考虑无中断升级。如果您正在运行版本 N 的应用程序并想要升级到版本 N+1,则可以使用对象序列化来执行此操作。但是,“版本 N+1”线程不会与版本 N 线程不同。

于 2008-10-08T18:58:10.977 回答