Linux下访问内存物理地址
内存寻址的三种模式

内存寻址的三种模型1. 地址的种类首先明确一下逻辑地址和线性地址这两个概念:1. 逻辑地址2. 线性地址3. 物理地址1.1 逻辑地址:逻辑地址是编译器生成的,我们使用在linux环境下,使用C语言指针时,指针的值就是逻辑地址。
对于每个进程而言,他们都有一样的进程地址空间,类似的逻辑地址,甚至很可能相同。
1.2 线性地址:线性地址是由分段机制将逻辑地址转化而来的,如果没有分段机制作用,那么程序的逻辑地址就是线性地址了。
1.3 物理地址物理地址是CPU在地址总线上发出的电平信号,要得到物理地址,必须要将逻辑地址经过分段,分页等机制转化而来。
2. 三种寻址模型x86体系结构下,使用的较多的内存寻址模型主要有三种:1. 实模式扁平模型real mode flat model2. 实模式分段模型real mode segment model3. 保护模式扁平模型protected mode flat model下面是对这三种模型的描述实模式和保护模式相对,实模式运行于20位地址总线,保护模式则启用了32位地址总线,地址使用的是虚拟地址,引入了描述符表;虽然二者都引入了段这样一个概念,但是实模式的段是64KB固定大小,只有16KB个不同的段,CS,DS等存储的是段的序号(想想为什么?)。
保护模式则引入了GDT和LDT段描述符表的数据结构来定义每个段。
扁平模型和分段模型相对,区别在于程序的线性地址是共享一个地址空间还是需要分成多个段,即为多个程序同时运行在同一个CS,DS的范围内还是每个程序都拥有自己的CS,DS:前者(flat)指令的逻辑地址要形成线性地址,不需要切换CS,DS;后者的逻辑地址,必须要经过段选择子去查找段描述符,切换CS,DS,才能形成线性地址。
3. 实模式扁平模型该模式只有在386及更高的处理器中才能出现!80386的实模式,就是指CPU可用的地址线只有20位,能寻址0~1MB的地址空间。
查看Linux系统内存、CPU、磁盘使用率和详细信息

查看Linux系统内存、CPU、磁盘使⽤率和详细信息⼀、查看内存占⽤1、free# free -m以MB为单位显⽰内存使⽤情况[root@localhost ~]# free -mtotal used free shared buff/cache availableMem: 118521250866841019349873Swap: 601506015# free -h以GB为单位显⽰内存使⽤情况[root@localhost ~]# free -htotal used free shared buff/cache availableMem: 11G 1.2G 8.5G 410M 1.9G 9.6GSwap: 5.9G 0B 5.9G# free -t以总和的形式查询内存的使⽤信息[root@localhost ~]# free -ttotal used free shared buff/cache availableMem: 1213733212853448870628420268198136010105740Swap: 616038006160380Total: 18297712128534415031008# free -s 5周期性的查询内存使⽤信息每5秒执⾏⼀次命令[root@localhost ~]# free -s 5total used free shared buff/cache availableMem: 1213733212807968875008420268198152810110136Swap: 616038006160380解释:Mem:内存的使⽤情况总览表(物理内存)Swap:虚拟内存。
即可以把数据存放在硬盘上的数据shared:共享内存,即和普通⽤户共享的物理内存值buffers:⽤于存放要输出到disk(块设备)的数据的cached:存放从disk上读出的数据total:机器总的物理内存used:⽤掉的内存free:空闲的物理内存注:物理内存(total)=系统看到的⽤掉的内存(used)+系统看到空闲的内存(free)2、查看某个pid的物理内存使⽤情况# cat /proc/PID/status | grep VmRSS[root@localhost ~]# pidof nginx2732727326[root@localhost ~]#[root@localhost ~]# cat /proc/27327/status | grep VmRSSVmRSS: 2652 kB[root@localhost ~]#[root@localhost ~]# cat /proc/27326/status | grep VmRSSVmRSS: 1264 kB[root@localhost ~]#[root@localhost ~]# pidof java1973[root@localhost ~]# cat /proc/1973/status | grep VmRSSVmRSS: 1166852 kB由上⾯可知,nginx服务进程的两个pid所占物理内存为"2652+1264=3916k"3、查看本机所有进程的内存占⽐之和# cat mem_per.sh[root@localhost ~]# cat mem_per.sh#!/bin/bashps auxw|awk '{if (NR>1){print $4}}' > /opt/mem_listawk '{MEM_PER+=$1}END{print MEM_PER}' /opt/mem_list[root@localhost ~]#[root@localhost ~]# chmod755 mem_per.sh[root@localhost ~]#[root@localhost ~]# sh mem_per.sh64.4[root@localhost ~]#脚本配置解释:ps -auxw|awk '{print $3}' 表⽰列出本机所有进程的cpu利⽤率情况,结果中第⼀⾏带"%CPU"字符ps -auxw|awk '{print $4}' 表⽰列出本机所有进程的内存利⽤率情况,结果中第⼀⾏带"%MEM"字符ps auxw|awk '{if (NR>1){print $4}} 表⽰将"ps auxw"结果中的第⼀⾏过滤(NR>1)掉,然后打印第4⾏⼆、查看CPU使⽤情况1、toptop后键⼊P看⼀下谁占⽤最⼤# top -d 5周期性的查询CPU使⽤信息每5秒刷新⼀次top - 02:37:55 up 4 min, 1 user, load average: 0.02, 0.10, 0.05Tasks: 355 total, 1 running, 354 sleeping, 0 stopped, 0 zombie%Cpu(s): 3.0 us, 2.8 sy, 0.0 ni, 94.2id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st# us:表⽰⽤户空间程序的cpu使⽤率(没有通过nice调度)# sy:表⽰系统空间的cpu使⽤率,主要是内核程序。
linux系统下内存通道与物理槽位对应关系

linux系统下内存通道与物理槽位对应关系Linux系统下,内存通道与物理槽位的对应关系是非常重要的,因为了解这些对应关系可以帮助我们正确识别和配置内存模块,以优化系统的性能和稳定性。
在Linux系统中,内存通道对应着物理槽位。
物理槽位是安装内存模块的插槽,每个插槽可以安装一个内存模块。
内存通道是处理器与内存之间的通道,它决定了内存模块的访问速度和宽度。
在一个典型的服务器或工作站上,通常会有多个内存通道和物理槽位。
每个内存通道可以连接一个或多个物理槽位,这取决于主板的设计和规格。
通常,每个物理槽位都会标有一个数字或字母代码,以帮助用户识别其位置。
要确定内存通道与物理槽位的对应关系,我们可以通过以下几种方式:1.通过物理槽位标记:在服务器或工作站的主板上,通常会有一个或多个物理槽位标记的标志。
这些标记可以是数字或字母代码,标识了每个物理槽位的位置和编号。
通常来说,物理槽位从0开始编号,依次递增。
例如,一个四通道主板上的物理槽位标记可能是"0A"、"0B"、"1A"、"1B"、"2A"、"2B"、"3A"和"3B"。
2.通过BIOS设置:BIOS是计算机的基本输入输出系统,它可以提供一些硬件信息,包括内存通道和物理槽位的对应关系。
在开机启动过程中,可以通过按下特定的功能键来进入BIOS设置界面(不同的主板厂商可能有不同的设置方式)。
在BIOS设置界面中,可能会有一个“内存配置”或类似的选项,其中可以找到内存通道和物理槽位的相关信息。
3.通过系统信息工具:Linux系统提供了一些命令和工具,可以获取系统硬件信息。
例如,dmidecode命令可以显示主板、内存和其他硬件的详细信息。
通过执行以下命令可以获取内存通道和物理槽位的对应关系:```sudo dmidecode -t memory```该命令将显示系统中安装的所有内存模块的信息,包括每个内存模块的位置、大小和速度。
devmem_rw案例

devmem_rw案例`devmem_rw` 是一个在 Linux 内核中用于读写物理内存的接口。
它可以被用于访问物理地址空间,但是使用它需要小心,因为错误地使用可能会导致系统崩溃或数据损坏。
下面是一个简单的 `devmem_rw` 使用的例子:```cinclude <linux/>include <asm/>include <linux/>define MAP_SIZE 4096ULdefine MAP_MASK (MAP_SIZE - 1)void my_devmem_alloc(struct device dev, size_t size,unsigned long physaddr, int flags){unsigned long mapsize = size + MAP_SIZE;void vpage = NULL;unsigned long kpage;unsigned long offset;struct page page;struct address_space mapping;int ret;offset = physaddr & ~MAP_MASK;kpage = physaddr - offset + (unsigned long) __va(offset);page = virt_to_page(kpage);mapping = page_mapping(page);if (!mapping) {ret = get_user_pages_fast(kpage, 1, flags & ~FOLL_FORCE, &page);if (ret != 1) {return NULL;}} else {get_page(page);}vpage = kmap(page);if (vpage) {physaddr = kpage + offset;} else {put_page(page);}return vpage;}EXPORT_SYMBOL(my_devmem_alloc);```这个函数首先检查物理地址是否可以被映射到用户空间,如果可以,则获取该页面的引用并映射到内核空间。
linux dma使用技巧

linux dma使用技巧Linux的DMA(直接内存访问)是一种高性能的数据传输技术,它允许设备直接访问系统内存,而无需CPU的干预。
这样可以提高数据传输的速度和效率,特别适用于高速设备和实时应用。
下面是一些Linux DMA使用的技巧:1. 使用DMA缓冲区:DMA传输需要有一个专门的缓冲区来存储数据。
在Linux中,可以使用kmalloc()函数来为DMA传输分配内存。
使用时需要注意大小和对齐问题,以确保DMA 传输的正确进行。
2. 设置DMA传输标志:DMA传输有不同的标志,可以用来控制传输的行为和属性。
在Linux中,可以使用DMA API中的函数和宏来设置和获取这些标志。
例如,可以使用dma_set_coherent_mask()来设置一致性掩码,以确保DMA传输的一致性。
3. 使用合适的DMA引擎:Linux内核支持多种DMA引擎,每种DMA引擎有不同的特性和性能。
选择合适的DMA引擎可以提高数据传输的效率。
可以使用DMA API中的函数和宏来选择和配置DMA引擎。
4. 处理中断和回调:DMA传输完成后,通常会触发一个中断来通知CPU。
在Linux中,可以使用中断处理程序来处理这些中断。
可以使用request_irq()函数来注册中断处理程序,并使用complete()函数来通知等待的线程。
此外,还可以使用回调函数来处理DMA传输完成后的操作。
5. 控制DMA传输的优先级:Linux内核为DMA传输提供了优先级控制的机制。
可以使用DMA API中的函数和宏来设置和获取DMA传输的优先级。
通过设置适当的优先级,可以确保关键数据的传输和处理优先完成。
6. 进行DMA内存映射:在一些情况下,可能需要将DMA缓冲区的物理地址映射到用户空间。
在Linux中,可以使用dma_map_single()函数将物理地址映射到虚拟地址。
使用完成后,可以使用dma_unmap_single()函数来取消映射。
Linux下的段错误(Segmentationfault)产生的原因及调试方法(经典)

Linux下的段错误(Segmen tatio n fault)产生的原因及调试方法(经典)2009-04-05 11:25简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址.一般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gd t的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。
一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segm entat ion fault就出现了.在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的1)访问系统数据区,尤其是往系统保护的内存地址写数据最常见就是给一个指针以0地址2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域解决方法我们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。
实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。
但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个内存访问越界的错误谈谈如何快速定位这些"段错误"的语句。
下面将就以下的一个存在段错误的程序介绍几种调试方法:1 dummy_funct ion (void)2 {3 unsign ed char *ptr = 0x00;4 *ptr = 0x00;5 }67 int main (void)8 {9 dummy_funct ion ();1011 return 0;12 }作为一个熟练的C/C++程序员,以上代码的b ug应该是很清楚的,因为它尝试操作地址为0的内存区域,而这个内存区域通常是不可访问的禁区,当然就会出错了。
linux下查看内存频率,内核函数,cpu频率

linux下查看内存频率,内核函数,cpu频率查看CPU:cat /proc/cpuinfo# 总核数 = 物理CPU个数 X 每颗物理CPU的核数# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数# 查看物理CPU个数cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l# 查看每个物理CPU中core的个数(即核数)cat /proc/cpuinfo| grep "cpu cores"| uniq# 查看逻辑CPU的个数cat /proc/cpuinfo| grep "processor"| wc -l# 查看CPU信息(型号)cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -cprocessor :系统中逻辑处理核的编号。
对于单核处理器,则课认为是其CPU编号,对于多核处理器则可以是物理核、或者使⽤超线程技术虚拟的逻辑核vendor_id :CPU制造商cpu family :CPU产品系列代号model :CPU属于其系列中的哪⼀代的代号model name:CPU属于的名字及其编号、标称主频stepping :CPU属于制作更新版本cpu MHz :CPU的实际使⽤主频cache size :CPU⼆级缓存⼤⼩physical id :单个CPU的标号siblings :单个CPU逻辑物理核数core id :当前物理核在其所处CPU中的编号,这个编号不⼀定连续cpu cores :该逻辑核所处CPU的物理核数apicid :⽤来区分不同逻辑核的编号,系统中每个逻辑核的此编号必然不同,此编号不⼀定连续fpu :是否具有浮点运算单元(Floating Point Unit)fpu_exception :是否⽀持浮点计算异常cpuid level :执⾏cpuid指令前,eax寄存器中的值,根据不同的值cpuid指令会返回不同的内容wp :表明当前CPU是否在内核态⽀持对⽤户空间的写保护(Write Protection)flags :当前CPU⽀持的功能bogomips :在系统内核启动时粗略测算的CPU速度(Million Instructions Per Second)clflush size :每次刷新缓存的⼤⼩单位cache_alignment :缓存地址对齐单位address sizes :可访问地址空间位数power management :对能源管理的⽀持,有以下⼏个可选⽀持功能: ts: temperature sensor fid: frequency id control vid: voltage id control ttp: thermal trip tm: stc: 100mhzsteps: hwpstate:查看内存:sudo cat /proc/meminfo这个命令只能看当前内存⼤⼩,已⽤空间等等。
物理地址映射是什么

物理地址映射是什么物理地址映射方法一般情况下,linux系统中,进程的4gb内存空间被划分成为两个部分------用户空间和内核空间,大小分别为0~3g,3~4g。
用户进程通常情况下,只能访问用户空间的虚拟地址,不能访问到内核空间。
每个进程的用户空间都是完全独立、互不相干的,用户进程各自有不同的页表。
而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。
内核空间地址有自己对应的页表,内核的虚拟空间独立于其他程序。
3~4g之间的内核空间中,从低地址到高地址依次为:物理内存映射区—隔离带—vmalloc虚拟内存分配区—隔离带—高端内存映射区—专用页面映射区—保留区。
【内核空间内存动态申请】主要包括三个函数:kmalloc(), __get_free_pages, vmalloc。
kmalloc(), __get_free_pages申请的内存位于物理地址映射区,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。
而vmalloc申请的内存位于vmalloc虚拟内存分配区(这些区都是以线性地址为度量),它在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而vmalloc申请的虚拟内存和物理内存之间也没有简单的换算关系。
因为vmalloc申请的在虚拟内存空间连续的内存区在物理内存中并不一定连续,可以想象为了完成vmalloc,新的页表需要被建立,因此,知识调用vmalloc来分配少量内存是不妥的。
一般来讲,kmalloc用来分配小于128k的内存,而更大的内存块需要用vmalloc来实现。
【虚拟地址与物理地址关系】对于内核物理内存映射区的虚拟内存(用kmalloc(), __get_free_pages申请的),使用virt_to_phys()和phys_to_virt()来实现物理地址和内核虚拟地址之间的互相转换。
它实际上,仅仅做了3g的地址移位。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux下访问内存物理地址by tmsonhsut<tmsonust@> 2008.4.28Linux内核里提供的/dev/mem驱动,为我们读写内存物理地址,提供了一个渠道。
下面讲述2种利用mem设备文件进行物理地址读写的方法,一种是设备驱动的方法,另一种是系统调用的方法。
首先我们看下mem这个设备文件,/dev/mem是linux下的一个字符设备,源文件是~/drivers/char/mem.c,这个设备文件是专门用来读写物理地址用的。
里面的内容是所有物理内存的地址以及内容信息。
通常只有root用户对其有读写权限。
1.设备驱动的方法下面是mem.c文件里定义的file_operations结构,提供了llseek,read,write,mmap以及open等方法。
static structfile_operationsmem_fops ={.llseek = memory_lseek,.read = read_mem,.write = write_mem,.mmap = mmap_mem,.open = open_mem,};因此我们可以通过一般驱动的使用方法,将内存完全当作一个设备来对对待。
应用程序如下:#include <stdio.h>#include <fcntl.h>int main(void){intfd;char *rdbuf;char *wrbuf = "butterfly";int i;fd = open("/dev/mem",O_RDWR);if(fd< 0){printf("open /dev/mem failed.");}read(fd,rdbuf,10);for(i = 0;i < 10;i++){printf("old mem[%d]:%c\n",i,*(rdbuf + i));}lseek(fd,5,0);write(fd,wrbuf,10);lseek(fd,0,0);//move f_ops to the frontread(fd,rdbuf,10);for(i = 0;i < 10;i++){printf("new mem[%d]:%c\n",i,*(rdbuf + i));}return 0;}执行结果如下:将内存最开始10个字节的内容进行替换。
[root@VOIP-IPCAM app]# ./memtestold mem[0]:bold mem[1]:uold mem[2]:told mem[3]:told mem[4]:eold mem[5]:rold mem[6]:fold mem[7]:lold mem[8]:yold mem[9]:!new mem[0]:bnew mem[1]:unew mem[2]:tnew mem[3]:tnew mem[4]:enew mem[5]:bnew mem[6]:unew mem[7]:tnew mem[8]:tnew mem[9]:e2.系统调用的方法细心的你可能会发现,既然你前面说了这个文件里存放的就是内存的地址及内容信息,那我可不可以直接查看到呢,答案是:可以的。
linux内核的开发者为我们提供了一个命令hexedit,通过它就可以将/dev/mem的内容显示出来(如果你使用cat /dev/mem将会看到乱码),执行hexedit /dev/mem的结果如下:00000000 62 75 74 74 65 62 75 74 74 65 72 66 6C 79 21 20 butterfly!00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 2000000020 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 2000000030 6F EF 00 F0 6F EF 00 F0 57 EF 00 F0 6F EF 00 F0 o...o...W...o...00000040 02 11 00 C0 4D F8 00 F0 41 F8 00 F0 34 85 00 F0 ....M...A...4...00000050 39 E7 00 F0 59 F8 00 F0 2E E8 00 F0 D2 EF 00 F0 9...Y...........00000060 A4 E7 00 F0 F2 E6 00 F0 6E FE 00 F0 53 FF 00 F0 ........n...S...00000070 53 FF 00 F0 A4 F0 00 F0 C7 EF 00 F0 1C 42 00 C0 S............B..从上图可见,最左边显示的是地址,接下来24列显示的是各内存字节单元内容的ASCII码信息,最右边显示的是对应的字符信息。
让人欣慰的是,这个文件可以直接修改,按下tab 键进入修改模式,修改过程中修改内容会以粗体显示,按下F2保存后粗体消失。
上面的butterfly就是通过这种方式修改的。
既然内存的地址以及内容信息全部被保存在mem这个设备文件里,那么我们可以想到通过另外一种方式来实现对物理地址的读写了。
那就是将mem设备文件和mmap系统调用结合起来使用,将文件里的物理内存地址映射到进程的地址空间,从而实现对内存物理地址的读写。
下面谈一下mmap系统调用。
mmap的函数原型为:void *mmap(void *start,size_tlength,intprot,intflags,intfd,off_t offset),该函数定义在/usr/include/sys/mman.h中,使用时要包含:#include<sys/mman.h>,mmap()用来将某个文件中的内容映射到进程的地址空间,对该空间的存取即是对该文件内容的读写。
参数说明如下:start:指向欲映射到的地址空间的起始地址,通常设为null或者0.表示让系统融自动选定地址,映射成功后该地址会返回。
length:表示映射的文件内容的大小,以字节为单位。
prot:表示映射区域的保护方式,有如下四种组合:--PROT_EXEC 映射区域可执行,--PROT_READ 映射区域可读,--PROT_WRITE 映射区域可写,--PROT_NONE 映射区域不能被访问flags:映射区域的一些特性,主要有:--MAP_FIXED 如果映射不成功则出错返回,--MAP_SHARED 对映射区域的写入数据会写回到原来的文件--MAP_PRIVATE 对映射区域的写入数据不会写回原来的文件--MAP_ANONYMOUS--MAP_DENYWRITE 只允许对映射区域的写入操作,其他对文件直接写入的操作将被拒绝--MAP_LOCKED 锁定映射区域在调用mmap()时,必须要指定MAP_SHARED或MAP_PRIVATE。
fd:open()返回的文件描述符。
offset:为被映射文件的偏移量,表示从文件的哪个地方开始映射,一般设置为0,表示从文件的最开始位置开始映射。
offset必须是分页大小(4096字节)的整数倍。
应用程序如下:#include <stdio.h>#include <fcntl.h>#include <sys/mman.h>//mmap head fileint main (void){int i;intfd;char *start;char *buf = "butterfly!";//open /dev/mem with read and write modefd = open ("/dev/mem", O_RDWR);if (fd< 0){printf("cannot open /dev/mem.");return -1;}//map physical memory 0-10 bytesstart = (char *)mmap(0, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(start < 0){printf("mmap failed.");return -1;}//Read old valuefor (i = 0; i < 10; i++){printf("old mem[%d]:%c\n", i, *(start + i));}//write memorymemcpy(start, buf, 10);//Read new valuefor (i = 0;i < 10;i++){printf("new mem[%d]:%c\n", i,*(start + i));}munmap(start, 10); //destroy map memoryclose(fd); //close filereturn 0;}程序执行结果如下:[root@VOIP-IPCAM app]# ./rwphyold mem[0]:bold mem[1]:uold mem[2]:told mem[3]:told mem[4]:eold mem[5]:bold mem[6]:uold mem[7]:told mem[8]:told mem[9]:enew mem[0]:bnew mem[1]:unew mem[2]:tnew mem[3]:tnew mem[4]:enew mem[5]:rnew mem[6]:fnew mem[7]:lnew mem[8]:ynew mem[9]:!“/dev/mem是个很好玩的东西,你竟然可以直接访问物理内存。
这在LINUX下简直是太神奇了,这种感觉象一个小偷打算偷一个银行,可是这个银行戒备森严,正当这个小偷苦无对策时,突然发现在一个不起眼的地方有个后门,这个后门可以直接到银行的金库。