Linux進程間通信——使用信號量

//轉自http://blog.csdn.net/ljianhui/article/details/10243617

這篇文章將講述別一種進程間通信的機制——信號量。注意請不要把它與之前所說的信號混淆起來,信號與信號量是不同的兩種事物。有關信號的更多內容,可以閱讀我的另一篇文章:Linux進程間通信——使用信號。下面就進入信號量的講解。


一、什么是信號量
為了防止出現因多個程序同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成并使用令牌來授權,在任一時刻只能有一個執行線程訪問代碼的臨界區域。臨界區域是指執行數據更新的代碼需要獨占式地執行。而信號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的。

信號量是一個特殊的變量,程序對其訪問都是原子操作,且只允許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操作。最簡單的信號量是只能取0和1的變量,這也是信號量最常見的一種形式,叫做二進制信號量。而可以取多個正整數的信號量被稱為通用信號量。這里主要討論二進制信號量。

二、信號量的工作原理
由于信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進程的執行
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就給它加1.

舉個例子,就是兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操作,它將得到信號量,并可以進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,因為當它試圖執行P(sv)時,sv為0,它會被掛起以等待第一個進程離開臨界區域并執行V(sv)釋放信號量,這時第二個進程就可以恢復執行。

三、Linux的信號量機制
Linux提供了一組精心設計的信號量接口來對信號進行操作,它們不只是針對二進制信號量,下面將會對這些函數進行介紹,但請注意,這些函數都是用來對成組的信號量值進行操作的。它們聲明在頭文件sys/sem.h中。

1、semget函數
它的作用是創建一個新信號量或取得一個已有信號量,原型為:
[cpp] view plaincopyprint?
  1. int?semget(key_t?key,?int?num_sems,?int?sem_flags);??
第一個參數key是整數值(唯一非零),不相關的進程可以通過它訪問一個信號量,它代表程序可能要使用的某個資源,程序對所有信號量的訪問都是間接的,程序先通過調用semget函數并提供一個鍵,再由系統生成一個相應的信號標識符(semget函數的返回值),只有semget函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數返回的信號量標識符。如果多個程序使用相同的key值,key將負責協調工作。

第二個參數num_sems指定需要的信號量數目,它的值幾乎總是1。

第三個參數sem_flags是一組標志,當想要當信號量不存在時創建一個新的信號量,可以和值IPC_CREAT做按位或操作。設置了IPC_CREAT標志后,即使給出的鍵是一個已有信號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則可以創建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。

semget函數成功返回一個相應信號標識符(非零),失敗返回-1.

2、semop函數
它的作用是改變信號量的值,原型為:
[cpp] view plaincopyprint?
  1. int?semop(int?sem_id,?struct?sembuf?*sem_opa,?size_t?num_sem_ops);??
sem_id是由semget返回的信號量標識符,sembuf結構的定義如下:
[cpp] view plaincopyprint?
  1. struct?sembuf{??
  2. ????short?sem_num;//除非使用一組信號量,否則它為0??
  3. ????short?sem_op;//信號量在一次操作中需要改變的數據,通常是兩個數,一個是-1,即P(等待)操作,??
  4. ????????????????????//一個是+1,即V(發送信號)操作。??
  5. ????short?sem_flg;//通常為SEM_UNDO,使操作系統跟蹤信號,??
  6. ????????????????????//并在進程沒有釋放該信號量而終止時,操作系統釋放信號量??
  7. };??
3、semctl函數
該函數用來直接控制信號量信息,它的原型為:
[cpp] view plaincopyprint?
  1. int?semctl(int?sem_id,?int?sem_num,?int?command,?...);??
如果有第四個參數,它通常是一個union semum結構,定義如下:
[cpp] view plaincopyprint?
  1. union?semun{??
  2. ????int?val;??
  3. ????struct?semid_ds?*buf;??
  4. ????unsigned?short?*arry;??
  5. };??
前兩個參數與前面一個函數中的一樣,command通常是下面兩個值中的其中一個
SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。
IPC_RMID:用于刪除一個已經無需繼續使用的信號量標識符。

四、進程使用信號量通信
下面使用一個例子來說明進程間如何使用信號量來進行通信,這個例子是兩個相同的程序同時向屏幕輸出數據,我們可以看到如何使用信號量來使兩個進程協調工作,使同一時間只有一個進程可以向屏幕輸出數據。注意,如果程序是第一次被調用(為了區分,第一次調用程序時帶一個要輸出到屏幕中的字符作為一個參數),則需要調用set_semvalue函數初始化信號并將message字符設置為傳遞給程序的參數的第一個字符,同時第一個啟動的進程還負責信號量的刪除工作。如果不刪除信號量,它將繼續在系統中存在,即使程序已經退出,它可能在你下次運行此程序時引發問題,而且信號量是一種有限的資源。

在main函數中調用semget來創建一個信號量,該函數將返回一個信號量標識符,保存于全局變量sem_id中,然后以后的函數就使用這個標識符來訪問信號量。

源文件為seml.c,代碼如下:
[cpp] view plaincopyprint?
  1. #include?<unistd.h>??
  2. #include?<sys/types.h>??
  3. #include?<sys/stat.h>??
  4. #include?<fcntl.h>??
  5. #include?<stdlib.h>??
  6. #include?<stdio.h>??
  7. #include?<string.h>??
  8. #include?<sys/sem.h>??
  9. ??
  10. union?semun??
  11. {??
  12. ????int?val;??
  13. ????struct?semid_ds?*buf;??
  14. ????unsigned?short?*arry;??
  15. };??
  16. ??
  17. static?int?sem_id?=?0;??
  18. ??
  19. static?int?set_semvalue();??
  20. static?void?del_semvalue();??
  21. static?int?semaphore_p();??
  22. static?int?semaphore_v();??
  23. ??
  24. int?main(int?argc,?char?*argv[])??
  25. {??
  26. ????char?message?=?'X';??
  27. ????int?i?=?0;??
  28. ??
  29. ????//創建信號量??
  30. ????sem_id?=?semget((key_t)1234,?1,?0666?|?IPC_CREAT);??
  31. ??
  32. ????if(argc?>?1)??
  33. ????{??
  34. ????????//程序第一次被調用,初始化信號量??
  35. ????????if(!set_semvalue())??
  36. ????????{??
  37. ????????????fprintf(stderr,?"Failed?to?initialize?semaphore\n");??
  38. ????????????exit(EXIT_FAILURE);??
  39. ????????}??
  40. ????????//設置要輸出到屏幕中的信息,即其參數的第一個字符??
  41. ????????message?=?argv[1][0];??
  42. ????????sleep(2);??
  43. ????}??
  44. ????for(i?=?0;?i?<?10;?++i)??
  45. ????{??
  46. ????????//進入臨界區??
  47. ????????if(!semaphore_p())??
  48. ????????????exit(EXIT_FAILURE);??
  49. ????????//向屏幕中輸出數據??
  50. ????????printf("%c",?message);??
  51. ????????//清理緩沖區,然后休眠隨機時間??
  52. ????????fflush(stdout);??
  53. ????????sleep(rand()?%?3);??
  54. ????????//離開臨界區前再一次向屏幕輸出數據??
  55. ????????printf("%c",?message);??
  56. ????????fflush(stdout);??
  57. ????????//離開臨界區,休眠隨機時間后繼續循環??
  58. ????????if(!semaphore_v())??
  59. ????????????exit(EXIT_FAILURE);??
  60. ????????sleep(rand()?%?2);??
  61. ????}??
  62. ??
  63. ????sleep(10);??
  64. ????printf("\n%d?-?finished\n",?getpid());??
  65. ??
  66. ????if(argc?>?1)??
  67. ????{??
  68. ????????//如果程序是第一次被調用,則在退出前刪除信號量??
  69. ????????sleep(3);??
  70. ????????del_semvalue();??
  71. ????}??
  72. ????exit(EXIT_SUCCESS);??
  73. }??
  74. ??
  75. static?int?set_semvalue()??
  76. {??
  77. ????//用于初始化信號量,在使用信號量前必須這樣做??
  78. ????union?semun?sem_union;??
  79. ??
  80. ????sem_union.val?=?1;??
  81. ????if(semctl(sem_id,?0,?SETVAL,?sem_union)?==?-1)??
  82. ????????return?0;??
  83. ????return?1;??
  84. }??
  85. ??
  86. static?void?del_semvalue()??
  87. {??
  88. ????//刪除信號量??
  89. ????union?semun?sem_union;??
  90. ??
  91. ????if(semctl(sem_id,?0,?IPC_RMID,?sem_union)?==?-1)??
  92. ????????fprintf(stderr,?"Failed?to?delete?semaphore\n");??
  93. }??
  94. ??
  95. static?int?semaphore_p()??
  96. {??
  97. ????//對信號量做減1操作,即等待P(sv)??
  98. ????struct?sembuf?sem_b;??
  99. ????sem_b.sem_num?=?0;??
  100. ????sem_b.sem_op?=?-1;//P()??
  101. ????sem_b.sem_flg?=?SEM_UNDO;??
  102. ????if(semop(sem_id,?&sem_b,?1)?==?-1)??
  103. ????{??
  104. ????????fprintf(stderr,?"semaphore_p?failed\n");??
  105. ????????return?0;??
  106. ????}??
  107. ????return?1;??
  108. }??
  109. ??
  110. static?int?semaphore_v()??
  111. {??
  112. ????//這是一個釋放操作,它使信號量變為可用,即發送信號V(sv)??
  113. ????struct?sembuf?sem_b;??
  114. ????sem_b.sem_num?=?0;??
  115. ????sem_b.sem_op?=?1;//V()??
  116. ????sem_b.sem_flg?=?SEM_UNDO;??
  117. ????if(semop(sem_id,?&sem_b,?1)?==?-1)??
  118. ????{??
  119. ????????fprintf(stderr,?"semaphore_v?failed\n");??
  120. ????????return?0;??
  121. ????}??
  122. ????return?1;??
  123. }??
運行結果如下:


注:這個程序的臨界區為main函數for循環不的semaphore_p和semaphore_v函數中間的代碼。

例子分析?:同時運行一個程序的兩個實例,注意第一次運行時,要加上一個字符作為參數,例如本例中的字符‘O’,它用于區分是否為第一次調用,同時這個字符輸出到屏幕中。因為每個程序都在其進入臨界區后和離開臨界區前打印一個字符,所以每個字符都應該成對出現,正如你看到的上圖的輸出那樣。在main函數中循環中我們可以看到,每次進程要訪問stdout(標準輸出),即要輸出字符時,每次都要檢查信號量是否可用(即stdout有沒有正在被其他進程使用)。所以,當一個進程A在調用函數semaphore_p進入了臨界區,輸出字符后,調用sleep時,另一個進程B可能想訪問stdout,但是信號量的P請求操作失敗,只能掛起自己的執行,當進程A調用函數semaphore_v離開了臨界區,進程B馬上被恢復執行。然后進程A和進程B就這樣一直循環了10次。

五、對比例子——進程間的資源競爭
看了上面的例子,你可能還不是很明白,不過沒關系,下面我就以另一個例子來說明一下,它實現的功能與前面的例子一樣,運行方式也一樣,都是兩個相同的進程,同時向stdout中輸出字符,只是沒有使用信號量,兩個進程在互相競爭stdout。它的代碼非常簡單,文件名為normalprint.c,代碼如下:
[cpp] view plaincopyprint?
  1. #include?<stdio.h>??
  2. #include?<stdlib.h>??
  3. ??
  4. int?main(int?argc,?char?*argv[])??
  5. {??
  6. ????char?message?=?'X';??
  7. ????int?i?=?0;????
  8. ????if(argc?>?1)??
  9. ????????message?=?argv[1][0];??
  10. ????for(i?=?0;?i?<?10;?++i)??
  11. ????{??
  12. ????????printf("%c",?message);??
  13. ????????fflush(stdout);??
  14. ????????sleep(rand()?%?3);??
  15. ????????printf("%c",?message);??
  16. ????????fflush(stdout);??
  17. ????????sleep(rand()?%?2);??
  18. ????}??
  19. ????sleep(10);??
  20. ????printf("\n%d?-?finished\n",?getpid());??
  21. ????exit(EXIT_SUCCESS);??
  22. }??
運行結果如下:


例子分析
從上面的輸出結果,我們可以看到字符‘X’和‘O’并不像前面的例子那樣,總是成對出現,因為當第一個進程A輸出了字符后,調用sleep休眠時,另一個進程B立即輸出并休眠,而進程A醒來時,再繼續執行輸出,同樣的進程B也是如此。所以輸出的字符就是不成對的出現。這兩個進程在競爭stdout這一共同的資源。通過兩個例子的對比,我想信號量的意義和使用應該比較清楚了。

六、信號量的總結
信號量是一個特殊的變量,程序對其訪問都是原子操作,且只允許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操作。我們通常通過信號來解決多個進程對同一資源的訪問競爭的問題,使在任一時刻只能有一個執行線程訪問代碼的臨界區域,也可以說它是協調進程間的對同一資源的訪問權,也就是用于同步進程的

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/253118.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/253118.shtml
英文地址,請注明出處:http://en.pswp.cn/news/253118.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

麥克風陣列音頻檢查方法和標準

為確保產品能夠符合算法要求&#xff0c;務必提前做好相關設計&#xff0c;盡量確保各項指標滿足如下標準。 音頻評測工作&#xff0c;主要集中在研發設計階段&#xff1b;針對產品形態的不同&#xff0c;測試可分為裸板測試和整機測 試&#xff0c;下表為不同階段需要測試的…

Linux下git的使用——將已有項目放到github上

本地已經有一個項目了&#xff0c;需要將該項目放到github上&#xff0c;怎么操作&#xff1f; 步驟&#xff1a; 本地安裝git&#xff0c;有github賬號是前提。 &#xff08;1&#xff09;先在github創建一個空的倉庫&#xff0c;并復制鏈接地址。使用https&#xff0c;以.git…

SVG格式圖片轉成HTML中SVG的Path路徑

AI圖標制作完成之后&#xff0c;保存的svg文件包含許多AI的信息&#xff0c;如果要在HTML中使用&#xff0c;我們需要在svg文件中提取/修改信息&#xff0c;重新保存。 1、在AI中已經完成圖標&#xff0c;要保存SVG文件&#xff0c;點擊“文件(File)”-“另存為(Save As)”&…

11-5 筆記

函數&#xff1a; 函數在調用的時候&#xff0c;會形成一個私有作用域&#xff0c;內部的變量不會被外面訪問&#xff0c;這種保護機制叫閉包。這就意味著函數調用完畢&#xff0c;這個函數形成的棧內存會被銷毀。 函數歸屬誰跟它在哪調用沒有關系&#xff0c;跟在哪定義有關。…

linux下socket連接下的心跳機制

1&#xff0c;在長連接下&#xff0c;有可能很長一段時間都沒有數據往來。 理論上說&#xff0c;這個連接是一直保持連接的&#xff0c;但是實際情況中&#xff0c;如果中間節點出現什么故障是難以知道的。 有的節點&#xff08;防火墻&#xff09;會自動把一定時間之內沒有數…

大力智能臺燈與飛利浦臺燈 智能調光功能體驗

目前市面上絕大部分智能臺燈幾乎都宣稱有自動調光功能&#xff0c;即臺燈隨環境光變化自動調節LED光的亮度&#xff0c;或者臺燈在固定環境光下&#xff0c;一旦開啟了自動調光模式LED燈將自動調光至一個最適合讀寫作業的亮度&#xff1b; 下面對比體驗了大力臺燈T6 和 飛利浦…

php-驗證碼

<html><body> <h2>用戶注冊&#xff1a;</h2> <br> <form action"a.php" method"post"> 賬 號&#xff1a;<input type"text" name"zh" id""> <br> 密 碼&#xff1a;&l…

Linux內核Socket參數調優

可調優的內核變量存在兩種主要接口&#xff1a;sysctl命令和/proc文件系統&#xff0c;proc中與進程無關的所有信息都被移植到sysfs中。IPV4協議棧的sysctl參數主要是sysctl.net.core、sysctl.net.ipv4&#xff0c;對應的/proc文件系統是/proc/sys/net/ipv4和/proc/sys/net/cor…

Mango 的組織重構

為了提高敏捷性&#xff0c;企業應將自己劃分為一些負責業務戰略計劃價值中心&#xff0c;承擔端到端的責任&#xff0c;并完全獲取有關客戶需求的信息。企業需要為員工營造可交叉協作的空間&#xff0c;可以學習和使用自組織的改進圈、實踐社群&#xff08;CoP&#xff0c;Com…

vue.js單頁面應用實例

一&#xff1a;npm的安裝由于新版的node.js已經集成了npm的環境&#xff0c;所以只需去官網下載node.js并安裝&#xff0c;安裝完成后使用cmd檢測是否成功。測試node的版本號&#xff1a;node -v測試npm的版本號&#xff1a;npm -v以上提示代表安裝成功二&#xff1a;vue.js環境…

大屏拼接控制器的發展歷程與現狀分析

【中國數字視聽網訊】在大屏幕拼接系統中&#xff0c;我們很容易將焦點聚集在拼接單元上&#xff0c;殊不知幕后還有一個英雄在默默的支持&#xff0c;這個英雄就是是拼接控制器。拼接控制器的優劣直接決定著整個大屏幕顯示系統效果的好與壞&#xff0c;也決定了整套顯示系統的…

AA級與AAA級臺燈 重要指標對比

讀寫作業臺燈&#xff0c;按照國家標準&#xff08;GB/T 9473-2017 讀寫作業臺燈性能要求 &#xff09;臺燈只有兩個等級 即為A級和AA級&#xff1a; 但是大家在各個購物網站挑選臺燈尤其是挑選孩子學習用的讀寫臺燈時&#xff0c;會發現很多廠家宣稱臺 燈為AAA級&#xff0c…

windbg調試相關命令

windbg查找函數:x exe!main*條件斷點打印字符&#xff1a;bp 7199a2b0 ".printf \"message:%ma\", poi(esp8);.echo;g"//搜索內存:s -a 0000000000780000 L8000000 "This is a test2"!address,可以查看進程的堆布局&#xff0c;堆屬性。!addre…

零基礎學python,看完這篇文章,你的python基礎就差不多了!干貨【1】

2019獨角獸企業重金招聘Python工程師標準>>> Python基礎語法和面向對象&#xff08;下一篇分享面向對象&#xff09; Python基礎語法 1. 認識Python 1.1 Python 簡介 Python 的創始人為吉多范羅蘇姆&#xff08;Guido van Rossum&#xff09;。 Python 的設計目標&a…

消費類電子認證測試資料清單

消費類電子上市前必須取得相關認證&#xff0c;其中最常見的有3C、SRRC和CTA等強制性認證&#xff0c;還有類似TUV和Rohs等自愿性認證&#xff0c;現將常見認證測試資料清單小結如下&#xff1a; CCC測試認證&#xff1a; 測試項&#xff1a;EMC、安規和隨機等。 SRRC核準&am…

SVG 相關整理

1. 中文參考手冊&#xff1a; http://www.runoob.com/svg/svg-reference.html SVG HTML5 資源教程 http://www.html5tricks.com/tag/svg/ 2.SVG 入門到精通 http://www.w3cplus.com/blog/tags/411.html 3.SVG開發包整理 http://www.oschina.net/project/tag/420/svg http://www…

液晶拼接控制器

液晶拼接墻系統是由液晶拼接顯示單元、液晶拼接支架、液晶拼接控制器器和信號源組合而成的。液晶拼接控制器則是液晶拼接系統的重要組成部分。 液晶拼接控制器一般分為兩種&#xff1a;內置嵌入式液晶拼接器、外置液晶拼接控制器。 內置嵌入式液晶拼接器 內置嵌入式液晶拼接器只…

03-類與對象——課后動手動腦

1.早期我們經常這樣定義變量 int value100&#xff1b; 前面的示例中這樣定義變量 MyClass obj new MyClass(); 這兩種方式定義的變量是一樣的嗎&#xff1f; 這兩種方式定義的變量是一樣的&#xff0c;因為它們都是類的實例化&#xff0c;只是第一種是一個簡便的寫法&#xf…

IPython 使用記錄

記錄使用的一些技巧處理 1 自動重載修改的文件 局部的會話中使用 如果想在當前 ipython 會話中使用&#xff0c;則運行 ipython 后&#xff0c;依次鍵入下面語句即可 %load_ext autoreload %autoreload 2 全局使用如果想為了懶省事&#xff0c;我希望 IPython 一直會自動重載&a…

有道智能學習燈 初體驗

有道詞典筆在業內樹立了一個標桿&#xff0c;自認為有道出品必須精品&#xff01; 但是今天剛初步體驗了一下有道智能學習燈&#xff0c;硬件方面說實話有點意外&#xff0c;猜測這應該不是有道詞典 筆團隊打造的硬件產品吧。 現在將個人體驗感受表格化陳述如下&#xff1a;…