为什么在C语言中清除中断标志会导致段错误?

7

我正在学习汇编语言和C语言的基础知识。为了学习目的,我决定编写一个简单的程序来禁用中断,当用户想在控制台输入内容时,无法实现:

#include <stdio.h>
int main(){
    int a;
    printf("enter your number : ");
    asm ("cli");
    scanf("%d", &a);
    printf("your number is %d\n" , a);     
    return 0;
}

但是当我使用GCC进行编译时,出现了分段错误:
Segmentation fault (core dumped)

当我使用gdb调试时,当程序到达asm("cli");一行时,我得到了这个消息:

Program received signal SIGSEGV, Segmentation fault.
main () at cli.c:6
6       asm ("cli");

4
据我所知,用户模式无法调用它。只有内核模式驱动程序才能清除中断标志。 - bkausbk
奇怪的错误,顺便说一句,我期望看到的是非法指令而不是SIGSEGV。 - PMF
@bkausbk:实际上,Linux有一个iopl系统调用,可以让你提高特权级别并从用户空间进程中禁用中断。虽然这可能是个可怕的想法,但并不被支持。 - Peter Cordes
@PMF:SIGILL仅用于指令解码错误。SIGSEGV是特权指令以及访问未映射内存的总称。 - Peter Cordes
2个回答

13

这是因为用户空间程序无法禁用中断。所有中断都在内核控制下。您需要从内核空间进行操作。在此之前,您需要先学习内核内部,并且根据我的了解,处理中断非常关键,需要更多的内核知识。

您需要编写一个可以通过 /dev/(或其他)接口与用户空间交互的内核模块。用户空间代码应请求内核模块禁用中断。


谢谢。我不知道这个。你能给我一些入门资源吗? - mojibuntu
1
@mojibuntu 我正在给我的答案添加描述。 - Chinna

4

cli是一条特权指令。如果当前程序或过程的CPL(代码特权级)比IOPL(I/O特权级)低,则会引发#GP(0)异常。这个#GP异常导致Linux向您的进程发送SIGSEGV信号。

在Linux下,您可以进行一个iopl(3)系统调用,将您的IO特权级提高到与您的环 3 CPL相匹配,然后您就可以在用户空间禁用中断。(但不要这样做,据我所知,这不受支持iopl 的预期使用情况是在用户空间使用高端口号的inout指令,而不是cli/sti。x86恰好对两者都使用相同的权限。)

如果您不立即重新启用中断,甚至在重新启用中断后,您的系统可能会崩溃。或者至少会在多核系统上损坏CPU。基本上,除非您准备按下重置按钮,即关闭X11,保存文件并运行sync,否则不要这样做。还要将文件系统重新挂载为只读。
或者在像BOCHS这样的虚拟机或模拟器中尝试它,即使中断被禁用,也可以使用调试器打断点。或者尝试从USB驱动器引导。
请注意,禁用中断仅禁用外部中断。像int $0x80这样的软件生成的中断仍然会被执行,但是在禁用中断的情况下进行系统调用可能是一个更糟糕的想法。(它可能会起作用,但是内核会保存/恢复EFLAGS,因此它可能不会返回到用户空间并重新启用中断。)但是,长时间禁用中断对于中断延迟来说是一件坏事。

如果你作为初学者想要尝试禁用中断,那么你应该从一个使用BIOS调用来进行I/O操作的玩具引导扇区程序中进行。或者,如果你好奇为什么要这样做,可以查看Linux内核源代码中一些禁用/启用中断的地方。

在我看来,“普通”的用户空间汇编语言已经足够有趣了。通过性能计数器,你可以看到CPU如何解码和执行指令的细节。请参见标签维基中的链接,获取手册、指南和性能调整信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接