模块和编译内核_第1页
模块和编译内核_第2页
模块和编译内核_第3页
模块和编译内核_第4页
模块和编译内核_第5页
已阅读5页,还剩61页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

编译内核、模块编程编译内核添加系统调用模块编程体会用户空间和系统空间学习模块的操作(加载、卸载)本实验讨论内容编译内核linux内核源码公开。并可以编译新内核。对redhat9来说,内核源码在/usr/src,对FC7来说,在/usr/src/kernels。[root@localhostroot]#cd/usr/src[root@localhost

src]#lsdebuglinux-2.4linux-2.4.20-8redhat[root@localhostsrc]#cd/usr/src/kernels[root@localhostkernels]#ls2.6.11-1.1369_FC4-i686为什么要编译内核学习与体会。需要使用新内核中的功能。修补系统新发现的安全漏洞和缺陷,维护系统的正常使用和运行。提高系统性能,包括升级新内核,修改现有内核。硬件发生变动,需要在内核中进行调整。编译内核的过程(1)uname–r2.6.11-1到,下载内核源码。linux-.tar.bz2或linux-.tar.gzcplinux-.tar.bz2到/usr/srctarjxvflinux-.tar.bz2解压。如果是gz文件,则用tarzxvflinux-.tar.gz[root@localhostboot]#cd/usr/src[root@localhost

src]#lskernelslinux-linux-.tar.bz2redhat在FC7上编译2.6内核的过程(2)内核源码/pub/linux/kernel/v2.6/cp/mnt/hgfs/share/linux-.tar.gz/usr/srctarzxvflinux-.tar.gz[root@localhost

src]#lskernelslinux-linux-.tar.gzredhat[root@localhost

src]#cdlinux-[root@localhostlinux-]#lsarchcryptoincludekernelmmscriptsblockDocumentationinitlibnetsecurityCOPYINGdriversipcMAINTAINERSREADMEsoundCREDITSfs

Kbuild

MakefileREPORTING-BUGSusr在FC7上编译2.6内核的过程(2)启动文件[root@localhost/]cp–rbootboot1//备份boot目录,最好要这样。[root@localhost/]#cdboot1[root@localhostboot1]#lsconfig-2.6.11-1.1369_FC4编译配置文件

lost+foundgrubSystem.map-2.6.11-1.1369_FC4内核符号表initrd-2.6.11-1.1369_FC4.imgvmlinuz-2.6.11-1.1369_FC4内核映像文件initrd是linux在系统引导过程中使用的一个临时的根文件系统,用来支持两阶段的引导过程。在内核之前进入内存过程(3)在源码中添加系统调用-调用过程系统调用总控程序地址挂在0x80号中断上。通过0x80号中断进入总控程序,并通过寄存器eax传入系统调用号。总控程序找到系统调用向量表sys_call_table(作为基址),再将eax中传入的系统调用号×4B找到对应系统调用偏移地址(向量表每个表项占4B),基址+偏移地址即为系统调用服务程序入口地址。转到系统调用服务程序asmlinkagesys_×××()asmlinkage

表示函数不通过寄存器,而是通过堆栈获得并修改参数。过程(3)在源码中添加系统调用[root@localhostlinux-]#vikernel/sys.c在尾部添加:asmlinkage

int

testprint(char*str){

printk("mynameis%s\n",str);return0;}过程(3)在源码中添加系统调用[root@localhostlinux-]#viarch/i386/kernel/syscall_table.S.longsys_tee/*315*/.longsys_vmsplice.longsys_move_pages.longsys_getcpu.longsys_epoll_pwait.longsys_testprint/*320*/过程(3)在源码中添加系统调用[root@localhostlinux-]#viinclude/asm-i386/unistd.h#defineNR_syscalls320#define__NR_epoll_pwait319#define__NR_testprint320#ifdef__KERNEL__#defineNR_syscalls321在FC7上编译2.6内核的过程(4)编译过程makemrproper:清除过去编译留下的中间文件,若是第一次编译的新内核,可以不要这一步。配置编译选项:用makemenuconfig等,产生编译配置文件.config产生内核映像文件bzImage。并将其放到/boot目录产生可加载模块。并将它们加入到/lib/modules目录。产生System.map文件等文件,将其放到/boot目录。#makeconfig:基于文本的最为传统的配置界面,不推荐使用#makemenuconfig:基于文本菜单的配置界面,字符终端下推荐使用#makexconfig:基于图形窗口模式的配置界面,Xwindow下推荐使用#makeoldconfig:如果只想在原来内核配置的基础上修改一些小地方,用此方法会省去不少麻烦上述几种方法的目的是一样的,那就是生成一个.config文件常用的编译配置方式:选择相应的配置时,有三种选择,它们分别代表的含义如下:Y--将该功能编译进内核N--不将该功能编译进内核M--将该功能编译成可以在需要时动态插入到内核中的模块可用空格键在这三者之间切换。或按y/m/n进行选择方括号:只有*、空(不编译)两种选择尖括号:可以是空,"*"和"M"圆括号:在提供的几项中选择按两次ESC键可退出;或选择EXIT用menuconfig方式:#makedep

链接程序代码与函数库;实际上读取配置过程生成的配置文件,来创建对应于配置的依赖关系树,从而决定哪些需要编译而那些不需要;#makeclean删除前面步骤产生的不必要的文件;#makebzImage

开始编译产生内核映像文件;makeall相当于:#makemodules编译模块;#makemodules_install

安装编译完成的模块。makeinstall安装编译完成的模块。将bzImage、System.map

复制到/boot继续操作整个编译过程(1)[root@localhost/]cp–rbootboot1//备份boot目录[root@localhostlinux-]#makemrproper[root@localhostboot]#cp/boot/config-2.6.21-1.3194.fc7/usr/src/linux-[root@localhostlinux-]#mvconfig-2.6.21-1.3194.fc7.config[root@localhostlinux-]#makeoldconfig[root@localhostlinux-]#viMakefileVERSION=2PATCHLEVEL=6SUBLEVEL=21EXTRAVERSION=.6改为:EXTRAVERSION=.6-jiangjian整个编译过程(2)[root@localhostlinux-]#makeallNAME=NocturnalMonsterPuppyLDvmlinuxSYSMAPSystem.mapSYSMAP.tmp_System.mapBUILDarch/i386/boot/bzImageRootdeviceis(253,0)Bootsector512bytes.Setupis7353bytes.Systemis1801kBKernel:arch/i386/boot/bzImageisready(#1)整个编译过程(3)[root@localhostlinux-]#makemodules_install[root@localhostlinux-]#makeinstallsh/usr/src/linux-/arch/i386/boot/install.sh-jiangjianarch/i386/boot/bzImageSystem.map"/boot“[root@localhostlinux-]#vi/boot/grub/menu.lst#hiddenmenu[root@localhostlinux-]#reboot在FC7上编译2.6内核的过程说明(1)[root@localhostboot]#cdlinux-[root@localhost

linux-]#makemrproper

用来保证新内核树无残留.o或是其他附属文件;第一次编译可以不用。[root@localhost/]cp–rbootboot1//备份boot目录。[root@localhost/]#cdboot1[root@localhostboot1]#lsconfig-2.6.11-1.1369_FC4编译配置文件

lost+foundgrubSystem.map-2.6.11-1.1369_FC4内核符号表initrd-2.6.11-1.1369_FC4.imgvmlinuz-2.6.11-1.1369_FC4内核映像文件在FC7上编译2.6内核的过程说明(2)[root@localhostboot]#cd/boot[rootboot]#cpconfig-2.6.11-1.1369_FC4/usr/src/linux-/.config[root

linux-]#makeoldconfig

接着修改新源代码目录/usr/src/linux-/下的Makefile文件。将EXTRAVERSION=.1改为EXTRAVERSION=.1-jiangjian在新内核源码目录/usr/src/linux-/下:#makeall//最关键最长的一步,就是编译产生内核映像和模块。#makemodules_install

//将模块安装到/lib/modules/<内核版本号>#makeinstall//将内核映像等文件复制到/boot,并修改引导程序grub的配置。在FC7上编译2.6内核的过程说明(3)[root@localhostboot]#lsconfig-2.6.11-1.1369_FC4System.map-2.6.11-1.1369_FC4grubSystem.map--jiangjianinitrd-2.6.11-1.1369_FC4.imgvmlinuzinitrd--jiangjian.imgvmlinuz-2.6.11-1.1369_FC4lost+foundvmlinuz--jiangjianSystem.map[root@localhostboot]#llvmlinuzlrwxrwxrwx1rootroot2611月2417:05vmlinuz->vmlinuz--jiangjian[root@localhostboot]#ll

System.maplrwxrwxrwx1rootroot2911月2417:05System.map->System.map--jiangjian在FC7上编译2.6内核的过程说明(4)[root@localhostboot]#vi/boot/grub/menu.lst

(RH9是grub.conf)修改grub配置文件在hiddenmenu上加注释符#,注释掉该句(RH9不需要)#reboot

//重启,从新内核启动[root@localhost~]#uname-r-jiangjian启动后,查看内核版本:修改后的/boot/grub/menu.lst

#grub.confgeneratedbyanaconda##Notethatyoudonothavetorerungrubaftermakingchangestothisfile#NOTICE:Youhavea/bootpartition.Thismeansthat#allkernelandinitrdpathsarerelativeto/boot/,eg.#root(hd0,0)#kernel/vmlinuz-versionroroot=/dev/VolGroup00/LogVol00#initrd/initrd-version.img#boot=/dev/sdadefault=1timeout=10splashimage=(hd0,0)/grub/splash.xpm.gz#hiddenmenutitleFedoraCore(-jiangjian) root(hd0,0) kernel/vmlinuz--jiangjianroroot=/dev/VolGroup00/LogVol00rhgbquiet initrd/initrd--jiangjian.imgtitleFedoraCore(2.6.11-1.3194.fc7) root(hd0,0) kernel/vmlinuz-2.6.11-1.1369_FC4roroot=/dev/VolGroup00/LogVol00rhgbquiet initrd/initrd-2.6.11-1.1369_FC4.img在FC7上编译2.6内核的过程说明(5)在新内核测试新添加的系统调用sys_testprint()[root@localhost~]#uname-r-jiangjian[root@localhost~]#cattestprint.c#include<unistd.h>#include<sys/syscall.h>#include<stdio.h>intmain(){

intret;ret=syscall(320,"jiangjian");

printf("returnvalueis%d\n",ret);return0;}在新内核测试新添加的系统调用sys_testprint()[root@localhost~]#gcc

testprint.c-otestprint[root@localhost~]#./testprintreturnvalueis0[root@localhost~]#dmesgmynameisjiangjian模块编程模块是linux操作系统的特有机制。可以在不需要对内核进行重新编译和引导的情况下,动态地加载和卸载模块,也就是所谓LKM(可加载模块,LoadableKernelModules)。将该模块作为linux内核空间的扩展来执行。通过insmod命令手工加载,通过rmmod来手工卸载。内核也可以通过守护进程kerneld动态加载和卸载模块。嵌入式系统等采用微内核。UNIX系统采用宏内核。linux采用折衷方案,内核实现最基本功能,其他部分采用模块方式,根据需要加载模块。模块工作在内核空间。微内核和宏内核模块工作在内核空间。模块是一个目标文件(*.o,在2.6内核中是.ko),不能单独运行。不是一个独立的进程。模块由一组函数和数据结构组成,作为独立程序编译。可以采用动态加载或是静态加载。不能使用c库函数,只能使用内核函数,比如不能用printf,只能用printk()。内核栈空间有限,一般4KB到8KB,所以不要定义过多自动变量,如需要大的结构,建议使用动态分配的空间。不能进行浮点运算。模块的特性内核符号表管理。内核将资源登记在符号表(symboltable)中,模块加载时,模块就可以通过内核符号表使用内核的资源,解决资源引用问题。Linux支持模块堆栈(modulestacking)的概念,一个模块可以请求其他模块为之服务。同时,当通过init_module()调用加载新模块时,该模块定义的符号(包括函数和全局变量等),此时也被导出,供其他以后可能加载的模块使用。Linux内核对加载的内核模块进行管理内核符号表管理。通过/proc/ksyms文件(在2.6内核中是/proc/kallsyms),可以查看内核符号表中各类符号及其地址。如:Linux内核对加载的内核模块进行管理[root@localhostsimple]#cat/proc/ksyms|grepprintkc011c2a0printk_R1b7d4074c03d7b88tux_Dprintk_Ra12c9a12c03d7b84tux_TDprintk_Rc0ce7778维持内核模块的引用计数。引用数为0才允许卸载该模块。Linux内核对加载的内核模块进行管理[root@localhostsimple]#lsmod|tail-4jbd518922[ext3]BusLogic1007963sd_mod134528scsi_mod1071283[usb-storageBusLogicsd_mod]模块名称模块大小引用计数。lsmod(listmodules)用来查看当前内核加载的模块最小的模块结构也必须包括两个函数:init_modules():加载模块时被调用,可以启动模块加载期间的操作;cleanup_modules():卸载模块时被调用,停止相关操作。模块通过加入新函数来实现期望的功能,每个新函数必须在该模块加载到内核中时进行注册,在模块卸载时注销;注册在init_modules()中完成;注销在cleanup_modules()中完成。模块的组织结构(2.4内核)#include<linux/module.h>//声明是一个模块#include<linux/kernel.h>//说明是个内核功能int

init_module()//用到了module.h{...//加载时,初始化模块的编码} //read()、ioctl()等函数...voidcleanup_module()//用到了module.h{...//卸载时,注销模块的编码}初步印象:没有main函数#gcc−O2−Wall−DMODULE−D_KERNEL_−c

filename.c

filename.c为自己编写的模块程序源代码文件-Wall显示所有的警告消息,发生警报时取消编译,即将警报看作出错;

−Dmacro:其功能类似于在源码中的#define;-O2:对编译进行优化,比-O更好的优化,编译速度较之更慢一些,但产生可执行文件的执行速度更快;-g:产生调试信息,gcc允许-g和-O2同时使用。KERNEL前后各有两个短横杠。模块的编译许多情况下,头文件和源文件会单独存放在不同的目录中。例如,假设存放源文件的子目录名为./src,而包含文件则放在层次的其他目录下,如./inc。当我们在./src

目录下进行编译工作时,如何告诉GCC到哪里找头文件呢?方法如下所示:

$gcc

test.c–I../inc-otest

上面的命令告诉GCC包含文件存放在./inc目录下,在当前目录的上一级。如果在编译时需要的包含文件存放在多个目录下,可以使用多个-I来指定各个目录:

$gcc

test.c–I../inc–I../../inc2-otest

这里指出了另一个包含子目录inc2,较之前目录它还要在再上两级才能找到。lsmod:查看加载的模块,实际使用的是/proc/modules文件。insmod:加载模块。rmmod;卸载模块。dmesg:查看日志信息。实际上是用/var/log/messages文件。以上命令只能由超级用户执行。产生模块文件以后的操作模块的加载方式:用insmod手工加载模块;请求kerneld加载。insmod在/lib/modules/kernel-version中找到请求加载的模块,然后执行:

create_module(),init_module()执行完insmod后,用dmesg查看日志,然后可以在/proc/modules文件中看到加载的模块。或是使用lsmod查看。模块的加载

模块的卸载:用rmmod卸载模块;cleanup_module()断开内核模块链表中的链接。模块的卸载该模块只要两个函数:init_module()cleanup_module()编写一个简单的内核模块#defineMODULE//MODULE需要写在module.h之前#include<linux/kernel.h>//表示内核模块,printk需要#include<linux/module.h>//一个模块,init_module等需要//处理CONFIG_MODVERSIONS

#ifCONFIG_MODVERSIONS==1#defineMODVERSIONS#include<linux/modversions.h>#endif

int

init_module()//初始化模块

{

printk("Hello!Thisisatestingmodule!\n");return0;}voidcleanup_module()//取消init_module()函数所做的操作

{

printk("Sorry!Thetestingmoduleisunloadingnow!\n");}

[root@linux/]#gcc–O2–Wall–DMODULE–D__KERNEL__−c-I/usr/src/linux/include/simple_module.c[root@linux/]#ls

//在当前目录下查看生成的目标文件testmodule.o

[root@linux/]#insmod–f

testmodule.o

//-f表示强行加载,为什么?

Hello!Thisisatestingmodule!

[root@linux/]#rmmodtestmodule

Sorry!Thetestingmoduleisunloadingnow!模块的加载和卸载报“gcc和linux内核版本冲突”,解决方法:1.使用insmod–f强行加载2.修改/usr/include/linux/version.h:将2.4.20改为2.4.20-8报“MODULE重复定义”,为什么?解决方法:1.去掉源程序中的”defineMODULE”2.去掉编译选项的“-DMODULE”printk()在纯字符界面(纯console)上有输出,但是在图形界面的模拟终端无输出。此程序注意三个问题上面的程序执行结果(1)[root@localhostsimple]#lsmakefile

simple_module.c

simple_module.o[root@localhostsimple]#lsmod|grep

simple_module[root@localhostsimple]#cat/proc/modules|grep

simple_module[root@localhostsimple]#insmodsimple_moduleinsmod:simple_module:nomodulebythatnamefound[root@localhostsimple]#insmodsimple_module.oWarning:loadingsimple_module.owilltaintthekernel:nolicenseSeehttp:///lkml/#export-taintedforinformationabouttaintedmodulesModulesimple_moduleloaded,withwarningsinsmod要加*.o上面的程序执行结果(2)[root@localhostsimple]#lsmod|grep

simple_modulesimple_module8920(unused)[root@localhostsimple]#cat/proc/modules|grep

simple_modulesimple_module8920(unused)[root@localhostsimple]#dmesg|tail-1Hello!Thisisatestingmodule!上面的程序执行结果(3)[root@localhostsimple]#rmmodsimple_module.ormmod:modulesimple_module.oisnotloaded[root@localhostsimple]#rmmodsimple_module[root@localhostsimple]#lsmod|grep

simple_module[root@localhostsimple]#dmesg|tail-2Hello!Thisisatestingmodule!Sorry!Thetestingmoduleisunloadingnow![root@localhostsimple]#grepSorry/var/log/messagesNov2501:45:32localhostkernel:Sorry!Thetestingmoduleisunloadingnow!rmmod不要加*.o另外注意lsmod和/proc/modules的关系:实际上lsmod就是查看/proc/modules[root@localhostsimple]#cat/proc/modulessimple_module8920(unused)vmblock119524vmxnet127160(unused)vmmemctl80040(unused)vmhgfs474241nls_iso8859-135161(autoclean)nls_cp43751161(autoclean)vfat130041(autoclean)fat388080(autoclean)[vfat]注意:寄存器cr3用于存放当前进程的“页表目录结构”,访问该寄存器须在内核空间进行。再看一个复杂的例子,看出模块在内核空间工作的特点。#include<stdio.h>voidGetCr3(){

int

iValue;__asm____volatile__("movl%%cr3,%0":"=r"(iValue));

printf("thevalueofcr3is:%d",iValue);}intmain(){GetCr3();return0;}用户模式下,程序出错!#defineMODULE#include<linux/module.h>int

init_module(){

int

iValue;__asm____volatile__("movl%%cr3,%0":"=r"(iValue));printk("Cr3:%d\n",iValue);return0;}voidcleanup_module(){

printk("UninstallGetCr3.\n");}采用模块实现上一实验没有实现的功能__asm____volatile__("movl%%cr3,%0":"=r"(iValue));printk("Cr3:%d\n",iValue);CR3:内存页面目录信息;movx

source,destination:GNU汇编器使用AT&T语法,其中源和目的操作数的顺序和Intel顺序是相反的;x如果是l/w/b,分别代表32位长字/16位字值/8位字节值;内联汇编(inlineassembly):将汇编语言函数直接放在c和c++语言程序中;本来用asm(“assemblycode”),但是ANSIC中asm另有他用,所以按ANSIC约定编码,必须使用__asm__替换关键字asm;__volatile__表示不希望编译器优化内联汇编代码。__asm____volatile__("movl%%cr3,%0":"=r"(iValue));基本asm格式有缺陷:所有输入值和输出值必须使用c程序的全局变量,不能在asm段中使用局部变量;另外内联代码中不能改变任何寄存器的值(如果代码中有改变,代码前后要有保存和恢复寄存器的操作)采用扩展asm格式,此时必须采用新格式:asm(“assembly

code”:output

location:input

operands:changedregisters)输出位置:包含内联汇编代码的输出值的寄存器和内存地址的列表。输出和输入值列表的格式:”constraint”(variable)variable是程序中声明的变量名;”=“表示只能写入操作数,variable是个输出值;”r”表示通过任何可用的通用寄存器;%0表示第一个输出值内存地址或是对应寄存器。%1表示下一个输出值,依次类推。__asm____volatile__("movl%%cr3,%0":"=r"(iValue));Linux下的AT&T语法(即GNUas汇编语法)起源于AT&T贝尔实验室,是在当时用于实现Unix系统的处理器操作码语法之上而形成的,AT&T语法和Intel语法主要区别如下:AT&T使用$表示立即数,Intel不用,因此表示十进制2时。AT&T为$2,而Intel就是2。AT&T在寄存器前加%,比如eax寄存器表示为%eaxAT&T处理操作数的顺序和Intel相反,比如,movl%eax,%ebx是将eax中的值传递给ebx,而Intel是这样的mov

ebx,eaxAT&T在助记符的后面加上一个单独字符表示操作中数据的长度,比如movl$foo,%eax等同于Intel的mov

eax,wordptr

fooDFLAGS=-D_KERNEL_-DMODULE-I/usr/src/linux/includeGetCr3.o:GetCr3.c

gcc-c$(DFLAGS)GetCr3.cclean:

rm-f*.oMakefile文件注意:编译后,加载模块insmodGetCr3.c时,报“Warning:loadingGetCr3.owilltaintthekernel:nolicense”,在GetCr3.c程序中加”MODULE_LICENSE(“GPL”)”,详见教材P302。模块程序和普通程序比较普通程序模块程序入口和出口main()init_module()cleanup_module()编译链接gccgcc–c–D__KERNEL__-DMDULE运行直接运行insmod调试gdbkdb

kgdb

2.6内核模块编程#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE("GPL");staticint

hello_init(void){

printk(KERN_ALERT"hello,world!\n");return0;}staticvoidhello_exit(void){

printk(KERN_ALERT"goodbye!\n");return;}

module_init(hello_init);module_exit(hello_exit);#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE("GPL");staticint

hello_init(void){

printk(KERN_ALERT"hello,world!\n");return0;}staticvoidhello_exit(void){

printk(KERN_ALERT"goodbye!\n");return;}

module_init(hello_init);module_exit(hello_exit);KERN_ALERT用于需要立即采取动作的情况。之所以把优先级定得较高,是怕无法输出到控制台。和后面的字符串之间不能有逗号。最好以“\n”结尾printk()和printf()最大的不同点在于不支持浮点数。2.4内核模块的编译只需要内核源码头文件,产生的模块文件为*.o;2.6内核模块的编译需要配置过的内核源码,产生的模块文件为*.ko;2.4内核中默认非静态全局变量和函数在模块加载之后会输出到内核符号表中去。2.6内核中不会,如果输出符号,要用:EXPORT_SYMBOL(name)EXPORT_SYMBOL_GPL(name)2.6内核模块编程和2.4内核的不同之处2.6内核模块编程-Makefile文件ifneq($(KERNELRELEASE),)obj-m:=hello.oelseKERNELDIR?=/lib/modules/$(shelluname-r)/buildPWD:=$(shellpwd)default: $(MAKE)-C$(KERNELDIR)M=$(PWD)modulesclean: $

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论