linux 信号_Linux的信号和线程
什么是線程
線程,有時(shí)被稱為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成,每一個(gè)程序都至少有一個(gè)線程,若程序只有一個(gè)線程,那就是程序本身。
同時(shí)線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。
一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。由于線程之間的相互制約,致使線程在運(yùn)行中呈現(xiàn)出間斷性。因此線程也有就緒、阻塞和運(yùn)行三種基本狀態(tài)。就緒狀態(tài)是指線程具備運(yùn)行的所有條件,邏輯上可以運(yùn)行,在等待處理機(jī);運(yùn)行狀態(tài)是指線程占有處理機(jī)正在運(yùn)行;阻塞狀態(tài)是指線程在等待一個(gè)事件(如某個(gè)信號(hào)量),邏輯上不可執(zhí)行。
什么是信號(hào)
信號(hào)是一種IPC通信的形式,一般在Unix,類Unix或POSIX兼容的系統(tǒng)中使用。信號(hào)是一種異步通知進(jìn)程或同進(jìn)程中某個(gè)指定線程的方式。 當(dāng)信號(hào)被發(fā)送到進(jìn)程的時(shí)候,操作系統(tǒng)會(huì)中斷進(jìn)程的控制流程,并且在執(zhí)行非原子性的CPU指令時(shí)可以中斷進(jìn)程。
信號(hào)使用的風(fēng)險(xiǎn)(新手坑)
信號(hào)處理在存在競態(tài)的,因?yàn)樾盘?hào)本身是異步的,在處理一個(gè)信號(hào)的過程中,令一個(gè)信號(hào)(甚至肯能是同類型的信號(hào))會(huì)被直接發(fā)送到進(jìn)程中請(qǐng)求進(jìn)程處理。 信號(hào)是可以打斷系統(tǒng)調(diào)用的,不謹(jǐn)慎處理會(huì)引起程序自身的混亂,所以進(jìn)程的信號(hào)處理過程,盡量做到?jīng)]有副作用,也不要使用不可重入的函數(shù)。
Linux的線程
LinuxThreads
在Linux的上古時(shí)代,Linux的線程技術(shù)和POSIX的標(biāo)準(zhǔn)是不同的,它使用自己的LinuxThreads庫。這會(huì)為我們帶來什么影響呢?
讓我們來回顧一下 LinuxThreads 設(shè)計(jì)細(xì)節(jié)的一些基本理念:
為了維護(hù)線程本地?cái)?shù)據(jù)和內(nèi)存,LinuxThreads使用了進(jìn)程地址空間的高位內(nèi)存(就在堆棧地址之下)。 同步元語是使用信號(hào)來實(shí)現(xiàn)的。例如,線程會(huì)一直阻塞,直到被信號(hào)喚醒為止。并且,LinuxThreads將每個(gè)線程都是作為一個(gè)具有惟一進(jìn)程ID的進(jìn)程實(shí)現(xiàn)的。LinuxThreads接收到終止信號(hào)之后,管理線程就會(huì)使用相同的信號(hào)殺死所有其他線程(進(jìn)程)。 由于異步信號(hào)是內(nèi)核以進(jìn)程為單位分發(fā)的,而LinuxThreads的每個(gè)線程對(duì)內(nèi)核來說都是一個(gè)進(jìn)程,且沒有實(shí)現(xiàn)“線程組”,因此,某些語義不符合POSIX標(biāo)準(zhǔn),比如沒有實(shí)現(xiàn)向進(jìn)程中所有線程發(fā)送信號(hào)。如果核心不提供實(shí)時(shí)信號(hào),LinuxThreads將使用SIGUSR1和SIGUSR2作為內(nèi)部使用的restart和cancel信號(hào),這樣應(yīng)用程序就不能使用這兩個(gè)原本為用戶保留的信號(hào)了。在Linux kernel 2.1.60以后的版本都支持?jǐn)U展的實(shí)時(shí)信號(hào)(從_SIGRTMIN到_SIGRTMAX),因此不存在這個(gè)問題。根據(jù) LinuxThreads 的設(shè)計(jì),如果一個(gè)異步信號(hào)被發(fā)送了,那么管理線程就會(huì)將這個(gè)信號(hào)發(fā)送給一個(gè)線程,如果這個(gè)線程現(xiàn)在阻塞了這個(gè)信號(hào),那么這個(gè)信號(hào)也就會(huì)被掛起,因此某些信號(hào)的缺省動(dòng)作難以在現(xiàn)行體系上實(shí)現(xiàn),比如SIGSTOP和SIGCONT,LinuxThreads只能將一個(gè)線程掛起,而無法掛起整個(gè)進(jìn)程。
LinuxThreads帶來了什么問題
首先我們說下POSIX是如何定義多線程的:POSIX下一個(gè)多線程的進(jìn)程只有一個(gè)PID。 根據(jù)上面我們對(duì)LinuxThreads的描述,我們可以總結(jié)出LinuxThreads有下面這些問題:
我們?cè)谶@里不關(guān)注性能如何只關(guān)注POSIX兼容和信號(hào)處理問題。
NPTL
LinuxThreads的問題,特別是兼容性上的問題,嚴(yán)重阻礙了Linux上的跨平臺(tái)應(yīng)用(如Apache)采用多線程設(shè)計(jì),從而使得Linux上的線程應(yīng)用一直保持在比較低的水平。在Linux社區(qū)中,已經(jīng)有很多人在為改進(jìn)線程性能而努力,其中既包括用戶級(jí)線程庫,也包括核心級(jí)和用戶級(jí)配合改進(jìn)的線程庫。目前最為人看好的有兩個(gè)項(xiàng)目,一個(gè)是RedHat公司牽頭研發(fā)的NPTL(Native Posix Thread Library),另一個(gè)則是IBM投資開發(fā)的NGPT(Next Generation Posix Threading),二者都是圍繞完全兼容POSIX 1003.1c,同時(shí)在核內(nèi)和核外做工作以而實(shí)現(xiàn)多對(duì)多線程模型。這兩種模型都在一定程度上彌補(bǔ)了LinuxThreads的缺點(diǎn),且都是重起爐灶全新設(shè)計(jì)的。 NPTL的設(shè)計(jì)目標(biāo)歸納可歸納為以下幾點(diǎn):
在技術(shù)實(shí)現(xiàn)上,NPTL仍然采用1:1的線程模型,并配合glibc和最新的Linux Kernel2.5.x開發(fā)版在信號(hào)處理、線程同步、存儲(chǔ)管理等多方面進(jìn)行了優(yōu)化。和LinuxThreads不同,NPTL沒有使用管理線程,核心線程的管理直接放在核內(nèi)進(jìn)行,這也帶了性能的優(yōu)化。
Linux線程總結(jié)
比較新的Linux都已經(jīng)開始使用NPTL了,所以我們可以忽略LinuxThreads的存在了,介紹它主要是為了讓諸位讀者更深入的了解線程和信號(hào)的恩恩怨怨(不要丟雞蛋)。
Linux的信號(hào)
Linux是如何處理信號(hào)的
隨著Linux的內(nèi)核版本不斷提升,Linux的信號(hào)現(xiàn)在已經(jīng)可以按照線程級(jí)別的觸發(fā)了,換句話說就是,每個(gè)線程可以關(guān)注自己的信號(hào)了,并且可以區(qū)別性對(duì)待了。那我們需要注意什么呢?
在多線程應(yīng)用中,我們應(yīng)當(dāng)使用sigaction來代替singal函數(shù),因?yàn)榘碢OSIX的說法singal函數(shù)并沒有明確定義自己在多線程應(yīng)用中的行為。
可以使用pthread_sigmask來為每個(gè)線程設(shè)置獨(dú)立的信號(hào)掩碼。同時(shí)在多線程應(yīng)用中應(yīng)當(dāng)避免使用sigprocmask這個(gè)函數(shù),原因也是POSIX中該函數(shù)并沒有明確定義自己在多線程應(yīng)用中的行為。
這個(gè)時(shí)候,有人會(huì)產(chǎn)生疑問了,那么多線程下kill發(fā)出的進(jìn)程級(jí)別的信號(hào)A怎么辦?Linux是這樣解決的,它會(huì)把這個(gè)信號(hào)交付給任意一個(gè)沒有屏蔽信號(hào)A的線程。如果這信號(hào)沒有被任何線程設(shè)置handler進(jìn)行處理,就會(huì)觸發(fā)POSIX規(guī)定的默認(rèn)動(dòng)作。
接著有人就會(huì)問,我怎么向某個(gè)線程發(fā)消息呢,POSIX為我們準(zhǔn)備了pthread_kill函數(shù),我們可以直接向特定的線程發(fā)送消息。那么如果一個(gè)線程收到信號(hào)A,但是自己沒有安裝handler會(huì)發(fā)生什么?其實(shí)和進(jìn)程級(jí)別的信號(hào)處理方法一樣,直接觸發(fā)默認(rèn)動(dòng)作,同樣會(huì)結(jié)束整個(gè)進(jìn)程。
如何避免新手坑
在具有事件循環(huán)的應(yīng)用中,在信號(hào)的的handler中,可以將信號(hào)直接放入程序的隊(duì)列中,立刻返回。這樣直到線程從程序的隊(duì)列中取出這個(gè)信號(hào)為止,整個(gè)線程看起來就像沒有“中斷”。 如果不知道該怎么做,去看看著名的libev吧。
信號(hào)SIGSEGV
這個(gè)信號(hào),也許是大家最不想見到,為什么呢?我們看這個(gè)信號(hào)的定義:
當(dāng)當(dāng)前程序?qū)?nèi)存的引用無效時(shí),就會(huì)產(chǎn)生當(dāng)前信號(hào),也就是我們常說的“段違例”。
以下幾種情況會(huì)產(chǎn)生該信號(hào):
1.進(jìn)程引用的內(nèi)存頁面不存在(例如,該頁面位于堆和棧之間的映射的區(qū)域) 2.進(jìn)程試圖更新只讀內(nèi)存頁(例如,程序文本段或已經(jīng)被標(biāo)記為只讀的內(nèi)存映射區(qū)域) 3.進(jìn)程試圖在用戶態(tài)去訪問內(nèi)核部分的內(nèi)存好了,我們都知道這個(gè)信號(hào)引發(fā)的結(jié)果就是進(jìn)程退出。不過我們都忽視了一個(gè)問題,在現(xiàn)代的Linux上,按照POSIX的定義,這個(gè)信號(hào)是系統(tǒng)產(chǎn)生的線程級(jí)別的信號(hào)。換句話說,如果某個(gè)線程A出現(xiàn)了內(nèi)存引用無效,那么產(chǎn)生的信號(hào),會(huì)投遞到線程A的信號(hào)隊(duì)列中,而不是像進(jìn)程級(jí)別的信號(hào)無法確定接受者是誰。
JVM的安全區(qū)域
如果我們想讓所有Java線程停下來的時(shí)候,在JVM的JavaThread執(zhí)行到大家所知道的test 特定頁面的指令時(shí),就會(huì)因?yàn)楦虏豢勺x頁面而觸發(fā)SIGSEGV信號(hào)。那么對(duì)于那些正在執(zhí)行native代碼的JavaThread該怎么辦,JVM中的注釋寫的非常清楚,native返回JVM時(shí)會(huì)檢查是否能返回的。
好了再多說一句,JVM是如果將特定內(nèi)存保護(hù)起來的呢?這個(gè)需要看操作系統(tǒng)的API了,在Linux中是mprotect。
總結(jié)
多讀讀POSIX標(biāo)準(zhǔn)和Intel的CPU體系結(jié)構(gòu),會(huì)讓自己在開發(fā)變的容易些。
總結(jié)
以上是生活随笔為你收集整理的linux 信号_Linux的信号和线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CAD/CASS城市坐标转换到CGCS2
- 下一篇: linux命令无视错误,llinux 的