ARM架构中断与异常向量表机制解析

往期内容

本专栏往期内容,interrtupr子系统:

  1. 深入解析Linux内核中断管理:从IRQ描述符到irq domain的设计与实现
  2. Linux内核中IRQ Domain的结构、操作及映射机制详解
  3. 中断描述符irq_desc成员详解
  4. Linux 内核中断描述符 (irq_desc) 的初始化与动态分配机制详解
  5. 中断的硬件框架
  6. GIC介绍
  7. GIC寄存器介绍

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

目录

  • 往期内容
  • 1.回顾中断的发生、处理过程
  • 2.异常向量表的安装
    • 2.1 复制向量表
    • 2.2 向量表在哪
  • 3.中断向量
  • 4.处理流程
  • 5.处理函数

1.回顾中断的发生、处理过程

  • 中断发生的硬件过程

img

  • 中断处理的软件处理流程

    • CPU执行完当前指令,检查到发生了中断,跳到向量表
    • 保存现场、执行GIC提供的处理函数、恢复现场

中断的硬件框架-CSDN博客

2.异常向量表的安装

对于arm架构,异常向量表有两个位置:0和0xffff0000。前者一般在裸机中会用到;后者是上了操作系统后的,并且这个地址是虚拟地址,需要在物理地址上存放好vector向量表后,将虚拟地址0xffff0000映射到该存放好向量表的物理地址

2.1 复制向量表

  • 汇编代码
// arch\arm\kernel\head.S
1. bl	__lookup_processor_type...... 
2. bl	__create_page_tables  //创建页表:建立虚拟地址和物理地址之间的映射关系
3. ldr	r13, =__mmap_switched //address to jump to after mmu has been enabled 当使能mmu后会跳到mmap_switched函数,将该函数地址保存到r13
4. b	__enable_mmub	__turn_mmu_onmov	r3, r13ret	r3
5. __mmap_switched: // arch\arm\kernel\head-common.S
6. b	start_kernel  //这里是跳转指令,也就是跳转去仔细start_kernel函数
  • 创建新向量表,将向量表的地址复制到新分配的vectors中,主要是在early_trap_init函数中实现的。
start_kernel // init\main.csetup_arch(&command_line); // arch\arm\kernel\setup.cpaging_init(mdesc);    // arch\arm\mm\mmu.cdevicemaps_init(mdesc); // arch\arm\mm\mmu.cvectors = early_alloc(PAGE_SIZE * 2); // 1.分配新向量表 -- 物理内存early_trap_init(vectors);             // 2.在代码中将vectors中的向量表复制到新向量表,具体看下图// 3. 映射新向量表到虚拟地址0xffff0000//存放向量表的物理地址和虚拟地址0xffff0000之间的关联/** Create a mapping for the machine vectors at the high-vectors* location (0xffff0000).  If we aren't using high-vectors, also* create a mapping at the low-vectors virtual address.*/map.pfn = __phys_to_pfn(virt_to_phys(vectors));map.virtual = 0xffff0000;map.length = PAGE_SIZE;#ifdef CONFIG_KUSER_HELPERSmap.type = MT_HIGH_VECTORS;#elsemap.type = MT_LOW_VECTORS;#endifcreate_mapping(&map);

下面是devicemaps_init函数中具体的内容:

static void __init devicemaps_init(const struct machine_desc *mdesc)
{struct map_desc map;  // 用于定义映射区域的结构体unsigned long addr;   // 循环使用的地址变量void *vectors;        // 向量表的虚拟地址指针/** 1. 提前分配异常向量页的内存,通常大小为两个页面。*    异常向量用于处理处理器的各种异常(如中断、错误等)。*/vectors = early_alloc(PAGE_SIZE * 2);// 提前初始化异常向量,将 vectors 地址传递给异常初始化函数early_trap_init(vectors);/** 2. 清除页表,排除顶层 PMD 页面,以便稍后使用 early_fixmaps。*    VMALLOC_START 表示虚拟内存的起始地址。*/for (addr = VMALLOC_START; addr < (FIXADDR_TOP & PMD_MASK); addr += PMD_SIZE)pmd_clear(pmd_off_k(addr));/** 3. 如果内核是 XIP (Execute In Place) 模式,则在 modulearea 中映射内核代码。*    XIP 模式允许直接从 ROM 中执行代码,不必将代码拷贝到 RAM 中。*/
#ifdef CONFIG_XIP_KERNELmap.pfn = __phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK);  // XIP 的物理基地址map.virtual = MODULES_VADDR;   // 模块区的起始虚拟地址map.length = ((unsigned long)_exiprom - map.virtual + ~SECTION_MASK) & SECTION_MASK;  // 映射长度map.type = MT_ROM;  // 映射类型为只读create_mapping(&map);  // 创建内核的只读映射
#endif/** 4. 映射缓存刷新区域。此区域用于缓存清除和同步 CPU 缓存。*/
#ifdef FLUSH_BASEmap.pfn = __phys_to_pfn(FLUSH_BASE_PHYS);  // 映射物理地址map.virtual = FLUSH_BASE;  // 缓存刷新区域的虚拟地址map.length = SZ_1M;  // 映射长度为 1 MBmap.type = MT_CACHECLEAN;  // 设置为缓存清理类型create_mapping(&map);  // 创建映射
#endif
#ifdef FLUSH_BASE_MINICACHEmap.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M);  // 小缓存物理地址map.virtual = FLUSH_BASE_MINICACHE;  // 小缓存虚拟地址map.length = SZ_1M;  // 映射长度 1 MBmap.type = MT_MINICLEAN;  // 小缓存清理类型create_mapping(&map);  // 创建映射
#endif/** 5. 为处理器的高向量位置(0xffff0000)创建一个映射。*    如果不使用高向量,也在低向量位置(0x00000000)创建映射。*/map.pfn = __phys_to_pfn(virt_to_phys(vectors));  // 计算向量的物理页帧号map.virtual = 0xffff0000;  // 设置向量的虚拟地址为高向量位置map.length = PAGE_SIZE;  // 设置映射大小
#ifdef CONFIG_KUSER_HELPERSmap.type = MT_HIGH_VECTORS;  // 高向量类型
#elsemap.type = MT_LOW_VECTORS;  // 低向量类型
#endifcreate_mapping(&map);  // 创建高向量映射// 如果不使用高向量,则在低向量地址(0x00000000)创建映射if (!vectors_high()) {map.virtual = 0;map.length = PAGE_SIZE * 2;  // 为两个页面创建映射map.type = MT_LOW_VECTORS;  // 低向量类型create_mapping(&map);  // 创建低向量映射}/** 6. 为内核创建一个只读映射,用于保护高向量的内核页面。*/map.pfn += 1;  // 下一个物理页面map.virtual = 0xffff0000 + PAGE_SIZE;  // 设置为高向量第二页面的虚拟地址map.length = PAGE_SIZE;  // 映射一个页面map.type = MT_LOW_VECTORS;  // 低向量类型create_mapping(&map);  // 创建只读映射/** 7. 如果机器描述结构中包含 map_io 函数,则调用它映射静态映射设备;*    否则使用默认的 debug_ll_io_init() 进行初始化。*/if (mdesc->map_io)mdesc->map_io();elsedebug_ll_io_init();// 填补 PMD 缺口fill_pmd_gaps();/** 8. 为 VMALLOC 区域保留固定的 I/O 空间,用于 PCI 设备。*/pci_reserve_io();/** 9. 刷新缓存和 TLB 确保所有内存操作都一致。*    此外确保写缓存中的向量页已写回。*/local_flush_tlb_all();  // 刷新整个 TLBflush_cache_all();      // 刷新缓存/** 10. 启用异步中止异常处理。*/early_abt_enable();
}
  • 向量页分配与初始化:提前分配两个页面的大小用于异常向量表,然后调用 early_trap_init 初始化异常向量。
  • 页表清理:清除页表,以便为早期的固定映射区域做准备。
  • XIP 模式下的内核映射:如果配置了 XIP(即在 ROM 中直接执行内核代码),则为 XIP 内核创建只读映射。
  • 缓存刷新区域映射:创建缓存清理区域的映射,用于 CPU 缓存同步。
  • 向量表映射:如果使用高向量位置,则创建映射在 0xffff0000 地址;若不使用高向量,还会在 0x00000000 地址创建低向量映射。
  • 只读映射创建:为向量表的只读区域创建映射,提供额外保护。
  • 静态映射设备的 I/O 映射:根据机器描述结构 mdesc 中的 map_io 函数映射 I/O 设备,否则使用默认方法初始化。
  • VMALLOC 区域 I/O 空间保留:为 PCI 设备保留固定的 I/O 空间。
  • 缓存与 TLB 刷新:刷新缓存和 TLB 确保内存一致性,防止写缓存中的向量页未写回而影响异常处理。
  • 启用异步异常:开启处理器的异步异常(如数据预取异常)。

而该函数中的 early_trap_init(vectors);就是将vectors段进行初始化,比如向量表起始地址设置为__vectors_start

void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7Munsigned long vectors = (unsigned long)vectors_base;  // 向量页的起始地址extern char __stubs_start[], __stubs_end[];  // 存根代码的起始和结束地址extern char __vectors_start[], __vectors_end[];  // 向量表的起始和结束地址unsigned i;  // 用于遍历向量页的循环变量// 将传入的向量页基地址存入全局变量 vectors_page,供其他地方使用vectors_page = vectors_base;/** 1. 初始化向量页,使所有未定义的指令都跳转到同一个未定义指令。*    这里使用的指令 0xe7fddef1 在 ARM 和 Thumb 两种指令集下都是未定义的。*    这种处理方式确保任何未捕获的异常都将被捕获。*/for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)((u32 *)vectors_base)[i] = 0xe7fddef1;/** 2. 将向量表和存根代码复制到向量页(地址 0xffff0000 处);*    向量表用于异常处理入口,存根代码提供了具体的异常处理逻辑。*/memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);  // 复制向量表memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);  // 复制存根代码// 初始化用户空间的辅助代码(kuser helpers),用于用户态和内核态的交互kuser_init(vectors_base);/** 3. 刷新指令缓存确保向量表和存根代码对处理器指令流可见。*    刷新范围为向量页的两个页面大小(即 8 KB)。*/flush_icache_range(vectors, vectors + PAGE_SIZE * 2);#else /* ifndef CONFIG_CPU_V7M *//** 在 ARM Cortex-M 处理器(如 Cortex-M3 和 Cortex-M4)上,向量表位置可以通过配置寄存器指定,* 因此不需要复制向量表到专用内存区域。直接在内核镜像中使用即可。*/
#endif
}
  • 指令未定义初始化:对整个向量页填充一条未定义的指令 0xe7fddef1。该指令在 ARM 和 Thumb 指令集中都是未定义的,因此任何未捕获的异常都会导致未定义的指令异常,从而统一跳转到异常处理逻辑中。这种方法在初始化阶段为所有异常提供一个默认处理入口。

  • 向量表与存根代码复制

    • 将向量表和存根代码从内核镜像复制到向量页。向量表用于存放异常入口地址,例如中断、系统调用等入口。
    • 存根代码是实际的异常处理逻辑,它们与向量表中的入口地址配合实现对异常的处理。
  • 用户辅助功能初始化kuser_init 函数用于初始化 kuser helpers。kuser helpers 是一组用于用户空间与内核进行低级交互的辅助函数。

  • 指令缓存刷新:调用 flush_icache_range 函数刷新指令缓存,使得新的向量表和存根代码对处理器的指令流可见。这确保处理器在异常发生时能够正确获取最新的异常处理代码。

  • Cortex-M 特殊处理:在 Cortex-M 系列处理器(如 ARMv7-M 架构)上,向量表的位置可以直接配置,而不需要复制向量表到一个特定的内存区域。

img

2.2 向量表在哪

上面说到在代码中就有将向量表的起始地址__vectors_start复制到新分配的vectors段中,vectros段的内容怎么去找到它??就是靠__vectors_start

上面代码中可以看到代码中向量表位于__vectors_start处( 向量表起始地址),它在arch/arm/kernel/vmlinux.lds中定义, 也就是下面的连接脚本,指定 .vectors 段的加载位置(0xffff0000),确保在 CPU 触发异常时可以正确找到中断向量表的位置(也就是找到vectors段中的向量表) :

__vectors_start = .;                            // 定义向量表起始地址
.vectors 0xffff0000 : AT(__vectors_start) {     // 将 .vectors 段加载到物理地址0xffff0000*(.vectors)                                    // 匹配名为 .vectors 的段
}
. = __vectors_start + SIZEOF(.vectors);          // 更新当前地址指针到 .vectors 结束
__vectors_end = .;                               // 定义向量表结束地址
__stubs_start = .;                               // 定义 stubs 区域起始地址
.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { // 将 .stubs 段放置在偏移 0x1000 处*(.stubs)                                      // 匹配名为 .stubs 的段
}
  • __vectors_start = .;:定义 .vectors 段的起始地址为当前地址。
  • **.vectors 0xffff0000 : AT(__vectors_start) { \*(.vectors) }**:将 .vectors 段分配到内存地址 0xffff0000,并将 __vectors_start 设置为该段的物理加载地址。*(.vectors) 指令将所有 .vectors 名称的段匹配到这里。
  • **. = __vectors_start + SIZEOF(.vectors);**:更新当前地址到 .vectors 段结束处,计算 .vectors 段大小。
  • .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { \*(.stubs) }:将 .stubs 段放置在 .vectors 段的偏移 0x1000 位置,为跳转指令提供目的地。

在arch\arm\kernel\entry-armv.S里搜.vectors,可以找到vectors段的内容。 这段汇编代码定义了一个 .vectors 段,其中包含 ARM 处理器的中断向量表。ARM 处理器在发生异常时,会跳转到固定的地址来执行特定的中断向量代码:

.section .vectors, "ax", %progbits
.L__vectors_start:                 // 向量表起始地址标签W(b)   vector_rst              // 重置向量W(b)   vector_und              // 未定义指令异常向量W(ldr) pc, .L__vectors_start + 0x1000 // 软件中断向量,跳转到偏移0x1000的位置W(b)   vector_pabt             // 预取指令异常向量W(b)   vector_dabt             // 数据访问异常向量W(b)   vector_addrexcptn       // 地址异常向量W(b)   vector_irq              // 外部中断(IRQ)向量W(b)   vector_fiq              // 快速中断(FIQ)向量

其中每一行的 W(b)W(ldr) 指令表示一个 ARM 指令:

  • W(b):生成一个跳转指令 b,用于跳转到指定的中断向量处理函数。
  • W(ldr):生成一个加载指令 ldr,将 .L__vectors_start + 0x1000 处的地址加载到 pc 寄存器,实现对软件中断的处理。

中断向量定义如下:

  • vector_rst:复位向量,发生复位时跳转执行。
  • vector_und:未定义指令异常向量。
  • vector_pabt:预取指令异常向量,指令预取时发生异常。
  • vector_dabt:数据访问异常向量,数据访问时发生异常。
  • vector_addrexcptn:地址异常向量。
  • vector_irq:外部中断(IRQ)向量。
  • vector_fiq:快速中断(FIQ)向量。

img

关联

  • .vectors 段包含了 ARM 异常中断向量,指向特定异常的处理函数。
  • 连接脚本将 .vectors 段放置在 0xffff0000 地址,该地址是 ARM 处理器默认的异常向量位置。
  • ldr 指令将 .stubs 段(偏移 0x1000)作为跳转目标,从而执行不同的异常处理代码。

3.中断向量

发生中断时,CPU跳到向量表去执行b vector_irq

vector_irq函数使用宏来定义:

img

4.处理流程

img

5.处理函数

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/471307.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文翻译 | The Capacity for Moral Self-Correction in Large Language Models

摘要 我们测试了一个假设&#xff0c;即通过人类反馈强化学习&#xff08;RLHF&#xff09;训练的语言模型具有“道德自我纠正”的能力——避免产生有害的输出——如果指示这样做的话。我们在三个不同的实验中发现了支持这一假设的有力证据&#xff0c;每个实验都揭示了道德自…

华为云前台用户可挂载数据盘和系统盘是怎么做到的?

用户可以选择磁盘类型和容量&#xff0c;其后台是管理员对接存储设备 1.管理员如何在后台对接存储设备&#xff08;特指业务存储&#xff09; 1.1FusionSphere CPS&#xff08;Cloud Provisionivice&#xff09;云装配服务 它是first node https://10.200.4.159:8890 对接存…

【Excel】身份证号最后一位“X”怎么计算

大多数人身份证号最后一位都是数字&#xff0c;但有个别号码最后一位却是“X"。 如果你查百度&#xff0c;会得到如下答案&#xff1a; 当最后一位编码是10的时候&#xff0c;因为多出一位&#xff0c;所以就用X替换。 可大多数人不知道的是&#xff0c;这个10是怎么来的…

【常见问题解答】远程桌面无法复制粘贴的解决方法

提示:“奔跑吧邓邓子” 的常见问题专栏聚焦于各类技术领域常见问题的解答。涵盖操作系统(如 CentOS、Linux 等)、开发工具(如 Android Studio)、服务器软件(如 Zabbix、JumpServer、RocketMQ 等)以及远程桌面、代码克隆等多种场景。针对如远程桌面无法复制粘贴、Kuberne…

python解析网页上的json数据落地到EXCEL

安装必要的库 import requests import pandas as pd import os import sys import io import urllib3 import json测试数据 网页上的数据结构如下 {"success": true,"code": "CIFM_0000","encode": null,"message": &quo…

change buffer:到底应该选择普通索引还是唯一索引

文章目录 引言第一章&#xff1a;普通索引和唯一索引在查询逻辑与效率上的对比1.1 查询逻辑分析1.2 查询效率对比 第二章&#xff1a;普通索引和唯一索引在更新逻辑与效率上的对比2.1 更新逻辑分析2.2 更新效率对比 第三章&#xff1a;底层原理详解 - 普通索引和唯一索引的区别…

3D编辑器教程:如何实现3D模型多材质定制效果?

想要实现下图这样的产品DIY定制效果&#xff0c;该如何实现&#xff1f; 可以使用51建模网线上3D编辑器的材质替换功能&#xff0c;为产品3D模型每个部位添加多套材质贴图&#xff0c;从而让3D模型在展示时实现DIY定制效果。 具体操作流程如下&#xff1a; 第1步&#xff1a;上…

git入门环境搭建

git下载 git官网地址&#xff1a;https://git-scm.com/ 如果没有魔法的话&#xff0c;官网这个地址能卡死你 这里给个国内的git镜像链接 git历史版本镜像链接 然后一路next 默认路径 默认勾选就行。 今天就写到这吧&#xff0c;11点多了该睡了&#xff0c;&#xff0c;&#x…

Oracle ADB 导入 BANK_GRAPH 的学习数据

Oracle ADB 导入 BANK_GRAPH 的学习数据 1. 下载数据2. 导入数据运行 setconstraints.sql 1. 下载数据 访问 https://github.com/oracle-quickstart/oci-arch-graph/tree/main/terraform/scripts&#xff0c;下载&#xff0c; bank_accounts.csvbank_txns.csvsetconstraints.…

985研一学习日记 - 2024.11.14

一个人内耗&#xff0c;说明他活在过去&#xff1b;一个人焦虑&#xff0c;说明他活在未来。只有当一个人平静时&#xff0c;他才活在现在。 日常 1、起床6:00 2、健身2h 3、LeetCode刷了题 动态规划概念 如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的…

1.两数之和-力扣(LeetCode)

题目&#xff1a; 解题思路&#xff1a; 在解决这个问题之前&#xff0c;首先要明确两个点&#xff1a; 1、参数returnSize的含义是返回答案的大小&#xff08;数目&#xff09;&#xff0c;由于这里的需求是寻找数组中符合条件的两个数&#xff0c;那么当找到这两个数时&#…

【excel】easy excel如何导出动态列

动态也有多重含义&#xff1a;本文将描述两种动态场景下的解决方案 场景一&#xff1a;例如表头第一列固定为动物&#xff0c;且必定有第二列&#xff0c;第二列的表头可能为猫 也可能为狗&#xff1b;这是列数固定&#xff0c;列名不固定的场景&#xff1b; 场景二&#xff1…

〔 MySQL 〕数据类型

目录 1.数据类型分类 2 数值类型 2.1 tinyint类型 2.2 bit类型 2.3 小数类型 2.3.1 float 2.3.2 decimal 3 字符串类型 3.1 char 3.2 varchar 3.3 char和varchar比较 4 日期和时间类型 5 enum和set mysql表中建立属性列&#xff1a; 列名称&#xff0c;类型在后 n…

LlamaIndex

一、大语言模型开发框架 SDK:Software Development Kit,它是一组软件工具和资源的集合,旨在帮助开发者创建、测试、部署和维护应用程序或软件。 所有开发框架(SDK)的核心价值,都是降低开发、维护成本。 大语言模型开发框架的价值,是让开发者可以更方便地开发基于大语言…

【FFmpeg】FFmpeg 函数简介 ③ ( 编解码相关函数 | FFmpeg 源码地址 | FFmpeg 解码器相关 结构体 和 函数 )

文章目录 一、FFmpeg 解码器简介1、解码流程分析2、FFmpeg 编解码器 本质3、FFmpeg 编解码器 ID 和 名称 二、FFmpeg 解码器相关 结构体 / 函数1、AVFormatContext 结构体2、avcodec_find_decoder 函数 - 根据 ID 查找 解码器3、avcodec_find_decoder_by_name 函数 - 根据 名称…

Linux——GPIO输入输出裸机实验

学习了正点原子Linux环境下的GPIO的输入输出的裸机实验学习&#xff0c;现在进行一下小结&#xff1a; 启动文件start.S的编写 .global _start .global _bss_start _bss_start:.word __bss_start.global _bss_end _bss_end:.word __bss_end_start:/*设置处理器进入SVC模式*/m…

zabbix搭建钉钉告警流程

目录 &#x1f324;️zabbix实验规划 &#x1f324;️zabbix实验步骤 &#x1f4d1;1 使用钉钉添加一个自定义的机器人 ​ &#x1f4d1;2在zabbix-server上编写钉钉信息发送脚本&#xff0c;设置钉钉报警媒介 ☁️ 设置钉钉报警媒介​编辑​编辑 ☁️在添加消息模板​编辑​…

Java 多线程(三)—— 死锁

死锁的产生 我们先从简单的死锁最后到难一些的死锁问题开始展开讨论。 首先一个线程&#xff0c;一把锁&#xff0c;因为多次加锁而导致死锁问题&#xff0c;由于Java 的synchronized 实现了可重入锁&#xff0c;因此这个死锁问题就不存在了&#xff0c;意味着当一个线程拥有…

makefile 设置动态库路径参数

目录 一、makefile 动态库相关1.1 Libs 变量1.2 LDFLAGS 变量1.3 二者的作用和区别 二、设置方式2.1 编译时指定库路径2.2 运行时指定库路径 三、测试 一、makefile 动态库相关 1.1 Libs 变量 在 Makefile 中&#xff0c;Libs 通常是一个变量&#xff0c;用于存储链接器&…

Servlet入门 Servlet生命周期 Servlet体系结构

一.Servlet入门 1.Servlet介绍 Servlet (server applet) 是运行在服务端(tomcat)的Java小程序&#xff0c;是sun公司提供一套定义动态资源规范; 从代码层面上来讲Servlet就是一个接口 狭义的Servlet是指Java语言编写的一个接口。 广义的Servlet是指任何实现了这个Servlet接口…