万象直播app破解版_欧美国产日韩无遮挡在线一区二区,亚洲国产综合精品中久,强奷白丝女仆在线观看,超碰三级大陆在线

您的位置:首頁 > 軟件問答

正點鬧鐘語音包(直播預告 | 疫情過后,我們還需要在線辦公嗎?)

導讀正點鬧鐘語音包文章列表:1、直播預告 | 疫情過后,我們還需要在線辦公嗎?2、對話 CTO | 健身新物種,超級猩猩帶來了哪些改變?3、果殼智能圓表閃耀上海服裝展4、32年甘為火車司機

正點鬧鐘語音包文章列表:

正點鬧鐘語音包(直播預告 | 疫情過后,我們還需要在線辦公嗎?)

直播預告 | 疫情過后,我們還需要在線辦公嗎?

近幾年來,一直有聲音說在線辦公是未來的趨勢。突如其來的疫情無疑加快了在線辦公的進程。大量公司無法正常返工導致了“遠程協同辦公”的需求集中爆發,一時間,在線辦公成為了剛需。跟據釘釘的數據,2月3日當天,就有近2億人開啟在家辦公模式。

在節后復工的過程中,我們看到有的企業迅速過渡到遠程辦公或在線辦公模式,有條不紊地推進工作;也有大量的企業迫于形勢,手忙腳亂地試用各種在線辦公工具,臨時開啟“在線化”。

實際上,企業如何正確地開始遠程辦公,使用哪些在線辦公工具,以及背后的管理方法都會對企業能否順利度過疫情起到影響。

本期公益直播中,《中歐商業評論》聯合“數字中歐”邀請到企業級研發管理工具ONES的創始人&CEO王穎奇,就遠程辦公背后的管理問題、在線辦公與遠程辦公的特點與方式、企業落地在線辦公的實踐經驗、以及疫情過后,我們還需不需要繼續投入在線辦公等話題進行分享。

講師介紹

王穎奇

研發管理工具ONES 創始人&CEO

講師資歷

16年軟件行業從業經歷

曾任職于金山 WPS Office 核心研發團隊,任金山安全“云安全”負責人,移動事業部負責人

2011年 創辦正點科技,旗下產品正點鬧鐘、正點日歷全球累計用戶1億

2014年任知名基金晨興創投EIR,參與數百企業投資評估與咨詢

2015年創辦ONES,旗下企業級研發管理工具已服務于各行各業的5萬余家企業及組織的研發團隊。代表客戶有人民日報新媒體中心、招商基金、喜茶、中國電信等各行業領軍企業。

直播內容

本次公益直播中,王穎奇老師將為大家帶來以下主題分享:

1、疫情沖擊下,企業所凸顯出來的日常管理問題和遠程辦公帶來的問題

2、理解遠程辦公和在線辦公的特點與區別

3、在線辦公的經驗分享

4、疫情之后,企業還要不要繼續為在線辦公投入?在線辦公將有著怎樣的前景?

2月26日19:00 直播等你來!

— END—

對話 CTO | 健身新物種,超級猩猩帶來了哪些改變?

摘要:「對話 CTO」是極客公園的一檔最新專欄,以技術人的視角聊聊研發管理者的發展和成長。

我們特別邀請到了 ONES 的創始人&CEO 王穎奇作為特邀訪談者。王穎奇曾參與金山軟件 WPS、金山毒霸等大型軟件的核心開發工作;2011 年創立了正點科技,旗下產品正點鬧鐘、正點日歷在全球用戶過億;2014 年,王穎奇在知名美元基金晨興資本任 EIR,并以個人身份參與十余家公司的管理咨詢工作;2015 年,王穎奇創立 ONES,致力于提供企業級研發管理解決方案。

剛剛加入超級猩猩時,刻奇就在思考,如何解決大則幾千平的傳統健身房需要承擔的高昂租金成本問題。從健身房選址到擺脫大面積店面束縛,通過技術實現了對健身房進行化整為零和精細化運營的改造,互聯網出身的他將分布式概念帶入了實體行業。

刻奇說,完全不同于傳統健身房,超級猩猩省去了前臺、銷售的角色,將這些行為線上化,通過得到的運營數據去賦能各個部門。依靠按次收費、小程序約課、游戲化的線上運營手段……健身房、健身教練和用戶之間的關系被重新「定義」了。

通過 IT 和技術實現打破健身房租金和選址的經營鐵律,穿過坪效天花板,關于超級猩猩如何將線上化、數據化、智能化落地,利用技術和運營手段提高課程購買率和復購率,刻奇聊了聊他的想法。在他看來,(健身房)IT 化必然成為未來的行業標準。

化零為整,分布式概念建店

穎奇:非常感謝超級猩猩聯合創始人刻奇接受我們今天的采訪。外界看到超級猩猩可能是連鎖的健身房,或者更多的是在線下的健身場景,同時也有訂課小程序。作為產品或者技術負責人,您在超級猩猩里擔任的角色是怎樣?除了我們見到的部分,超級猩猩還有哪些東西是我們沒有理解到的呢?

刻奇:對于外界來說,大家可能第一反應,超級猩猩是一個健身房。但從我們的角度,超級猩猩和過往的健身房是一個完全不同的定義。舉個例子,傳統健身房一定要有銷售這些角色,但是在我們這里卻沒有。我們把所有的前臺、銷售都砍掉了,取而代之的是回歸到健身的本質,回歸到課程本身、內容本身。

用技術方案取代了健身房傳統的必要人員,這樣做之后,我們才能把所有的支付行為、交易行為、瀏覽行為都線上化,線上化之后才能數據化。這是我們自己的一個三步概念,首先線上化,第二數據化,第三智能化。

穎奇:這三方面真正落地,會是什么樣子的?

刻奇:首先是線上化。我們可能是最早的一批做小程序的團隊,超級猩猩全國接近一百家門店,做到了真的沒有前臺,所有的用戶都是通過微信端進入到門店。

第二點是數據化。很早我們就內部成立了自己的數據平臺組這樣一個中臺部門。所有的運營數據都能以各種自定義的報表形式呈現給不同的部門。比如,傳統健身房教練是通過人來評定的,我們認為很主觀也不公平。超級猩猩未來幾百個幾千個教練團隊,我們直接通過自己的算法系統,基于一個核心指標:滿員率,給到教練及時的反饋。

第三是還有一些我們在嘗試的智能化的應用,比如完全自動化的排課。

穎奇:大家會說傳統健身房是地產的生意,我覺得更多是生意難擴大,利潤空間薄。超級猩猩利用技術化的手段讓效率變高了,核心會變嗎?

刻奇:核心也發生很大的變化。傳統健身房的模式是這樣一個商業模型,通過建幾千平的大店要到一個相對低的租金。租金早期是一個誘惑,但是中期會上漲,因為無法在周圍隨便找到另一個相同面積的場地,所以間接被地產綁架了。傳統健身房運營了七年八年,短則甚至兩年三年,就會陷入這個問題。所以健身房幾乎無法走到上市的環節,因為無法在財務上說服股東,保證租金成本不上漲。

但是我們一開始就強調大量拆散。我是互聯網出身,所以我會將分布式這種概念應用到實業行業。拆散之后有兩個好處,從用戶角度來看,門店距離用戶更近了。同時我們不受地產的控制,一兩百平相對靈活,更換一個地方成本相對小。我可以在周圍開很多家,客流是沒有損失的。

穎奇:這應該是一個非常核心的邏輯了,我們把店變小變多,就更加需要IT的能力,否則的話我們也沒法去管理。

刻奇:對,這個是跟技術相關的、支持超級猩猩從大變小的一個非常核心的邏輯。如果沒有 IT 的支持,店面的增加會直接導致管理成本急劇增加。大的健身房管理上面就一個店,相對管理成本會變低一點,但是你分散的話,管理成本就會變高,但是通過提高 IT 能力,即便我們有很多店,我們依然沒有前臺,沒有額外的管理成本,還是能夠開這種小的店,能夠保證同樣的管理效率。

舉個例子,可能十年、二十年前大超市是主力。而現在有越來越多的社區店、便利店,雖然它們很長時間內還是會并存,但社區店和便利店還是在蠶食大超市的生存空間。大健身房也面臨同樣的問題,租金水平不斷上漲,小的健身房在蠶食市場。而且在這個過程中,小的依然離用戶更近。這個也是我覺得很核心的一個邏輯。

穎奇:如果有些健身房沒有太強的IT能力,可能就會買SaaS系統或者做外包,去做一些程序并進行一些運營,這個會不會形成一種行業標準?

刻奇:我覺得 IT 化一定會成為行業標準的。我們這個行業也就幾家有自己的開發能力,另外很多小型公司確實都是買 SaaS,通過 SaaS 的形式去做這個東西。

迭代決策系統,數據化建模選址

穎奇:IT的這些能力是怎樣幫助你們的?信息傳遞速度、服務能力上是否有進步?支撐起那么多小的店,提高聚集份額的速度等等這些事情上是否有起到輔助作用?

刻奇:是的,基于剛才提到的分布化,加上 IT 能力,標準化能力,我們門店標準的選址可能只需要 1-2 個月,甚至更快。2 個月門店就可以開起來,管理成本的其中一些部分都是 IT 化的,硬件一裝,門店空間的硬件控制系統一裝,課程也是標準化的,經過培訓和招募優秀的教練,一個標準化的門店就直接下去了,是非常快捷的一個過程。

穎奇:我有個非常好奇的點,超級猩猩是如何選址進行快速擴張的,是否有一些數據建模的決策機制,有沒有一些東西可以和大家聊一聊。

刻奇:首先我們自己有內部的一套系統,是基于過往運營情況,加上你對于當下城市,就是門店選址的地方,進行數據對比,然后你就能夠大概有一個維度指標了。同時,我們也需要把一個區位的維度指標收集的更全。比如說它旁邊的租金水平,旁邊有多少個相關的消費品類,人流量等等。這套體系其實做得比較好的是海底撈,他們也是通過這種方式選址。當然,我覺得算法比重會越來越大,現在可能五五吧。一部分是基于算法的能力去理解這個地方合不合適,包括未來適合什么樣的運營的方式、策略、排什么課;然后還有一半是能在過程中主觀進行感知。

穎奇:數據選址是有別于傳統的高效率選址方法。

刻奇:是的,我覺得數據維度越來越全面之后,人的維度會越來越小,未來我們可能 80% 的決策來自于數據的量化指標,20% 是由人的感性的直覺和經驗去做這個決定,這是一個過程。當然我們選址其實也有一個自己的流程系統,這個流程系統使得內部的決策環節更科學更高效,并且一定是全公司一起參與進來響應。其他的部門都要參與到這個角色里面去,提供各自相應的專業分析,市場有自己市場的考量,門店有門店運營的考量,教練有教練的考量。如何把內部這套系統做的既科學又不要太繁瑣。我們自己迭代了 80 多個版本去做這套內部的決策系統,并且保證高效,保持我們的科學性,而且持續的進行迭代。

穎奇:現在超級猩猩在全國有多少門店?

刻奇:差不多九個城市,一百家門店。

穎奇:今年大概會有一個什么樣的目標?

刻奇:今年預計是 100 到 150 家的新增門店數。

穎奇:另一個問題,可能是跟所有健身房都有關系的。「健身」本身是個「反人性」的行為,超級猩猩是怎么解決「反人性」這個事情上的用戶心理和體驗呢?這里面會有哪些是通過我們的技術手段或者運營手段呢?

刻奇:有。所有的用戶能夠做到按次付費,也是線上技術支持之后能達到的一個效果。因為按次付費,所以來的人消費決策是認真的,投入是不一樣的,和在傳統健身房上免費團課的感覺是不一樣的。

傳統健身房按年收費,超級猩猩按次收費,我們的教練也知道課教得不好不僅會影響滿員率,還會影響下次的直接收入。學員和教練的投入會營造一個氛圍,一個人練會覺得「反人性」,但是當有了氛圍之后,學員也覺得挺開心的。

穎奇:這解決了單次來的投入問題,那怎么解決讓他每次都來的問題呢?

刻奇:對,這也是我們線上部分去做的,線上化運營之后可以引入很多游戲化的運營手段。比如我們去年做了「猩章」成長體系,現在很多人跟隨我們也在做了。頒發游戲等級徽章,按不同課程類型細化,還有解鎖,名人堂,累計排名等等。核心是這樣設計之后給學員帶來的是「多重的反饋」。學員會覺得「我不只是流了汗,我自己開心」,當然我們后面也在做新的嘗試,比如做硬件相關的新的產品。就好像游戲能刺激玩家玩下去的原因一樣,游戲中的反饋很豐富很及時,這也是提高用戶粘性的一個重要方式。

另外,我們也給教練提供一些能力幫助他們去提高課程的體驗。教練端產品會看到每一節課學員能力的分布,有多少學生是第一次來超級猩猩,有多少同學是第一次上這個教練的課,然后教練就能快速理解這節課他該怎么樣動態地調整的授課方式和交流方式。

「規模」中打開邊界,重新「發明」健身房

穎奇:接下來聊聊您自己,首先可以講一下您的名字「刻奇」是怎么來的?

刻奇:我在青年時代喜歡米蘭·昆德拉的《生命中不能承受之輕》,里面比較重要的一個概念就是「刻奇」。大家對這個的翻譯有很多討論,書里面可能會比較常規的翻譯是「媚俗」這種概念去講的,我個人覺得它是用一個詞概括了一種社會心理狀態。

像喝醉酒了之后會說很多的話,所有的情緒都帶入進去,同時希望全世界都要陷入到這種情緒之中。這是在社會心理學里面非常常見的一種從眾狀態,比如一次災難性事件,所有人都要在朋友圈去點蠟燭,類似這樣的心理狀態。「刻奇」這個詞當時引發了我很深的思考,就作為我自己名字了。

穎奇:那是什么契機讓您加入了超級猩猩呢?您在來超級猩猩之前,大概一些工作的履歷是什么樣的呢?

刻奇:我最早是在迅雷。雖然那時候是產品經理,但是之前也是技術出身。在迅雷的時候也比較有幸參與了迅雷當時可能是最盈利的部門——會員事業部,也經歷了從 PC 到移動互聯網遷移的過程。后面短暫的去了一下支付寶,在支付寶無線端負責賬號和安全這部分的產品業務。然后就又回到了深圳參與超級猩猩的創業過程。

穎奇:最后您可以分享幾本最近看的、認為比較好的書給大家。

刻奇:我最近看的一本比較好的是《規模》,有一章我印象很深,為什么在發展過程中大多數公司會越做越衰敗,但城市會越來越大。為什么同樣是在規模增長過程中會發生不同的命運,里面提到一個很重要的概念是維度性。公司隨著增長它的維度是越來越低的,因為公司越來越大,里面的溝通效率、反饋效率在不停的降低,這幾乎是常態,所有的公司慢慢的效率都會變低,溝通反饋機制會越來越慢。而城市不一樣,城市隨著規模擴大,它會不停的引進新的人、新的技術,并且邊界不斷放大,它永遠是充滿生命的這種活力,它的維度是隨著它的規模增大在變高的。

穎奇:這更多可能還是你自己的規模在變大,實際上量變走向質變了。

刻奇:對,可能是發生質變了。以健身行業為例,為什么健身連鎖在世界范圍里都沒有一個能夠走到那么高的位置,它是不是在過程中有某種原因導致的。而比如說可口可樂公司能夠增長如此快,能夠跨越時間周期慣例周期,這個過程中是不是由他們不同的增長模式導致的?

穎奇:對,我覺得有些東西可能是設計出來的,有些東西可能是他早期基因決定的。包括現在我們作為觀察者來看超級猩猩,也是看到超級猩猩在一點點改變,也不是完全設計出來的。我覺得你是在參與到重新發明健身房,包括重新發明健身房、用戶和健身教練之間的關系之中的。

刻奇:所以我也覺得你們 ONES 很重要,因為所有的公司都應該是互聯網公司,都一定要有自己的研發能力。現在如果哪個公司沒有自己的研發團隊,我都不太看好公司的前景,它可能只是一個短期的小生意。如果一個公司真的是要做一個企業長期發展,它一定會需要自己內部的技術基因。如果以前沒有,現在一定要有,不然就會被時代淘汰。

穎奇:近期有一些傳統行業例如家具廠商、海鮮企業也來采購我們的研發管理工具,就說明他們內部都有軟件的部門了,這個發展是比我們之前想象的要快很多的。今天非常感謝您的分享。

本文作者:王穎奇

聯系方式:wangyingqi@gmail.com

果殼智能圓表閃耀上海服裝展

2016中國國際服裝服飾博覽會(春季)近日在國家會展中心舉行啟幕。本屆展會匯聚了全球20多個國家及地區的1177家企業、1300余個品牌參展。作為唯一一家在展會上亮相的智能手表產品,果殼智能手表獲得了不少參展觀眾和展商人士的好評。

果殼智能圓表是世界上第一塊采用圓形屏幕的智能手表。據悉,在果殼智能圓表最初立項階段,果殼電子做過一次10萬人規模的調查,得出的一個非常重要的結論是——對于智能手表來說,外表的美觀成都比智能化更重要。所以果殼電子將果殼智能圓表的表盤設計成了圓形,外形沿用了奢侈名表的CushionRound造型,因為這樣才能傳承傳統名表的氣質和優雅,消費者才會經常佩帶在手腕上。

果殼智能圓表還擁有不少獨特的創新點和人性化功能,比如可以“常顯”時間的屏幕,遠超同類產品的待機時間,內置應用商店以及Wi-Fi、藍牙多種連接方式,標準24mm表帶軸,消費者可以隨自己所好任意更換表帶等等。

除了外觀非常吸引人之外,果殼智能圓表的視網膜屏幕分辨率為320*320,像素密度為359PPI,并且能在電子墨水顯示模式和高清真彩顯示模式之間任意切換,不用抬手或者點擊屏幕就可以看清楚時間,這塊屏幕也是目前已知智能手表分辨率最高的,豐富的視覺表現,和個性簡潔的交互給予用戶更好的使用體驗。

在待機時間上,果殼智能圓表的極限待機時間長達18天,即使在正常使用的情況下,也能做到待機5天。

另外,果殼智能圓表標配頭層牛皮的表帶,但考慮到用戶的個人喜好復雜多樣,果殼智能圓表采用24MM的標準表帶軸,用戶可以自由選擇各種類型、材質的表帶,隨心搭配。

在功能上,果殼智能圓表同時也有豐富的實用功能。它是一個終極遙控器,也是用戶的健康助理,可以通過傳感器實時測量心率、血氧等數據。它也是用戶的貼身天氣預報助手,會提示用戶穿衣指數,防曬指數,PM2.5數據等。還會同步接收手機上的電話、短信、微信等各種提示,成為用戶開車或者開會時的小秘書,重要信息從此不再錯過。

另外果殼智能圓表在設計時進行了高達157項的系統級的優化,所以其正常使用的電量能以周為單位,是同類產品的5倍。

果殼智能圓表的應用商店名為果殼市場,已經提供包括“心率監測”“計步器”“WiFi萬能鑰匙”“正點鬧鐘”以及音樂遙控播放、錄音、PPT控制、智能家居設備遙控等應用。

此外,果殼智能圓表還可以隨時更換表盤,目前內置表盤中既有簡潔的兩針、三針表盤,也有清晰易讀的數字表盤,最贊的是非常對機械表迷胃口的透窗、陀飛輪樣式表盤。

果殼智能圓表提供多種網絡連接方式,除了用藍牙與幾億的藍牙設備連接之外,還配置Wi-Fi功能,能獨立上網,或者通過藍牙代理上網,其內置“WiFi萬能鑰匙”應用能讓果殼智能圓表一鍵鏈接全國10億免費熱點,任何時候可以接收到來自互聯網、日程和應用程序的通知提醒,不會錯過任何一條短信和來電。

32年甘為火車司機當活鬧鐘,只為數萬趟次列車正點開車

紅網時刻2月25日訊(通訊員 陳智敏)2月24日(農歷正月十三),節后春運第13天。當天晚上17時30分,魯新萍提前30分來到備班樓接班。

今年50歲魯新萍,現是中國鐵路廣州局集團公司株洲機務段郴州運用車間備班樓一名叫班員、黨員工長。1988年10月,她參加工作起就一直在備班樓擔任服務員工作。2009年7月份,她光榮地加入了中國共產黨。

按照政策規定:2月28日,是魯新萍光榮退休的日子。2月24日的晚班也是她最后一次在備班樓當班。

“備班樓”和“叫班員”是鐵路系統獨有的名詞,“備班樓”有點類似賓館,火車司機在晚上開車前,必須先到備班樓進行充分休息。“叫班員”被火車司機美稱為“活鬧鐘”,她們的工作職責就是把火車司機正點叫醒、叫起、叫走,確保讓火車司機正點出乘開車。

她的上班期間不但要負責整棟備班樓6層48個房間的衛生清理,臥具整理,還要負責本車間待乘火車司機的叫班工作。

魯新萍在電腦叫班系統里核對當天晚上的行車計劃。

魯新萍接班后的第一項工作,就是在備班樓值班室的電腦里的備班系統中核對列車車次、開點和人員信息。

這幾天,湖南地區氣溫漸漸升高。為了讓待乘的司機有溫暖舒適的休息環境,魯新萍提前更換了床上的被子。

現在正是京廣線郴州至廣州區段都會迎來臨客列車與貨物列車交替開行的旺季,在車間備班樓待乘的火車司機也隨之增多。

車間備班樓以前是人工叫班方式,叫班員先把列車車次和開車時間抄好以后,再拿個本子去火車司機休息的房間輕輕地敲門,去通知火車司機。一旦遇到瞌睡重的火車司機,叫班員還得敲第二門,再叫一次班,直至火車司機起床。

10年前,由人工叫班改為廣播呼叫班。叫班員坐在值班室里通過廣播與火車司機直接對話叫班。現在只要根據列車開行計劃輸入叫班數據信息,就能通過電腦系統自動叫班。隨著科技改革,叫班員的勞動強度漸漸減少,工作效率逐步提高。

魯新萍根據天氣變化情況更換備班樓房間里的臥具。

據魯新萍介紹,叫班員的工作看似簡單,但責任重大。叫班時間早了,影響火車司機休息,埋下安全風險。叫班時間晚了,造成火車司機出勤晚到,就會影響列車的正點開行。要根據司機接班地點的不同,分別提前不同的時間叫班,能讓火車司機多休息一分鐘,就是對安全最大的保證。

據統計:32年,她甘為火車司機當“活鬧鐘”,為火車司機準時叫班數萬次,為數萬趟次列車安全正點保駕護航。

正點原子I.MX6U嵌入式Linux C應用編程 第八章 信號

信號:基礎

本章將討論信號,雖然信號的基本概念比較簡單,但是其所涉及到的細節內容比較多,所以本章篇幅也會相對比較長。事實上,在很多應用程序當中,都會存在處理異步事件這種需求,而信號提供了一種處理異步事件的方法,所以信號機制在Linux早期版本中就已經提供了支持,隨著Linux內核版本的更新迭代,其對信號機制的支持更加完善。

本章將會討論如下主題內容。

信號的基本概念;

信號的分類、Linux提供的各種不同的信號及其作用;

發出信號以及響應信號,信號由“誰”發送、由“誰”處理以及如何處理;

進程在默認情況下對信號的響應方式;

使用進程信號掩碼來阻塞信號、以及等待信號等相關概念;

如何暫停進程的執行,并等待信號的到達。

基本概念

信號是事件發生時對進程的通知機制,也可以把它稱為軟件中斷。信號與硬件中斷的相似之處在于能夠打斷程序當前執行的正常流程,其實是在軟件層次上對中斷機制的一種模擬。大多數情況下,是無法預測信號達到的準確時間,所以,信號提供了一種處理異步事件的方法。

信號的目的是用來通信的

一個具有合適權限的進程能夠向另一個進程發送信號,信號的這一用法可作為一種同步技術,甚至是進程間通信(IPC)的原始形式。信號可以由“誰”發出呢?以下列舉的很多情況均可以產生信號:

硬件發生異常,即硬件檢測到錯誤條件并通知內核,隨即再由內核發送相應的信號給相關進程。硬件檢測到異常的例子包括執行一條異常的機器語言指令,諸如,除數為0、數組訪問越界導致引用了無法訪問的內存區域等,這些異常情況都會被硬件檢測到,并通知內核、然后內核為該異常情況發生時正在運行的進程發送適當的信號以通知進程。

用于在終端下輸入了能夠產生信號的特殊字符。譬如在終端上按下CTRL C組合按鍵可以產生中斷信號(SIGINT),通過這個方法可以終止在前臺運行的進程;按下CTRL Z組合按鍵可以產生暫停信號(SIGCONT),通過這個方法可以暫停當前前臺運行的進程。

進程調用kill()系統調用可將任意信號發送給另一個進程或進程組。當然對此是有所限制的,接收信號的進程和發送信號的進程的所有者必須相同,亦或者發送信號的進程的所有者是root超級用戶。

用戶可以通過kill命令將信號發送給其它進程。kill命令想必大家都會使用,通常我們會通過kill命令來“殺死”(終止)一個進程,譬如在終端下執行"kill -9 xxx"來殺死PID為xxx的進程。kill命令其內部的實現原理便是通過kill()系統調用來完成的。

發生了軟件事件,即當檢測到某種軟件條件已經發生。這里指的不是硬件產生的條件(如除數為0、引用無法訪問的內存區域等),而是軟件的觸發條件、觸發了某種軟件條件(進程所設置的定時器已經超時、進程執行的CPU時間超限、進程的某個子進程退出等等情況)。

進程同樣也可以向自身發送信號,然而發送給進程的諸多信號中,大多數都是來自于內核。

以上便是可以產生信號的多種不同的條件,總的來看,信號的目的都是用于通信的,當發生某種情況下,通過信號將情況“告知”相應的進程,從而達到同步、通信的目的。

信號由誰處理、怎么處理

信號通常是發送給對應的進程,當信號到達后,該進程需要做出相應的處理措施,通常進程會視具體信號執行以下操作之一:

忽略信號。也就是說,當信號到達進程后,該進程并不會去理會它、直接忽略,就好像是沒有發出該信號,信號對該進程不會產生任何影響。事實上,大多數信號都可以使用這種方式進行處理,但有兩種信號卻決不能被忽略,它們是SIGKILL和SIGSTOP,這兩種信號不能被忽略的原因是:它們向內核和超級用戶提供了使進程終止或停止的可靠方法。另外,如果忽略某些由硬件異常產生的信號,則進程的運行行為是未定義的。

捕獲信號。當信號到達進程后,執行預先綁定好的信號處理函數。為了做到這一點,要通知內核在某種信號發生時,執行用戶自定義的處理函數,該處理函數中將會對該信號事件作出相應的處理,Linux系統提供了signal()系統調用可用于注冊信號的處理函數,將會在后面向大家介紹。

執行系統默認操作。進程不對該信號事件作出處理,而是交由系統進行處理,每一種信號都會有其對應的系統默認的處理方式,8.3小節中對此有進行介紹。需要注意的是,對大多數信號來說,系統默認的處理方式就是終止該進程。

信號是異步的

信號是異步事件的經典實例,產生信號的事件對進程而言是隨機出現的,進程無法預測該事件產生的準確時間,進程不能夠通過簡單地測試一個變量或使用系統調用來判斷是否產生了一個信號,這就如同硬件中斷事件,程序是無法得知中斷事件產生的具體時間,只有當產生中斷事件時,才會告知程序、然后打斷當前程序的正常執行流程、跳轉去執行中斷服務函數,這就是異步處理方式。

信號本質上是int類型數字編號

信號本質上是int類型的數字編號,這就好比硬件中斷所對應的中斷號。內核針對每個信號,都給其定義了一個唯一的整數編號,從數字1開始順序展開。并且每一個信號都有其對應的名字(其實就是一個宏),信號名字與信號編號乃是一一對應關系,但是由于每個信號的實際編號隨著系統的不同可能會不一樣,所以在程序當中一般都使用信號的符號名(也就是宏定義)。

這些信號在<signum.h>頭文件中定義,每個信號都是以SIGxxx開頭,如下所示:

示例代碼 8.1.1 信號定義

/* Signals. */

#define SIGHUP 1 /* Hangup (POSIX). */

#define SIGINT 2 /* Interrupt (ANSI). */

#define SIGQUIT 3 /* Quit (POSIX). */

#define SIGILL 4 /* Illegal instruction (ANSI). */

#define SIGTRAP 5 /* Trace trap (POSIX). */

#define SIGABRT 6 /* Abort (ANSI). */

#define SIGIOT 6 /* IOT trap (4.2 BSD). */

#define SIGBUS 7 /* BUS error (4.2 BSD). */

#define SIGFPE 8 /* Floating-point exception (ANSI). */

#define SIGKILL 9 /* Kill, unblockable (POSIX). */

#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */

#define SIGSEGV 11 /* Segmentation violation (ANSI). */

#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */

#define SIGPIPE 13 /* Broken pipe (POSIX). */

#define SIGALRM 14 /* Alarm clock (POSIX). */

#define SIGTERM 15 /* Termination (ANSI). */

#define SIGSTKFLT 16 /* Stack fault. */

#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */

#define SIGCHLD 17 /* Child status has changed (POSIX). */

#define SIGCONT 18 /* Continue (POSIX). */

#define SIGSTOP 19 /* Stop, unblockable (POSIX). */

#define SIGTSTP 20 /* Keyboard stop (POSIX). */

#define SIGTTIN 21 /* Background read from tty (POSIX). */

#define SIGTTOU 22 /* Background write to tty (POSIX). */

#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */

#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */

#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */

#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */

#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */

#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */

#define SIGPOLL SIGIO /* Pollable event occurred (System V). */

#define SIGIO 29 /* I/O now possible (4.2 BSD). */

#define SIGPWR 30 /* Power failure restart (System V). */

#define SIGSYS 31 /* Bad system call. */

#define SIGUNUSED 31

不存在編號為0的信號,從示例代碼 8.1.1中也可以看到,信號編號是從1開始的,事實上kill()函數對信號編號0有著特殊的應用,關于這個文件將會在后面的內容向大家介紹。

信號的分類

Linux系統下可對信號從兩個不同的角度進行分類,從可靠性方面將信號分為可靠信號與不可靠信號;而從實時性方面將信號分為實時信號與非實時信號,本小節將對這些信號的分類進行簡單地介紹。

可靠信號與不可靠信號

Linux信號機制基本上是從UNIX系統中繼承過來的,早期UNIX系統中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題,它的主要問題是:

進程每次處理信號后,就將對信號的響應設置為系統默認操作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號處理函數結尾再一次調用signal(),重新為該信號綁定相應的處理函數。

因此導致,早期UNIX下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失(處理信號時又來了新的信號,則導致信號丟失)。

Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數后,不必重新調用signal()。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。在Linux系統下,信號值小于SIGRTMIN(34)的信號都是不可靠信號,這就是"不可靠信號"的來源,所以示例代碼 8.1.1中所列舉的信號都是不可靠信號。

隨著時間的發展,實踐證明,有必要對信號的原始機制加以改進和擴充,所以,后來出現的各種UNIX版本分別在這方面進行了研究,力圖實現"可靠信號"。由于原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號(SIGRTMIN~SIGRTMAX),并在一開始就把它們定義為可靠信號,在Linux系統下使用"kill -l"命令可查看到所有信號,如下所示:

圖 8.2.1 kill命令查看所有信號

Tips:括號" ) "前面的數字對應該信號的編號,編號1~31所對應的是不可靠信號,編號34~64對應的是可靠信號,從圖中可知,可靠信號并沒有一個具體對應的名字,而是使用了SIGRTMIN N或SIGRTMAX-N的方式來表示。

可靠信號支持排隊,不會丟失,同時,信號的發送和綁定也出現了新版本,信號發送函數sigqueue()及信號綁定函數sigaction()。

早期UNIX系統只定義了31種信號,而Linux 3.x支持64種信號,編號1-64(SIGRTMIN=34,SIGRTMAX=64),將來可能進一步增加,這需要得到內核的支持。前31種信號已經有了預定義值,每個信號有了確定的用途、含義以及對應的名字,并且每種信號都有各自的系統默認操作。如按鍵盤的CTRL C時,會產生SIGINT信號,對該信號的系統默認操作就是終止進程,后32個信號表示可靠信號。

實時信號與非實時信號

實時信號與非實時信號其實是從時間關系上進行的分類,與可靠信號與不可靠信號是相互對應的,非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。實時信號保證了發送的多個信號都能被接收,實時信號是POSIX標準的一部分,可用于應用進程。

一般我們也把非實時信號(不可靠信號)稱為標準信號,如果文檔中用到了這個詞,那么大家要知道,這里指的就是非實時信號(不可靠信號)。關于更多實時信號相關內容將會在8.11小節中介紹。

常見信號與默認行為

前面說到,Linux下對標準信號(不可靠信號、非實時信號)的編號為1~31,如示例代碼 8.1.1所示,接下來將介紹這些信號以及這些信號所對應的系統默認操作。

SIGINT

當用戶在終端按下中斷字符(通常是CTRL C)時,內核將發送SIGINT信號給前臺進程組中的每一個進程。該信號的系統默認操作是終止進程的運行。所以通常我們都會使用CTRL C來終止一個占用前臺的進程,原因在于大部分的進程會將該信號交給系統去處理,從而執行該信號的系統默認操作。

SIGQUIT

當用戶在終端按下退出字符(通常是CTRL )時,內核將發送SIGQUIT信號給前臺進程組中的每一個進程。該信號的系統默認操作是終止進程的運行、并生成可用于調試的核心轉儲文件。進程如果陷入無限循環、或不再響應時,使用SIGQUIT信號就很合適。所以對于一個前臺進程,既可以在終端按下中斷字符CTRL C、也可以按下退出字符CTRL 來終止,當然前提條件是,此進程會將SIGINT信號或SIGQUIT信號交給系統處理(也就是沒有將信號忽略或捕獲),進入執行該信號所對應的系統默認操作。

SIGILL

如果進程試圖執行非法(即格式不正確)的機器語言指令,系統將向進程發送該信號。該信號的系統默認操作是終止進程的運行。

SIGABRT

當進程調用abort()系統調用時(進程異常終止),系統會向該進程發送SIGABRT信號。該信號的系統默認操作是終止進程、并生成核心轉儲文件。

SIGBUS

產生該信號(總線錯誤,bus error)表示發生了某種內存訪問錯誤。該信號的系統默認操作是終止進程。

SIGFPE

該信號因特定類型的算術錯誤而產生,譬如除以0。該信號的系統默認操作是終止進程。

SIGKILL

此信號為“必殺(sure kill)”信號,用于殺死進程的終極辦法,此信號無法被進程阻塞、忽略或者捕獲,故而“一擊必殺”,總能終止進程。使用SIGINT信號和SIGQUIT信號雖然能終止進程,但是前提條件是該進程并沒有忽略或捕獲這些信號,如果使用SIGINT或SIGQUIT無法終止進程,那就使用“必殺信號”SIGKILL吧。Linux下有一個kill命令,kill命令可用于向進程發送信號,我們會使用"kill -9 xxx"命令來終止一個進程(xxx表示進程的pid),這里的-9其實指的就是發送編號為9的信號,也就是SIGKILL信號。

SIGUSR1

該信號和SIGUSR2信號供程序員自定義使用,內核絕不會為進程產生這些信號,在我們的程序中,可以使用這些信號來互通通知事件的發生,或是進程彼此同步操作。該信號的系統默認操作是終止進程。

SIGSEGV

這一信號非常常見,當應用程序對內存的引用無效時,操作系統就會向該應用程序發送該信號。引起對內存無效引用的原因很多,C語言中引發這些事件往往是解引用的指針里包含了錯誤地址(譬如,未初始化的指針),或者傳遞了一個無效參數供函數調用等。該信號的系統默認操作是終止進程。

SIGUSR2

與SIGUSR1信號相同。

SIGPIPE

涉及到管道和socket,當進程向已經關閉的管道、FIFO或套接字寫入信息時,那么系統將發送該信號給進程。該信號的系統默認操作是終止進程。

SIGALRM

與系統調用alarm()或setitimer()有關,應用程序中可以調用alarm()或setitimer()函數來設置一個定時器,當定時器定時時間到,那么內核將會發送SIGALRM信號給該應用程序,關于alarm()或setitimer()函數的使用,后面將會進行講解。該信號的系統默認操作是終止進程。

SIGTERM

這是用于終止進程的標準信號,也是kill命令所發送的默認信號(kill xxx,xxx表示進程pid),有時我們會直接使用"kill -9 xxx"顯式向進程發送SIGKILL信號來終止進程,然而這一做法通常是錯誤的,精心設計的應用程序應該會捕獲SIGTERM信號、并為其綁定一個處理函數,當該進程收到SIGTERM信號時,會在處理函數中清除臨時文件以及釋放其它資源,再而退出程序。如果直接使用SIGKILL信號終止進程,從而跳過了SIGTERM信號的處理函數,通常SIGKILL終止進程是不友好的方式、是暴力的方式,這種方式應該作為最后手段,應首先嘗試使用SIGTERM,實在不行再使用最后手段SIGKILL。

SIGCHLD

當父進程的某一個子進程終止時,內核會向父進程發送該信號。當父進程的某一個子進程因收到信號而停止或恢復時,內核也可能向父進程發送該信號。注意這里說的停止并不是終止,你可以理解為暫停。該信號的系統默認操作是忽略此信號,如果父進程希望被告知其子進程的這種狀態改變,則應捕獲此信號。

SIGCLD

與SIGCHLD信號同義。

SIGCONT

將該信號發送給已停止的進程,進程將會恢復運行。當進程接收到此信號時并不處于停止狀態,系統默認操作是忽略該信號,但如果進程處于停止狀態,則系統默認操作是使該進程繼續運行。

SIGSTOP

這是一個“必停”信號,用于停止進程(注意停止不是終止,停止只是暫停運行、進程并沒有終止),應用程序無法將該信號忽略或者捕獲,故而總能停止進程。

SIGTSTP

這也是一個停止信號,當用戶在終端按下停止字符(通常是CTRL Z),那么系統會將SIGTSTP信號發送給前臺進程組中的每一個進程,使其停止運行。

SIGXCPU

當進程的CPU時間超出對應的資源限制時,內核將發送此信號給該進程。

SIGVTALRM

應用程序調用setitimer()函數設置一個虛擬定時器,當定時器定時時間到時,內核將會發送該信號給進程。

SIGWINCH

在窗口環境中,當終端窗口尺寸發生變化時(譬如用戶手動調整了大小,應用程序調用ioctl()設置了大小等),系統會向前臺進程組中的每一個進程發送該信號。

SIGPOLL/SIGIO

這兩個信號同義。這兩個信號將會在高級IO章節內容中使用到,用于提示一個異步IO事件的發生,譬如應用程序打開的文件描述符發生了I/O事件時,內核會向應用程序發送SIGIO信號。

SIGSYS

如果進程發起的系統調用有誤,那么內核將發送該信號給對應的進程。

以上就是關于這些信號的簡單介紹內容,以上所介紹的這些信號并不包括Linux下所有的信號,僅給大家介紹了一下常見信號,表 8.3.1將對這些信號進行總結。

表 8.3.1 Linux信號總結

信號名稱

編號

描述

系統默認操作

SIGINT

2

終端中斷符

term

SIGQUIT

3

終端退出符

term core

SIGILL

4

非法硬件指令

term core

SIGABRT

6

異常終止(abort)

term core

SIGBUS

7

內存訪問錯誤

term core

SIGFPE

8

算術異常

term core

SIGKILL

9

終極終止信號

term

SIGUSR1

10

用戶自定義信號1

term

SIGSEGV

11

無效的內存引用

term core

SIGUSR2

12

用戶自定義信號2

term

SIGPIPE

13

管道關閉

term

SIGALRM

14

定時器超時(alarm)

term

SIGTERM

15

終止進程

term

SIGCHLD/SIGCLD

17

子進程終止或停止

ignore

SIGCONT

18

使停止狀態的進程繼續運行

cont

SIGSTOP

19

停止進程

stop

SIGTSTP

20

終端停止符

stop

SIGXCPU

24

超過CPU限制

term core

SIGVTALRM

26

虛擬定時器超時

term

SIGWINCH

28

終端窗口尺寸發生變化

ignore

SIGPOLL/SIGIO

29

異步I/O

term/ignore

SIGSYS

31

無效系統調用

term core

Tips:上表中,term表示終止進程;core表示生成核心轉儲文件,核心轉儲文件可用于調試,這個便不再給介紹了;ignore表示忽略信號;cont表示繼續運行進程;stop表示停止進程(注意停止不等于終止,而是暫停)。

進程對信號的處理

當進程接收到內核或用戶發送過來的信號之后,根據具體信號可以采取不同的處理方式:忽略信號、捕獲信號或者執行系統默認操作。Linux系統提供了系統調用signal()和sigaction()兩個函數用于設置信號的處理方式,本小節將向大家介紹這兩個系統調用的使用方法。

signal()函數

本節描述系統調用signal(),signal()函數是Linux系統下設置信號處理方式最簡單的接口,可將信號的處理方式設置為捕獲信號、忽略信號以及系統默認操作,此函數原型如下所示:

#include <signal.h>

typedef void (*sig_t)(int);

sig_t signal(int signum, sig_t handler);

使用該函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

signum:此參數指定需要進行設置的信號,可使用信號名(宏)或信號的數字編號,建議使用信號名。

handler:sig_t類型的函數指針,指向信號對應的信號處理函數,當進程接收到信號后會自動執行該處理函數;參數handler既可以設置為用戶自定義的函數,也就是捕獲信號時需要執行的處理函數,也可以設置為SIG_IGN或SIG_DFL,SIG_IGN表示此進程需要忽略該信號,SIG_DFL則表示設置為系統默認操作。sig_t函數指針的int類型參數指的是,當前觸發該函數的信號,可將多個信號綁定到同一個信號處理函數上,此時就可通過此參數來判斷當前觸發的是哪個信號。

Tips:SIG_IGN、SIG_DFL分別取值如下:

/* Fake signal functions. */

#define SIG_ERR ((sig_t) -1) /* Error return. */

#define SIG_DFL ((sig_t) 0) /* Default action. */

#define SIG_IGN ((sig_t) 1) /* Ignore signal. */

返回值:此函數的返回值也是一個sig_t類型的函數指針,成功情況下的返回值則是指向在此之前的信號處理函數;如果出錯則返回SIG_ERR,并會設置errno。

由此可知,signal()函數可以根據第二個參數handler的不同設置情況,可對信號進行不同的處理。

測試

signal()函數的用法其實非常簡單,為信號設置相應的處理方式,接下來編寫一個簡單地示例代碼對signal()函數進行測試。

示例代碼 8.4.1 signal()函數使用示例

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

static void sig_handler(int sig)

{

printf("Received signal: %dn", sig);

}

int main(int argc, char *argv[])

{

sig_t ret = NULL;

ret = signal(SIGINT, (sig_t)sig_handler);

if (SIG_ERR == ret) {

perror("signal error");

exit(-1);

}

/* 死循環 */

for ( ; ; ) { }

exit(0);

}

在上述示例代碼中,我們通過signal()函數將SIGINT(2)信號綁定到了一個用戶自定的處理函數上sig_handler(int sig),當進程收到SIGINT信號后會執行該函數然后運行printf打印語句。當運行程序之后,程序會卡在for死循環處,此時在終端按下中斷符CTRL C,系統便會給前臺進程組中的每一個進程發送SIGINT信號,我們測試程序便會收到該信號。

運行測試:

圖 8.4.1 測試結果

當運行程序之后,程序會占用終端稱為一個前臺進程,此時按下中斷符便會打印出信息(^C表示按下了中斷符)。平時大家使用CTRL C可以終止一個進程,而這里卻不能通過這種方式來終止這個測試程序,原因在于測試程序中捕獲了該信號,而對應的處理方式僅僅只是打印一條語句、而并不終止進程。

那此時該怎么關閉這個測試程序呢?前面給大家介紹了“一擊必殺”信號SIGKILL(編號為9),可向該進程發送SIGKILL暴力終止該進程,當然一般不推薦大家這樣使用,如果實在沒辦法才采取這種措施。新打開一個終端,使用ps命令找到該進程的pid號,再使用kill命令,如下所示:

圖 8.4.2 一擊必殺

此時測試程序就會強制終止:

圖 8.4.3 測試程序被終止

Tips:普通用戶只能殺死該用戶自己的進程,無權限殺死其它用戶的進程。

我們再執行一次測試程序,這里將測試程序放在后臺運行,然后再按下中斷符:

圖 8.4.4 測試結果

按下中斷符發現進程并沒有收到SIGINT信號,原因很簡單,因為進程并不是前臺進程、而是一個后臺進程,按下中斷符時系統并不會給后臺進程發送SIGINT信號。可以使用kill命令手動發送信號給我們的進程:

圖 8.4.5 測試結果

兩種不同狀態下信號的處理方式

通過上面的介紹,以及我們的測試實驗,不知大家是否出現了一個疑問?如果程序中沒有調用signal()函數為信號設置相應的處理方式,亦或者程序剛啟動起來并未運行到signal()處,那么這時進程接收到一個信號后是如何處理的呢?帶著這個問題來聊一聊。

程序啟動

當一個應用程序剛啟動的時候(或者程序中沒有調用signal()函數),通常情況下,進程對所有信號的處理方式都設置為系統默認操作。所以如果在我們的程序當中,沒有調用signal()為信號設置處理方式,則默認的處理方式便是系統默認操作。

所以為什么大家平時都可以使用CTRL C中斷符來終止一個進程,因為大部分情況下,應用程序中并不會為SIGINT信號設置處理方式,所以該信號的處理方式便是系統默認操作,當接收到信號之后便執行系統默認操作,而SIGINT信號的系統默認操作便是終止進程。

進程創建

當一個進程調用fork()創建子進程時,其子進程將會繼承父進程的信號處理方式,因為子進程在開始時復制了父進程的內存映像,所以信號捕獲函數的地址在子進程中是有意義的。

sigaction()函數

除了signal()之外,sigaction()系統調用是設置信號處理方式的另一選擇,事實上,推薦大家使用sigaction()函數。雖然signal()函數簡單好用,而sigaction()更為復雜,但作為回報,sigaction()也更具靈活性以及移植性。

sigaction()允許單獨獲取信號的處理函數而不是設置,并且還可以設置各種屬性對調用信號處理函數時的行為施以更加精準的控制,其函數原型如下所示:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

使用該函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

signum:需要設置的信號,除了SIGKILL信號和SIGSTOP信號之外的任何信號。

act:act參數是一個struct sigaction類型指針,指向一個struct sigaction數據結構,該數據結構描述了信號的處理方式,稍后介紹該數據結構;如果參數act不為NULL,則表示需要為信號設置新的處理方式;如果參數act為NULL,則表示無需改變信號當前的處理方式。

oldact:oldact參數也是一個struct sigaction類型指針,指向一個struct sigaction數據結構。如果參數oldact不為NULL,則會將信號之前的處理方式等信息通過參數oldact返回出來;如果無意獲取此類信息,那么可將該參數設置為NULL。

返回值:成功返回0;失敗將返回-1,并設置errno。

struct sigaction結構體

示例代碼 8.4.2 struct sigaction結構體

struct sigaction {

void (*sa_handler)(int);

void (*sa_sigaction)(int, siginfo_t *, void *);

sigset_t sa_mask;

int sa_flags;

void (*sa_restorer)(void);

};

結構體成員介紹:

sa_handler:指定信號處理函數,與signal()函數的handler參數相同。

sa_sigaction:也用于指定信號處理函數,這是一個替代的信號處理函數,他提供了更多的參數,可以通過該函數獲取到更多信息,這些信號通過siginfo_t參數獲取,稍后介紹該數據結構;sa_handler和sa_sigaction是互斥的,不能同時設置,對于標準信號來說,使用sa_handler就可以了,可通過SA_SIGINFO標志進行選擇。

sa_mask:參數sa_mask定義了一組信號,當進程在執行由sa_handler所定義的信號處理函數之前,會先將這組信號添加到進程的信號掩碼字段中,當進程執行完處理函數之后再恢復信號掩碼,將這組信號從信號掩碼字段中刪除。當進程在執行信號處理函數期間,可能又收到了同樣的信號或其它信號,從而打斷當前信號處理函數的執行,這就好點像中斷嵌套;通常我們在執行信號處理函數期間不希望被另一個信號所打斷,那么怎么做呢?那么就是通過信號掩碼來實現,如果進程接收到了信號掩碼中的這些信號,那么這個信號將會被阻塞暫時不能得到處理,直到這些信號從進程的信號掩碼中移除。在信號處理函數調用時,進程會自動將當前處理的信號添加到信號掩碼字段中,這樣保證了在處理一個給定的信號時,如果此信號再次發生,那么它將會被阻塞。如果用戶還需要在阻塞其它的信號,則可以通過設置參數sa_mask來完成(此參數是sigset_t類型變量,關于該類型的介紹信息請看8.8小節內容,關于信號掩碼還會在8.9小節中進一步介紹),信號掩碼可以避免一些信號之間的競爭狀態(也稱為競態)。

sa_restorer:該成員已過時,不要再使用了。

sa_flags:參數sa_flags指定了一組標志,這些標志用于控制信號的處理過程,可設置為如下這些標志(多個標志使用位或" | "組合):

SA_NOCLDSTOP

如果signum為SIGCHLD,則子進程停止時(即當它們接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU中的一種時)或恢復(即它們接收到SIGCONT)時不會收到SIGCHLD信號。

SA_NOCLDWAIT

如果signum是SIGCHLD,則在子進程終止時不要將其轉變為僵尸進程。

SA_NODEFER

不要阻塞從某個信號自身的信號處理函數中接收此信號。也就是說當進程此時正在執行某個信號的處理函數,默認情況下,進程會自動將該信號添加到進程的信號掩碼字段中,從而在執行信號處理函數期間阻塞該信號,默認情況下,我們期望進程在處理一個信號時阻塞同種信號,否則引起一些競態條件;如果設置了SA_NODEFER標志,則表示不對它進行阻塞。

SA_RESETHAND

執行完信號處理函數之后,將信號的處理方式設置為系統默認操作。

SA_RESTART

被信號中斷的系統調用,在信號處理完成之后將自動重新發起。

SA_SIGINFO

如果設置了該標志,則表示使用sa_sigaction作為信號處理函數、而不是sa_handler,關于sa_sigaction信號處理函數的參數信息。

以上就是關于struct sigaction結構體相關的內容介紹了,接下編寫程序進行實戰測試。

siginfo_t結構體

示例代碼 8.4.3 siginfo_t結構體

siginfo_t {

int si_signo; /* Signal number */

int si_errno; /* An errno value */

int si_code; /* Signal code */

int si_trapno; /* Trap number that caused hardware-generated signal(unused on most architectures) */

pid_t si_pid; /* Sending process ID */

uid_t si_uid; /* Real user ID of sending process */

int si_status; /* Exit value or signal */

clock_t si_utime; /* User time consumed */

clock_t si_stime; /* System time consumed */

sigval_t si_value; /* Signal value */

int si_int; /* POSIX.1b signal */

void *si_ptr; /* POSIX.1b signal */

int si_overrun; /* Timer overrun count; POSIX.1b timers */

int si_timerid; /* Timer ID; POSIX.1b timers */

void *si_addr; /* Memory location which caused fault */

long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */

int si_fd; /* File descriptor */

short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */

void *si_call_addr; /* Address of system call instruction(since Linux 3.5) */

int si_syscall; /* Number of attempted system call(since Linux 3.5) */

unsigned int si_arch; /* Architecture of attempted system call(since Linux 3.5) */

}

這個結構體就不給大家介紹了,使用man手冊查看sigaction()函數幫助信息時,在下面會有介紹。

測試

這里使用sigaction()函數實現與示例代碼 8.4.1相同的功能。

示例代碼 8.4.4 sigaction()函數使用示例

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

static void sig_handler(int sig)

{

printf("Received signal: %dn", sig);

}

int main(int argc, char *argv[])

{

struct sigaction sig = {0};

int ret;

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

ret = sigaction(SIGINT, &sig, NULL);

if (-1 == ret) {

perror("sigaction error");

exit(-1);

}

/* 死循環 */

for ( ; ; ) { }

exit(0);

}

運行測試:

圖 8.4.6 測試結果

關于信號處理函數說明

一般而言,將信號處理函數設計越簡單越好,這就好比中斷處理函數,越快越好,不要在處理函數中做大量消耗CPU時間的事情,這一個重要的原因在于,設計的越簡單這將降低引發信號競爭條件的風險。

向進程發送信號

與kill命令相類似,Linux系統提供了kill()系統調用,一個進程可通過kill()向另一個進程發送信號;除了kill()系統調用之外,Linux系統還提供了系統調用killpg()以及庫函數raise(),也可用于實現發送信號的功能,本小節將向大家進行介紹。

kill()函數

kill()系統調用可將信號發送給指定的進程或進程組中的每一個進程,其函數原型如下所示:

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int sig);

使用該函數需要包含頭文件<sys/types.h>和<signal.h>。

函數參數和返回值含義如下:

pid:參數pid為正數的情況下,用于指定接收此信號的進程pid;除此之外,參數pid也可設置為0或-1以及小于-1等不同值,稍后給說明。

sig:參數sig指定需要發送的信號,也可設置為0,如果參數sig設置為0則表示不發送信號,但任執行錯誤檢查,這通常可用于檢查參數pid指定的進程是否存在。

返回值:成功返回0;失敗將返回-1,并設置errno。

參數pid不同取值含義:

如果pid為正,則信號sig將發送到pid指定的進程。

如果pid等于0,則將sig發送到當前進程的進程組中的每個進程。

如果pid等于-1,則將sig發送到當前進程有權發送信號的每個進程,但進程1(init)除外。

如果pid小于-1,則將sig發送到ID為-pid的進程組中的每個進程。

進程中將信號發送給另一個進程是需要權限的,并不是可以隨便給任何一個進程發送信號,超級用戶root進程可以將信號發送給任何進程,但對于非超級用戶(普通用戶)進程來說,其基本規則是發送者進程的實際用戶ID或有效用戶ID必須等于接收者進程的實際用戶ID或有效用戶ID。

從上面介紹可知,當sig為0時,仍可進行正常執行的錯誤檢查,但不會發送信號,這通常可用于確定一個特定的進程是否存在,如果向一個不存在的進程發送信號,kill()將會返回-1,errno將被設置為ESRCH,表示進程不存在。

測試

(1)使用kill()函數向一個指定的進程發送信號。

示例代碼 8.5.1 kill()函數使用示例

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <signal.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

int pid;

/* 判斷傳參個數 */

if (2 > argc)

exit(-1);

/* 將傳入的字符串轉為整形數字 */

pid = atoi(argv[1]);

printf("pid: %dn", pid);

/* 向pid指定的進程發送信號 */

if (-1 == kill(pid, SIGINT)) {

perror("kill error");

exit(-1);

}

exit(0);

}

以上代碼通過kill()函數向指定進程發送SIGINT信號,可通過外部傳參將接收信號的進程pid傳入到程序中,再執行該測試代碼之前,需要運行先一個用于接收此信號的進程,接收信號的進程直接使用示例代碼 8.4.3程序。

運行測試:

圖 8.5.1 測試結果

testApp1是示例代碼 8.4.3對應的程序,testApp則是示例代碼 8.5.1對應的程序,首先執行"./testApp1 &"將接收信號的程序置于后臺運行(其進程pid為21825),接著執行"./testApp 21825"向接收信號的進程發送SIGINT信號。

raise()

有時進程需要向自身發送信號,raise()函數可用于實現這一要求,raise()函數原型如下所示(此函數為C庫函數):

#include <signal.h>

int raise(int sig);

使用該函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

sig:需要發送的信號。

返回值:成功返回0;失敗將返回非零值。

raise()其實等價于:

kill(getpid(), sig);

Tips:getpid()函數用于獲取進程自身的pid。

測試

示例代碼 8.5.2 raise()函數使用示例

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig)

{

printf("Received signal: %dn", sig);

}

int main(int argc, char *argv[])

{

struct sigaction sig = {0};

int ret;

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

ret = sigaction(SIGINT, &sig, NULL);

if (-1 == ret) {

perror("sigaction error");

exit(-1);

}

for ( ; ; ) {

/* 向自身發送SIGINT信號 */

if (0 != raise(SIGINT)) {

printf("raise errorn");

exit(-1);

}

sleep(3); // 每隔3秒發送一次

}

exit(0);

}

運行結果:

圖 8.5.2 測試結果

alarm()和pause()函數

本小節向大家介紹兩個系統調用alarm()和pause()。

alarm()函數

使用alarm()函數可以設置一個定時器(鬧鐘),當定時器定時時間到時,內核會向進程發送SIGALRM信號,其函數原型如下所示:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

函數參數和返回值:

seconds:設置定時時間,以秒為單位;如果參數seconds等于0,則表示取消之前設置的alarm鬧鐘。

返回值:如果在調用alarm()時,之前已經為該進程設置了alarm鬧鐘還沒有超時,則該鬧鐘的剩余值作為本次alarm()函數調用的返回值,之前設置的鬧鐘則被新的替代;否則返回0。

參數seconds的值是產生SIGALRM信號需要經過的時鐘秒數,當這一刻到達時,由內核產生該信號,每個進程只能設置一個alarm鬧鐘;雖然SIGALRM信號的系統默認操作是終止進程,但是如果程序當中設置了alarm鬧鐘,但大多數使用鬧鐘的進程都會捕獲此信號。

需要注意的是alarm鬧鐘并不能循環觸發,只能觸發一次,若想要實現循環觸發,可以在SIGALRM信號處理函數中再次調用alarm()函數設置定時器。

測試

使用alarm()來設計一個鬧鐘。

示例代碼 8.6.1 alarm()函數使用示例

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig)

{

puts("Alarm timeout");

exit(0);

}

int main(int argc, char *argv[])

{

struct sigaction sig = {0};

int second;

/* 檢驗傳參個數 */

if (2 > argc)

exit(-1);

/* 為SIGALRM信號綁定處理函數 */

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

if (-1 == sigaction(SIGALRM, &sig, NULL)) {

perror("sigaction error");

exit(-1);

}

/* 啟動alarm定時器 */

second = atoi(argv[1]);

printf("定時時長: %d秒n", second);

alarm(second);

/* 循環 */

for ( ; ; )

sleep(1);

exit(0);

}

運行測試:

圖 8.6.1 測試結果

pause()函數

pause()系統調用可以使得進程暫停運行、進入休眠狀態,直到進程捕獲到一個信號為止,只有執行了信號處理函數并從其返回時,pause()才返回,在這種情況下,pause()返回-1,并且將errno設置為EINTR。其函數原型如下所示:

#include <unistd.h>

int pause(void);

測試

通過alarm()和pause()函數模擬sleep功能。

示例代碼 8.6.2 alarm()和pause()模擬sleep

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig)

{

puts("Alarm timeout");

}

int main(int argc, char *argv[])

{

struct sigaction sig = {0};

int second;

/* 檢驗傳參個數 */

if (2 > argc)

exit(-1);

/* 為SIGALRM信號綁定處理函數 */

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

if (-1 == sigaction(SIGALRM, &sig, NULL)) {

perror("sigaction error");

exit(-1);

}

/* 啟動alarm定時器 */

second = atoi(argv[1]);

printf("定時時長: %d秒n", second);

alarm(second);

/* 進入休眠狀態 */

pause();

puts("休眠結束");

exit(0);

}

運行測試:

圖 8.6.2 測試結果

信號集

通常我們需要有一個能表示多個信號(一組信號)的數據類型---信號集(signal set),很多系統調用都使用到了信號集這種數據類型來作為參數傳遞,譬如sigaction()函數、sigprocmask()函數、sigpending()函數等。本小節向大家介紹信號集這個數據類型。

信號集其實就是sigset_t類型數據結構,來看看:

# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))

typedef struct

{

unsigned long int __val[_SIGSET_NWORDS];

} sigset_t;

使用這個結構體可以表示一組信號,將多個信號添加到該數據結構中,當然Linux系統了用于操作sigset_t信號集的API,譬如sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember(),接下來向大家介紹。

初始化信號集

sigemptyset()和sigfillset()用于初始化信號集。sigemptyset()初始化信號集,使其不包含任何信號;而sigfillset()函數初始化信號集,使其包含所有信號(包括所有實時信號),函數原型如下:

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

使用這些函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

set:指向需要進行初始化的信號集變量。

返回值:成功返回0;失敗將返回-1,并設置errno。

使用示例

初始化為空信號集:

sigset_t sig_set;

sigemptyset(&sig_set);

初始化信號集,使其包含所有信號:

sigset_t sig_set;

sigfillset(&sig_set);

向信號集中添加/刪除信號

分別使用sigaddset()和sigdelset()函數向信號集中添加或移除一個信號,函數原型如下:

#include <signal.h>

int sigaddset(sigset_t *set, int signum);

int sigdelset(sigset_t *set, int signum);

函數參數和返回值含義如下:

set:指向信號集。

signum:需要添加/刪除的信號。

返回值:成功返回0;失敗將返回-1,并設置errno。

使用示例

向信號集中添加信號:

sigset_t sig_set;

sigemptyset(&sig_set);

sigaddset(&sig_set, SIGINT);

從信號集中移除信號:

sigset_t sig_set;

sigfillset(&sig_set);

sigdelset(&sig_set, SIGINT);

測試信號是否在信號集中

使用sigismember()函數可以測試某一個信號是否在指定的信號集中,函數原型如下所示:

#include <signal.h>

int sigismember(const sigset_t *set, int signum);

函數參數和返回值含義如下:

set:指定信號集。

signum:需要進行測試的信號。

返回值:如果信號signum在信號集set中,則返回1;如果不在信號集set中,則返回0;失敗則返回-1,并設置errno。

使用示例

判斷SIGINT信號是否在sig_set信號集中:

sigset_t sig_set;

......

if (1 == sigismember(&sig_set, SIGINT))

puts("信號集中包含SIGINT信號");

else if (!sigismember(&sig_set, SIGINT))

puts("信號集中不包含SIGINT信號");

獲取信號的描述信息

在Linux下,每個信號都有一串與之相對應的字符串描述信息,用于對該信號進行相應的描述。這些字符串位于sys_siglist數組中,sys_siglist數組是一個char *類型的數組,數組中的每一個元素存放的是一個字符串指針,指向一個信號描述信息。譬如,可以使用sys_siglist[SIGINT]來獲取對SIGINT信號的描述。我們編寫一個簡單地程序進行測試:

Tips:使用sys_siglist數組需要包含<signal.h>頭文件。

示例代碼 8.8.1 從sys_siglist數組獲取信號描述信息

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

printf("SIGINT描述信息: %sn", sys_siglist[SIGINT]);

printf("SIGQUIT描述信息: %sn", sys_siglist[SIGQUIT]);

printf("SIGBUS描述信息: %sn", sys_siglist[SIGBUS]);

exit(0);

}

運行結果:

圖 8.8.1 測試結果

從圖中打印信息可知,這個描述信息其實非常簡潔,沒什么太多的信息。

strsignal()函數

除了直接使用sys_siglist數組獲取描述信息之外,還可以使用strsignal()函數。較之于直接引用sys_siglist數組,更推薦使用strsignal()函數,其函數原型如下所示:

#include <string.h>

char *strsignal(int sig);

使用strsignal()函數需要包含頭文件<string.h>,這是一個庫函數。

調用strsignal()函數將會獲取到參數sig指定的信號對應的描述信息,返回該描述信息字符串的指針;函數會對參數sig進行檢查,若傳入的sig無效,則會返回"Unknown signal"信息。

使用示例

示例代碼 8.8.2 strsignal()函數使用示例

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

printf("SIGINT描述信息: %sn", strsignal(SIGINT));

printf("SIGQUIT描述信息: %sn", strsignal(SIGQUIT));

printf("SIGBUS描述信息: %sn", strsignal(SIGBUS));

printf("編號為1000的描述信息: %sn", strsignal(1000));

exit(0);

}

測試結果:

圖 8.8.2 測試結果

psignal()函數

psignal()可以在標準錯誤(stderr)上輸出信號描述信息,其函數原型如下所示:

#include <signal.h>

void psignal(int sig, const char *s);

調用psignal()函數會將參數sig指定的信號對應的描述信息輸出到標準錯誤,并且還允許調用者添加一些輸出信息,由參數s指定;所以整個輸出信息由字符串s、冒號、空格、描述信號編號sig的字符串和尾隨的換行符組成。

使用示例

示例代碼 8.8.3 psignal()函數使用示例

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

psignal(SIGINT, "SIGINT信號描述信息");

psignal(SIGQUIT, "SIGQUIT信號描述信息");

psignal(SIGBUS, "SIGBUS信號描述信息");

exit(0);

}

運行結果:

圖 8.8.3 運行結果

信號掩碼(阻塞信號傳遞)

內核為每一個進程維護了一個信號掩碼(其實就是一個信號集),即一組信號。當進程接收到一個屬于信號掩碼中定義的信號時,該信號將會被阻塞、無法傳遞給進程進行處理,那么內核會將其阻塞,直到該信號從信號掩碼中移除,內核才會把該信號傳遞給進程從而得到處理。

向信號掩碼中添加一個信號,通常有如下幾種方式:

當應用程序調用signal()或sigaction()函數為某一個信號設置處理方式時,進程會自動將該信號添加到信號掩碼中,這樣保證了在處理一個給定的信號時,如果此信號再次發生,那么它將會被阻塞;當然對于sigaction()而言,是否會如此,需要根據sigaction()函數是否設置了SA_NODEFER標志而定;當信號處理函數結束返回后,會自動將該信號從信號掩碼中移除。

使用sigaction()函數為信號設置處理方式時,可以額外指定一組信號,當調用信號處理函數時將該組信號自動添加到信號掩碼中,當信號處理函數結束返回后,再將這組信號從信號掩碼中移除;通過sa_mask參數進行設置,參考8.4.2小節內容。

除了以上兩種方式之外,還可以使用sigprocmask()系統調用,隨時可以顯式地向信號掩碼中添加/移除信號。

sigprocmask()函數原型如下所示:

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

使用該函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

how:參數how指定了調用函數時的一些行為。

set:將參數set指向的信號集內的所有信號添加到信號掩碼中或者從信號掩碼中移除;如果參數set為NULL,則表示無需對當前信號掩碼作出改動。

oldset:如果參數oldset不為NULL,在向信號掩碼中添加新的信號之前,獲取到進程當前的信號掩碼,存放在oldset所指定的信號集中;如果為NULL則表示不獲取當前的信號掩碼。

返回值:成功返回0;失敗將返回-1,并設置errno。

參數how可以設置為以下宏:

SIG_BLOCK:將參數set所指向的信號集內的所有信號添加到進程的信號掩碼中。換言之,將信號掩碼設置為當前值與set的并集。

SIG_UNBLOCK:將參數set指向的信號集內的所有信號從進程信號掩碼中移除。

SIG_SETMASK:進程信號掩碼直接設置為參數set指向的信號集。

使用示例

將信號SIGINT添加到進程的信號掩碼中:

int ret;

/* 定義信號集 */

sigset_t sig_set;

/* 將信號集初始化為空 */

sigemptyset(&sig_set);

/* 向信號集中添加SIGINT信號 */

sigaddset(&sig_set, SIGINT);

/* 向進程的信號掩碼中添加信號 */

ret = sigprocmask(SIG_BLOCK, &sig_set, NULL);

if (-1 == ret) {

perror("sigprocmask error");

exit(-1);

}

從信號掩碼中移除SIGINT信號:

int ret;

/* 定義信號集 */

sigset_t sig_set;

/* 將信號集初始化為空 */

sigemptyset(&sig_set);

/* 向信號集中添加SIGINT信號 */

sigaddset(&sig_set, SIGINT);

/* 從信號掩碼中移除信號 */

ret = sigprocmask(SIG_UNBLOCK, &sig_set, NULL);

if (-1 == ret) {

perror("sigprocmask error");

exit(-1);

}

下面我們編寫一個簡單地測試代碼,驗證信號掩碼的作用,測試代碼如下所示:

示例代碼 8.9.1 測試信號掩碼的作用

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig)

{

printf("執行信號處理函數...n");

}

int main(void)

{

struct sigaction sig = {0};

sigset_t sig_set;

/* 注冊信號處理函數 */

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

if (-1 == sigaction(SIGINT, &sig, NULL))

exit(-1);

/* 信號集初始化 */

sigemptyset(&sig_set);

sigaddset(&sig_set, SIGINT);

/* 向信號掩碼中添加信號 */

if (-1 == sigprocmask(SIG_BLOCK, &sig_set, NULL))

exit(-1);

/* 向自己發送信號 */

raise(SIGINT);

/* 休眠2秒 */

sleep(2);

printf("休眠結束n");

/* 從信號掩碼中移除添加的信號 */

if (-1 == sigprocmask(SIG_UNBLOCK, &sig_set, NULL))

exit(-1);

exit(0);

}

上述代碼中,我們為SIGINT信號注冊了一個處理函數sig_handler,當進程接收到該信號之后就會執行它;然后調用sigprocmask函數將SIGINT信號添加到信號掩碼中,然后再調用raise(SIGINT)向自己發送一個SIGINT信號,如果信號掩碼沒有生效、也就意味著SIGINT信號不會被阻塞,那么調用raise(SIGINT)之后應該就會立馬執行sig_handler函數,從而打印出"執行信號處理函數..."字符串信息;如果設置的信號掩碼生效了,則并不會立馬執行信號處理函數,而是在2秒后才執行,因為程序中使用sleep(2)休眠了2秒鐘之后,才將SIGINT信號從信號掩碼中移除,故而進程才會處理該信號,在移除之前接收到該信號會將其阻塞。

編譯測試結果如下:

圖 8.9.1 測試結果

阻塞等待信號sigsuspend()

上一小節已經說明,更改進程的信號掩碼可以阻塞所選擇的信號,或解除對它們的阻塞。使用這種技術可以保護不希望由信號中斷的關鍵代碼段。如果希望對一個信號解除阻塞后,然后調用pause()以等待之前被阻塞的信號的傳遞,這將如何?譬如有如下代碼段:

sigset_t new_set, old_set;

/* 信號集初始化 */

sigemptyset(&new_set);

sigaddset(&new_set, SIGINT);

/* 向信號掩碼中添加信號 */

if (-1 == sigprocmask(SIG_BLOCK, &new_set, &old_set))

exit(-1);

/* 受保護的關鍵代碼段 */

......

/**********************/

/* 恢復信號掩碼 */

if (-1 == sigprocmask(SIG_SETMASK, &old_set, NULL))

exit(-1);

pause();/* 等待信號喚醒 */

執行受保護的關鍵代碼時不希望被SIGINT信號打斷,所以在執行關鍵代碼之前將SIGINT信號添加到進程的信號掩碼中,執行完畢之后再恢復之前的信號掩碼。最后調用了pause()阻塞等待被信號喚醒,如果此時發生了信號則會被喚醒、從pause返回繼續執行;考慮到這樣一種情況,如果信號的傳遞恰好發生在第二次調用sigprocmask()之后、pause()之前,如果確實發生了這種情況,就會產生一個問題,信號傳遞過來就會導致執行信號的處理函數,而從處理函數返回后又回到主程序繼續執行,從而進入到pause()被阻塞,知道下一次信號發生時才會被喚醒,這有違代碼的本意。

雖然信號傳遞發生在這個時間段的可能性并不大,但并不是完全沒有可能,這必然是一個缺陷,要避免這個問題,需要將恢復信號掩碼和pause()掛起進程這兩個動作封裝成一個原子操作,這正是sigsuspend()系統調用的目的所在,sigsuspend()函數原型如下所示:

#include <signal.h>

int sigsuspend(const sigset_t *mask);

使用該函數需要包含頭文件#include <signal.h>。

函數參數和返回值含義如下:

mask:參數mask指向一個信號集。

返回值:sigsuspend()始終返回-1,并設置errno來指示錯誤(通常為EINTR),表示被信號所中斷,如果調用失敗,將errno設置為EFAULT。

sigsuspend()函數會將參數mask所指向的信號集來替換進程的信號掩碼,也就是將進程的信號掩碼設置為參數mask所指向的信號集,然后掛起進程,直到捕獲到信號被喚醒(如果捕獲的信號是mask信號集中的成員,將不會喚醒、繼續掛起)、并從信號處理函數返回,一旦從信號處理函數返回,sigsuspend()會將進程的信號掩碼恢復成調用前的值。

調用sigsuspend()函數相當于以不可中斷(原子操作)的方式執行以下操作:

sigprocmask(SIG_SETMASK, &mask, &old_mask);

pause();

sigprocmask(SIG_SETMASK, &old_mask, NULL);

使用示例

示例代碼 8.10.1 sigsuspend()函數使用示例

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig)

{

printf("執行信號處理函數...n");

}

int main(void)

{

struct sigaction sig = {0};

sigset_t new_mask, old_mask, wait_mask;

/* 信號集初始化 */

sigemptyset(&new_mask);

sigaddset(&new_mask, SIGINT);

sigemptyset(&wait_mask);

/* 注冊信號處理函數 */

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

if (-1 == sigaction(SIGINT, &sig, NULL))

exit(-1);

/* 向信號掩碼中添加信號 */

if (-1 == sigprocmask(SIG_BLOCK, &new_mask, &old_mask))

exit(-1);

/* 執行保護代碼段 */

puts("執行保護代碼段");

/******************/

/* 掛起、等待信號喚醒 */

if (-1 != sigsuspend(&wait_mask))

exit(-1);

/* 恢復信號掩碼 */

if (-1 == sigprocmask(SIG_SETMASK, &old_mask, NULL))

exit(-1);

exit(0);

}

在上述代碼中,我們希望執行受保護代碼段時不被SIGINT中斷信號打斷,所以在執行保護代碼段之前將SIGINT信號添加到進程的信號掩碼中,執行完受保護的代碼段之后,調用sigsuspend()掛起進程,等待被信號喚醒,被喚醒之后再解除SIGINT信號的阻塞狀態。

實時信號

如果進程當前正在執行信號處理函數,在處理信號期間接收到了新的信號,如果該信號是信號掩碼中的成員,那么內核會將其阻塞,將該信號添加到進程的等待信號集(等待被處理,處于等待狀態的信號)中,為了確定進程中處于等待狀態的是哪些信號,可以使用sigpending()函數獲取。

sigpending()函數

其函數原型如下所示:

#include <signal.h>

int sigpending(sigset_t *set);

使用該函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

set:處于等待狀態的信號會存放在參數set所指向的信號集中。

返回值:成功返回0;失敗將返回-1,并設置errno。

使用示例

判斷SIGINT信號當前是否處于等待狀態

/* 定義信號集 */

sigset_t sig_set;

/* 將信號集初始化為空 */

sigemptyset(&sig_set);

/* 獲取當前處于等待狀態的信號 */

sigpending(&sig_set);

/* 判斷SIGINT信號是否處于等待狀態 */

if (1 == sigismember(&sig_set, SIGINT))

puts("SIGINT信號處于等待狀態");

else if (!sigismember(&sig_set, SIGINT))

puts("SIGINT信號未處于等待狀態");

發送實時信號

等待信號集只是一個掩碼,僅表明一個信號是否發生,而不能表示其發生的次數。換言之,如果一個同一個信號在阻塞狀態下產生了多次,那么會將該信號記錄在等待信號集中,并在之后僅傳遞一次(僅當做發生了一次),這是標準信號的缺點之一。

實時信號較之于標準信號,其優勢如下:

實時信號的信號范圍有所擴大,可應用于應用程序自定義的目的,而標準信號僅提供了兩個信號可用于應用程序自定義使用:SIGUSR1和SIGUSR2。

內核對于實時信號所采取的是隊列化管理。如果將某一實時信號多次發送給另一個進程,那么將會多次傳遞此信號。相反,對于某一標準信號正在等待某一進程,而此時即使再次向該進程發送此信號,信號也只會傳遞一次。

當發送一個實時信號時,可為信號指定伴隨數據(一整形數據或者指針值),供接收信號的進程在它的信號處理函數中獲取。

不同實時信號的傳遞順序得到保障。如果有多個不同的實時信號處于等待狀態,那么將率先傳遞具有最小編號的信號。換言之,信號的編號越小,其優先級越高,如果是同一類型的多個信號在排隊,那么信號(以及伴隨數據)的傳遞順序與信號發送來時的順序保持一致。

Linux內核定義了31個不同的實時信號,信號編號范圍為34~64,使用SIGRTMIN表示編號最小的實時信號,使用SIGRTMAX表示編號最大的實時信號,其它信號編號可使用這兩個宏加上一個整數或減去一個整數。

應用程序當中使用實時信號,需要有以下的兩點要求:

發送進程使用sigqueue()系統調用向另一個進程發送實時信號以及伴隨數據。

接收實時信號的進程要為該信號建立一個信號處理函數,使用sigaction函數為信號建立處理函數,并加入SA_SIGINFO,這樣信號處理函數才能夠接收到實時信號以及伴隨數據,也就是要使用sa_sigaction指針指向的處理函數,而不是sa_handler,當然允許應用程序使用sa_handler,但這樣就不能獲取到實時信號的伴隨數據了。

使用sigqueue()函數發送實時信號,其函數原型如下所示:

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

使用該函數需要包含頭文件<signal.h>。

函數參數和返回值含義如下:

pid:指定接收信號的進程對應的pid,將信號發送給該進程。

sig:指定需要發送的信號。與kill()函數一樣,也可將參數sig設置為0,用于檢查參數pid所指定的進程是否存在。

value:參數value指定了信號的伴隨數據,union sigval數據類型。

返回值:成功將返回0;失敗將返回-1,并設置errno。

union sigval數據類型(共用體)如下所示:

typedef union sigval

{

int sival_int;

void *sival_ptr;

} sigval_t;

攜帶的伴隨數據,既可以指定一個整形的數據,也可以指定一個指針。

使用示例

(1)發送進程使用sigqueue()系統調用向另一個進程發送實時信號

示例代碼 8.11.1 使用sigqueue()函數發送信號

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

int main(int argc, char *argv[])

{

union sigval sig_val;

int pid;

int sig;

/* 判斷傳參個數 */

if (3 > argc)

exit(-1);

/* 獲取用戶傳遞的參數 */

pid = atoi(argv[1]);

sig = atoi(argv[2]);

printf("pid: %dnsignal: %dn", pid, sig);

/* 發送信號 */

sig_val.sival_int = 10; //伴隨數據

if (-1 == sigqueue(pid, sig, sig_val)) {

perror("sigqueue error");

exit(-1);

}

puts("信號發送成功!");

exit(0);

}

(2)接收進程使用sigaction()函數為信號綁定處理函數

示例代碼 8.11.2 使用sigaction()函數為實時信號綁定處理函數

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig, siginfo_t *info, void *context)

{

sigval_t sig_val = info->si_value;

printf("接收到實時信號: %dn", sig);

printf("伴隨數據為: %dn", sig_val.sival_int);

}

int main(int argc, char *argv[])

{

struct sigaction sig = {0};

int num;

/* 判斷傳參個數 */

if (2 > argc)

exit(-1);

/* 獲取用戶傳遞的參數 */

num = atoi(argv[1]);

/* 為實時信號綁定處理函數 */

sig.sa_sigaction = sig_handler;

sig.sa_flags = SA_SIGINFO;

if (-1 == sigaction(num, &sig, NULL)) {

perror("sigaction error");

exit(-1);

}

/* 死循環 */

for ( ; ; )

sleep(1);

exit(0);

}

異常退出abort()函數

在3.3小節中給大家介紹了應用程序中結束進程的幾種方法,譬如使用exit()、_exit()或_Exit()這些函數來終止進程,然后這些方法使用于正常退出應用程序,而對于異常退出程序,則一般使用abort()庫函數,使用abort()終止進程運行,會生成核心轉儲文件,可用于判斷程序調用abort()時的程序狀態。

abort()函數原型如下所示:

#include <stdlib.h>

void abort(void);

函數abort()通常產生SIGABRT信號來終止調用該函數的進程,SIGABRT信號的系統默認操作是終止進程運行、并生成核心轉儲文件;當調用abort()函數之后,內核會向進程發送SIGABRT信號。

使用示例

示例代碼 8.12.1 abort()終止進程

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

static void sig_handler(int sig)

{

printf("接收到信號: %dn", sig);

}

int main(int argc, char *argv[])

{

struct sigaction sig = {0};

sig.sa_handler = sig_handler;

sig.sa_flags = 0;

if (-1 == sigaction(SIGABRT, &sig, NULL)) {

perror("sigaction error");

exit(-1);

}

sleep(2);

abort(); // 調用abort

for ( ; ; )

sleep(1);

exit(0);

}

運行測試:

圖 8.12.1 測試結果

從打印信息可知,即使在我們的程序當中捕獲了SIGABRT信號,但是程序依然會無情的終止,無論阻塞或忽略SIGABRT信號,abort()調用均不收到影響,總會成功終止進程。

免責聲明:本文由用戶上傳,如有侵權請聯系刪除!