




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第1章LinuxLab概 Linux0.11操作系 第2章Linux0.11编程基 Linux0.11内核源代码的结 C和汇编的相 使用工具阅读Linux0.11源代 实验一实验环境的使 实验二操作系统的启 实验三 程序设 实验四系统调 实验五进程的创 实验六进程的状态和进程调 实验七信号量的实现和应 实验八地址与内存共 实验九页面置换算法与动态内存分 实验符显示的控 实验十一proc文件系统的实 实验十二MINIX1.0文件系统的实 1LinuxLab最好的参考资料是博士编写的《Linux内核完全注释》,其可以从OldLinux。Linux0.11其中包含的内容基本上都是Linux的精髓。虽然读者在使用Linux0.11的过会遇到很多处,才给了读者的想象空间。Linux0.11C语言编写,有少量的汇编语言代码。Linux0.11开放了全中文注释,让阅读和理解Linux0.11源代码更加容易。Linux0.11X86Linux0.11应用程序之间(1-1所示扮演了极其重要的角色。一方面,Linux0.11X86平台中的各种硬件进行统一的管理,提高了系统资源的利用率。另一方面,Linux0.11操作系统提供了一个“虚拟机”和一Linux0.11Linux0.11操作系统组系统调用函数,Linux0.11Linux0.11Linux0.11操作系统集成实验环LinuxLab是使用Linux0.11进行操作系统实验的IDE(IntegratedDevelopmentEnvironmentIDE环境可以直接Windows操作系统上安装和卸载,用户界面和操作与VisualStudio完全类似,有经验的读者可以迅速上手。正因为有了这样一个要精力放在对操作系统原理和Linux0.11源代码的分析与理解上。所示。编辑功能可以用来阅读和修改Linux0.11源代码;编译功能可以将Linux0.11源代时显示对应位置的C码等功能。灵活运用各种调试功能对分析Linux0.11的源代码有很大帮助。IDE环境除了提供以上的主要功能外,还提供了一些工具软件。使用FloppyImageWindowsLinux0.11IDEBochsBochsLinux0.111-2:使IDE环境编辑、编Linux0.11(1-3:使LinuxLab进行操作系Windows平台上使GDB交叉调试内核源代码。并且,由于生成的调试信息能够aoutLinuxLabGCC1.4为Linux0.11开发应用程序的过程。(setup这与国内读者学习的IBM汇编语言相差较大。LinuxLab使用与IBM汇编语法更加原版中无法正常使用od、mkdir、rm行,从而允许读者可以通过使用这些命令,来练习Linux中的基本文件操作。原版使用命令行工具在软盘BFAT12内的文件,操作十分不便。LinuxLabFloppyimageeditorBLinux0.11操作系统内核从源代码变为可以在虚拟机上时,会将bootse 文件生成为setup.bin文件(加载程序),将head.s和其它的源代码文件生成为linux011.bin文件。其中linux011.bin文件是Linux0.11操作系统的内核。在IDE环境生成Linux0.11内核项目时,就会将bootsect.bin、setup.bin和linux011.binLinux0.11Linux0.111-4:Linux0.11操作系统内核从源代码变为可以在虚拟机上运行的过Linux内核集成实验环LinuxLab虚拟机工具BochsLinux0.11操作系统,这里对Bochs这种虚拟机工具软件进行简单的介绍。BochsCIA-32(x86)PCInx86CPU、通用I/O设备以及可定制的BIOS程序。这种完全使用软件模拟的方式又被(Emulatormachine。大多数操作系统都可以在Bochs上运行,例如LinuxDOSWindows95/98/NT/2000/XP/Vista。由于Bochs完全使用软件模拟X86硬件平台,所以可以用来调试BIOS程序和操作系统第一条指令)处开始调试。LinuxLab正是利用了Bochs的这个特点,使用Bochs调试软盘行时占用大量CPU资源且性能较差。为了使Bochs能够更好的调试Linux0.11操作系统,对Bochs的源代码进行了必要的修改,重新编译生成了能够与LinuxLab无缝融合的Bochs版本。所以,必须使用与LinuxLab一起提供的Bochs,而不能直接使用Bochs提供的安装包。类命操csnqvbpbdinforx/nufn用数字代替,表示数量,ub认使用十六进制表示。例如命令x/1024b0x0000:0x00000x0000xp/nuf显示从指定物理地址处开始的内存中的数据。u2Linux0.11本章主要介绍在Linux0.11源代码中涉及到的C语言、汇编语言、数据结构等一些基Linux0.11内核源代码的结2-1Linux0.11linux011开文件夹,可以浏览文件夹所包含的文件。Linux0.11块或其实现的功能,组织在不同的文件夹中,详细的说明可以参见表2-2。2-1:Linux0.11内核源代码的树 ,包括进程调度、系统调用函数以及blk_dev 下的字符设备驱动程序和math 2-2:Linux0.11内核源代码组织方式的详细说NASM汇MASMMASMNASMNASM需要方括号来内存地址中的内fooequ1bardw2movmovax,movmovax,NASM的操作则不需要。所以,形如“movax,foo”的指令总是代表一个编译时常数,无论它是“movax,word[bar]”。这就NASM不需MASM“OFFSET”关键字,因MASM代码“movaxoffsetbarNASM的“movax,word[bar]”是完全等效的。wordtable+bx。同MASM“movax,es:[diNAMS中应该是“movax,wordNASM不变量的类NASM不会记住的变量的类型。然而,MASM在看到“vardw0”时会记住类型,也就是var是一个字大小的变量,然后就可以隐式地使用“movvar,2”给变量赋值。NASM[var],2因此,NASMNASM不支持内存NASM同样不含有任何操作符来支持不同的16位内存模型。在使用NASM16代码时,必须自己哪些函数需要FARCALL,哪些需要NEARCALL,并有责任确定放置正确的必须在调用外部函数时在需要的地方编写CALLFAR指令,并必须哪些外部变量定义是FAR,哪些是NEAR。NASM不支持pushbpmovebp,sp以,以上由MASM自动添加的代码在NASM中必须手动添加。NASM可以生成BIN外,还可以将ASM文件汇编成一个只包含编写的代码的BIN文件。BIN文件主要用于制作操作系统的引导程序和加载程序,例如由Linux0.11内核源代码文件boot/bootsem和boot/setup.asm所生成的boot.binsetup.bin文件。此行语句告诉NASMBIN文件将要被加载到内存偏移地址0x7C00处,这样NASM汇编器就可以根据此偏移地址定位程序中变量和的位置。times510-($-$$)db节都填充0。C和汇编的相法就是使用汇编语言。Linux0.11本的硬件操作包装成可供C语言调用的函数。本节内容主要介绍C和汇编在相用时应该遵守的一些约定,Linux0.11中的代码也同样遵守这些约定。下面示例中的汇编代码使用的是NASM汇编语法,如果读者有不明白的地方,可以参考2.2节。CC在硬件上执行的机器语言。下面简单介绍各种C器在将C代码翻译成汇编时所遵守的mov[_c],mov[_c],c[sectiondata]_cdd[sectiontext] (CallStackFrame) push movebp, ebp subesp, L2:moveax,[ebp+ ;eax=a。通过 movecx,[ebp+ ;ecx= addeax, mov[ebp–4], push movebp,L1:pushdword pushdword call L3:addesp, xoreax,;eax=}} return c=add(5,}int{ return int c=a+intc=//定义求和函数。intadd(inta,intb){NASM汇编Cmoveax,[ebp-;;;movesp,;pop表2-3:C代码对应的NASM汇编代码表2-3使用一段非常简单的C程序和其对应的NASM汇编代码来举例说明上面的各项约定。仔细观察表2-3代码可以发现,C代码中的全局变量cadd到了汇编代码中都在其前面添加了一个下划线,这是符合第一条调用约定的。在main函数和add函数X86CPU从硬件层次就支持堆栈操作,提供了SS、ESP、EBP等寄存器,其中SS寄存器保存PUSH、POP等。由于X86CPU的堆栈是从高地址向低地址生长的,所以,PUSH指令会先减少ESP寄存器的值(CPU16位实模式下减232护模式下数据放入栈顶,POP指令会先从栈顶取出数据,再增加ESP寄存器的值。强调了关于堆栈的基本知识,我们32main()返回地main()返回地
图2-4:初始的堆栈
参数参数参数main()返回地图2-5:参数入栈后的
自动变自动变量add()返回地参数参数main()返回地图2-6:进入函数后的自动变自动变量add()返回地参数参数main()返回地图2-7:LEAVE指令执行后的堆栈状
自动变自动变量add()返回地参数参数main()返回地2-8:RET指令执行后的堆栈2-8所示的状态。至此,调用约定二和调用约定三也讲解完毕。CCC原语操CPUCPU恢复响应外部中断。X86CPU是根据eflags状态字寄存器中的IF位来决定是否响应外部中断的,并提供了STI指令设置IF位从而允许响应外部中断,还提供了CLI指令清空IF停止响应外部在Linux0.11内核中应该成对使用指令STI和CLI来实现原语操作。例如,内核中的A需要实现一个原语操作,就应该按照下面的方式编写代码:voidAvoidA{ }voidBvoidB{BOOLBOOL}C语言中变量的内存布体来同时,例如,CPU可以同时1、2、4或个字节。可以这样来理解,CPU在数据类型描述的内存chara='A';shortb=0x1234;longc=0x12345678;void*p=&c;量c的地址放入变量p所在的内存。(void地 内
bcp图2-9:最简单的数据类型所描述的内存布局unsignedcharByteArray[8]={0x10,0x20,0x30,0x40,0x50,0x60,0x70,typedefstruct_FOO long long}FOO,PFOOPointer=指针Pointer指向的内存基址和内存布局可以像图2-10所示的样子。此时,表达式可以说明第一条准则是成立的;表达式&Pointer->Memeber2得到的地址大于&Pointer->Member1
图2-10:结构体类型所描述的内存布结构体FOO4,与Pointer0x402000x40204,再结合域Member2的数据类型(内存布局)内存中的数据。内存的速度,默认情况下,C语言编译器会保证基本数据类型的内存基址是某个数k(通常为2或4)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数。以用来编译Linux0.11源代码的GCC编译器为例,默认情况下,任何基本数据类型的对齐模数就是该数据类型的大小。比如对于double类型(大小为8字节),就要求该数据类型的内//#pragmatypedefstruct_FOO1 short long}FOO1,typedefstruct_FOO2 unsignedchar short long}FOO2,//#pragma以像图2-11所示的样子。地 内
图2-11:默认情况下结构体的内存布Cpragmapack(n)”用来指定数据类型的对齐模数。将地 内
图2-12:按照1字节对齐后的结构体的内存布局typedefstruct_FOO3 union short long long}FOO3,图2-13所示的样子。Member2Member2
图2-13:联合体的内存Linux0.11typedefstruct_FOO4 long long long}FOO4,2-14long0x4020004Pointer->Head的值为0x10(0000010000),表达式Pointer->Middle的值为0x08(0000001000)Pointer->Tail的值为0x403(010000001
3130292827262524232221 765432101000000001100000010000000010000 图2-14:位域的内存布局变量在内存中的位形式参数和局部变量(未加static。(stack字节顺Little-endian与Big-字节顺方式一般相同。对于多字节数据,在不同的处理器的存放方式主要有两种,一种是Little-endian,另一种是Big-endian。Little-a0x0Da+10x0Ca+20x0Ba+3Big-a0x0Aa+10x0Ba+20x0Ca+3Motorola6800,Motorola68000,PowerPC970,System/370,SPARC(V9)等处理器为Bigendian。字节顺序才能完成工作,例如在采用InX86处理器的计算机上调试程序,如果想查看一样在人工的时候才能够识别出正确的数据。使用工具阅Linux0.11源代LinuxLab提供的源代码编辑器为多种源代码文件(.c,.cpp,.h,.asm等)提供了符号图2-15:使用大纲将源代码折文本查找工LinuxLab提供了“查找和替换”框,用于在多个文件中查找文本。如果在阅读代码时遇到了一个函数调用Fun(),想查看此函数是如何实现的(即函数的定义,可以使用鼠标选中函数的名称,然后按Ctrl+Shift+F快捷键弹出“查找和替换”框,在框Lab 书签工Ctrl+F2快捷键编译器工具可以帮助读者定位代码中的警告和错者可以适时的使用快捷键调试器工望分析例程的操作时按F11实验一实验环境的使一、实验目二、预备知请读者认真阅读本书第1章的内容,同时可以阅读博士编写的《Linux内核完全注三、实验内启动 在桌面上双击“EngintimeLinuxLab”图标。EngintimeLinuxLab”“EngintimeLinuxLab”。练习LinuxLab编写Windows台应用程序,熟LinuxLab使用方新建Windows控制台应用程序项LinuxLabC项目的源代码主要包含一个头文件“console.h”和一个C语言源文件“console.c”。图1-1:打开Windows控制台应用程序项目后的“项目管理器生成项含语法错误,会在最后提示生成成功,如图1-2:图1-2:成功生成Windows控制台应用程序项目后的“输出控制台应用程序项目后,默认会在“C:\test\lab1\debug"下生成一个名称为“console.o”的对象文件和名称为“console.exe”的Windows控制台应用程序,可以使用Windows资源管理器查看这些文件。执行项LinuxLabWindows控制台应用程序。启动执行后会弹出一个Windows控制台窗口,显示控制台应用程序输出的内容。按任意键即可关闭此Windows控制台窗口。调试项LinuxLab图1-3:添加func.c文件后的“项目管理器”窗intFuncintFunc(int{n=n+1;returnn;}intmain(intargc,char* int intFuncintn); intn= printf o printf o return}使用断点中断执intn=图1-4:在console.c文件的main函数中添加断点后的代行代码1-5:图1-5:Windows控制台应用程序启动调试后在断点处中断单步调F15按Shift+F5查看变量的n速监视”,可以使用“快速监视”框查看变量n的值。然后,可以点击“关nnF10量n的值会变为0,如图1-6:图1-6:使用“监视”窗口查看变量的值和类如果需要使用十进制查看变量的值,可以点击上的“十六进制”按钮,从而在调用堆按F11“逐语句”功能的快捷键)调试,直到进入Func函数,查看“调用堆栈”窗口可以发现在堆栈上有两个函数Funcmain。其中当前正在调试的Func栈顶位置,mainmainFuncmainmain在的源代码文件打开,并也使用一个绿色箭头指向Func函数返回后的位置。Funcmain口中n的值,可以看到在不同的堆栈帧被激活时,LinuxLab调试器会自动更新之前练习了Windows控制台应用程序项目的各项操作,对Linux0.11内核项目的各种此项目就是一个Linux0.11操作系统内核项目,包含了Linux0.11操作系统内核的所使用Windows资源管理器打开项目所在的文件夹C:\linux011,查看所有源代码文件。0.11操作系统需要运行的可执行文件。LinuxLab在成功生成Linux0.11操作系统内核后,会将这三个二进制文件写入大小为1.44MB的软盘镜像文件floppya.img中,并将该软盘镜像文件虚拟机的软盘驱动器A中,然后让虚拟机从软盘镜像A开始引导,并最终运行其中的Linux0.11操作系统(相当于将写有三个二进制文件的软盘一个物理机的软盘驱动器A中,然后按下开机按钮。使用源代码编辑器打开main.c文件。的第一个内核函数就是这个start函数。1-8Linux0.11内核项init/main.c文件中添加一此时,激活虚拟机窗口可以看到Linux0.11也不再继续运行了。各种调试功能(包括单步调试、查看变量的值和各个调试工具窗口)Windows制台程序完全相同,读者可以在这里自己练下。F5Linux0.11且Linux0.11的终端已经打开,可以接受用户输入令了。命命令功选项含命令命令形选功- 件- - 件--cp[选项]源 2- - rm- -- - 录符][mode]ugoa符文件属主同组的用户有相同的权s注意练习使用查找功Linux0.11的源代码虽然不足两万行,仅为任何一个具有的软件代码量的十阅读和理解Linux0.11的源代码带来不小的。即便是一位有丰富经验的开发者,在没请读者按照下面的步骤练习使用查找功能,查找Linux0.11内核源代码中对fork函数的、定义以及所有调用fork函数的代码:1-10:“查找范新建Linux0.11应用程序项目此项目就是一个Linux0.11WindowsC:\linux011\Floppya.img文件拷贝覆盖C:\linuxapp\Floppya.img文件。这样Linux0.11应用程序就可以使用版本的Linux0.11操作系统内核了。1-11Linux0.11应用程打开C:\linuxapp\debug文件夹,查看生成的对象文件和目标文件。其中的linuxapp.exeLinux0.11aout不能在Windows中运行,只能在Linux0.11中运行。LinuxLab每次生成应用程序项目时,都会将应用程序的可执行文件自动写入软盘镜像文件floppyb.img中。查看软盘镜像文件中floppyb.imgFloppyImageEditor开该项目中的floppyb.img文件,查看软盘镜像中的文件。linuxapp.exe是从查看一下该文件的修改日期,如图1-12:执行Linux0.11应用程序项目Linux0.11“mcopyb:linuxapp.exelinuxapp”命令将软盘镜像文件floppyb.img中的可执行文件linuxapp.exe拷贝到硬盘的当前linuxapp1-图1-13:将可执行文件linuxapp.exe拷贝至硬而没有x权限,如图1-14:图1-14:使用ls命令查看文件linuxapp的权使用“od+xlinuxapp”ls认文件权限修改后,就可以运行该文件了,如图1-15:1-15linuxapp增加可执行权限并LinuxLab有必要深入学下这种操作方式,为今后在Linux操作系统中编程打下基础。vi编辑器的使用方编写程序的得力工具。vi有3种操作模式:命令模式、模式和末行模式。入模式下,才可以进行文字数据输入及添加代码,按Esc键可回到命令模式。命令类命令形说vi:wq要确认是否放弃修改的内容:iaoscr命说xXYpvi和GCCLinux0.11Linux系统上工作时,恐怕就只有使用这种方法来写程序了。按F5启动Linux0.11,使用ls命令查看当前,并用rm命令将o.c文一个可以输出“oworld”的程序,保存源代码文件并退出vi。gcco.c-oo编写Makefile文件管理Makefile可以用来管理一个项目中多个源代码文件的编译和过程,也可以用来管读者今后在Linux下开发程序还是非常必要的。gcco.c-o第一行描述依赖关系,目标文件o依赖于源代码文件o.c,第二行描述操作命令,要用gcc将源代码文件o.c编译为可执行文件o。当修改o.c文件后,需要编译时,只需在相同make令即可(makefile依赖关系命令行之后的操作命令行必须用制表符(Tab键)缩进,否则会收到“missing o.o-o gcc- o.c- o.o-o gcc- o.c- rm-f rm-f 用于清理操作。可在任何时刻激活clean目标,删掉makefile生成的文件。可以构建任何一个目标,例如指定clean目标令如下:make完成下面的练习在LinuxLab关闭后默认会自动使用Windows资源管理器打开数据文件所在的文件夹,并且选中刚刚保存的数据文件(OUD。将数据文件备份(例如拷贝到自己的U盘中或者发送到服务器上实验注意事LinuxLabLinux0.11Linux0.11应用LinuxLabLinux0.11像文floppyb.img中Linux0.11后需mcopy命令将软B中的可执行文件到硬盘中才能运行。在Linux0.11中对硬盘上的文件进行修改后,需要执行sync命令才能将修改保存在硬盘上,否则,关闭Linux0.11后,修改会丢失。四、思考与练练习使用Linux0.11提供的各种文件操作命令。使用这些命令浏览硬盘中的结构,了解Linux中各个的主要功能。五、相关阅http:/ 可以参与中的讨论或者进行答疑。实验二操作系统的启一、实验目 二、预备知现代操作系统启动比较复杂,涉及较多的名词,如:GDT、GDTR、IDT、A20地址源代码文件,分别是boot/bootse NASMbootsect.bin。此二进制文0x7c00处,并从该地址开始执行引导程序。引导程序会首先把自己移动到物理内存区中的内核模块(linux011.bin)0x10000处。最后,在确定根文件系统所在的磁盘后,跳转运行setup模块。NASMsetup.bin。此二进制文件floppya.img14个扇区,作为操作系统加载程序。它首先利用BIOS中断机器参数,并放置在0x90000开始的物理地址处以备描述符表寄存器(idtr)等,并在开启A20地址线后对8259A中断控制重新编程。最后进入保护模式,跳转到地址0:0(即内核模块中head.s的第一条指令处)运行。在时写入内核模块linux011.bin的代码段的开始位置。head.s在执行时会首先加载idt,并使各个表项(256项)指向一个/init/main.cstart函数的地址弹出,并跳转到start函数中去执行。三、实验内bootbootsemsetup.asm两个汇F7WindowsDebug文件夹。找到0.11文件中的内核函数(start函数)的地址首先按照下面的步骤记录下start函数的地址,留待后续使用:添加的断点处中断。在start函数名称上点击右键,选择菜单中的“添加监视start2-1:图2-1:start函数的地为了调试BIOS程序和软盘引导扇区程序,需要按照下面的步骤将目标机修改Bochs 值修改为“BochsDebug 点击“确定”按钮关闭“属性页”框。接下来就可以使用Bochs模拟器调F5Bochs窗口。标题为“BochsforwindowsDisplay”的窗口相当于计算机的显示器,显示操作系统的输出。标题为“BochsforwindowsConsole”的窗口是Bochs的控制台,用来输入调试命令和输出调试信息。启动调试后,BochsCPU要执行的第一条指令(BIOS的第一条指令)处中断。此时,Display窗口没有显示任何内容,ConsoleBIOS的第一条指令,并等待用户输入调试命令,如图2-2:图2-2:Console窗口显示在BIOS第一条指令处中图2-3:使用sreg查看寄存器中的令r后按回车,显示当前CPU中各个通用寄存器的值,如图2-4。其中令的地址,可以验证CPU将要执行的指令地址为CS:IP。图2-4:使用r寄存器查看寄存器中令 0,说明软盘引导扇区还没有被加载到此处。IP寄存器的值是一致的。由于内存还没有被使用,所以其中的值都0。(以bootsem为例表文件,此文件中的信息可以帮助开发者调试bootsem文件中的汇编代码。 movBIOS在执行完自检和初始化工作后,会将软盘引导扇区(512字节)加载到物理地址vb0x0000:0x7c000x0000:0x7c00(要执行的指令,即软盘引导扇区程序的第一条指令,如图2-5:图2-5:软盘引导扇区程序的第一条指(b8c007程序此时已经执行完毕,输入调试命令 0x0000验证此512b0x7c00显示软盘引导扇区程序的所有字节码。观察此块内存最开始的三个0x550xaa操作系统,这两个字节对应bootsem中最后一行语句(注意,字节endiandw0xAA55输入调试命令xp 动到0x9000:0x0000处。输入调试命令 xp/512b0x9000:0x0000,并与之前的内存比较,可以知道引导程序已经将自己移到了0x90000处,并在0x9000:0x0018处中断执行了。输入调试命令xp 根据bootsect.lst文件下面一行的内容(执行此行指令时说明setup.bin加载完毕) 000000310F830B00 jncok_load_setup 输入调试命令vb 0x9000:0x0031,在逻辑地址0x9000:0x0031处设置一个断点。xp/512b0x9000:0x0200,此块内存已经发生改变,可以知道此时setup.bin模块已经被载入内存。xp/512b0x1000:0x0000,可以看到除前面有少量数据(BIOS自检119 call输入调试命令 0x9000:0x006e。在0x9000:0x006e处设置一个断点输入调试命令xp 明Linux0.11将要开始执行setup模块。图2-6:在断点0x9020:0x0000处中 图2-7:取得机器参数前物理内存0x90000处的值 输入调试命令 入0x90000-0x901FF的物理内存中。 图2-8:取得机器参数后0x90000 jmp输入调试命令 会跳转到head.s的第一条指令处执行。s movl 0x0000处设置一个断点(注意执行到这里时,CPU已经处于保护模式了pb0x0000。c2-9 pushl 图2-10:在将start函数地址压栈的指令处中是start函数的地址。最终就是通过这个地址跳转到操作系统内核的点函数开 输入调试命令:pb0x54a5令c继续执行,在ret指令处中断。然后接着输入调试命令s,单步执行此行的ret指令,可以得到图2-11:图2-11:准备执行start函数的第一start函数中的指令了。将内核入调试到这里,init/main.c的start函数之前部分就全部结束了,系统将跳入2-12-11init/main.cstart函数中去执行。说明此时已经进入了内核,但从刚进入内核到系统最终稳定下来,全注释》第7章的内容。在LinuxLab的“新建项目”框中可以选择mkfloppy项目模板或pe2bin项目模板来Linux0.11linux011.binLinuxLab时,已经将这两个mkfloppy.exepe2bin.exeLinuxlabbin文件夹中,但是的功能,从而加深对Linux0.11系统的理解。mkfloppy项目模板创建的应用程序mkfloppy.exe用于将bootsect.bin、setup.bin和linux011.binfloppya.img512字节,bootsect.bin0扇区,setup.bin1-4扇区,linux011.bin文件占用软盘的第5-388扇区。pe2bin项目模板创建的应用程序pe2bin.exe用于将生成的PElinux011.exelinux011.bin文件。PElinux011.exe中存放着Linux0.11操作系统内核的数据和指令以及调试信息,由于其格式比较复杂,还无法被bootsect程序识别,所以无法直接从软盘加载到内存中,所以,pe2bin.exe应用程序的目的linux011.exelinux011.bin文件,从而允许bootsect程序直接将其加载到内存中。linux0.11linux011节点,单击弹出菜单中echo正在生成内核映像文件strip"$(TargetDir)$(TargetName).tmp"del"$(TargetDir)$(TargetName).tmp"echo正在制作引导软盘映像文件mkfloppy.exe"$(OutDir)\bootsect.bin""$(OutDir)\setup.bin""$(TargetDir)$(TargetName).bin"这些Windows控制台命令会在生成Linux0.11内核项目的最后阶段自动执行。其中echo命令用于打印信息copy命令linux011.exe文件拷贝成一个linux011.tmp临时文件;striplinux011.tmp文件中的调试信息删除;pe2bin.exelinux011.tmp文件转换linux011.bin文件;del命令将linux011.tmp临时文件删除;mkfloppy.exe将bootsect.bin、setup.binlinux011.binfloppya.img软盘镜像文件中。四、思考与练习mkfloppy模板新建一个应用程序项目,仔细阅读其中的源代码。此应用程序默认linux011.bin5号扇区开始的位置,尝试修改该应用程序linux011.bin8F7生成项目,将生成的mkfloppy.exe拷贝替换Linuxlab安装bin文件夹下的mkfloppy.exe,修改boot\bootsem中的汇编代码,使之能够从软盘A的8号扇区加载linux011.bin文件,确保Linux0.11能够正常启动。echo正在生成内核映像文件...strip"$(TargetPath)"echo正在制作引导软盘映像文件mkfloppy.exe"$(OutDir)\bootsect.bin"$(OutDir)\setup.bin"$(TargetPath)floppya.img"PElinux011.exe直接写入软盘镜像文件中,这就需要读者Linux0.11能够正常启动。可以参考pe2bin项目模板中的源代码完成此练习。五、相关阅打开LinuxLab的“帮助”菜单,选择“其它帮助文档”中的“NASM手册”,阅读该手可以参看第10章的10.1和10.2节。阅读第14章可以了解关于实模式的信息。实验三S序设建议学时:2学时一、实验目 二、预备知S为用户和Linux操作系统之间令交互提供最基本的接口,在使用者和操作系统早期S主要用作命令解释器,经过不断扩充和发展,现在已是命令语言、命令解释器、S是一种解释型程序设计语言,它支持大多数高级程序设计语言中能见到的程序结 包含一系列命令,运行就是执行中的每个命令,可用一个在一次运行中三、实验内3.1准备实1.启动EngintimeLinuxLab 3.2回显语echo语句用来显示变量值或字符串。 使用vi新建 编辑S oworld o 3.3特殊字字作星号问号编写如下S 3.4变 用户自定义变用户自定义变量也称局部变量,是用户在S 编写如下S o oecho2.位置变位置变量类似于C语言程序令行参数,可以写在S程序文件名之后,共有9个,用$n表示(n为一个十进制数。在参数传递时必须使命令行中提供的参数与程位置变量名为$1,第2个位置变量名为$2,以此类推。编写如下S echo$0echo$1echo3.S变S变量的名字和含义是固定的。$?为得到上次命令的十进制返回码,$$为得到编写如下S 文件S5.sh,并观察运行结果:echo$$echo4.命令替编写如下S 文件S6.sh,并观察运行结果: $ll3.5运算语编写如下S 文件S7.sh,并观察运行结果:echo$k3.6函 {} return关键字返回,返回值只能为整数或整数型数值 参数传 函数的参数传递与S 的位置变量类似,也是通过$n表示,n是一个十调在 编写如下S functionMul(){ let return} 3.7控制语的条件执行不同的流,这就要求S提供各种流程控制语句。 测试语测试语句计算表达式的值,并返回真(0)或假(1。其在S 中常常缩写为[expression]测试条返回值说 -r -w -x -b -c -d -f -p -s -t 测试条返回值说 -z -n 测试条返回值说 int1-gt int1-eq int1-ne int1-lt int1-le int1-ge if条件表达式thenelse命令else后的语句。条件表达式也可以使用两个预定义的值truefalse。编写如下S文件S9.sh,用于判断一个普通文件是否存在。然后在控制 9.sh, thenecho“Fileelseecho“Cannotfindfile.” case字符串in模式字符1)命令1;;…模式n)n;;case允许多重条件选择,执行过程是:用字符串去匹配各模式字符串,发现某个编写如下S 文件S10.sh,用于判断传递给S “0 “1argument” “2 循环语while测试命令do命令表回false为止。编写如下S echo$iforfor变量indo束for循环。编写如下S 012345untiluntil测试命令do时结束until语句。请读者编写一个S 编写如下S - `echo let echo aaaaaaaaaa 读入语编写如下 echo“pleaseinput readaecho6.退出语break[n]n1。continue[nn层,7.退出程序语 编写如下S 123 echo echo 等待语四、思考与练习编写一个S 2+4=6编写一个S文件,要求执行时,如果第一个位置参数是合法的,那么就把该及其所有子下的文件名称显示出来,如果第一个位置参数不是合法的,则显示名不对。(提示:函数也可以使用递归)实验调一、实验目二、预备知行,从而可以操作的各种资源,实现应用程序与内核的交互。这些系统调用号定义在文件include/unistd.h中,系统调用号实际上对应于include/linux/sys.hsys_call_table[]中项的索在Linux中规定int0x80指令是系统调用的总,系统调用号存放在EAX寄存器中,应用程序通过系统调用传递给内核函数的参数依次存放EBX、ECXEDX个寄存器中(即系统调用最多只能有3个参数。接或间接(通过库函数)int0x80函数(kernel/system_call.s件中第101);最后,由_system_call调用对应的内核函数,该内核函数将被执行并通过EAX寄存器返回结果。三、实验内准备试EngintimeLinuxLab添加新的系统调为Linux0.11添加一个新的系统调用max函数,该函数实现比较两个参数的大小文件,在第162行添加新的系统调用号,如图4-1:图4-1添加新的系统调用图4-2修改Linux内核的系统调用总在include/linux/sys.h文件中的第88行使用C语言内核函数的原型,如图义函数的返回值为int类型,参数为空,能够让sys_max标识符代表一个函数名称图4-3使用C语言内核函数的原sys_call_table[]中添加新系统调用函引一一对应4-4所示:图4-4在系统调用函数指针表中增加内核函数的指图4-5系统调用对应的的源代码(如图4-6所示。其中,需要定义 NR_max宏,并使用_syscall2宏(在文件就会在源代码文件中使用C语言添加max函数的完整实现。4-6main.c文依次执行下面令gcc-Emain.c-omain.txtmcopymain.txt具打开。将其中的main.txt文件到windows的某个文件夹中。将文件夹中的main.txt文件拖动到Linuxlab中释放,在文件的最后部分就可以看到_syscall2宏展开后得到的max函数了。可以重点学下通过在C代码中嵌AT&Tint0x80根据_syscall0、_syscall1、_syscall2、_syscall3这四个宏(在文件include/unistd.h167)可知系统调用执行的过程如下:应用程序通过用EAX寄存器返回结果。断点条件框,在编辑框中设置断点条件$eax==87后按“确定”,如下图:maxmax图4-9使用“监视”窗口查看EBX和ECX寄存器的F10119行。其中119EAX寄存4-10进入系统调用对应的内恢复为200(返回值,100(参数一,200(参数二4-12出栈操作EAX,EBX和ECX寄存器的四、思考与练intIam(constchar*name);将字符串name的内容保存到内核中,返回值是intWhoami(char*name,intsize);将Iam保存到内核中的字符串拷贝到数据缓冲区name中,sizename的长size小于所需空间,则返回-1,并置errnoEINVAL。编写两个应用程序。其中,Iam序调用Iam函数,并使用命令行中的第一个参数作为Iam函数的参数;Whoami应用程序调用Whoami函数,并打印输出获取的字4-13测试应用程序的执行结用“查找和替换”框找到errno是在哪里定义的。该数组和两个系统调用的内核函数定义在kernel/sys.c文件中。不是用户空间的数据。所以,在Iam函数中需要在一个循环中重复调用include/asm/segment.h文件。实验的创一、实验目二、预备知在Linux操作系统上可以同时运行多个进程。分时技术的基本原理是把CPU的运行时间分时,操作系统就利用调度程序切换到另一个进程去运行。因此,本质上对于具有单个对于Linux0.11内核来讲,系统最多可有64个进程同时存在。除了第一个进程用“手工”建立以外,其余的都是现有进程使用系统调用fork函数创建的新进程。被创建的进程称为程,创建者则称为父进程。Linux内核使用进程标识号来标识每个进三、实验内准备试EngintimeLinuxLab调用fork函数创建intmain(intargc,char*argv[]{ intprintf(oworld\n",getpid()pid=fork();if(pid=fork();if(pid!=0){{ printf("%dparentprocess\n",getpid() printf("%dchildprocess\n",getpid() printf("%dexit\n",getpid() return}mcopyb:linuxapp.exe od+x od+xls–lfork的神奇之处,调用一次,返回两次。两个函数的原型在include/sys/wait.h文件中定义如下:pid_twait(pid_t*wait_locpid_twaitpid(pid_tpid,pid_t*wait_loc,intoptionswait(NULL调试fork函数的执行过Kernel项目。Linux011harddisk.img$eax==2&¤t->executable->i_size=appfork数时,才会命中此条件断“$eax==2”中2fork函数的系统调用号current是一个全局变量(kernel/sched.c文件76行定义),此时,由于在应用程序中调用了fork函数,所以就进入了int0x80的中断处理程文件kernel/fork.c的第30行定义)记录了的进程号。current->pid的值是counter=11查看“监视”窗口,可看到last_pid的值已经发生了变化,该值即为程的进F10279后按F11copy_process278行将此时在“快速监视”窗口查看表达式*p的值,可以看到新创建的程控制块中各个成员的值都为0。 和相同,所以,程和父进程会从相同的位置(fork函数返回的位置)继续运行置程的父进程号;第122行将p->tss.eax的值设置为0,就是fork()在在第165行添加一个断点,按F5继续调试,命中后删除该断点。刚刚执行的这窗口分别查看*current和*p的值,比较二者的异同。F10copy_process函数返回kernel/system_call.s的第280行。copy_process函数的返回值是程的进程号,会被放入EAX寄存器中,也就是父进程从fork函数返回时得到的返回值。继续按F10单步调试,直到第133行。可以看到在从fork系统调用返回之前,并reschedule函数,所以父进程会继续运行。但是,如果fork返回后,在父进程调用了wait或waitpid系统调用函数等待程结束的情况下,就会调用execve使用fork系统调用函数可以为父进程创建一个程,但是程和父进程执行的按照下面的步骤编写一个Linux0.11execveintmain(intargc,char*argv[] oworld\n",getpid() return}mcopyb:linuxapp.exe od+x od+xintmain(intargc,char*argv[] oexecve\n",getpid() execve("newapp",NULL,NULL oexit\n",getpid() return}intexecve(char*file,char**argv,char**envp程序参数file是需要被加载的程序文件名,参数argv是传递给新程序令行参数指针数组,参数envp是传递给新程序的环境变量指针数组。mcopyb:linuxapp.exe od+x od+xls-lexecve系统调用会清理掉当前进程的页 和页表项,并释放对应的物理页,然后该进开始执行新程序的代码。Linux0.11为加载执行新程序提供了exec函数族调试execve函数的执行过Kernel项目。和newapp文件了。$eax==11&¤t->executable->i_size程序可执行文件app的大小。此时,由于在应用程序中调用了execve函数,所以就进入了int0x80的中断处理程序并命中了断点。接下来会调用execveF10262行F11do_execve载执在第314行添加一个断点,按F5继续调试,命中后删除该断点。第303到第304行初始化参数和环境变量空间的页面指针数组。306执行i节点号。第309到第310行计算参数个数和环境变量个数。472添加一个断点,按F5调试,命中后删除该断点。该过程主要完成对IDID在第515行添加一个断点,按F5继续调试,命中后删除该断点。第508到第509行初始bss段数据。513514forkexecve(task_structLinux开发应用程序时,往往会同forkexecve。一个程序在使用fork函数创建了一个程时,通常会在该调用execve函数加载执行另一个if(fork(){ /*parentprocess}{ /*childprocess }四、思考与练内容编写一Linux应用main函数中使fork函数创建一个使用execve函数加载执行另外一个程序的可执行文件(例如newapp,并且让父进程退出后再结束运行。实验六进程的状态和进程调建议学时:2学时一、实验目二、预备知一个进其整个生命周期内,不同阶段可能具有不同的进程状态(在include/linux/sched.h文件的第20行定义。一个进程的状态由其进程控制块中的state字段用户运0内核运0内核运系统调用或中返中断,中断返睡终 睡暂调状 唤2唤0继4interruptib不可中
就绪(TASK_RUNNING这些状态在内核中表示方法相同,都被称为处于TASK_RUNNING状态。例如,(TASK_INTERRUPTIBLE(TASK_UNINTERRUPTIBLEwake_up(TASK_STOPPED僵死状态(TASK_ZOMBLE。当程已停止运行,但其父进程还没有调用wait,去执行其他进程,此进程则进入睡眠状态(TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE内核中的调度程序schedule(在文件kernel/sched.c中的第135行定义)用于选Linux三、实验内准备试EngintimeLinuxLab编写一个多进程程intmain(intargc,char*argv[]{ if(0==fork() printf("childprocesspid=%dppid=%d getpid(),getppid(),LINE elseif(0==fork() printf("childprocesspid=%dppid=%d getpid(),getppid(),LINE elseif(0==fork() printf("childprocesspid=%dppid=%d getpid(),getppid(),LINE wait(NULL printf("parentprocesspid=%dppid=%d\n",getpid(), return}mcopyb:linuxapp.exe od+x od+x进程的运行轨在系统初始化时打开日志文件process.log文件。内核的函数是init/main.c文件中的start函数,其中从第181行开始的一段if(!fork()) /*wecountonthisgoing }程1,程1接着调用了本文件中的init()函数。setup((void*)&drive_info(void)open("dev/tty0",O_RDWR,0);(void)dup(0);(void)dup(0输入、标准输出和标准错误。可以在这里把process.log文件关联到文件描述符3。为了能尽早process.log文件,可以让上述建立文件描述符的工作在进程0中完成。所以,将这一码从init函数中移动到start函数中,而且要放在调用修改后的start函数如下: /************yourcodebegin setup((void*)&drive_info (void)open("dev/tty0",O_RDWR,0 (void)dup(0 (void)dup(0 (void)open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666 /************yourcodeend if(!fork()) /*wecountonthisgoing 编写fprintk函数用于向process.log文件写入数内核状态下调用printf函数,而只能调用printk函数。编写可以在内核状态下被调用的fprintk函数的难度较大,所以这里直接给出源代码,主要是参考了printk函数和sys_write函数而写成的:staticstaticcharintfprintk(intfd,constchar*fmt,...{intintva_liststructstructfile*structstructm_inode*va_startva_start(args,ii=vsprintf(logbuf,fmt,va_endva_endif(if(fd<3{{"push"push ("push"pop"pop"pushl"pushl"pushl"pushl"pushl"pushl"call"call"addl"addl"popl"popl"pop"pop::"r"::"r"(i),"r"(fd):"ax",}}{{if(if(!(file=task[0]->filp[fd])returnreturn ("push"push"push"pop"pushl"pushl"pushl"pushl"call"addl"popl"pop::"r"(i),"r"(file),"r" }}kernel/printk.c文件中添加该函数的。第一个参数fd是文件描述符,为1时将信息输入标准输出,一个参数;后面的是可变参数列表,类似于printf的可变参数列表。记录进程的运行轨就绪到运行:schedule函数(在文件kernel/sehed.c135行)运行到就绪:通过schedule运行到睡眠:sleep_on函数(kernel/sehed.c202)和192行)sys_waitpid(在文件kernel/exit.c183行)完成。睡眠到就绪:wake_up函数(在文件kernel/sehed.c251行)完成进程的创建:copy_process函数(在文件kernel/fork.c89行)完成进程的退出:do_exit函数(kernel/exit.c122行)定义process.log文件中每行日志的格式为: 意NJRWEjiffies(参见之后的详细介绍。这三个字段之间用制表符“\t”分隔。例如“06J5296529jiffies的值增加1(在文件kernel/system_call.s243)。outb_p(LATCH&0xff,0x40);outb(LATCH>>8,0x40);宏定义,在文件kernel/sched.c的第57行定义如下:#defineLATCH#defineHZ1/100(10)产生一次时钟中断。所以,jiffies历了多少个10毫秒。为了进程的创建,可以修改kernel/fork.c文件中的copy_process函数。在114行后面增加语句(一定在程控制块的pid和start_time被赋值之后):>start_time=fprintk(3,"%ld\t%c\t%ld\n",p->pid,'N',jiffiesfprintk(3,"%ld\t%c\t%ld\n",p->pid,'J',jiffies中的do_exit函数。在第159行将进程状态设置为TASK_ZOMBIE的后面一条语current->state=fprintk(3,"%ld\t%c\t%ld\n",current->pid,'E',jiffies接下来,修改kernel/sched.c文件中的Schedule函数,进程进入就绪状态或运行状态。注意,Schedule函数找到的next进程是接下来将要运行的进程。如果fprintk(3,"%ld\t%c\t%ld\n",(*p)->pid,'J',jiffiesif(current->state==TASK_RUNNING&¤t!= fprintk(3,"%ld\t%c\t%ld\n",current->pid,'J',jiffies);if(current!=task[next]) fprintk(3,"%ld\t%c\t%ld\n",task[next]->pid,'R',另外,只要操作系统处于空闲状态,就会让进程0运行,进程0会不停的调用等待。所以,在修改sys_pause函数时,添加的语句应该为:(在第202行if(current->pid!= fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies在Schedule函数的最后,被选中为next进程才会进入实际的运行状态(使用标志R也就是说,在读者添加的所有调用fprintkSchedule函15)。使用在第3.2节创建的Linux011用程序项目的硬盘镜像文件harddisk.img前Linux011Kernel项目的harddisk.img文件,这样就可以继续使用之前生成的应用程序可执行文件app了。mcopy/var/process.log 盘B中的log.txt文件到Windows的C盘根 06 06 04 06 07 07 08 08 09 09 06 09 09 06 08 08 07 07 06 06 I/OI/Ovoidcpuio_bound(intlast,intcpu_time,intio_time{ structtmsstart_time, clock_tutime, int while(last>0 times(&start_time times(¤t_time }while(((utime+stime)/HZ)<cpu_time last- if(last<=0 while(sleep_time<io_time sleep(1 last- }lastCPUI/OCPUI/Ostructtmsinclude/sys/times.h义,clock_tinclude/time.hmain函数修改为如下的代码:intmain(intargc,char*argv[]{ pid_tp1,p2,p3, if((p1=fork())==0 printf("inchild1\n");cpuio_bound(5,2,2 elseif((p2=fork())==0 printf("inchild2\n");cpuio_bound(5,4,0elseelseif((p3=fork())==0 printf("inchild3\n");cpuio_bound(5,0,4 elseif((p4=fork())==0 printf("inchild4\n");cpuio_bound(4,2,2 printf("========Thisisparentprocess=======\n" printf("pid=%d\n",getpid() printf("pid1=%d\n",p1 printf("pid2=%d\n",p2 printf("pid3=%d\n",p3 printf("pid4=%d\n",p4 wait(NULL return}mcopyb:linuxapp.exe od+x od+x结束调试,用Linux011应用程序项目中的硬盘镜像文件harddisk.img覆盖用刚刚生成的app可执行文件了。mcopy/var/process.log 盘B中的log.txt文件到Windows的C盘根下。log.txt(log.txt文件拖LinuxLab。查看日志文件中记录的信息,根据之前记idapp应用程序运行时产生的进程完成下面练习根据信息以及app的源文件思考app运行时父进程及生命周期状态转换的统计并分析数“stat_log.py”文件到C盘根下。(在“学生包”本实验对应文件夹下提Python的安装包,有需要的读者可自行安装到自己的电脑上)C:\>stat_log.pylog.txt-gC:\>stat_log.pylog.txt-g> (Unit:08014030445312060778090040302090 Throughout:名意CPUI/O因为进程0比较特殊,进程0运行轨迹没有进完全行记录,信息误差比较大可以忽略进程0;其它进程的信息可能也存在误差,但在误差允许范围之内。根据Linux0.11的进程调度函数schedule的代码(在文件kernel/sched.c的135行,可知Linux0.11的调度算法是选取counter(时间片)最大的就绪进程占用函数中完kernel/sched.c416所以是一种比较典型的时间片轮转调度算法。另外,由schedule函数可以看出,当没有counter大于0的就绪进程时,要对所有进程做“(*p)->counter=((*p)->counter>>1)+(*p)->priority;”操作(kernel/sched.c182),效果是对所有的进程(包括阻塞进程)进行counter列中停留的时间越长,其优先级越大,被分配的时间片就越大(不会大于优先级的2以总的来说,Linux0.11*p=p->counter=p-时,由于每个进程的优先级都是从父进程继承的,除非自己通过调用nicenice00INIT_TASK(在文件include/linux/sched.h的第176行)定义如下:#defineINIT_TASK{0,15,15,\//state,counter,实验七信号量的实现和应建议学时:2学时一、实验目二、预备知信号信号量(semaphore)最早由荷兰科学家、获得者E.W.Dijkstra设计。Linux还没有实现信号量,也不支持”man”命令。Linux0.11Linux0.11函数原说sem_t*sem_open(constchar*name,unsignedintvalue)创建/打开信号量。name就是信号量的名字,不同的进程可以通过提供同样的name一个信号量。value是信号量的初始值,仅当intsem_wait(sem_t*semintsem_post(sem_t*intsem_unlink(constchar*生产者—消费者问三、实验内准备实EngintimeLinuxLab3.2按照下面的内容在Linux0.11内核项目中实现简易版的信号量。只需要修改量的四个系统调用函数sem_open、sem_wait、sem_post和sem_unlink即可:#defineNR_sem_open87#defineNR_sem_wait88#defineNR_sem_post89#defineNR_sem_unlink90nr_system_calls=externintsys_sem_open(
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 房产测绘合同
- 2025年CRDIC系列探地雷达合作协议书
- 2025年油苫布、天篷、遮阳篷及类似品合作协议书
- 2025-2030中国邻苯二甲酸二异壬酯(DINP)(CAS-28553-12-0)行业市场发展趋势与前景展望战略研究报告
- 2025-2030中国速溶豆奶粉行业发展分析及发展前景与投资研究报告
- 2025-2030中国通信继电器行业市场现状供需分析及投资评估规划分析研究报告
- 2025-2030中国轻质建筑材料行业市场发展趋势与前景展望战略研究报告
- 2025-2030中国轴性脊柱关节炎药物行业市场现状供需分析及投资评估规划分析研究报告
- 2025-2030中国足球行业发展趋势与前景展望战略研究报告
- 2025-2030中国货运行业市场深度调研及投资策略与投资前景研究报告
- 大班安全教育:不攀爬高处
- 2024年医师定期考核考题《临床练习》
- 法律职业伦理知到智慧树章节测试课后答案2024年秋温州大学
- 2025年数字安徽有限责任公司招聘笔试参考题库含答案解析
- Unit 9 Hot Soup Lesson 1 I'm thirsty(说课稿)-2023-2024学年北师大版(三起)英语四年级下册
- 成都二调考试数学试卷
- 提高发票额度的合同6篇
- 金融风险细则解读
- 信息系统运行管理员(基础知识、应用技术)合卷软件资格考试(初级)试题与参考答案(2024年)
- 2024至2030年高氯酸铵项目投资价值分析报告
- 2025届浙江省温州市九校高三第二次调研数学试卷含解析
评论
0/150
提交评论