【问题标题】:Does C++ exception framework require kernel support?C++ 异常框架是否需要内核支持?
【发布时间】:2021-02-09 12:42:03
【问题描述】:

我正在阅读 C++ 中的异常支持,结果可能也适用于其他语言。

https://wiki.osdev.org/C%2B%2B_Exception_Support

http://www.ucw.cz/~hubicka/papers/abi/node25.html

发生了很多事情,我有点不知所措。我的问题是在展开过程中,C++ 是否需要内核的帮助(例如 setjmp/longjmp 如 cmets 中所述,这两个函数不涉及内核,但是我的问题仍然相同)?还是整个过程只发生在用户空间?

我可以理解本地线程的展开(想想 setjmp/longjmp),但无法理解远程线程是如何展开的。

如果所有的展开魔法都发生在用户空间,一个线程如何在没有内核帮助的情况下修改另一个错误线程以将其发送到处理代码?由于我相信线程上下文信息(任务结构)存在于内核中并且不允许直接从用户空间进行修改,而不访问线程上下文,它是如何实现的?谢谢!


更新:看起来我对任务结构有一些基本的误解。刚刚碰到一张关于信号处理的幻灯片 (signal handlers) (http://users.cms.caltech.edu/~donnie/cs124/lectures/CS124Lec15.pdf),在进入信号处理程序时,内核堆栈是空的,所有上下文信息都存在于用户堆栈中。我想可以使用信号与其他故障线程进行通信以放松?如果是这样,是否意味着 C++ 异常支持需要内核帮助,因为生成信号是内核调用?


更新:就问题而言,我对 Paul 的回答感到满意。我想根据我对评论/答案的阅读,从更广泛的意义上总结我对该主题的理解,如果我错了,请纠正我。

C++异常框架并没有指定具体的实现方式,下面总结就是我认为的“常见”。

异常对象有3个来源,产生的展开实现如下:

+------------------------------------------------+--------------------------------------------------------------------------+
|                exception source                |                             unwinding method                             |
+------------------------------------------------+--------------------------------------------------------------------------+
| a) exception created by `throw`                | via code to walk the stack and assembly to transfer control to call site |
| b) exception created by hardware e.g. SIGFPE   | via synchronous signal, and code to walk the stack                       |
|                                                | and assembly to transfer control to call site                            |
| c)* exception created by remote process/thread | via asynchronous signal, and code to walk the stack                      |
|                                                | and assembly to transfer control to call site                            |
+------------------------------------------------+--------------------------------------------------------------------------+

*对于案例 c),我不确定是否有任何实际使用需要由外部进程创建并在本地处理的异常,它不符合 throw/catch 语义要么(至少它是可行的),我把它放在这里只是为了完整性。

【问题讨论】:

  • 我的问题是在展开过程中,C++ 是否需要内核的帮助 -- C++ 标准没有提及编译器如何实现异常处理,只提及异常处理是如何实现的应该表现得很好。
  • 没有。最初它是作为一组宏实现的,它告诉您所有您需要了解的有关内核支持的信息。 setjmp() 就足够了。
  • setjmp 也不需要内核帮助。它可能会从中受益,但仅此而已。
  • @MSalters 我既没有声明也没有暗示setjmp() 需要内核支持。

标签: c++ exception kernel


【解决方案1】:

没有。

AFAIK 这一切都在 C++ 运行时库中实现(对于 GCC,这将是 libgcc_s.so,至少在 Linux 上是这样)。这需要代码遍历堆栈以查找 catch 块和一些机器代码在找到时跳转到这些块中。如前所述,setjmp/longjmp 也不需要内核。

更新:不要混淆 C++ 异常和信号。如果你想看更多 C++ 异常如何工作是一个良好的开端(如果您喜欢阅读 assembler) 将使用godbolt 查看生成的汇编程序中的一些简单函数,这些函数会抛出和捕获异常。请注意 浮点异常 (FPE) 确实是信号。

C++ 异常和 setjmp/longjmp + 信号之间的一个区别是 控制流。后两者通过一种“调用”(保存上下文)和“返回”(恢复上下文)来实现“恢复语义”。另一方面,C++ 异常实现了“终止语义”并且无法返回到throw 的站点。

信号分为两类:立即传递的同步信号(如 SIGBUS)和在下一个调度程序时间片上传递的异步信号(如 SIGINT)。两种情况都通过内核。没有真正的 从用户的角度来看的区别。

线程不能很好地与其他任何进行堆栈操作的东西(setjmp/longjmp 和信号)混合。特别是多线程应用程序可以 在任何线程上接收异步信号。为了便于管理, 通常的做法是使用pthread_sigmask 来阻止所有信号 除一个之外的所有线程,这将是信号处理线程。

【讨论】:

  • 谢谢保罗,一个后续问题,假设我有一个多线程应用程序,它由一个信号处理线程和其他工作线程组成,来自信号处理线程,我该如何更新程序故障工作线程的计数器(现在因为它有异常而被中断)所以它会在下次计划运行时跳转到处理代码块?或者我的问题是否有意义?
  • 您通常让线程检查某处的未决异常或停止标志。
  • @Botje 你能详细说明一下吗?如果线程已经导致异常,因此中断了 CPU,那么线程如何设法分支到其他地方以运行代码以检查未决异常?谢谢
  • 用户空间异常不会“中断 CPU”,它们只是定期检查+跳转到异常处理代码。除以零被捕获在内核中并作为信号发送回导致它的线程。异常处理代码应该已经安装了一个重定向到异常处理代码的处理程序。我看不出专用信号处理线程有什么用。
猜你喜欢
  • 2021-02-28
  • 2021-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-01
  • 2017-02-11
  • 2011-10-09
相关资源
最近更新 更多