溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何在STM32上移植Linux

發(fā)布時間:2021-11-01 17:00:27 來源:億速云 閱讀:201 作者:柒染 欄目:系統(tǒng)運維

本篇文章給大家分享的是有關(guān)如何在STM32上移植Linux,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

剛從硬件跳槽為嵌軟時,沒有任何一絲的準備。一入職,領(lǐng)導就交代了一項特難的任務——在stm32上移植linux!

瞬間我就懵了,沒辦法硬著頭皮上吧,先搜集資料,我之前跑的是ok6410的板子上運行的linux,現(xiàn)在是在stm32上移植,以前stm32倒是玩過,研究生期間就搗鼓過它,但現(xiàn)在還沒從抓烙鐵的硬件當中緩過神來,就轉(zhuǎn)到嵌入式軟件的開發(fā),更頭疼的是stm32沒有MMU!沒有MMU!找了一下,好吧,有個uClinux!

于是開始學習各種相關(guān)的知識,了解到linux的啟動一般是u-boot liunx內(nèi)核根文件系統(tǒng),那么首先要做個基于stm32的u-boot,先初始化時鐘、外設、中斷什么的,看了韋東山老師的視頻感覺很好,理解了不少,從一無所知到有點明白了。

移植u-boot到stm32f407

其實說白了u-boot就是一裸板程序,就是跟跑跑馬燈、串口通信一個性質(zhì)的,而裸板程序從正點原子的stm32開發(fā)板學習了不少,加上自己研究生階段有點積累,首先我是參照http://www.cnblogs.com/fozu/p/3618076.html寫這位博文的大神寫的程序,這篇文章寫得很好,后面還分析到內(nèi)核了,反復看受益匪淺,這個程序不是u-boot程序但是實現(xiàn)的作用一樣,初始化時鐘,外設。。。***傳遞內(nèi)核參數(shù),跳轉(zhuǎn)內(nèi)核。。。,一開始用keil編譯這個程序,結(jié)果一堆錯誤,人家用的板子和你用的板子不一樣,硬件的led燈、串口都可能連接不一樣啊,比如人家用的是串口1,你用的是串口2,還有缺少一些頭文件等等都會引發(fā)錯誤,所以根據(jù)自己的實際來修改,費了一陣功夫終于把錯誤全干掉,順利編譯成功。

這時用的板子是stm32f103,ST對這個板子早在08年就發(fā)布了支持它的u-boot、Uclinux內(nèi)核(領(lǐng)導額外買的,說是要我對照著對應修改支持stm32f407的uClinux內(nèi)核),但是只有Uclinux內(nèi)核有源碼,u-boot就給了個hex文件尷尬,其實cortx  m3與cortx  m4之前架構(gòu)已經(jīng)大不一樣了,這樣修改的話對于我來說無疑是很難的,我一聽頭都大了,又是單干,煩,沒辦法照做唄!那就先弄stm32f103的,把之前那個編譯沒有錯誤的引導程序拷入,在stm32的0x08003000的位置拷入官方提供的uClinux內(nèi)核,一啟動,接上串口,打開串口助手,一看啥都沒有。。。

到底錯在哪啦?仔細想想,先是要看看***跳轉(zhuǎn)內(nèi)核那步到底有沒有成功,那就先驗證這一步,參照原子的IAR跳轉(zhuǎn)歷程,編了個跑馬燈跳轉(zhuǎn)程序,就是引導程序沒變,拷在地址0x08000000,而跑馬燈程序拷在0x08003000上,如果led燈亮滅就說明跳轉(zhuǎn)無誤,于是一啟動,燈不亮。抓狂抓狂怎么情況啊,后仔細排查發(fā)現(xiàn)是跳轉(zhuǎn)函數(shù),引導程序參照的是u-boot源碼來編寫的,里面的函數(shù)用函數(shù)指針賦個地址(0x08003000),***跳轉(zhuǎn)過去。折騰了兩天***對著原子的程序修改,燈居然可以亮滅了,我現(xiàn)在想想也不知道是什么問題,不過至少現(xiàn)在可以實現(xiàn)跳轉(zhuǎn)了。

如何在STM32上移植Linux

再把內(nèi)核拷到0x08003000,一啟動,串口助手還是沒有任何輸出,這下就真的煩了,郁悶死了,stm32f103還搞不定還想搞stm32f407。。。之后開始各種找原因,各種修改,領(lǐng)導各種催,在stm32f103和stm32f407兩個板子之間這搞搞,那搞搞,休息時間就看看韋老師的視頻,找資料看看有什么靈感,但是還是沒什么進展。

后來在網(wǎng)上搜到一個哥們居然在stm32f407上移植u-boot成功了,而且還有啟動圖曬出來,這下我就想,人家可以我為什么就不能?于是繼續(xù)找,終于在網(wǎng)上找到了這個u-boot的源碼,根據(jù)自己的stm32f407的板子修改串口,時鐘等,安裝好對應的交叉編譯鏈,注意應該是arm-non-eabi不帶linux的,因為是裸板程序不關(guān)linux啥事,然后一跑,終于在串口助手看到久違的u-boot啟動圖,狂喜!想想那段日子確實是在壓力之下成長的,感覺技術(shù)上有了很大的提升了。

領(lǐng)導過來一看見有u-boot(有點東西交差了。。。)就說要把外部的SRAM驅(qū)動加上,以便于跑linux內(nèi)核,這個sram只有512K,這么小能跑得了linux內(nèi)核嗎?這是后話,先把sram驅(qū)動加到u-boot上再說。

先參照原子的sram程序修改運行試試看看,結(jié)果可以運行但是寫入再讀出,有幾個地址的數(shù)據(jù)總有錯誤,于是一直苦思冥想,想到了一個可能,驅(qū)動外部SRAM用到的是stm32的FSMC配置,它有btcr寄存器設置,分為bcr和btr設置,原子的開發(fā)板用的是1M16位的,而我的是512k8位,在btr寄存器設置那里應該是設成8位而不是16位,于是把相關(guān)設置位置0,這下數(shù)據(jù)正常了。

接下來就是在u-boot上添加sram的驅(qū)動,這個u-boot編寫得還蠻好,不過它配置的是外部8M的SDRAM,那么我就在sdram_init()的函數(shù)上添加配置sram的代碼,把原來配置sdram的代碼通通刪去。折騰了兩天,編寫修改成功,一開機,串口助手正常輸出啟動信息,用u-boot的md、mw指令驗證sram的驅(qū)動是否可行,之間也遇到一些問題,如在前100個地址寫ff,md查看有幾個地址數(shù)據(jù)不對,不是顯示ff,用之前的sram裸板程序也是如此,一想軟件程序肯定是沒問題,那就是硬件問題,幸虧還搞過一段時間硬件,不然被公司硬件工程師給坑了,用萬用表仔細檢測,果然發(fā)現(xiàn)sram有幾個數(shù)據(jù)線虛焊了,怪不得數(shù)據(jù)有誤,拿烙鐵一拖,OK!數(shù)據(jù)正常了,嗯!想成為合格的嵌入式軟件工程師還是要軟件硬件相結(jié)合,不能脫離了硬件啊!!!!

好!至此基于stm32f407的u-boot移植成功,外加外部2M的SRAM驅(qū)動(后來把512K升級為2M,因為后來內(nèi)核內(nèi)存不夠跑到一半kernel  panic掛了,此乃后話),***上一張u-boot啟動圖。人生***篇在CSDN的博文,希望以后自己不斷學習技術(shù)不斷提升,努力!

如何在STM32上移植Linux

移植uClinux內(nèi)核到stm32f407

上面介紹了先移植基于stm32f407的u-boot,下面會講到其中最難的移植stm32f407的內(nèi)核這部分,這個內(nèi)核源代碼我也是在網(wǎng)上找到了,看介紹是國外大神修改而成的,真的萬分感謝這位大神,網(wǎng)上的資源其實很多,要善于挖掘,善于搜尋。

內(nèi)核代碼是我無意中down下來的,剛得到代碼時并沒有對在stm32f407上跑uClinux有太多的信心,一是網(wǎng)上還沒有在stm32f407跑uClinux的資料(至少我沒找到過)網(wǎng)上都對在stm32上跑uClinux都是唱衰的態(tài)度,的確stm32跑起uClinux系統(tǒng),資源是有些匱乏,而stm32f407內(nèi)部flash只有1M的空間,其中u-boot占了128K,那么內(nèi)核就存儲在0x08020000處,剩下900k的空間使用,還有我的板子還有外部2M  的SRAM,但更要命的是得到的代碼是基于stm32f429的uClinux,很多人都在stm32f429上成功運行了,但是卻從沒在stm32f407成功過,但我已經(jīng)沒有退路了,項目需要、領(lǐng)導要求,只能硬著頭皮瞎改,其實對于stm32f103改成stm32f429已經(jīng)好很多了,最起碼stm32f429的架構(gòu)和stm32f407的架構(gòu)大致相同(內(nèi)部存儲和時鐘和gpio等略有不同),于是就按照自己手上的板子來改,期間遇到了不少的問題,也想過放棄,不過好歹堅持了下來,因為著急壓力山大所以看了不少書,查了很多資料也學到了很多東西對u-boot和內(nèi)核代碼有了深入的理解,

特別感謝的是jserv老師,我走投無路之下給他發(fā)了幾封郵件,他回答了我兩個極為重要的問題,建議把外部的512K換成至少2M的SRAM,不然內(nèi)核就真的跑不動了,跑到一半就kernel  panic….

然后就是針對stm32f407來修改內(nèi)核代碼,stm32f429用的是串口3,我用的是串口1,改!時鐘不對,改!儲存地址不同,改!stm32f429不單是有外部的SRAM,空間8M還有NOR  flash,財大氣粗,資源隨便用,不像我的stm32f407只有外部2M的SRAM(領(lǐng)導說硬件就那樣,節(jié)約成本,無語。。),幸好uClinux代碼是用XIP的方式來運行的,就是代碼段放在內(nèi)部flash中就地執(zhí)行,數(shù)據(jù)段和bss段其它段就放在sram上運行,這樣算算,空間還是足夠的。

其間還出現(xiàn)這樣的問題:

如何在STM32上移植Linux

卡了我一個星期,當時我就百思不得其解,在創(chuàng)建高速緩存那里就出現(xiàn)內(nèi)核錯誤運行不下去了,仔細比對了stm32f103的uClinux源代碼,也沒發(fā)現(xiàn)什么錯誤,一個多星期沒有進展,內(nèi)核恐慌我也恐慌了,幸好領(lǐng)導知道情況后也不催促我,而是買了一本《ARM  Linux內(nèi)核源代碼分析》給我,叫我好好研讀,解決問題,于是就看里面構(gòu)建kmem  cache那一篇,linux內(nèi)核源碼過于復雜,看得我頭都大了,后來想想這不是辦法啊,是不是又是硬件問題?因為原先用的是512k的sram升級到2M,公司的硬件工程師又重新改版了,于是我又用電烙鐵把stm32芯片,sram芯片,和他們之間的上拉電阻,又重新焊了一遍,一上電就正常運行到下一步了,唉~之前移植u-boot的sram驅(qū)動也是硬件坑我的啊,真不敢相信我不懂點硬件的話會坑到我什么時候。。。

接著瞎搗鼓著,前后花了將近兩個月,就成這樣了:

如何在STM32上移植Linux

- 想想還真是運氣好。。。

接下來遇到的問題,應該是少了根文件系統(tǒng),這個uClinux代碼原來是配有根文件系統(tǒng)的,是romfs,但是存儲空間不夠了。

uClinux的根文件系統(tǒng)未能掛載起來,因為系統(tǒng)原來配置的根文件系統(tǒng)是romfs,是基于stm32f429的,stm32f429的內(nèi)部flash存儲空間有2M,romfs占用空間為300多kB,這樣存放顯然是充足的,但是對于stm32f407來說,它的內(nèi)部flash存儲空間為1M,這樣存放的話,存儲空間是不夠的(u-boot占用空間0x08000000-0x08020000,內(nèi)核占用空間約為0x08020000-0x080BB000,約620多KB,那么只有剩下約250多KB的空間供根文件系統(tǒng)存放),所以根據(jù)這個情況,我想是另外搭建占用內(nèi)存空間更小的initramfs作為uClinux的根文件系統(tǒng)來掛載。

構(gòu)建stm32f407-uClinux的initramfs根文件系統(tǒng)

上文講到內(nèi)核運行到free init  memory:8k這個地方就卡住,運行不下去了,在查閱了相關(guān)資料后,推測是缺少根文件系統(tǒng)所導致的,原來的內(nèi)核源代碼是搭配有根文件系統(tǒng)的bin文件,是romfs但沒有源碼,前面講過我現(xiàn)在項目使用的是stm32f407,內(nèi)部flash容量和外部SRAM都不足以拷入這個原配的romfs掛起為根文件系統(tǒng)來使用。

接下來就是尋找一種經(jīng)濟適用的文件系統(tǒng)來作為內(nèi)核的根文件系統(tǒng),從網(wǎng)上查閱相關(guān)資料可以知道,YAFFS2支持的是nandflash,jffs支持nor  flash,這些看來對于我手上的stm32f407來說是不適用的,于是我仔細研究了stm32f103的源代碼,發(fā)現(xiàn)它是有兩種啟動的方式,一種采用的是用iniramfs作為根文件系統(tǒng),xip啟動,在stm32f103內(nèi)部flash只有512k的情況居然跑起了Uclinux,另一種是jfss2掛在外部nor  flash上,顯然我這種情況目前只有參考***種方式來,采用initramfs作為根文件系統(tǒng)。于是開始構(gòu)建initramfs相關(guān)文件。 仔細研究stm32f103  XIP啟動方式的內(nèi)核配置make menuconfig中, CONFIG_INITRAMFS_SOURCE=”initramfs-filelist”  而initramfs-filelist位于Uclinux/linux-2.6.x下,打開一看是這樣的:

如何在STM32上移植Linux

一開始我看不懂這里面的shell什么意思,網(wǎng)上找到一篇文章,寫得很清楚,把它copy過來,學習一下:

把initramfs編譯到內(nèi)核里面去

使用initramfs最簡單的方式,莫過于用已經(jīng)做好的cpio.gz把kernel里面那個空的給換掉。這是2.6  kernel天生支持的,所以,你不用做什么特殊的設置。

kernel的config option里面有一項CONFIG_INITRAMFS_SOURCE(I.E. General  setup—>Initramfs source file(s) in  menuconfig)。這個選項指向放著內(nèi)核打包initramfs需要的所有文件。默認情況下,這個選項是留空的,所以內(nèi)核編譯出來之后initramfs也就是空的,也就是前面提到的rootfs什么都不做的情形。

CONFIG_INITRAMFS_SOURCE 可以是一個絕對路徑,也可以是一個從kernel’s top build  dir(你敲入build或者是make的地方)開始的相對路徑。而指向的目標可以有以下三種:一個已經(jīng)做好的cpio.gz,或者一個已經(jīng)為制作cpio.gz準備好所有內(nèi)容的文件夾,或者是一個text的配置文件。第三種方式是最靈活的,我們先依次來介紹這三種方法。

1)使用一個已經(jīng)做好的cpio.gz檔案

If you already have your own initramfs_data.cpio.gz file (because you created  it yourself, or saved the cpio.gz file produced by a previous kernel build), you  can point CONFIG_INITRAMFS_SOURCE at it and the kernel build will autodetect the  file type and link it into the resulting kernel image.

You can also leave CONFIG_INITRAMFS_SOURCE empty, and instead copy your  cpio.gz file to usr/initramfs_data.cpio.gz in your kernel’s build directory. The  kernel’s makefile won’t generate a new archive if it doesn’t need to.

Either way, if you build a kernel like this you can boot it without supplying  an external initrd image, and it’ll still finish its boot by running your init  program out of rootfs. This is packaging method #2, if you’d like to try it  now.

2)指定給內(nèi)核一個文件或者文件夾

If CONFIG_INITRAMFS_SOURCE points to a directory, the kernel will archive it  up for you. This is a very easy way to create an initramfs archive, and is  method #3.

Interestingly, the kernel build doesn’t use the standard cpio command to  create initramfs archives. You don’t even need to have any cpio tools installed  on your build system. Instead the kernel build (in usr/Makefile) generates a  text file describing the directory with the script “gen_initramfs_list.sh”, and  then feeds that descript to a program called “gen_init_cpio” (built from C  source in the kernel’s usr directory), which create the cpio archive. This looks  something like the following:

scripts/gen_initramfs_list.sh $CONFIG_INITRAMFS_SOURCE >  usr/initramfs_list

usr/gen_init_cpio usr/initramfs_list > usr/initramfs_data.cpio

gzip usr/initramfs_data.cpio

To package up our hello world program, you could simply copy it into its own  directory, name it “init”, point CONFIG_INITRAMFS_SOURCE at that directory, and  rebuild the kernel. The resulting kernel should end its boot by printing “hello  world”. And if you need to tweak the contents of that directory, rebuilding the  kernel will re-package the contents of that directory if anything has  changed.

The downside of this method is that it if your initramfs has device nodes, or  cares about file ownership and permissions, you need to be able to create those  things in a directory for it to copy. This is hard to do if you haven’t got root  access, or are using a cross-compile environment like cygwin. That’s where the  fourth and final method comes in.

3)使用configuration文件initramfs_list來告訴內(nèi)核initramfs在哪里

This is the most flexible method. The kernel’s gen_initramfs_list.sh script  creates a text description file listing the contents of initramfs, and  gen_init_cpio uses this file to produce an archive. This file is a standard text  file, easily editable, containing one line per file. Each line starts with a  keyword indicating what type of entry it describes.

The config file to create our “hello world” initramfs only needs a single  line:

file /init usr/hello 500 0 0

This takes the file “hello” and packages it so it shows up as /init in  rootfs, with permissions 500, with uid and gid 0. It expects to find the source  file “hello” in a “usr” subdirectory under the kernel’s build directory. (If  you’re building the kernel in a different directory than the source directory,  this path would be relative to the build directory, not the source  directory.)

To try it yourself, copy “hello” into usr in the kernel’s build directory,  copy the above configuration line to its own file, use “make menuconfig” to  point CONFIG_INITRAMFS_SOURCE to that file, run the kernel build, and test boot  the new kernel. Alternately, you can put the “hello” file in its own directory  and use “scripts/gen_initramfs_list.sh dirname” to create a configuration file  (where dirname is the path to your directory, from the kernel’s build  directory). For large projects, you may want to generate a starting  configuration with the script, and then customize it with any text editor.

This configuration file can also specify device nodes (with the “nod”  keyword), directories (“dir”), symbolic links (“slink”), named FIFO pipes  (“pipe”), and unix domain sockets (“sock”). Full documentation on this file’s  format is available by running “usr/gen_init_cpio” (with no arguments) after a  kernel build.

A more complicated example containing device nodes and symlinks could look  like this:

dir /dev 755 0 0

nod /dev/console 644 0 0 c 5 1

nod /dev/loop0 644 0 0 b 7 0

dir /bin 755 1000 1000

slink /bin/sh busybox 777 0 0

file /bin/busybox initramfs/busybox 755 0 0

dir /proc 755 0 0

dir /sys 755 0 0

dir /mnt 755 0 0

file /init initramfs/init.sh 755 0 0

One significant advantage of the configuration file method is that any  regular user can create one, specifying ownership and permissions and the  creation of device nodes in initramfs, without any special permissions on the  build system. Creating a cpio archive using the cpio command line tool, or  pointing the kernel build at a directory, requires a directory that contains  everything initramfs will contain. The configuration file method merely requires  a few source files to get data from, and a description file.

This also comes in handy cross-compiling from other environments such as  cygwin, where the local filesystem may not even be capable of reproducing  everything initramfs should have in it.

總結(jié)一下

這四種給rootfs提供內(nèi)容的方式都有一個共同點:在kernel啟動時,一系列的文件被解壓到rootfs,如果kernel能在其中找到可執(zhí)行的文件“/init”,kernel就會運行它;這意味著,kernel不會再去理會“root=”是指向哪里的。

此外,一旦initramfs里面的init  進程運行起來,kernel就會認為啟動已經(jīng)完成。接下來,init將掌控整個宇宙!它擁有霹靂無敵的專門為它預留的Process ID  #1,整個系統(tǒng)接下來的所有都將由它來創(chuàng)造!還有,它的地位將是不可剝奪的,嗯哼,PID 1 退出的話,系統(tǒng)會panic的。

接下來我會介紹其他一些,在rootfs中,init程序可以做的事。

Footnote 1: The kernel doesn’t allow rootfs to be unmounted for the same  reason the same reason it won’t let the first process (PID 1, generally running  init) be killed. The fact the lists of mounts and processes are never empty  simplifies the kernel’s implementation.

Footnote 2: The cpio format is another way of combining files together, like  tar and zip. It’s an older and simpler storage format that dates back to the  original unix, and it’s the storage format used inside RPM packages. It’s not as  widely used as tar or zip because the command line syntax of the cpio command is  unnecessarily complicated (type “man 1 cpio” at a Linux or Cygwin command line  if you have a strong stomach). Luckily, we don’t need to use this command.

Footnote 3: The kernel will always panic if PID 1 exits; this is unrelated to  initramfs. All of the signals that might kill init are blocked, even “kill -9”  which will reliably kill any other process. But init can still call the exit()  syscall itself, and the kernel panics if this happens in PID 1. Avoiding it here  is mostly a cosmetic issue: we don’t want the panic scrolling our “Hello World!”  message off the top of the screen.

Footnote 4: Statically linking programs against glibc produces enormous,  bloated binaries. Yes, this is expected to be over 400k for a hello world  proram. You can try using the “strip” command on the resulting binary, but it  won’t help much. This sort of bloat is why uClibc exists.

Footnote 5: Older 2.6 kernels had a bug where they would append to duplicate  files rather than overwriting. Test your kernel version before depending on this  behavior.

Footnote 6:User Mode Linux or QEMU can be very helpful testing out initramfs,  but are beyond the scope of this article.

Footnote 7: Well, sort of. The default one is probably meant to be empty, but  due to a small bug (gen_initramfs_list.sh spits out an example file when run  with no arguments) the version in the 2.6.16 kernel actually contains a  “/dev/console” node and a “/root” directory, which aren’t used for anything. It  gzips down to about 135 bytes, and might as well actually be empty. On Intel you  can run “readelf -S vmlinux” and look for section “.init.ramfs” to see the  cpio.gz archive linked into a 2.6 kernel. Elf section names might vary a bit on  other platforms.

顯然stm32f103使用的是第三種方法,里面那些指令無非是設置文件權(quán)限、設置軟連接等等操作,期間還學了一點bash  shell,又有點收獲,明白了道理之后就好辦了,直接照貓畫虎,我采用的是第二種方法:

1、先創(chuàng)建rootfs這個文件夾,再在這個文件夾下面分別創(chuàng)建bin、dev、etc、proc、sys等目錄

2、編譯busybox,把生成的bin文件復制到rootfs/bin下

3、新建linuxrc文件,設置權(quán)限chmod 777,然后在u-boot傳給內(nèi)核參數(shù)中一定要加上init=/linuxrc

4、在dev目錄下,加設備節(jié)點,不然會沒有輸出信息哦!

· 1 mknod -m 666 console c 5 1

· 2 mknod -m 666 null c 1 3

5、在內(nèi)核make menuconfig上CONFIG_INITRAMFS_SOURCE=“你剛剛構(gòu)建的文件夾的絕對路徑”

編譯內(nèi)核,initramfs直接和內(nèi)核編譯在一起,不用另外分出一個bin文件拷,這比較方便,啟動,在串口調(diào)試助手中可看到相關(guān)顯示信息:

如何在STM32上移植Linux

說一下自己的感想,用initramfs根文件系統(tǒng)雖然方便實用,但是有弊端就是它只讀不可寫,這對開發(fā)很不利,領(lǐng)導說會加個spi  flash再在里面掛載個根文件系統(tǒng)(spi  flash能掛jfss2嗎?),那是以后的事了以后再說,目前這種情況以我的技術(shù)水平只能做到這個份上了,加了根文件之后,stm32內(nèi)部flash還有200多k的存儲空間,應該可以添加些驅(qū)動和應用程序,那下面我的任務是編寫簡單的驅(qū)動與應用程序,好吧,又得繼續(xù)學習,努力工作了。。

以上就是如何在STM32上移植Linux,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI