Arm 2440——Nand flash啟動(dòng)模式詳解(LED程序?yàn)槔?/h1>
斷斷續(xù)續(xù)的研究arm也有2個(gè)月了,現(xiàn)在才感覺理解了arm在Nand flash模式下的啟動(dòng)過程,現(xiàn)在來這里記錄下來以表達(dá)我無比喜悅的心情。閑話少說,趁著還沒有忘記學(xué)習(xí)過程中的感受,直接進(jìn)入正題。大家都知道,arm在Nand flash啟動(dòng)模式下啟動(dòng)時(shí)系統(tǒng)會(huì)將Nand flash中的前4KB代碼拷貝到SRAM(也就是Steppingstone中),由SRAM配置中斷向量表和完成Nand flash訪問的必要初始化,然后將Nand flash中的全部程序代碼拷貝到SDRAM中,最后由SRAM跳轉(zhuǎn)到SDRAM,然后程序就正常執(zhí)行了,這一過程看上去很簡(jiǎn)單,但是真正理解這一過程還是不簡(jiǎn)單的,盡管這樣,還是想告訴大家仔細(xì)理解還是比較容易理解這個(gè)過程的。如果您是ADS用戶,你省去了很多麻煩,但我不確定你省去的這些麻煩是否值得,這里介紹的是一種麻煩的方式,linux下的led程序。
本文引用地址:http://m.butianyuan.cn/article/201611/322828.htm代碼Head.s
- .externmain
- .text
- .global_start
- _start:
- breset
- reset:
- ldrsp,=4096
- bldisable_watch_dog
- blclock_init
- blmemsetup
- blcopy_steppingstone_to_sdram
- ldrpc,=on_sdram
- on_sdram:
- msrcpsr_c,#0xdf
- ldrsp,=0x34000000
- ldrlr,=halt_loop
- ldrpc,=Main
- halt_loop:
- bhalt_loop
我認(rèn)為,最需要理解的就是這段代碼了。先簡(jiǎn)單的解釋下這段代碼。
(1)由于arm執(zhí)行reset之后pc會(huì)被清零,也就是reset中斷的中斷入口地址,因此,第一條指令就是b reset,跳轉(zhuǎn)到reset中斷處理函數(shù)。
(2)由于這里硬件配置都是C語言來完成的,而且我們的初始代碼比較小,完全不會(huì)超出4KB,因此可以在SRAM使用堆棧,故將SP設(shè)置為4096,以提供C運(yùn)行環(huán)境
(3)接下來的3個(gè)bl分別完成了關(guān)閉看門夠定時(shí)器,配置時(shí)鐘信號(hào)和存儲(chǔ)器配置的工作,第四個(gè)bl是將SRAM的4KB空間內(nèi)的代碼拷貝到了SDRAM中。
(4)接下來的ldr句將pc賦值為on_sdram的地址,實(shí)現(xiàn)了從SRAM到SDRAM的跳轉(zhuǎn)(下面會(huì)講為什么)
(5)on_sdram中切換到了了系統(tǒng)模式然后分配了系統(tǒng)模式堆棧,將鏈接寄存器設(shè)置為halt_loop然后就跳轉(zhuǎn)到了SDRAM中的Main
上面的解釋只是大體上說明了代碼的意思,但是初學(xué)者總會(huì)有個(gè)疑問就是為什么ldr pc,=on_sdram就實(shí)現(xiàn)了從SRAM到SDRAM的跳轉(zhuǎn)呢?我被這個(gè)問題困擾了很長(zhǎng)時(shí)間,到今天才想明白了這個(gè)問題,問題的關(guān)鍵就是相對(duì)跳轉(zhuǎn)和絕對(duì)跳轉(zhuǎn)的問題。為了說明這個(gè)問題我先解釋一下bl指令跟ldr指令在執(zhí)行過程中的區(qū)別。
B指令是相對(duì)跳轉(zhuǎn)指令,B 指令是最簡(jiǎn)單的跳轉(zhuǎn)指令。一旦遇到一個(gè) B 指令,ARM 處理器將立即跳轉(zhuǎn)到給定的目標(biāo)地址,從那里繼續(xù)執(zhí)行。注意存儲(chǔ)在跳轉(zhuǎn)指令中的實(shí)際值是相對(duì)當(dāng)前PC 值的一個(gè)偏移量,而不是一個(gè)絕對(duì)地址,它的值由匯編器來計(jì)算(參考尋址方式中的相對(duì)尋址)。它是 24 位有符號(hào)數(shù),左移兩位后有符號(hào)擴(kuò)展為 32 位,表示的有效偏移為 26 位(前后32MB 的地址空間),同樣的,BL、BX都是相對(duì)跳轉(zhuǎn)。
LDR偽指令是將第二操作直接賦值給第一操作數(shù),當(dāng)執(zhí)行l(wèi)dr pc,=Main時(shí)是將Main的絕對(duì)地址賦值給了PC。
好了,知道這兩個(gè)指令的區(qū)別之后我們來看代碼是如何實(shí)現(xiàn)的從SRAM到SDRAM的跳轉(zhuǎn),首先需要指出,2440的開發(fā)板有SRAM和SDRAM,SRAM是從地址0x00000000開始的4KB內(nèi)存空間,SDRAM是從0x30000000開始的64M空間。
無論用ADS編譯還是用arm-linux-gcc編譯都會(huì)將代碼鏈接到0x30000000以后(也就是SDRAM中),ADS用戶可以通過查看ADS的工程配置,其中有項(xiàng)配置是RO起始地址是0x30000000,linux用戶在鏈接時(shí)需要用-T指定代碼的其實(shí)地址為0x30000000。
根據(jù)編譯原理,在鏈接階段程序中函數(shù)的地址就已經(jīng)確定了,也就是說函數(shù)的實(shí)際地址都在0x30000000之后,而程序的入口函數(shù)也就是這里的_start的地址就是0x300000000,其他函數(shù)都會(huì)大于這個(gè)數(shù)。
但是由于arm上電后系統(tǒng)會(huì)將Nand flash的前4KB代碼拷貝到SRAM中,也就是_start函數(shù)開始的4KB指令將被拷貝到SRAM中執(zhí)行,根據(jù)上例,在0x00000000處執(zhí)行的指令就是“b reset”,由于b是相對(duì)跳轉(zhuǎn),是在當(dāng)前pc值的基礎(chǔ)上加減某個(gè)數(shù)而跳轉(zhuǎn)到將要執(zhí)行的代碼處,因此,pc加減該數(shù)之后將到達(dá)reset函數(shù)的位置,故reset函數(shù)不能寫到4KB之外的空間中,否則arm的啟動(dòng)將會(huì)失敗,同樣的,接下來的幾個(gè)bl都是執(zhí)行的相對(duì)跳轉(zhuǎn),所以都相對(duì)當(dāng)前pc進(jìn)行的跳轉(zhuǎn),由于Nand flash總共只有64M的空間,所以相對(duì)跳轉(zhuǎn)是不可能會(huì)跳轉(zhuǎn)到SDRAM的,因?yàn)樘D(zhuǎn)到SDRAM至少要發(fā)生0x30000000的跳轉(zhuǎn),而這個(gè)相對(duì)位移遠(yuǎn)遠(yuǎn)大于64M。
而ldr pc,=Main是將Main函數(shù)的實(shí)際地址賦值給pc,而Main的實(shí)際地址是在0x30000000之后,這樣,就從SRAM跳轉(zhuǎn)到了SDRAM。
由于這個(gè)過程設(shè)計(jì)到了硬件格局和編譯原理,所以對(duì)一般人來講,理解起來確實(shí)比較困難,而且受本人水平限制,很多地方只能說是只可意會(huì)不可言傳,如果誤導(dǎo)了大家請(qǐng)大家諒解。當(dāng)然如果看到這里還不能理解arm的啟動(dòng)過程可以留言討論這個(gè)問題。下面是相關(guān)的其他代碼,我附在這里,2440addr.h沒有貼出,由于我也是使用arm自帶示例程序中的代碼,而且代碼有四千多行,多數(shù)地址是沒有用到的。其他的代碼如下
代碼Init.s- #include"2440addr.h"
- voiddisable_watch_dog(void);
- voidclock_init(void);
- voidmemsetup(void);
- voidcopy_steppingstone_to_sdram(void);
- voidinituart(void);
- voiddisable_watch_dog(void)
- {
- rWTCON=0;
- }
- voidclock_init(void)
- {
- rCLKDIVN=0x03;
- /*
- *如果HDIVN非0,CPU的總線模式應(yīng)該從
- *“fastbusmode”變?yōu)?ldquo;asynchronous
- *busmode”
- */
- __asm__(
- "mrcp15,0,r1,c1,c0,0"
- "orrr1,r1,#0xc0000000"
- "mcrp15,0,r1,c1,c0,0"
- );
- rMPLLCON=(92<<12)|(1<<4)|(2);
- //rMPLLCON=((0x5c<<12)|(0x01<<4)|(0x02));
- }
- voidmemsetup(void)
- {
- volatileunsignedlong*p=(volatileunsignedlong*)0x48000000;
- /*這個(gè)函數(shù)之所以這樣賦值,而不是像前面的實(shí)驗(yàn)(比如mmu實(shí)驗(yàn))那樣將配置值
- *寫在數(shù)組中,是因?yàn)橐?rdquo;位置無關(guān)的代碼”,使得這個(gè)函數(shù)可以在被復(fù)制到
- *SDRAM之前就可以在steppingstone中運(yùn)行
- */
- /*存儲(chǔ)控制器13個(gè)寄存器的值*/
- p[0]=0x22011110;//BWSCON
- p[1]=0x00000700;//BANKCON0
- p[2]=0x00000700;//BANKCON1
- p[3]=0x00000700;//BANKCON2
- p[4]=0x00000700;//BANKCON3
- p[5]=0x00000700;//BANKCON4
- p[6]=0x00000700;//BANKCON5
- p[7]=0x00018005;//BANKCON6
- p[8]=0x00018005;//BANKCON7
- /*REFRESH,
- *HCLK=12MHz:0x008C07A3,
- *HCLK=100MHz:0x008C04F4
- */
- p[9]=0x008C04F4;
- p[10]=0x000000B1;//BANKSIZE
- p[11]=0x00000030;//MRSRB6
- p[12]=0x00000030;//MRSRB7
- }
- voidcopy_steppingstone_to_sdram(void)
- {
- unsignedint*pdwSrc=(unsignedint*)0;
- unsignedint*pdwDest=(unsignedint*)0x30000000;
- while(pdwSrc<(unsignedint*)4096)
- {
- *pdwDest=*pdwSrc;
- pdwDest++;
- pdwSrc++;
- }
- }
注意:由于我們的代碼比較小,遠(yuǎn)小于4KB,因此arm啟動(dòng)時(shí)自動(dòng)拷貝到SRAM中的4KB代碼包含我們的全部代碼,因此copy操作我是將stepingstone中的4KB代碼拷貝到了SDRAM中,在實(shí)際應(yīng)用中代碼多數(shù)是超過4KB,因此copy函數(shù)應(yīng)該是將Nand flash中的全部代碼拷貝到SDRAM,這樣才能成功運(yùn)行ARM。代碼Main.c:
- #include"2440addr.h"
- voidDelay(inti)
- {
- intm,n,p;
- for(m=0;m!=i;++m)
- {
- for(n=0;n!=255;++n)
- {
- for(p=0;p!=255;++p)
- ;
- }
- }
- }
- voidMain()
- {
- intcount;
- intleds[4]={0x1c0,0x1a0,0x160,0xe0};
- rGPBCON=0x00155555;
- rGPBUP=rGPBUP&0xFF00;
- while(1)
- {
- for(count=0;count!=4;++count)
- {
- rGPBDAT=leds[count];
- if(count%2)
- rGPBDAT+=1;
- Delay(2);
- }
- }
- }
鏈接文件led.lds如下:- SECTIONS
- {
- .=0x30000000;
- .text:{*(.text)}
- .rodataALIGN(4):{*(.rodata)}
- .dataALIGN(4):{*(.data)}
- .bssALIGN(4):{*(.bss)*(COMMON)}
- }
makefile如下:- objects:=Head.oInit.oMain.o
- led.bin:$(objects)
- arm-linux-ld-Tled.lds-nostdlib-oled_elf$^
- arm-linux-objcopy-Obinary-Sled_elf$@
- arm-linux-objdump-D-marmled_elf>led.dis
- %.o:%.c
- arm-linux-gcc-Wall-O2-c-o$@$<
- %.o:%.s
- arm-linux-gcc-Wall-O2-c-o$@$<;
- .PYTHON:clean
- clean:
- rm-fled.binled_elfled.dis*.o
如上除了2440addr.h之外就都全了,另外需要指出的是2440addr.h中引用了Option.h,為了簡(jiǎn)化代碼,可以將這句可以注釋掉,在我們這段代碼中完全用不到該文件相關(guān)功能。否則需要自行修改makefile文件完成Option.h相關(guān)的編譯和鏈接工作。好了,又浪費(fèi)了大家這么長(zhǎng)的時(shí)間,這里就不多說了,有什么問題求高手指出來。
大家都知道,arm在Nand flash啟動(dòng)模式下啟動(dòng)時(shí)系統(tǒng)會(huì)將Nand flash中的前4KB代碼拷貝到SRAM(也就是Steppingstone中),由SRAM配置中斷向量表和完成Nand flash訪問的必要初始化,然后將Nand flash中的全部程序代碼拷貝到SDRAM中,最后由SRAM跳轉(zhuǎn)到SDRAM,然后程序就正常執(zhí)行了,這一過程看上去很簡(jiǎn)單,但是真正理解這一過程還是不簡(jiǎn)單的,盡管這樣,還是想告訴大家仔細(xì)理解還是比較容易理解這個(gè)過程的。如果您是ADS用戶,你省去了很多麻煩,但我不確定你省去的這些麻煩是否值得,這里介紹的是一種麻煩的方式,linux下的led程序。
本文引用地址:http://m.butianyuan.cn/article/201611/322828.htm代碼Head.s
- .externmain
- .text
- .global_start
- _start:
- breset
- reset:
- ldrsp,=4096
- bldisable_watch_dog
- blclock_init
- blmemsetup
- blcopy_steppingstone_to_sdram
- ldrpc,=on_sdram
- on_sdram:
- msrcpsr_c,#0xdf
- ldrsp,=0x34000000
- ldrlr,=halt_loop
- ldrpc,=Main
- halt_loop:
- bhalt_loop
我認(rèn)為,最需要理解的就是這段代碼了。先簡(jiǎn)單的解釋下這段代碼。
(1)由于arm執(zhí)行reset之后pc會(huì)被清零,也就是reset中斷的中斷入口地址,因此,第一條指令就是b reset,跳轉(zhuǎn)到reset中斷處理函數(shù)。
(2)由于這里硬件配置都是C語言來完成的,而且我們的初始代碼比較小,完全不會(huì)超出4KB,因此可以在SRAM使用堆棧,故將SP設(shè)置為4096,以提供C運(yùn)行環(huán)境
(3)接下來的3個(gè)bl分別完成了關(guān)閉看門夠定時(shí)器,配置時(shí)鐘信號(hào)和存儲(chǔ)器配置的工作,第四個(gè)bl是將SRAM的4KB空間內(nèi)的代碼拷貝到了SDRAM中。
(4)接下來的ldr句將pc賦值為on_sdram的地址,實(shí)現(xiàn)了從SRAM到SDRAM的跳轉(zhuǎn)(下面會(huì)講為什么)
(5)on_sdram中切換到了了系統(tǒng)模式然后分配了系統(tǒng)模式堆棧,將鏈接寄存器設(shè)置為halt_loop然后就跳轉(zhuǎn)到了SDRAM中的Main
上面的解釋只是大體上說明了代碼的意思,但是初學(xué)者總會(huì)有個(gè)疑問就是為什么ldr pc,=on_sdram就實(shí)現(xiàn)了從SRAM到SDRAM的跳轉(zhuǎn)呢?我被這個(gè)問題困擾了很長(zhǎng)時(shí)間,到今天才想明白了這個(gè)問題,問題的關(guān)鍵就是相對(duì)跳轉(zhuǎn)和絕對(duì)跳轉(zhuǎn)的問題。為了說明這個(gè)問題我先解釋一下bl指令跟ldr指令在執(zhí)行過程中的區(qū)別。
B指令是相對(duì)跳轉(zhuǎn)指令,B 指令是最簡(jiǎn)單的跳轉(zhuǎn)指令。一旦遇到一個(gè) B 指令,ARM 處理器將立即跳轉(zhuǎn)到給定的目標(biāo)地址,從那里繼續(xù)執(zhí)行。注意存儲(chǔ)在跳轉(zhuǎn)指令中的實(shí)際值是相對(duì)當(dāng)前PC 值的一個(gè)偏移量,而不是一個(gè)絕對(duì)地址,它的值由匯編器來計(jì)算(參考尋址方式中的相對(duì)尋址)。它是 24 位有符號(hào)數(shù),左移兩位后有符號(hào)擴(kuò)展為 32 位,表示的有效偏移為 26 位(前后32MB 的地址空間),同樣的,BL、BX都是相對(duì)跳轉(zhuǎn)。
LDR偽指令是將第二操作直接賦值給第一操作數(shù),當(dāng)執(zhí)行l(wèi)dr pc,=Main時(shí)是將Main的絕對(duì)地址賦值給了PC。
好了,知道這兩個(gè)指令的區(qū)別之后我們來看代碼是如何實(shí)現(xiàn)的從SRAM到SDRAM的跳轉(zhuǎn),首先需要指出,2440的開發(fā)板有SRAM和SDRAM,SRAM是從地址0x00000000開始的4KB內(nèi)存空間,SDRAM是從0x30000000開始的64M空間。
無論用ADS編譯還是用arm-linux-gcc編譯都會(huì)將代碼鏈接到0x30000000以后(也就是SDRAM中),ADS用戶可以通過查看ADS的工程配置,其中有項(xiàng)配置是RO起始地址是0x30000000,linux用戶在鏈接時(shí)需要用-T指定代碼的其實(shí)地址為0x30000000。
根據(jù)編譯原理,在鏈接階段程序中函數(shù)的地址就已經(jīng)確定了,也就是說函數(shù)的實(shí)際地址都在0x30000000之后,而程序的入口函數(shù)也就是這里的_start的地址就是0x300000000,其他函數(shù)都會(huì)大于這個(gè)數(shù)。
但是由于arm上電后系統(tǒng)會(huì)將Nand flash的前4KB代碼拷貝到SRAM中,也就是_start函數(shù)開始的4KB指令將被拷貝到SRAM中執(zhí)行,根據(jù)上例,在0x00000000處執(zhí)行的指令就是“b reset”,由于b是相對(duì)跳轉(zhuǎn),是在當(dāng)前pc值的基礎(chǔ)上加減某個(gè)數(shù)而跳轉(zhuǎn)到將要執(zhí)行的代碼處,因此,pc加減該數(shù)之后將到達(dá)reset函數(shù)的位置,故reset函數(shù)不能寫到4KB之外的空間中,否則arm的啟動(dòng)將會(huì)失敗,同樣的,接下來的幾個(gè)bl都是執(zhí)行的相對(duì)跳轉(zhuǎn),所以都相對(duì)當(dāng)前pc進(jìn)行的跳轉(zhuǎn),由于Nand flash總共只有64M的空間,所以相對(duì)跳轉(zhuǎn)是不可能會(huì)跳轉(zhuǎn)到SDRAM的,因?yàn)樘D(zhuǎn)到SDRAM至少要發(fā)生0x30000000的跳轉(zhuǎn),而這個(gè)相對(duì)位移遠(yuǎn)遠(yuǎn)大于64M。
而ldr pc,=Main是將Main函數(shù)的實(shí)際地址賦值給pc,而Main的實(shí)際地址是在0x30000000之后,這樣,就從SRAM跳轉(zhuǎn)到了SDRAM。
由于這個(gè)過程設(shè)計(jì)到了硬件格局和編譯原理,所以對(duì)一般人來講,理解起來確實(shí)比較困難,而且受本人水平限制,很多地方只能說是只可意會(huì)不可言傳,如果誤導(dǎo)了大家請(qǐng)大家諒解。當(dāng)然如果看到這里還不能理解arm的啟動(dòng)過程可以留言討論這個(gè)問題。下面是相關(guān)的其他代碼,我附在這里,2440addr.h沒有貼出,由于我也是使用arm自帶示例程序中的代碼,而且代碼有四千多行,多數(shù)地址是沒有用到的。其他的代碼如下
代碼Init.s- #include"2440addr.h"
- voiddisable_watch_dog(void);
- voidclock_init(void);
- voidmemsetup(void);
- voidcopy_steppingstone_to_sdram(void);
- voidinituart(void);
- voiddisable_watch_dog(void)
- {
- rWTCON=0;
- }
- voidclock_init(void)
- {
- rCLKDIVN=0x03;
- /*
- *如果HDIVN非0,CPU的總線模式應(yīng)該從
- *“fastbusmode”變?yōu)?ldquo;asynchronous
- *busmode”
- */
- __asm__(
- "mrcp15,0,r1,c1,c0,0"
- "orrr1,r1,#0xc0000000"
- "mcrp15,0,r1,c1,c0,0"
- );
- rMPLLCON=(92<<12)|(1<<4)|(2);
- //rMPLLCON=((0x5c<<12)|(0x01<<4)|(0x02));
- }
- voidmemsetup(void)
- {
- volatileunsignedlong*p=(volatileunsignedlong*)0x48000000;
- /*這個(gè)函數(shù)之所以這樣賦值,而不是像前面的實(shí)驗(yàn)(比如mmu實(shí)驗(yàn))那樣將配置值
- *寫在數(shù)組中,是因?yàn)橐?rdquo;位置無關(guān)的代碼”,使得這個(gè)函數(shù)可以在被復(fù)制到
- *SDRAM之前就可以在steppingstone中運(yùn)行
- */
- /*存儲(chǔ)控制器13個(gè)寄存器的值*/
- p[0]=0x22011110;//BWSCON
- p[1]=0x00000700;//BANKCON0
- p[2]=0x00000700;//BANKCON1
- p[3]=0x00000700;//BANKCON2
- p[4]=0x00000700;//BANKCON3
- p[5]=0x00000700;//BANKCON4
- p[6]=0x00000700;//BANKCON5
- p[7]=0x00018005;//BANKCON6
- p[8]=0x00018005;//BANKCON7
- /*REFRESH,
- *HCLK=12MHz:0x008C07A3,
- *HCLK=100MHz:0x008C04F4
- */
- p[9]=0x008C04F4;
- p[10]=0x000000B1;//BANKSIZE
- p[11]=0x00000030;//MRSRB6
- p[12]=0x00000030;//MRSRB7
- }
- voidcopy_steppingstone_to_sdram(void)
- {
- unsignedint*pdwSrc=(unsignedint*)0;
- unsignedint*pdwDest=(unsignedint*)0x30000000;
- while(pdwSrc<(unsignedint*)4096)
- {
- *pdwDest=*pdwSrc;
- pdwDest++;
- pdwSrc++;
- }
- }
代碼Main.c:
- #include"2440addr.h"
- voidDelay(inti)
- {
- intm,n,p;
- for(m=0;m!=i;++m)
- {
- for(n=0;n!=255;++n)
- {
- for(p=0;p!=255;++p)
- ;
- }
- }
- }
- voidMain()
- {
- intcount;
- intleds[4]={0x1c0,0x1a0,0x160,0xe0};
- rGPBCON=0x00155555;
- rGPBUP=rGPBUP&0xFF00;
- while(1)
- {
- for(count=0;count!=4;++count)
- {
- rGPBDAT=leds[count];
- if(count%2)
- rGPBDAT+=1;
- Delay(2);
- }
- }
- }
鏈接文件led.lds如下:
- SECTIONS
- {
- .=0x30000000;
- .text:{*(.text)}
- .rodataALIGN(4):{*(.rodata)}
- .dataALIGN(4):{*(.data)}
- .bssALIGN(4):{*(.bss)*(COMMON)}
- }
makefile如下:
- objects:=Head.oInit.oMain.o
- led.bin:$(objects)
- arm-linux-ld-Tled.lds-nostdlib-oled_elf$^
- arm-linux-objcopy-Obinary-Sled_elf$@
- arm-linux-objdump-D-marmled_elf>led.dis
- %.o:%.c
- arm-linux-gcc-Wall-O2-c-o$@$<
- %.o:%.s
- arm-linux-gcc-Wall-O2-c-o$@$<;
- .PYTHON:clean
- clean:
- rm-fled.binled_elfled.dis*.o
如上除了2440addr.h之外就都全了,另外需要指出的是2440addr.h中引用了Option.h,為了簡(jiǎn)化代碼,可以將這句可以注釋掉,在我們這段代碼中完全用不到該文件相關(guān)功能。否則需要自行修改makefile文件完成Option.h相關(guān)的編譯和鏈接工作。
好了,又浪費(fèi)了大家這么長(zhǎng)的時(shí)間,這里就不多說了,有什么問題求高手指出來。
評(píng)論