linux系统中 库分为静态库和,Linux系统中“动态库”和“静态库”那点事儿-【经典好文】...
今天我們主要來(lái)說(shuō)說(shuō)Linux系統(tǒng)下基于動(dòng)態(tài)庫(kù)(.so)和靜態(tài)(.a)的程序那些貓膩。在這之前,我們需要了解一下源代碼到可執(zhí)行程序之間到底發(fā)生了什么神奇而美妙的事情。
在linux操作系統(tǒng)中,普遍使用ELF格式作為可執(zhí)行程序或者程序生成過(guò)程中的中間格式。ELF(Executable and Linking Format,可執(zhí)行連接格式)是UNIX系統(tǒng)實(shí)驗(yàn)室(USL)作為應(yīng)用程序二進(jìn)制接口(Application BinaryInterface,ABI)而開(kāi)發(fā)和發(fā)布的。工具接口標(biāo)準(zhǔn)委員會(huì)(TIS)選擇了正在發(fā)展中的ELF標(biāo)準(zhǔn)作為工作在32位Intel體系上不同操作系統(tǒng)之間可移植的二進(jìn)制文件格式。本文不對(duì)ELF文件格式及其組成做太多解釋,以免沖淡本文的主題,大家只要知道這么個(gè)概念就行。以后再詳解Linux中的ELF格式。源代碼到可執(zhí)行程序的轉(zhuǎn)換時(shí)需要經(jīng)歷如下圖所示的過(guò)程:
l?編譯是指把用高級(jí)語(yǔ)言編寫(xiě)的程序轉(zhuǎn)換成相應(yīng)處理器的匯編語(yǔ)言程序的過(guò)程。從本質(zhì)上講,編譯是一個(gè)文本轉(zhuǎn)換的過(guò)程。對(duì)嵌入式系統(tǒng)而言,一般要把用C語(yǔ)言編寫(xiě)的程序轉(zhuǎn)換成處理器的匯編代碼。編譯過(guò)程包含了c語(yǔ)言的語(yǔ)法解析和匯編碼的生成兩個(gè)步驟。編譯一般是逐個(gè)文件進(jìn)行的,對(duì)于每一個(gè)C語(yǔ)言編寫(xiě)的文件,可能還需要進(jìn)行預(yù)處理。
l?匯編是從匯編語(yǔ)言程序生成目標(biāo)系統(tǒng)的二進(jìn)制代碼(機(jī)器代碼)的過(guò)程。機(jī)器代碼的生成和處理器有密切的聯(lián)系。相對(duì)于編譯過(guò)程的語(yǔ)法解析,匯編的過(guò)程相對(duì)簡(jiǎn)單。這是因?yàn)閷?duì)于一款特定的處理器,其匯編語(yǔ)言和二進(jìn)制的機(jī)器代碼是一一對(duì)應(yīng)的。匯編過(guò)程的輸入是匯編代碼,這個(gè)匯編代碼可能來(lái)源于編譯過(guò)程的輸出,也可以是直接用匯編語(yǔ)言書(shū)寫(xiě)的程序。
l?連接是指將匯編生成的多段機(jī)器代碼組合成一個(gè)可執(zhí)行程序。一般來(lái)說(shuō),通過(guò)編譯和匯編過(guò)程,每一個(gè)源文件將生成一個(gè)目標(biāo)文件。連接器的作用就是將這些目標(biāo)文件組合起來(lái),組合的過(guò)程包括了代碼段、數(shù)據(jù)段等部分的合并,以及添加相應(yīng)的文件頭。
GCC是Linux下主要的程序生成工具,它除了編譯器、匯編器、連接器外,還包括一些輔助工具。在下面的分析過(guò)程中我會(huì)教大家這些工具的基本使用方法,Linux的強(qiáng)大之處在于,對(duì)于不太懂的命令或函數(shù),有一個(gè)很強(qiáng)大的“男人”時(shí)刻stand by your side,有什么不會(huì)的就去命令行終端輸入:man [命令名或函數(shù)名],然后阿拉神燈就會(huì)顯靈了。
對(duì)于最后編譯出來(lái)的可執(zhí)行程序,當(dāng)我們執(zhí)行它的時(shí)候,操作系統(tǒng)又是如何反應(yīng)的呢?我們先從宏觀上來(lái)個(gè)總體把握,如圖2所示:
作為UNIX操作系統(tǒng)的一種,Linux的操作系統(tǒng)提供了一系列的接口,這些接口被稱(chēng)為系統(tǒng)調(diào)用(System Call)。在UNIX的理念中,系統(tǒng)調(diào)用"提供的是機(jī)制,而不是策略"。C語(yǔ)言的庫(kù)函數(shù)通過(guò)調(diào)用系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn),庫(kù)函數(shù)對(duì)上層提供了C語(yǔ)言庫(kù)文件的接口。在應(yīng)用程序?qū)?#xff0c;通過(guò)調(diào)用C語(yǔ)言庫(kù)函數(shù)和系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)功能。一般來(lái)說(shuō),應(yīng)用程序大多使用C語(yǔ)言庫(kù)函數(shù)實(shí)現(xiàn)其功能,較少使用系統(tǒng)調(diào)用。
那么最后的可執(zhí)行文件到底是什么樣子呢?前面已經(jīng)說(shuō)過(guò),這里我們不深入分析ELF文件的格式,只是給出它的一個(gè)結(jié)構(gòu)圖和一些簡(jiǎn)單的說(shuō)明,以方便大家理解。
ELF文件格式包括三種主要的類(lèi)型:可執(zhí)行文件、可重定向文件、共享庫(kù)。
1.可執(zhí)行文件(應(yīng)用程序)
可執(zhí)行文件包含了代碼和數(shù)據(jù),是可以直接運(yùn)行的程序。
2.可重定向文件(*.o)
可重定向文件又稱(chēng)為目標(biāo)文件,它包含了代碼和數(shù)據(jù)(這些數(shù)據(jù)是和其他重定位文件和共享的object文件一起連接時(shí)使用的)。
*.o文件參與程序的連接(創(chuàng)建一個(gè)程序)和程序的執(zhí)行(運(yùn)行一個(gè)程序),它提供了一個(gè)方便有效的方法來(lái)用并行的視角看待文件的內(nèi)容,這些*.o文件的活動(dòng)可以反映出不同的需要。
Linux下,我們可以用gcc -c編譯源文件時(shí)可將其編譯成*.o格式。
3.共享文件(*.so)
也稱(chēng)為動(dòng)態(tài)庫(kù)文件,它包含了代碼和數(shù)據(jù)(這些數(shù)據(jù)是在連接時(shí)候被連接器ld和運(yùn)行時(shí)動(dòng)態(tài)連接器使用的)。動(dòng)態(tài)連接器可能稱(chēng)為ld.so.1,libc.so.1或者?ld-linux.so.1。我的CentOS6.0系統(tǒng)中該文件為:/lib/ld-2.12.so
一個(gè)ELF文件從連接器(Linker)的角度看,是一些節(jié)的集合;從程序加載器(Loader)的角度看,它是一些段(Segments)的集合。ELF格式的程序和共享庫(kù)具有相同的結(jié)構(gòu),只是段的集合和節(jié)的集合上有些不同。
那么到底什么是庫(kù)呢?
庫(kù)從本質(zhì)上來(lái)說(shuō)是一種可執(zhí)行代碼的二進(jìn)制格式,可以被載入內(nèi)存中執(zhí)行。庫(kù)分靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)兩種。
靜態(tài)庫(kù):這類(lèi)庫(kù)的名字一般是libxxx.a,xxx為庫(kù)的名字。利用靜態(tài)函數(shù)庫(kù)編譯成的文件比較大,因?yàn)檎麄€(gè)函數(shù)庫(kù)的所有數(shù)據(jù)都會(huì)被整合進(jìn)目標(biāo)代碼中,他的優(yōu)點(diǎn)就顯而易見(jiàn)了,即編譯后的執(zhí)行程序不需要外部的函數(shù)庫(kù)支持,因?yàn)樗惺褂玫暮瘮?shù)都已經(jīng)被編譯進(jìn)去了。當(dāng)然這也會(huì)成為他的缺點(diǎn),因?yàn)槿绻o態(tài)函數(shù)庫(kù)改變了,那么你的程序必須重新編譯。
動(dòng)態(tài)庫(kù):這類(lèi)庫(kù)的名字一般是libxxx.M.N.so,同樣的xxx為庫(kù)的名字,M是庫(kù)的主版本號(hào),N是庫(kù)的副版本號(hào)。當(dāng)然也可以不要版本號(hào),但名字必須有。相對(duì)于靜態(tài)函數(shù)庫(kù),動(dòng)態(tài)函數(shù)庫(kù)在編譯的時(shí)候并沒(méi)有被編譯進(jìn)目標(biāo)代碼中,你的程序執(zhí)行到相關(guān)函數(shù)時(shí)才調(diào)用該函數(shù)庫(kù)里的相應(yīng)函數(shù),因此動(dòng)態(tài)函數(shù)庫(kù)所產(chǎn)生的可執(zhí)行文件比較小。由于函數(shù)庫(kù)沒(méi)有被整合進(jìn)你的程序,而是程序運(yùn)行時(shí)動(dòng)態(tài)的申請(qǐng)并調(diào)用,所以程序的運(yùn)行環(huán)境中必須提供相應(yīng)的庫(kù)。動(dòng)態(tài)函數(shù)庫(kù)的改變并不影響你的程序,所以動(dòng)態(tài)函數(shù)庫(kù)的升級(jí)比較方便。linux系統(tǒng)有幾個(gè)重要的目錄存放相應(yīng)的函數(shù)庫(kù),如/lib /usr/lib。
當(dāng)要使用靜態(tài)的程序庫(kù)時(shí),連接器會(huì)找出程序所需的函數(shù),然后將它們拷貝到執(zhí)行文件,由于這種拷貝是完整的,所以一旦連接成功,靜態(tài)程序庫(kù)也就不再需要了。然而,對(duì)動(dòng)態(tài)庫(kù)而言,就不是這樣。動(dòng)態(tài)庫(kù)會(huì)在執(zhí)行程序內(nèi)留下一個(gè)標(biāo)記指明當(dāng)程序執(zhí)行時(shí),首先必須載入這個(gè)庫(kù)。由于動(dòng)態(tài)庫(kù)節(jié)省空間,linux下進(jìn)行連接的缺省操作是首先連接動(dòng)態(tài)庫(kù),也就是說(shuō),如果同時(shí)存在靜態(tài)和動(dòng)態(tài)庫(kù),不特別指定的話,將與動(dòng)態(tài)庫(kù)相連接。
OK,有了這些知識(shí),接下來(lái)大家就可以弄明白我所做的事情是干什么了。都說(shuō)例子是最好老師,我們就從例子入手。
1、靜態(tài)鏈接庫(kù)
我們先制作自己的靜態(tài)鏈接庫(kù),然后再使用它。制作靜態(tài)鏈接庫(kù)的過(guò)程中要用到gcc和ar命令。
準(zhǔn)備兩個(gè)庫(kù)的源碼文件st1.c和st2.c,用它們來(lái)制作庫(kù)libmytest.a,如下:
靜態(tài)庫(kù)文件libmytest.a已經(jīng)生成,用file命令查看其屬性,發(fā)現(xiàn)它確實(shí)是歸檔壓縮文件。用ar -t libmytest.a可以查看一個(gè)靜態(tài)庫(kù)包含了那些obj文件:
接下來(lái)我們就寫(xiě)個(gè)測(cè)試程序來(lái)調(diào)用庫(kù)libmytest.a中所提供的兩個(gè)接口print1()和print2()。
看到?jīng)],靜態(tài)庫(kù)的編寫(xiě)和調(diào)用就這么簡(jiǎn)單,學(xué)會(huì)了吧。這里gcc的參數(shù)-L是告訴編譯器庫(kù)文件的路徑是當(dāng)前目錄,-l是告訴編譯器要使用的庫(kù)的名字叫mytest。
2、動(dòng)態(tài)庫(kù)
靜態(tài)庫(kù)*.a文件的存在主要是為了支持較老的a.out格式的可執(zhí)行文件而存在的。目前用的最多的要數(shù)動(dòng)態(tài)庫(kù)了。
動(dòng)態(tài)庫(kù)的后綴為*.so。在Linux發(fā)行版中大多數(shù)的動(dòng)態(tài)庫(kù)基本都位于/usr/lib和/lib目錄下。在開(kāi)發(fā)和使用我們自己動(dòng)態(tài)庫(kù)之前,請(qǐng)容許我先落里羅嗦的跟大家嘮叨嘮叨Linux下和動(dòng)態(tài)庫(kù)相關(guān)的事兒吧。
有時(shí)候當(dāng)我們的應(yīng)用程序無(wú)法運(yùn)行時(shí),它會(huì)提示我們說(shuō)它找不到什么樣的庫(kù),或者哪個(gè)庫(kù)的版本又不合它胃口了等等之類(lèi)的話。那么應(yīng)用程序它是怎么知道需要哪些庫(kù)的呢?我們前面已幾個(gè)學(xué)了個(gè)很棒的命令ldd,用就是用來(lái)查看一個(gè)文件到底依賴(lài)了那些so庫(kù)文件。
Linux系統(tǒng)中動(dòng)態(tài)鏈接庫(kù)的配置文件一般在/etc/ld.so.conf文件內(nèi),它里面存放的內(nèi)容是可以被Linux共享的動(dòng)態(tài)聯(lián)庫(kù)所在的目錄的名字。我的系統(tǒng)中,該文件的內(nèi)容如下:
然后/etc/ld.so.conf.d/目錄下存放了很多*.conf文件,如下:
其中每個(gè)conf文件代表了一種應(yīng)用的庫(kù)配置內(nèi)容,以MySQL為例:
如果您是和我一樣裝的CentOS6.0的系統(tǒng),那么細(xì)心的讀者可能會(huì)發(fā)現(xiàn),在/etc目錄下還存在一個(gè)名叫l(wèi)d.so.cache的文件。從名字來(lái)看,我們知道它肯定是動(dòng)態(tài)鏈接庫(kù)的什么緩存文件。
對(duì),您說(shuō)的一點(diǎn)沒(méi)錯(cuò)。為了使得動(dòng)態(tài)鏈接庫(kù)可以被系統(tǒng)使用,當(dāng)我們修改了/etc/ld.so.conf或/etc/ld.so.conf.d/目錄下的任何文件,或者往那些目錄下拷貝了新的動(dòng)態(tài)鏈接庫(kù)文件時(shí),都需要運(yùn)行一個(gè)很重要的命令:ldconfig,該命令位于/sbin目錄下,主要的用途就是負(fù)責(zé)搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf里所列的目錄下搜索可用的動(dòng)態(tài)鏈接庫(kù)文件,然后創(chuàng)建處動(dòng)態(tài)加載程序/lib/ld-linux.so.2所需要的連接和(默認(rèn))緩存文件/etc/ld.so.cache(此文件里保存著已經(jīng)排好序的動(dòng)態(tài)鏈接庫(kù)名字列表)。
也就是說(shuō):當(dāng)用戶(hù)在某個(gè)目錄下面創(chuàng)建或拷貝了一個(gè)動(dòng)態(tài)鏈接庫(kù),若想使其被系統(tǒng)共享,可以執(zhí)行一下"ldconfig目錄名"這個(gè)命令。此命令的功能在于讓ldconfig將指定目錄下的動(dòng)態(tài)鏈接庫(kù)被系統(tǒng)共享起來(lái),即:在緩存文件/etc/ld.so.cache中追加進(jìn)指定目錄下的共享庫(kù)。請(qǐng)注意:如果此目錄不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目錄里面,則再次單獨(dú)運(yùn)行l(wèi)dconfig時(shí),此目錄下的動(dòng)態(tài)鏈接庫(kù)可能不被系統(tǒng)共享了。單獨(dú)運(yùn)行l(wèi)dconfig時(shí),它只會(huì)搜索/lib、/usr/lib以及在/etc/ld.so.conf文件里所列的目錄,用它們來(lái)重建/etc/ld.so.cache。
因此,等會(huì)兒我們自己開(kāi)發(fā)的共享庫(kù)就可以將其拷貝到/lib、/etc/lib目錄里,又或者修改/etc/ld.so.conf文件將我們自己的庫(kù)路徑添加到該文件中,再執(zhí)行l(wèi)dconfig命令。
非了老半天功夫,終于把基礎(chǔ)打好了,猴急的您早已按耐不住激情的想動(dòng)手嘗試了吧!哈哈。。。OK,說(shuō)整咱就開(kāi)整,接下來(lái)我就帶領(lǐng)大家一步一步來(lái)開(kāi)發(fā)自己的動(dòng)態(tài)庫(kù),然后教大家怎么去使用它。
我們有一個(gè)頭文件my_so_test.h和三個(gè)源文件test_a.c、test_b.c和test_c.c,將他們制作成一個(gè)名為libtest.so的動(dòng)態(tài)鏈接庫(kù)文件:
OK,萬(wàn)事俱備,只欠東風(fēng)。如何將這些文件編譯成一個(gè)我們所需要的so文件呢?可以分兩步來(lái)完成,也可以一步到位:
方法一:
1、先生成目標(biāo).o文件:
2、再生成so文件:
-shared該選項(xiàng)指定生成動(dòng)態(tài)連接庫(kù)(讓連接器生成T類(lèi)型的導(dǎo)出符號(hào)表,有時(shí)候也生成弱連接W類(lèi)型的導(dǎo)出符號(hào)),不用該標(biāo)志外部程序無(wú)法連接。相當(dāng)于一個(gè)可執(zhí)行文件。
-fPIC:表示編譯為位置獨(dú)立的代碼,不用此選項(xiàng)的話編譯后的代碼是位置相關(guān)的所以動(dòng)態(tài)載入時(shí)是通過(guò)代碼拷貝的方式來(lái)滿足不同進(jìn)程的需要,而不能達(dá)到真正代碼段共享的目的。
方法二:一步到位。
至此,我們制作的動(dòng)態(tài)庫(kù)文件libtest.so就算大功告成了。
接下來(lái),就是如何使用這個(gè)動(dòng)態(tài)庫(kù)了。動(dòng)態(tài)鏈接庫(kù)的使用有兩種方法:既可以在運(yùn)行時(shí)對(duì)其進(jìn)行動(dòng)態(tài)鏈接,又可以動(dòng)態(tài)加載在程序中是用它們。接下來(lái),我就這兩種方法分別對(duì)其介紹。
+++動(dòng)態(tài)庫(kù)的使用+++
用法一:動(dòng)態(tài)鏈接。
使用“-ltest”標(biāo)記來(lái)告訴GCC驅(qū)動(dòng)程序在連接階段引用共享函數(shù)庫(kù)libtest.so。“-L.”標(biāo)記告訴GCC函數(shù)庫(kù)可能位于當(dāng)前目錄。否則GNU連接器會(huì)查找標(biāo)準(zhǔn)系統(tǒng)函數(shù)目錄。
這里我們注意,ldd的輸出它說(shuō)我們的libtest.so它沒(méi)找到。還記得我在前面動(dòng)態(tài)鏈接庫(kù)一節(jié)剛開(kāi)始時(shí)的那堆嘮叨么,現(xiàn)在你應(yīng)該很明白了為什么了吧。因?yàn)槲覀兊膌ibtest.so既不在/etc/ld.so.cache里,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一個(gè)目錄中。怎么辦?還用我告訴你?管你用啥辦法,反正我用的ldconfig `pwd`搞定的:
執(zhí)行結(jié)果如下:
偶忍不住又要羅嗦一句了,相信俺,我的嘮叨對(duì)大家是有好處。我為什么用這種方法呢?因?yàn)槲沂窃诮o大家演示動(dòng)態(tài)庫(kù)的用法,完了之后我就把libtest.so給刪了,然后再重構(gòu)ld.so.cache,對(duì)我的系統(tǒng)不會(huì)任何影響。倘若我是開(kāi)發(fā)一款軟件,或者給自己的系統(tǒng)DIY一個(gè)非常有用的功能模塊,那么我更傾向于將libtest.so拷貝到/lib、/usr/lib目錄下,或者我還有可能在/usr/local/lib/目錄下新建一文件夾xxx,將so庫(kù)拷貝到那兒去,并在/etc/ld.so.conf.d/目錄下新建一文件mytest.conf,內(nèi)容只有一行“/usr/local/lib/xxx/libtest.so”,再執(zhí)行l(wèi)dconfig。如果你之前還是不明白怎么解決那個(gè)“not found”的問(wèn)題,那么現(xiàn)在總該明白了吧。
方法二:動(dòng)態(tài)加載。
動(dòng)態(tài)加載是非常靈活的,它依賴(lài)于一套Linux提供的標(biāo)準(zhǔn)API來(lái)完成。在源程序里,你可以很自如的運(yùn)用API來(lái)加載、使用、釋放so庫(kù)資源。以下函數(shù)在代碼中使用需要包含頭文件:dlfcn.h函數(shù)原型說(shuō)明
const char *dlerror(void)當(dāng)動(dòng)態(tài)鏈接庫(kù)操作函數(shù)執(zhí)行失敗時(shí),dlerror可以返回出錯(cuò)信息,返回值為NULL時(shí)表示操作函數(shù)執(zhí)行成功。
void *dlopen(const char *filename, int flag)用于打開(kāi)指定名字(filename)的動(dòng)態(tài)鏈接庫(kù),并返回操作句柄。調(diào)用失敗時(shí),將返回NULL值,否則返回的是操作句柄。
void *dlsym(void *handle, char *symbol)根據(jù)動(dòng)態(tài)鏈接庫(kù)操作句柄(handle)與符號(hào)(symbol),返回符號(hào)對(duì)應(yīng)的函數(shù)的執(zhí)行代碼地址。由此地址,可以帶參數(shù)執(zhí)行相應(yīng)的函數(shù)。
int dlclose (void *handle)用于關(guān)閉指定句柄的動(dòng)態(tài)鏈接庫(kù),只有當(dāng)此動(dòng)態(tài)鏈接庫(kù)的使用計(jì)數(shù)為0時(shí),才會(huì)真正被系統(tǒng)卸載。2.2在程序中使用動(dòng)態(tài)鏈接庫(kù)函數(shù)。
dlsym(void *handle, char *symbol)
filename:如果名字不以“/”開(kāi)頭,則非絕對(duì)路徑名,將按下列先后順序查找該文件。
(1)用戶(hù)環(huán)境變量中的LD_LIBRARY值;
(2)動(dòng)態(tài)鏈接緩沖文件/etc/ld.so.cache
(3)目錄/lib,/usr/lib
flag表示在什么時(shí)候解決未定義的符號(hào)(調(diào)用)。取值有兩個(gè):
1)?RTLD_LAZY :?表明在動(dòng)態(tài)鏈接庫(kù)的函數(shù)代碼執(zhí)行時(shí)解決。
2)?RTLD_NOW :表明在dlopen返回前就解決所有未定義的符號(hào),一旦未解決,dlopen將返回錯(cuò)誤。
dlsym(void *handle, char *symbol)
dlsym()的用法一般如下:
void(*add)(int x,int y);?/*說(shuō)明一下要調(diào)用的動(dòng)態(tài)函數(shù)add */
add=dlsym("xxx.so","add");?/*?打開(kāi)xxx.so共享庫(kù),取add函數(shù)地址?*/
add(89,369);?/*?帶兩個(gè)參數(shù)89和369調(diào)用add函數(shù)?*/
看我出招:
執(zhí)行結(jié)果:
使用動(dòng)態(tài)鏈接庫(kù),源程序中要包含dlfcn.h頭文件,寫(xiě)程序時(shí)注意dlopen等函數(shù)的正確調(diào)用,編譯時(shí)要采用-rdynamic選項(xiàng)與-ldl選項(xiàng)(不然編譯無(wú)法通過(guò)),以產(chǎn)生可調(diào)用動(dòng)態(tài)鏈接庫(kù)的執(zhí)行代碼。
OK,通過(guò)本文的指導(dǎo)、練習(xí)相信各位應(yīng)該對(duì)Linux的庫(kù)機(jī)制有了些許了解,最主要的是會(huì)開(kāi)發(fā)使用庫(kù)文件了。由于本人知識(shí)所限,文中某些觀點(diǎn)如果不到位或理解有誤的地方還請(qǐng)各位個(gè)人不吝賜教。
總結(jié)
以上是生活随笔為你收集整理的linux系统中 库分为静态库和,Linux系统中“动态库”和“静态库”那点事儿-【经典好文】...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 中和抗体提升8倍 美国mRNA疫苗对奥密
- 下一篇: linux创建目录的语句,Linux的