【Linux102】9-kernel/traps.c


公粽号「专注Linux」,专注Linux内核开发

Linux102系列会详细讲解Linux0.11版本中的102个文件,本文讲解linux0.11的第9个文件kernel/traps.s的文件源码。

1.从上文引出

上文说到,asm.s程序实现了大部分硬件异常所引起的中断的汇编处理过程。而本届讲解的traps.c就是这些中断的具体实现。

2.traps.s的主要作用

traps.c程序主要包括一些在处理异常故障(硬件中断)的底层代码文件asm.s中,调用的相应C函数的具体实现。用于显示出错位置出错号调试信息

其中:

die()通用函数用于在中断处理中显示详细的出错信息
traps_init()函数在前面init/main.c中被调用, 用于硬件异常处理中断问量(陷阱门)的初始化,并设置允许中断请求信号的到来。

阅读本程序时需要参考asm.s程序

3.源码注释版本

在通读源码前,需要了解
(这些都是traps.c中用到的):

C与汇编混用编程移步:【C语言】C语言嵌入汇编程序
printk实现移步:【Linux102】10-kernel/printk.c
exit实现知识屏蔽,暂时不讲,知道其作用为退出就好。
/*
 *  linux/kernel/traps.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'Traps.c' handles hardware traps and faults after we have saved some
 * state in 'asm.s'. Currently mostly a debugging-aid, will be extended
 * to mainly kill the offending process (probably by giving it a signal,
 * but possibly by killing it outright if necessary).
 */
#include <string.h> /*定义了字符操作的汇编、C嵌入式函数,string.h声明位于include/下,实现位于/lib/string.c*/

#include <linux/head.h>  /*head头文件,定义了段描述符的简单结构,和几个选择符常量*/
#include <linux/sched.h> /*sched调度程序头文件,定义了任务结构体task_struct、初始化任务0的数据*/
#include <linux/kernel.h>/*内核头文件。含有一些常见的内核常用函数的原型定义*/
#include <asm/system.h>  /*系统头文件,定义了设置、修改描述符、中断门等的嵌入式汇编函数*/
#include <asm/segment.h> /*段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。*/
#include <asm/io.h>      /*io头文件,定义了硬件端口输入输出的嵌入式汇编函数*/

/*这里保证原汁原味的源码,下面的嵌入式函数,就不分行写了*/

/*取地址addr处的字节*/
/*注意,这里的"0" 是 输入操作数的约束(constraint),表示 “使用与第 0 个输出操作数相同的寄存器”。即ax*/
/*"m"表示使用内存地址*/
#define get_seg_byte(seg, addr) ({ \
register char __res; \
__asm__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \
	:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res; })

/*取地址addr处的长字(4字节)*/

#define get_seg_long(seg, addr) ({ \
register unsigned long __res; \
__asm__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \
	:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res; })

/*取出fs寄存器的值(选择符)*/
#define _fs() ({ \
register unsigned short __res; \
__asm__("mov %%fs,%%ax":"=a" (__res):); \
__res; })

/*定义的一些函数原型*/
int do_exit(long code); /*退出系统调用,实现在kernel/exit.c 102行*/

void page_exception(void); /*页异常处理,实际是page_fault,mm/page.s 14行*/

/*下面定义了中断处理程序原型,代码定义在kernel/asm.s或者system_call.s中。*/
void divide_error(void);                /*int 0(kernel/asm.s 19)*/
void debug(void);                       /*int 1(kernel/asm.s 53)*/
void nmi(void);                         /*int 2(kernel/asm.s 57)*/
void int3(void);                        /*int 3(kernel/asm.s 61)*/
void overflow(void);                    /*int 4(kernel/asm.s 65)*/
void bounds(void);                      /*int 5(kernel/asm.s 69)*/
void invalid_op(void);                  /*int 6(kernel/asm.s 73)*/
void device_not_available(void);        /*int 7 (kernel/system_call.s 148)*/
void double_fault(void);                /*int 8 kernel/asm.s 97*/
void coprocessor_segment_overrun(void); /*int 9(kernel/asm.s 77)*/
void invalid_TSS(void);                 /*int 10(kernel/asm.s 131)*/
void segment_not_present(void);         /*int 11(kernel/asm.s 135)*/
void stack_segment(void);               /*int 12(kernel/asm.s 139)*/
void general_protection(void);          /*int 13(kernel/asm.s 143)*/
void page_fault(void);                  /*int 14(mm/page.s 14)*/
void coprocessor_error(void);           /*int 16(kernel/system_call.s 131)*/
void reserved(void);                    /*int 15(kernel/asm.s 81)*/
void parallel_interrupt(void);          /*int 39(kernel/system_call.s 280)*/
void irq13(void);                       /*int 45协处理器中断处理 (kernel/asm.s 85)*/

/*该子程序用来打印出错中断的名称、出错号、调用程序的EIP、ESP、EFLAGS、FS段寄存器值、*/
// 段的基址、段的长度、进程号pid、任务号、10字节指令码。
/*如果堆栈在用户段,则还打印16字节的堆栈内容。*/

static void die(char *str, long esp_ptr, long nr)
{
    /*强制转化,使得可以下标访问*/
    long *esp = (long *)esp_ptr;
    int i;

    printk("%s: %04x\n\r", str, nr & 0xffff);
    /*esp[1]是段选择符,esp[0]是偏移量,esp[2]是EFLAGS,esp[4]是FS,esp[3]是ESP*/
    printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",
           esp[1], esp[0], esp[2], esp[4], esp[3]);
    // 打印FS寄存器的值
    printk("fs: %04x\n", _fs());
    // 打印段描述符的基址和限长
    printk("base: %p, limit: %p\n", get_base(current->ldt[1]), get_limit(0x17));

    /*如果ESP在用户空间,打印堆栈内容,可是为什么这里是0x17呢?*/
    /*这里是检查段选择子,看是不是用户态,段选择子=0x17=00010111*/
    /*段选择子结构为:*/
    //     高13位           1位    2位
    // +---------------+-----+-----+
    // | 描述符索引     | TI  | RPL |
    // +---------------+-----+-----+
    // 在早期 x86 系统(如 Linux 0.0x/0.1x 内核、DOS 等)中,会为 “用户数据段” 分配固定的段选择子(如 0x17 ),方便内核快速识别 “用户态段”。

    if (esp[4] == 0x17)
    {
        printk("Stack: ");
        for (i = 0; i < 4; i++)
            printk("%p ", get_seg_long(0x17, i + (long *)esp[3]));
        printk("\n");
    }
    /*取出当前任务的任务号,位于include/linux/sched.h 159*/
    str(i);
    /*打印出进程的pid和任务号*/
    printk("Pid: %d, process nr: %d\n\r", current->pid, 0xffff & i);

    /*打印异常发生时 EIP 指针附近的 10 个字节内存数据(通常是指令代码),用于调试分析错误原因,最后触发进程退出。*/
    /*也就是打印异常时,CPU指向的附近10个指令,方便调试*/
    for (i = 0; i < 10; i++)
        printk("%02x ", 0xff & get_seg_byte(esp[1], (i + (char *)esp[0])));
    printk("\n\r");
    do_exit(11); /* play segment exception */
}

/**
 * func descp: 以下这些以 do_开头的函数是对应名称中断处理程序调用的c函数。
 */
void do_double_fault(long esp, long error_code)
{
    die("double fault", esp, error_code);
}

void do_general_protection(long esp, long error_code)
{
    die("general protection", esp, error_code);
}

void do_divide_error(long esp, long error_code)
{
    die("divide error", esp, error_code);
}

/*do_int3用于在程序触发断点指令时,打印当前 CPU 寄存器状态和关键系统信息,辅助调试。*/
void do_int3(long *esp, long error_code,
             long fs, long es, long ds,
             long ebp, long esi, long edi,
             long edx, long ecx, long ebx, long eax)
{
    int tr;

    __asm__("str %%ax" : "=a"(tr) : "0"(0));
    printk("eax\t\tebx\t\tecx\t\tedx\n\r%8x\t%8x\t%8x\t%8x\n\r",
           eax, ebx, ecx, edx);
    printk("esi\t\tedi\t\tebp\t\tesp\n\r%8x\t%8x\t%8x\t%8x\n\r",
           esi, edi, ebp, (long)esp);
    printk("\n\rds\tes\tfs\ttr\n\r%4x\t%4x\t%4x\t%4x\n\r",
           ds, es, fs, tr);
    printk("EIP: %8x   CS: %4x  EFLAGS: %8x\n\r", esp[0], esp[1], esp[2]);
}

void do_nmi(long esp, long error_code)
{
    die("nmi", esp, error_code);
}

void do_debug(long esp, long error_code)
{
    die("debug", esp, error_code);
}

void do_overflow(long esp, long error_code)
{
    die("overflow", esp, error_code);
}

void do_bounds(long esp, long error_code)
{
    die("bounds", esp, error_code);
}

void do_invalid_op(long esp, long error_code)
{
    die("invalid operand", esp, error_code);
}

void do_device_not_available(long esp, long error_code)
{
    die("device not available", esp, error_code);
}

void do_coprocessor_segment_overrun(long esp, long error_code)
{
    die("coprocessor segment overrun", esp, error_code);
}

void do_invalid_TSS(long esp, long error_code)
{
    die("invalid TSS", esp, error_code);
}

void do_segment_not_present(long esp, long error_code)
{
    die("segment not present", esp, error_code);
}

void do_stack_segment(long esp, long error_code)
{
    die("stack segment", esp, error_code);
}

void do_coprocessor_error(long esp, long error_code)
{
    if (last_task_used_math != current)
        return;
    die("coprocessor error", esp, error_code);
}

void do_reserved(long esp, long error_code)
{
    die("reserved (15,17-47) error", esp, error_code);
}

// void trap_init(void) 是操作系统内核初始化阶段的关键函数,用于设置中断向量表(IDT,中断描述符表),
// 将各种硬件 / 软件异常(trap)和中断(interrupt)与对应的处理函数关联起来。
void trap_init(void)
{
    int i;
    /**
     * data descp: 异常处理函数注册(0~16 号向量)
     */
    set_trap_gate(0, &divide_error); // 0号:除法错误(如除以0)
    set_trap_gate(1, &debug);        // 1号:调试异常
    set_trap_gate(2, &nmi);          // 2号:非屏蔽中断(NMI,不可屏蔽的硬件中断)
    /*3~5号中断可以被任意内核态、用户态调用*/
    set_system_gate(3, &int3);                      // 3号:int3断点中断(调试用)
    set_system_gate(4, &overflow);                  // 4号:溢出异常
    set_system_gate(5, &bounds);                    // 5号:数组越界异常
    set_trap_gate(6, &invalid_op);                  // 6号:无效指令异常
    set_trap_gate(7, &device_not_available);        // 7号:设备不可用(如协处理器未就绪)
    set_trap_gate(8, &double_fault);                // 8号:双重故障(严重错误,通常导致系统崩溃)
    set_trap_gate(9, &coprocessor_segment_overrun); // 9号:协处理器段溢出
    set_trap_gate(10, &invalid_TSS);                // 10号:无效TSS(任务状态段错误)
    set_trap_gate(11, &segment_not_present);        // 11号:段不存在(访问了未加载的段)
    set_trap_gate(12, &stack_segment);              // 12号:栈段错误(栈溢出或权限问题)
    set_trap_gate(13, &general_protection);         // 13号:通用保护错误(如权限越界)
    set_trap_gate(14, &page_fault);                 // 14号:页错误(虚拟内存未映射物理内存)
    set_trap_gate(15, &reserved);                   // 15号:保留(未使用)
    set_trap_gate(16, &coprocessor_error);          // 16号:协处理器错误(如浮点运算错误)

    /**
     * data descp: 早期内核中,17~47 号向量可能对应硬件中断(IRQ)或未使用的预留向量,暂时统一用 reserved 函数处理(通常打印 “未实现” 信息)。
     */
    for (i = 17; i < 48; i++)
        set_trap_gate(i, &reserved);
    // 45号:单独指定为irq13(协处理器中断)
    set_trap_gate(45, &irq13);
    // 配置主8259A PIC(中断控制器)
    outb_p(inb_p(0x21) & 0xfb, 0x21);
    // 配置从8259A PIC
    outb(inb_p(0xA1) & 0xdf, 0xA1);
    // 39号:并行端口(如打印机)中断处理
    set_trap_gate(39, &parallel_interrupt);
}  

4.源码完整版

/*
 *  linux/kernel/traps.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'Traps.c' handles hardware traps and faults after we have saved some
 * state in 'asm.s'. Currently mostly a debugging-aid, will be extended
 * to mainly kill the offending process (probably by giving it a signal,
 * but possibly by killing it outright if necessary).
 */
#include <string.h>

#include <linux/head.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/io.h>

#define get_seg_byte(seg, addr) ({ \
register char __res; \
__asm__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \
	:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res; })

#define get_seg_long(seg, addr) ({ \
register unsigned long __res; \
__asm__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \
	:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res; })

#define _fs() ({ \
register unsigned short __res; \
__asm__("mov %%fs,%%ax":"=a" (__res):); \
__res; })

int do_exit(long code);

void page_exception(void);

void divide_error(void);
void debug(void);
void nmi(void);
void int3(void);
void overflow(void);
void bounds(void);
void invalid_op(void);
void device_not_available(void);
void double_fault(void);
void coprocessor_segment_overrun(void);
void invalid_TSS(void);
void segment_not_present(void);
void stack_segment(void);
void general_protection(void);
void page_fault(void);
void coprocessor_error(void);
void reserved(void);
void parallel_interrupt(void);
void irq13(void);

static void die(char *str, long esp_ptr, long nr)
{
    long *esp = (long *)esp_ptr;
    int i;

    printk("%s: %04x\n\r", str, nr & 0xffff);
    printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",
           esp[1], esp[0], esp[2], esp[4], esp[3]);
    printk("fs: %04x\n", _fs());
    printk("base: %p, limit: %p\n", get_base(current->ldt[1]), get_limit(0x17));
    if (esp[4] == 0x17)
    {
        printk("Stack: ");
        for (i = 0; i < 4; i++)
            printk("%p ", get_seg_long(0x17, i + (long *)esp[3]));
        printk("\n");
    }
    str(i);
    printk("Pid: %d, process nr: %d\n\r", current->pid, 0xffff & i);
    for (i = 0; i < 10; i++)
        printk("%02x ", 0xff & get_seg_byte(esp[1], (i + (char *)esp[0])));
    printk("\n\r");
    do_exit(11); /* play segment exception */
}

void do_double_fault(long esp, long error_code)
{
    die("double fault", esp, error_code);
}

void do_general_protection(long esp, long error_code)
{
    die("general protection", esp, error_code);
}

void do_divide_error(long esp, long error_code)
{
    die("divide error", esp, error_code);
}

void do_int3(long *esp, long error_code,
             long fs, long es, long ds,
             long ebp, long esi, long edi,
             long edx, long ecx, long ebx, long eax)
{
    int tr;

    __asm__("str %%ax" : "=a"(tr) : "0"(0));
    printk("eax\t\tebx\t\tecx\t\tedx\n\r%8x\t%8x\t%8x\t%8x\n\r",
           eax, ebx, ecx, edx);
    printk("esi\t\tedi\t\tebp\t\tesp\n\r%8x\t%8x\t%8x\t%8x\n\r",
           esi, edi, ebp, (long)esp);
    printk("\n\rds\tes\tfs\ttr\n\r%4x\t%4x\t%4x\t%4x\n\r",
           ds, es, fs, tr);
    printk("EIP: %8x   CS: %4x  EFLAGS: %8x\n\r", esp[0], esp[1], esp[2]);
}

void do_nmi(long esp, long error_code)
{
    die("nmi", esp, error_code);
}

void do_debug(long esp, long error_code)
{
    die("debug", esp, error_code);
}

void do_overflow(long esp, long error_code)
{
    die("overflow", esp, error_code);
}

void do_bounds(long esp, long error_code)
{
    die("bounds", esp, error_code);
}

void do_invalid_op(long esp, long error_code)
{
    die("invalid operand", esp, error_code);
}

void do_device_not_available(long esp, long error_code)
{
    die("device not available", esp, error_code);
}

void do_coprocessor_segment_overrun(long esp, long error_code)
{
    die("coprocessor segment overrun", esp, error_code);
}

void do_invalid_TSS(long esp, long error_code)
{
    die("invalid TSS", esp, error_code);
}

void do_segment_not_present(long esp, long error_code)
{
    die("segment not present", esp, error_code);
}

void do_stack_segment(long esp, long error_code)
{
    die("stack segment", esp, error_code);
}

void do_coprocessor_error(long esp, long error_code)
{
    if (last_task_used_math != current)
        return;
    die("coprocessor error", esp, error_code);
}

void do_reserved(long esp, long error_code)
{
    die("reserved (15,17-47) error", esp, error_code);
}

void trap_init(void)
{
    int i;

    set_trap_gate(0, &divide_error);
    set_trap_gate(1, &debug);
    set_trap_gate(2, &nmi);
    set_system_gate(3, &int3); /* int3-5 can be called from all */
    set_system_gate(4, &overflow);
    set_system_gate(5, &bounds);
    set_trap_gate(6, &invalid_op);
    set_trap_gate(7, &device_not_available);
    set_trap_gate(8, &double_fault);
    set_trap_gate(9, &coprocessor_segment_overrun);
    set_trap_gate(10, &invalid_TSS);
    set_trap_gate(11, &segment_not_present);
    set_trap_gate(12, &stack_segment);
    set_trap_gate(13, &general_protection);
    set_trap_gate(14, &page_fault);
    set_trap_gate(15, &reserved);
    set_trap_gate(16, &coprocessor_error);
    for (i = 17; i < 48; i++)
        set_trap_gate(i, &reserved);
    set_trap_gate(45, &irq13);
    outb_p(inb_p(0x21) & 0xfb, 0x21);
    outb(inb_p(0xA1) & 0xdf, 0xA1);
    set_trap_gate(39, &parallel_interrupt);
}

5.源码图像版

traps.c源码图像版

traps.c源码注释版图像



汇编语言

😉【汇编语言】1—基础硬件知识

😉【汇编语言】2—寄存器基础知识

😉【汇编语言】3-寄存器与内存的交互

😉【汇编语言】4-第一个完整的汇编程序

😉【汇编语言】5-[BX]和loop指令

😉【汇编语言】6-包含多个段的程序

😉【汇编语言】7-灵活的5大寻址方式

😉【汇编语言】8-1-数据的位置

😉【汇编语言】8-2-数据的长度

😉【汇编语言】8-数据处理的两个基本问题(位置与长度)

😉【DOSBox】1-debug

😉【DOSBox】2-debug可执行文件

😉【DOSBox】3-完整开发流程


C语言

😉【C语言】C Token(C89 C99 C11)

😉【C语言】指针基础

😉【C语言】数组基础

😉【C语言】结构体对齐

😉【C语言】华为C语言进阶测试

😉【C语言】触发刷新到磁盘的方式总结

😉【C语言】C语言文件操作的mode详解

😉【C语言】C语言文件知识全讲解

😉【C语言】从extern到头文件包含的最优实践

😉【C语言】C语言的关键字与重载机制

😉【C语言】长字符串的2种处理方式

😉【C语言】C语言嵌入汇编程序

😉【C陷阱与缺陷】-1-词法陷阱

😉【C陷阱与缺陷】-2-语法陷阱

😉【C陷阱与缺陷】-3-语义陷阱


Linux101系列

专注讲解Linux中的常用命令,共计发布100+文章。

😉【Linux101-1】ls

😉【Linux101-1】ls -l命令输出结果全解析

😉【Linux101-2】cd

😉【Linux101-3】cat

😉【Linux101-4】tac

😉【Linux101-5】head

😉【Linux101-6】tail

😉【Linux101-7】pwd

😉【Linux101-8】touch

😉【Linux101-9】cal

😉【Linux101-10】bc

😉【Linux101-11】df

😉【Linux101-12】uname

😉【Linux101-13】mkdir

😉【Linux101-14】gzip

😉【Linux101-15】tar

😉【Linux101-16】lsof

😉【Linux101-17】du

😉【Linux101-18】stat


Linux102系列

本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。

😉【Linux102】1-Makefile

😉【Linux102】2-Makefile.header

😉【Linux102】3-system.map

😉【Linux102】4-bootsect.s

😉【Linux102】5-setup.s

😉【Linux102】6-head.s

😉【Linux102-D】/boot

😉【Linux102】7-main.c

😉【Linux102】8-kernel/asm.s

😉【Linux102】10-kernel/printk.c

😉【Linux102】11-kernel/vsprintf.c

😉【Linux102】12-include/stdarg.h


Linux内核精讲系列

和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。

😉【Linux】学习Linux前必备的知识点

😉【Linux】Linux内核对进程的内存抽象

😉【Linux】Linux概述1-linux对物理内存的使用

😉【Linux】软件从写下到运行的全部流程

😉【Linux】CPU的三寻址:实模式、保护模式、长模式

😉【Linux】实模式与保护模式的寻址, 这次讲明白了!

😉【Linux】linux0.11的源码目录架构

😉【Linux】Makefile机制及基础详解

😉【Linux】编译并运行Linux0.11

😉【Linux】“进进内网文”—Linux的内核结构全貌

😉【Linux】linux的中断机制

😉【Linux】linux进程描述



关于小希

😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言汇编等知识。

我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦


小希的座右铭:别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:

不妨关注、评论、转发,让更多朋友看到哦~~~🙈

下一期想看什么?在评论区留言吧!我们下期见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值