自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux信號(signal) 機制分析(1)

系統(tǒng) Linux
本文分析了Linux內(nèi)核對于信號的實現(xiàn)機制和應用層的相關(guān)處理。首先介紹了軟中斷信號的本質(zhì)及信號的兩種不同分類方法尤其是不可靠信號的原理。接著分析了內(nèi)核對于信號的處理流程包括信號的觸發(fā)/注冊/執(zhí)行及注銷等。最后介紹了應用層的相關(guān)處理,主要包括信號處理函數(shù)的安裝、信號的發(fā)送、屏蔽阻塞等,最后給了幾個簡單的應用實例。

【摘要】本文分析了Linux內(nèi)核對于信號的實現(xiàn)機制和應用層的相關(guān)處理。首先介紹了軟中斷信號的本質(zhì)及信號的兩種不同分類方法尤其是不可靠信號的原理。接著分析了內(nèi)核對于信號的處理流程包括信號的觸發(fā)/注冊/執(zhí)行及注銷等。***介紹了應用層的相關(guān)處理,主要包括信號處理函數(shù)的安裝、信號的發(fā)送、屏蔽阻塞等,***給了幾個簡單的應用實例。

【關(guān)鍵字】軟中斷信號,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t

[[181762]]

1. 信號本質(zhì)

軟中斷信號(signal,又簡稱為信號)用來通知進程發(fā)生了異步事件。在軟件層次上是對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是進程間通信機制中唯一的異步通信機制,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。進程之間可以互相通過系統(tǒng)調(diào)用kill發(fā)送軟中斷信號。內(nèi)核也可以因為內(nèi)部事件而給進程發(fā)送信號,通知進程發(fā)生了某個事件。信號機制除了基本通知功能外,還可以傳遞附加信息。

收到信號的進程對各種信號有不同的處理方法。處理方法可以分為三類:

***種是類似中斷的處理程序,對于需要處理的信號,進程可以指定處理函數(shù),由該函數(shù)來處理。

第二種方法是,忽略某個信號,對該信號不做任何處理,就象未發(fā)生過一樣。

第三種方法是,對該信號的處理保留系統(tǒng)的默認值,這種缺省操作,對大部分的信號的缺省操作是使得進程終止。進程通過系統(tǒng)調(diào)用signal來指定進程對某個信號的處理行為。

2. 信號的種類

可以從兩個不同的分類角度對信號進行分類:

  • 可靠性方面:可靠信號與不可靠信號;
  • 與時間的關(guān)系上:實時信號與非實時信號。

2.1 可靠信號與不可靠信號

Linux信號機制基本上是從Unix系統(tǒng)中繼承過來的。早期Unix系統(tǒng)中的信號機制比較簡單和原始,信號值小于SIGRTMIN的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是信號可能丟失。

隨著時間的發(fā)展,實踐證明了有必要對信號的原始機制加以改進和擴充。由于原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,并在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。

信號值位于SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數(shù)sigation()以及信號發(fā)送函數(shù)sigqueue()的同時,仍然支持早期的signal()信號安裝函數(shù),支持信號發(fā)送函數(shù)kill()。

信號的可靠與不可靠只與信號值有關(guān),與信號的發(fā)送及安裝函數(shù)無關(guān)。目前l(fā)inux中的signal()是通過sigation()函數(shù)實現(xiàn)的,因此,即使通過signal()安裝的信號,在信號處理函數(shù)的結(jié)尾也不必再調(diào)用一次信號安裝函數(shù)。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。

對于目前l(fā)inux的兩個信號安裝函數(shù):signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以后的信號都支持排隊。這兩個函數(shù)的***區(qū)別在于,經(jīng)過sigaction安裝的信號都能傳遞信息給信號處理函數(shù),而經(jīng)過signal安裝的信號不能向信號處理函數(shù)傳遞信息。對于信號發(fā)送函數(shù)來說也是一樣的。

2.2 實時信號與非實時信號

早期Unix系統(tǒng)只定義了32種信號,前32種信號已經(jīng)有了預定義值,每個信號有了確定的用途及含義,并且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產(chǎn)生SIGINT信號,對該信號的默認反應就是進程終止。后32個信號表示實時信號,等同于前面闡述的可靠信號。這保證了發(fā)送的多個實時信號都被接收。

非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。

3. 信號處理流程

對于一個完整的信號生命周期(從信號發(fā)送到相應的處理函數(shù)執(zhí)行完畢)來說,可以分為三個階段:

  1. 信號誕生
  2. 信號在進程中注冊
  3. 信號的執(zhí)行和注銷

3.1 信號誕生

信號事件的發(fā)生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發(fā)送信號的系統(tǒng)函數(shù)是kill, raise, alarm和setitimer以及sigqueue函數(shù),軟件來源還包括一些非法運算等操作。

這里按發(fā)出信號的原因簡單分類,以了解各種信號:

(1) 與進程終止相關(guān)的信號。當進程退出,或者子進程終止時,發(fā)出這類信號。

(2) 與進程例外事件相關(guān)的信號。如進程越界,或企圖寫一個只讀的內(nèi)存區(qū)域(如程序正文區(qū)),或執(zhí)行一個特權(quán)指令及其他各種硬件錯誤。

(3) 與在系統(tǒng)調(diào)用期間遇到不可恢復條件相關(guān)的信號。如執(zhí)行系統(tǒng)調(diào)用exec時,原有資源已經(jīng)釋放,而目前系統(tǒng)資源又已經(jīng)耗盡。

(4) 與執(zhí)行系統(tǒng)調(diào)用時遇到非預測錯誤條件相關(guān)的信號。如執(zhí)行一個并不存在的系統(tǒng)調(diào)用。

(5) 在用戶態(tài)下的進程發(fā)出的信號。如進程調(diào)用系統(tǒng)調(diào)用kill向其他進程發(fā)送信號。

(6) 與終端交互相關(guān)的信號。如用戶關(guān)閉一個終端,或按下break鍵等情況。

(7) 跟蹤進程執(zhí)行的信號。

Linux支持的信號列表如下。很多信號是與機器的體系結(jié)構(gòu)相關(guān)的

信號值 默認處理動作 發(fā)出信號的原因

SIGHUP 1 A 終端掛起或者控制進程終止

SIGINT 2 A 鍵盤中斷(如break鍵被按下)

SIGQUIT 3 C 鍵盤的退出鍵被按下

SIGILL 4 C 非法指令

SIGABRT 6 C 由abort(3)發(fā)出的退出指令

SIGFPE 8 C 浮點異常

SIGKILL 9 AEF Kill信號

SIGSEGV 11 C 無效的內(nèi)存引用

SIGPIPE 13 A 管道破裂: 寫一個沒有讀端口的管道

SIGALRM 14 A 由alarm(2)發(fā)出的信號

SIGTERM 1*** 終止信號

SIGUSR1 30,10,16 A 用戶自定義信號1

SIGUSR2 31,12,17 A 用戶自定義信號2

SIGCHLD 20,17,18 B 子進程結(jié)束信號

SIGCONT 19,18,25 進程繼續(xù)(曾被停止的進程)

SIGS***7,19,23 DEF 終止進程

SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵

SIGTTIN 21,21,26 D 后臺進程企圖從控制終端讀

SIGTTOU 22,22,27 D 后臺進程企圖從控制終端寫

處理動作一項中的字母含義如下

A 缺省的動作是終止進程

B 缺省的動作是忽略此信號,將該信號丟棄,不做處理

C 缺省的動作是終止進程并進行內(nèi)核映像轉(zhuǎn)儲(dump core),內(nèi)核映像轉(zhuǎn)儲是指將進程數(shù)據(jù)在內(nèi)存的映像和進程在內(nèi)核結(jié)構(gòu)中的部分內(nèi)容以一定格式轉(zhuǎn)儲到文件系統(tǒng),并且進程退出執(zhí)行,這樣做的好處是為程序員提供了方便,使得他們可以得到進程當時執(zhí)行時的數(shù)據(jù)值,允許他們確定轉(zhuǎn)儲的原因,并且可以調(diào)試他們的程序。

D 缺省的動作是停止進程,進入停止狀況以后還能重新進行下去,一般是在調(diào)試的過程中(例如ptrace系統(tǒng)調(diào)用)

E 信號不能被捕獲

F 信號不能被忽略

3.2 信號在目標進程中注冊

在進程表的表項中有一個軟中斷信號域,該域中每一位對應一個信號。內(nèi)核給一個進程發(fā)送軟中斷信號的方法,是在進程所在的進程表項的信號域設置對應于該信號的位。如果信號發(fā)送給一個正在睡眠的進程,如果進程睡眠在可被中斷的優(yōu)先級上,則喚醒進程;否則僅設置進程表中信號域相應的位,而不喚醒進程。如果發(fā)送給一個處于可運行狀態(tài)的進程,則只置相應的域即可。

進程的task_struct結(jié)構(gòu)中有關(guān)于本進程中未決信號的數(shù)據(jù)成員: struct sigpending pending:

  1. struct sigpending{ 
  2.  
  3.         struct sigqueue *head, *tail; 
  4.  
  5.         sigset_t signal; 
  6.  
  7. };  

第三個成員是進程中所有未決信號集,***、第二個成員分別指向一個sigqueue類型的結(jié)構(gòu)鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結(jié)構(gòu)刻畫一個特定信號所攜帶的信息,并指向下一個sigqueue結(jié)構(gòu):

  1. struct sigqueue{ 
  2.  
  3.         struct sigqueue *next
  4.  
  5.         siginfo_t info; 
  6.  
  7.  

信號在進程中注冊指的就是信號值加入到進程的未決信號集sigset_t signal(每個信號占用一位)中,并且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結(jié)構(gòu)中。只要信號在進程的未決信號集中,表明進程已經(jīng)知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。

當一個實時信號發(fā)送給一個進程時,不管該信號是否已經(jīng)在進程中注冊,都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中占有多個sigqueue結(jié)構(gòu)(進程每收到一個實時信號,都會為它分配一個結(jié)構(gòu)來登記該信號信息,并把該結(jié)構(gòu)添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中注冊)。

當一個非實時信號發(fā)送給一個進程時,如果該信號已經(jīng)在進程中注冊(通過sigset_t signal指示),則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味著同一個非實時信號在進程的未決信號信息鏈中,至多占有一個sigqueue結(jié)構(gòu)。

總之信號注冊與否,與發(fā)送信號的函數(shù)(如kill()或sigqueue()等)以及信號安裝函數(shù)(signal()及sigaction())無關(guān),只與信號值有關(guān)(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被注冊)

3.3 信號的執(zhí)行和注銷

內(nèi)核處理一個進程收到的軟中斷信號是在該進程的上下文中,因此,進程必須處于運行狀態(tài)。當其由于被信號喚醒或者正常調(diào)度重新獲得CPU時,在其從內(nèi)核空間返回到用戶空間時會檢測是否有信號等待處理。如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數(shù)前,進程會把信號在未決信號鏈中占有的結(jié)構(gòu)卸掉。

對于非實時信號來說,由于在未決信號信息鏈中最多只占用一個sigqueue結(jié)構(gòu),因此該結(jié)構(gòu)被釋放后,應該把信號在進程未決信號集中刪除(信號注銷完畢);而對于實時信號來說,可能在未決信號信息鏈中占用多個sigqueue結(jié)構(gòu),因此應該針對占用sigqueue結(jié)構(gòu)的數(shù)目區(qū)別對待:如果只占用一個sigqueue結(jié)構(gòu)(進程只收到該信號一次),則執(zhí)行完相應的處理函數(shù)后應該把信號在進程的未決信號集中刪除(信號注銷完畢)。否則待該信號的所有sigqueue處理完畢后再在進程的未決信號集中刪除該信號。

當所有未被屏蔽的信號都處理完畢后,即可返回用戶空間。對于被屏蔽的信號,當取消屏蔽后,在返回到用戶空間時會再次執(zhí)行上述檢查處理的一套流程。

內(nèi)核處理一個進程收到的信號的時機是在一個進程從內(nèi)核態(tài)返回用戶態(tài)時。所以,當一個進程在內(nèi)核態(tài)下運行時,軟中斷信號并不立即起作用,要等到將返回用戶態(tài)時才處理。進程只有處理完信號才會返回用戶態(tài),進程在用戶態(tài)下不會有未處理完的信號。

處理信號有三種類型:進程接收到信號后退出;進程忽略該信號;進程收到信號后執(zhí)行用戶設定用系統(tǒng)調(diào)用signal的函數(shù)。當進程接收到一個它忽略的信號時,進程丟棄該信號,就象沒有收到該信號似的繼續(xù)運行。如果進程收到一個要捕捉的信號,那么進程從內(nèi)核態(tài)返回用戶態(tài)時執(zhí)行用戶定義的函數(shù)。而且執(zhí)行用戶定義的函數(shù)的方法很巧妙,內(nèi)核是在用戶棧上創(chuàng)建一個新的層,該層中將返回地址的值設置成用戶定義的處理函數(shù)的地址,這樣進程從內(nèi)核返回彈出棧頂時就返回到用戶定義的函數(shù)處,從函數(shù)返回再彈出棧頂時,才返回原先進入內(nèi)核的地方。這樣做的原因是用戶定義的處理函數(shù)不能且不允許在內(nèi)核態(tài)下執(zhí)行(如果用戶定義的函數(shù)在內(nèi)核態(tài)下運行的話,用戶就可以獲得任何權(quán)限)。

4. 信號的安裝

如果進程要處理某一信號,那么就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關(guān)系,即進程將要處理哪個信號;該信號被傳遞給進程時,將執(zhí)行何種操作。

linux主要有兩個函數(shù)實現(xiàn)信號的安裝:signal()、sigaction()。其中signal()只有兩個參數(shù),不支持信號傳遞信息,主要是用于前32種非實時信號的安裝;而sigaction()是較新的函數(shù)(由兩個系統(tǒng)調(diào)用實現(xiàn):sys_signal以及sys_rt_sigaction),有三個參數(shù),支持信號傳遞信息,主要用來與 sigqueue() 系統(tǒng)調(diào)用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優(yōu)于signal()主要體現(xiàn)在支持信號帶有參數(shù)。

4.1 signal()

  1. #include <signal.h> 
  2.  
  3. void (*signal(int signum, void (*handler))(int)))(int);  

如果該函數(shù)原型不容易理解的話,可以參考下面的分解方式來理解:

  1. typedef void (*sighandler_t)(int); 
  2. sighandler_t signal(int signum, sighandler_t handler));  

***個參數(shù)指定信號的值,第二個參數(shù)指定針對前面信號值的處理,可以忽略該信號(參數(shù)設為SIG_IGN);可以采用系統(tǒng)默認方式處理信號(參數(shù)設為SIG_DFL);也可以自己實現(xiàn)處理方式(參數(shù)指定一個函數(shù)地址)。

如果signal()調(diào)用成功,返回***一次為安裝信號signum而調(diào)用signal()時的handler值;失敗則返回SIG_ERR。

傳遞給信號處理例程的整數(shù)參數(shù)是信號值,這樣可以使得一個信號處理例程處理多個信號。

  1. #include <signal.h> 
  2.  
  3. #include <unistd.h> 
  4.  
  5. #include <stdio.h> 
  6.  
  7. void sigroutine(int dunno) 
  8.  
  9. { /* 信號處理例程,其中dunno將會得到信號的值 */ 
  10.  
  11.         switch (dunno) { 
  12.  
  13.         case 1: 
  14.  
  15.         printf("Get a signal -- SIGHUP "); 
  16.  
  17.         break; 
  18.  
  19.         case 2: 
  20.  
  21.         printf("Get a signal -- SIGINT "); 
  22.  
  23.         break; 
  24.  
  25.         case 3: 
  26.  
  27.         printf("Get a signal -- SIGQUIT "); 
  28.  
  29.         break; 
  30.  
  31.         } 
  32.  
  33.         return
  34.  
  35.  
  36.   
  37.  
  38. int main() { 
  39.  
  40.         printf("process id is %d ",getpid()); 
  41.  
  42.         signal(SIGHUP, sigroutine); //* 下面設置三個信號的處理方法 
  43.  
  44.         signal(SIGINT, sigroutine); 
  45.  
  46.         signal(SIGQUIT, sigroutine); 
  47.  
  48.         for (;;) ; 
  49.  
  50.  

其中信號SIGINT由按下Ctrl-C發(fā)出,信號SIGQUIT由按下Ctrl-發(fā)出。該程序執(zhí)行的結(jié)果如下:

  1. localhost:~$ ./sig_test 
  2.  
  3. process id is 463 
  4.  
  5. Get a signal -SIGINT //按下Ctrl-C得到的結(jié)果 
  6.  
  7. Get a signal -SIGQUIT //按下Ctrl-得到的結(jié)果 
  8.  
  9. //按下Ctrl-z將進程置于后臺 
  10.  
  11.  [1]+ Stopped ./sig_test 
  12.  
  13. localhost:~$ bg 
  14.  
  15.  [1]+ ./sig_test & 
  16.  
  17. localhost:~$ kill -HUP 463 //向進程發(fā)送SIGHUP信號 
  18.  
  19. localhost:~$ Get a signal – SIGHUP 
  20.  
  21. kill -9 463 //向進程發(fā)送SIGKILL信號,終止進程 
  22.  
  23. localhost:~$  

4.2 sigaction()

  1. #include <signal.h> 
  2.  
  3. int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));  

sigaction函數(shù)用于改變進程接收到特定信號后的行為。該函數(shù)的***個參數(shù)為信號的值,可以為除SIGKILL及SIGSTOP外的任何一個特定有效的信號(為這兩個信號定義自己的處理函數(shù),將導致信號安裝錯誤)。第二個參數(shù)是指向結(jié)構(gòu)sigaction的一個實例的指針,在結(jié)構(gòu)sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以缺省方式對信號處理;第三個參數(shù)oldact指向的對象用來保存返回的原來對相應信號的處理,可指定oldact為NULL。如果把第二、第三個參數(shù)都設為NULL,那么該函數(shù)可用于檢查信號的有效性。

第二個參數(shù)最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數(shù)執(zhí)行過程中應屏蔽掉哪些信號等等。

sigaction結(jié)構(gòu)定義如下:

  1. struct sigaction { 
  2.  
  3.                        union
  4.  
  5.                                __sighandler_t _sa_handler; 
  6.  
  7.                                void (*_sa_sigaction)(int,struct siginfo *, void *); 
  8.  
  9.                        }_u 
  10.  
  11.             sigset_t sa_mask; 
  12.  
  13.             unsigned long sa_flags; 
  14.  
  15.  

1、聯(lián)合數(shù)據(jù)結(jié)構(gòu)中的兩個元素_sa_handler以及*_sa_sigaction指定信號關(guān)聯(lián)函數(shù),即用戶指定的信號處理函數(shù)。除了可以是用戶自定義的處理函數(shù)外,還可以為SIG_DFL(采用缺省的處理方式),也可以為SIG_IGN(忽略信號)。

2、由_sa_sigaction是指定的信號處理函數(shù)帶有三個參數(shù),是為實時信號而設的(當然同樣支持非實時信號),它指定一個3參數(shù)信號處理函數(shù)。***個參數(shù)為信號值,第三個參數(shù)沒有使用,第二個參數(shù)是指向siginfo_t結(jié)構(gòu)的指針,結(jié)構(gòu)中包含信號攜帶的數(shù)據(jù)值,參數(shù)所指向的結(jié)構(gòu)如下:

  1. siginfo_t { 
  2.  
  3.                   int      si_signo;  /* 信號值,對所有信號有意義*/ 
  4.  
  5.                   int      si_errno;  /* errno值,對所有信號有意義*/ 
  6.  
  7.                   int      si_code;   /* 信號產(chǎn)生的原因,對所有信號有意義*/ 
  8.  
  9.                                union{                               /* 聯(lián)合數(shù)據(jù)結(jié)構(gòu),不同成員適應不同信號 */ 
  10.  
  11.                                        //確保分配足夠大的存儲空間 
  12.  
  13.                                        int _pad[SI_PAD_SIZE]; 
  14.  
  15.                                        //對SIGKILL有意義的結(jié)構(gòu) 
  16.  
  17.                                        struct{ 
  18.  
  19.                                                       ... 
  20.  
  21.                                                  }... 
  22.  
  23.                                                ... ... 
  24.  
  25.                                                ... ...                                
  26.  
  27.                                        //對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結(jié)構(gòu) 
  28.  
  29.                                   struct{ 
  30.  
  31.                                                       ... 
  32.  
  33.                                                  }... 
  34.  
  35.                                                ... ... 
  36.  
  37.                                          } 
  38.  
  39.  

前面在討論系統(tǒng)調(diào)用sigqueue發(fā)送信號時,sigqueue的第三個參數(shù)就是sigval聯(lián)合數(shù)據(jù)結(jié)構(gòu),當調(diào)用sigqueue時,該數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)就將拷貝到信號處理函數(shù)的第二個參數(shù)中。這樣,在發(fā)送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發(fā)是非常有意義的。

3、sa_mask指定在信號處理程序執(zhí)行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發(fā)送,除非指定SA_NODEFER或者SA_NOMASK標志位。

注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數(shù)執(zhí)行過程中由sa_mask指定的信號才被阻塞。

4、sa_flags中包含了許多標志位,包括剛剛提到的SA_NODEFER及SA_NOMASK標志位。另一個比較重要的標志位是SA_SIGINFO,當設定了該標志位時,表示信號附帶的參數(shù)可以被傳遞到信號處理函數(shù)中,因此,應該為sigaction結(jié)構(gòu)中的sa_sigaction指定處理函數(shù),而不應該為sa_handler指定信號處理函數(shù),否則,設置該標志變得毫無意義。即使為sa_sigaction指定了信號處理函數(shù),如果不設置SA_SIGINFO,信號處理函數(shù)同樣不能得到信號傳遞過來的數(shù)據(jù),在信號處理函數(shù)中對這些信息的訪問都將導致段錯誤(Segmentation fault)。 

責任編輯:龐桂玉 來源: 嵌入式Linux中文站
點贊
收藏

51CTO技術(shù)棧公眾號