亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊(cè) 查看新帖 |

Chinaunix

  平臺(tái) 論壇 博客 文庫(kù)
最近訪問(wèn)板塊 發(fā)新帖
查看: 12882 | 回復(fù): 9
打印 上一主題 下一主題

Posix線程編程指南(轉(zhuǎn)貼) [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2004-04-21 16:13 |只看該作者 |倒序?yàn)g覽
最近在學(xué)習(xí)線程編程,發(fā)現(xiàn)CU的精華里面居然還沒(méi)有這方面的資料,從網(wǎng)上copy了一些,貼在這里,以饗諸位。

  1. Posix線程編程指南(1)   
  2. http://www-900.ibm.com/developerWorks/cn/linux/thread/posix_threadapi/part5/index.shtml

  3. 內(nèi)容:

  4. 一、 線程創(chuàng)建
  5. 二、線程取消
  6. 關(guān)于作者




  7. 線程創(chuàng)建與取消
  8. 楊沙洲(pubb@163.net)
  9. 2001 年 10 月

  10. 這是一個(gè)關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細(xì)講述Posix線程庫(kù)API。本文是第一篇將向您講述線程的創(chuàng)建與取消。
  11. 一、線程創(chuàng)建

  12. 1.1 線程與進(jìn)程
  13. 相對(duì)進(jìn)程而言,線程是一個(gè)更加接近于執(zhí)行體的概念,它可以與同進(jìn)程中的其他線程共享數(shù)據(jù),但擁有自己的棧空間,擁有獨(dú)立的執(zhí)行序列。在串行程序基礎(chǔ)上引入線程和進(jìn)程是為了提高程序的并發(fā)度,從而提高程序運(yùn)行效率和響應(yīng)時(shí)間。

  14. 線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):線程執(zhí)行開(kāi)銷小,但不利于資源的管理和保護(hù);而進(jìn)程正相反。同時(shí),線程適合于在SMP機(jī)器上運(yùn)行,而進(jìn)程則可以跨機(jī)器遷移。

  15. 1.2 創(chuàng)建線程
  16. POSIX通過(guò)pthread_create()函數(shù)創(chuàng)建線程,API定義如下:

  17. int  pthread_create(pthread_t  *  thread, pthread_attr_t * attr,
  18. void * (*start_routine)(void *), void * arg)



  19. 與fork()調(diào)用創(chuàng)建一個(gè)進(jìn)程的方法不同,pthread_create()創(chuàng)建的線程并不具備與主線程(即調(diào)用pthread_create()的線程)同樣的執(zhí)行序列,而是使其運(yùn)行start_routine(arg)函數(shù)。thread返回創(chuàng)建的線程ID,而attr是創(chuàng)建線程時(shí)設(shè)置的線程屬性(見(jiàn)下)。pthread_create()的返回值表示線程創(chuàng)建是否成功。盡管arg是void *類型的變量,但它同樣可以作為任意類型的參數(shù)傳給start_routine()函數(shù);同時(shí),start_routine()可以返回一個(gè)void *類型的返回值,而這個(gè)返回值也可以是其他類型,并由pthread_join()獲取。


  20. 1.3 線程創(chuàng)建屬性
  21. pthread_create()中的attr參數(shù)是一個(gè)結(jié)構(gòu)指針,結(jié)構(gòu)中的元素分別對(duì)應(yīng)著新線程的運(yùn)行屬性,主要包括以下幾項(xiàng):

  22. __detachstate,表示新線程是否與進(jìn)程中其他線程脫離同步,如果置位則新線程不能用pthread_join()來(lái)同步,且在退出時(shí)自行釋放所占用的資源。缺省為PTHREAD_CREATE_JOINABLE狀態(tài)。這個(gè)屬性也可以在線程創(chuàng)建并運(yùn)行以后用pthread_detach()來(lái)設(shè)置,而一旦設(shè)置為PTHREAD_CREATE_DETACH狀態(tài)(不論是創(chuàng)建時(shí)設(shè)置還是運(yùn)行時(shí)設(shè)置)則不能再恢復(fù)到PTHREAD_CREATE_JOINABLE狀態(tài)。

  23. __schedpolicy,表示新線程的調(diào)度策略,主要包括SCHED_OTHER(正常、非實(shí)時(shí))、SCHED_RR(實(shí)時(shí)、輪轉(zhuǎn)法)和SCHED_FIFO(實(shí)時(shí)、先入先出)三種,缺省為SCHED_OTHER,后兩種調(diào)度策略僅對(duì)超級(jí)用戶有效。運(yùn)行時(shí)可以用過(guò)pthread_setschedparam()來(lái)改變。

  24. __schedparam,一個(gè)struct sched_param結(jié)構(gòu),目前僅有一個(gè)sched_priority整型變量表示線程的運(yùn)行優(yōu)先級(jí)。這個(gè)參數(shù)僅當(dāng)調(diào)度策略為實(shí)時(shí)(即SCHED_RR或SCHED_FIFO)時(shí)才有效,并可以在運(yùn)行時(shí)通過(guò)pthread_setschedparam()函數(shù)來(lái)改變,缺省為0。

  25. __inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調(diào)度策略和調(diào)度參數(shù)(即attr中的值),而后者表示繼承調(diào)用者線程的值。缺省為PTHREAD_EXPLICIT_SCHED。

  26. __scope,表示線程間競(jìng)爭(zhēng)CPU的范圍,也就是說(shuō)線程優(yōu)先級(jí)的有效范圍。POSIX的標(biāo)準(zhǔn)中定義了兩個(gè)值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統(tǒng)中所有線程一起競(jìng)爭(zhēng)CPU時(shí)間,后者表示僅與同進(jìn)程中的線程競(jìng)爭(zhēng)CPU。目前LinuxThreads僅實(shí)現(xiàn)了PTHREAD_SCOPE_SYSTEM一值。

  27. pthread_attr_t結(jié)構(gòu)中還有一些值,但不使用pthread_create()來(lái)設(shè)置。

  28. 為了設(shè)置這些屬性,POSIX定義了一系列屬性設(shè)置函數(shù),包括pthread_attr_init()、pthread_attr_destroy()和與各個(gè)屬性相關(guān)的pthread_attr_get---/pthread_attr_set---函數(shù)。

  29. 1.4 線程創(chuàng)建的Linux實(shí)現(xiàn)
  30. 我們知道,Linux的線程實(shí)現(xiàn)是在核外進(jìn)行的,核內(nèi)提供的是創(chuàng)建進(jìn)程的接口do_fork()。內(nèi)核提供了兩個(gè)系統(tǒng)調(diào)用__clone()和fork(),最終都用不同的參數(shù)調(diào)用do_fork()核內(nèi)API。當(dāng)然,要想實(shí)現(xiàn)線程,沒(méi)有核心對(duì)多進(jìn)程(其實(shí)是輕量級(jí)進(jìn)程)共享數(shù)據(jù)段的支持是不行的,因此,do_fork()提供了很多參數(shù),包括CLONE_VM(共享內(nèi)存空間)、CLONE_FS(共享文件系統(tǒng)信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號(hào)句柄表)和CLONE_PID(共享進(jìn)程ID,僅對(duì)核內(nèi)進(jìn)程,即0號(hào)進(jìn)程有效)。當(dāng)使用fork系統(tǒng)調(diào)用時(shí),內(nèi)核調(diào)用do_fork()不使用任何共享屬性,進(jìn)程擁有獨(dú)立的運(yùn)行環(huán)境,而使用pthread_create()來(lái)創(chuàng)建線程時(shí),則最終設(shè)置了所有這些屬性來(lái)調(diào)用__clone(),而這些參數(shù)又全部傳給核內(nèi)的do_fork(),從而創(chuàng)建的"進(jìn)程"擁有共享的運(yùn)行環(huán)境,只有棧是獨(dú)立的,由__clone()傳入。

  31. Linux線程在核內(nèi)是以輕量級(jí)進(jìn)程的形式存在的,擁有獨(dú)立的進(jìn)程表項(xiàng),而所有的創(chuàng)建、同步、刪除等操作都在核外pthread庫(kù)中進(jìn)行。pthread庫(kù)使用一個(gè)管理線程(__pthread_manager(),每個(gè)進(jìn)程獨(dú)立且唯一)來(lái)管理線程的創(chuàng)建和終止,為線程分配線程ID,發(fā)送線程相關(guān)的信號(hào)(比如Cancel),而主線程(pthread_create())的調(diào)用者則通過(guò)管道將請(qǐng)求信息傳給管理線程。

  32. 二、線程取消

  33. 2.1 線程取消的定義
  34. 一般情況下,線程在其主體函數(shù)退出的時(shí)候會(huì)自動(dòng)終止,但同時(shí)也可以因?yàn)榻邮盏搅硪粋(gè)線程發(fā)來(lái)的終止(取消)請(qǐng)求而強(qiáng)制終止。

  35. 2.2 線程取消的語(yǔ)義
  36. 線程取消的方法是向目標(biāo)線程發(fā)Cancel信號(hào),但如何處理Cancel信號(hào)則由目標(biāo)線程自己決定,或者忽略、或者立即終止、或者繼續(xù)運(yùn)行至Cancelation-point(取消點(diǎn)),由不同的Cancelation狀態(tài)決定。

  37. 線程接收到CANCEL信號(hào)的缺省處理(即pthread_create()創(chuàng)建線程的缺省狀態(tài))是繼續(xù)運(yùn)行至取消點(diǎn),也就是說(shuō)設(shè)置一個(gè)CANCELED狀態(tài),線程繼續(xù)運(yùn)行,只有運(yùn)行至Cancelation-point的時(shí)候才會(huì)退出。

  38. 2.3 取消點(diǎn)
  39. 根據(jù)POSIX標(biāo)準(zhǔn),pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函數(shù)以及read()、write()等會(huì)引起阻塞的系統(tǒng)調(diào)用都是Cancelation-point,而其他pthread函數(shù)都不會(huì)引起Cancelation動(dòng)作。但是pthread_cancel的手冊(cè)頁(yè)聲稱,由于LinuxThread庫(kù)與C庫(kù)結(jié)合得不好,因而目前C庫(kù)函數(shù)都不是Cancelation-point;但CANCEL信號(hào)會(huì)使線程從阻塞的系統(tǒng)調(diào)用中退出,并置EINTR錯(cuò)誤碼,因此可以在需要作為Cancelation-point的系統(tǒng)調(diào)用前后調(diào)用pthread_testcancel(),從而達(dá)到POSIX標(biāo)準(zhǔn)所要求的目標(biāo),即如下代碼段:

  40. pthread_testcancel();
  41.     retcode = read(fd, buffer, length);
  42.     pthread_testcancel();





  43. 2.4 程序設(shè)計(jì)方面的考慮
  44. 如果線程處于無(wú)限循環(huán)中,且循環(huán)體內(nèi)沒(méi)有執(zhí)行至取消點(diǎn)的必然路徑,則線程無(wú)法由外部其他線程的取消請(qǐng)求而終止。因此在這樣的循環(huán)體的必經(jīng)路徑上應(yīng)該加入pthread_testcancel()調(diào)用。

  45. 2.5 與線程取消相關(guān)的pthread函數(shù)
  46. int pthread_cancel(pthread_t thread)
  47. 發(fā)送終止信號(hào)給thread線程,如果成功則返回0,否則為非0值。發(fā)送成功并不意味著thread會(huì)終止。

  48. int pthread_setcancelstate(int state, int *oldstate)
  49. 設(shè)置本線程對(duì)Cancel信號(hào)的反應(yīng),state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分別表示收到信號(hào)后設(shè)為CANCLED狀態(tài)和忽略CANCEL信號(hào)繼續(xù)運(yùn)行;old_state如果不為NULL則存入原來(lái)的Cancel狀態(tài)以便恢復(fù)。

  50. int pthread_setcanceltype(int type, int *oldtype)
  51. 設(shè)置本線程取消動(dòng)作的執(zhí)行時(shí)機(jī),type由兩種取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,僅當(dāng)Cancel狀態(tài)為Enable時(shí)有效,分別表示收到信號(hào)后繼續(xù)運(yùn)行至下一個(gè)取消點(diǎn)再退出和立即執(zhí)行取消動(dòng)作(退出);oldtype如果不為NULL則存入運(yùn)來(lái)的取消動(dòng)作類型值。

  52. void pthread_testcancel(void)
  53. 檢查本線程是否處于Canceld狀態(tài),如果是,則進(jìn)行取消動(dòng)作,否則直接返回。

  54. 關(guān)于作者
  55. 楊沙洲,男,現(xiàn)攻讀國(guó)防科大計(jì)算機(jī)學(xué)院計(jì)算機(jī)軟件方向博士學(xué)位。您可以通過(guò)電子郵件pubb@163.net 跟他聯(lián)系。
  56. (c) Copyright IBM Corp. 2001, (c) Copyright IBM China 2001, All Right Reserved
  57.   關(guān)于 IBM  |  隱私條約  |  使用條款  |  聯(lián)系 IBM

  58. -------------------------------------------------------------------------
  59. Posix線程編程指南(2)   
  60.    
  61. 內(nèi)容:

  62. 一. 概念及作用
  63. 二. 創(chuàng)建和注銷
  64. 三. 訪問(wèn)
  65. 四. 使用范例
  66. 關(guān)于作者


  67. 相關(guān)內(nèi)容:

  68. (1) 線程創(chuàng)建與取消  




  69. 線程私有數(shù)據(jù)
  70. 楊沙洲(pubb@163.net)
  71. 2001 年 10 月

  72. 這是一個(gè)關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細(xì)講述Posix線程庫(kù)API。本文是第二篇將向您講述線程的私有數(shù)據(jù)。
  73. 一.概念及作用
  74. 在單線程程序中,我們經(jīng)常要用到"全局變量"以實(shí)現(xiàn)多個(gè)函數(shù)間共享數(shù)據(jù)。在多線程環(huán)境下,由于數(shù)據(jù)空間是共享的,因此全局變量也為所有線程所共有。但有時(shí)應(yīng)用程序設(shè)計(jì)中有必要提供線程私有的全局變量,僅在某個(gè)線程中有效,但卻可以跨多個(gè)函數(shù)訪問(wèn),比如程序可能需要每個(gè)線程維護(hù)一個(gè)鏈表,而使用相同的函數(shù)操作,最簡(jiǎn)單的辦法就是使用同名而不同變量地址的線程相關(guān)數(shù)據(jù)結(jié)構(gòu)。這樣的數(shù)據(jù)結(jié)構(gòu)可以由Posix線程庫(kù)維護(hù),稱為線程私有數(shù)據(jù)(Thread-specific Data,或TSD)。

  75. 二.創(chuàng)建和注銷
  76. Posix定義了兩個(gè)API分別用來(lái)創(chuàng)建和注銷TSD:

  77. int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))

  78. 該函數(shù)從TSD池中分配一項(xiàng),將其值賦給key供以后訪問(wèn)使用。如果destr_function不為空,在線程退出(pthread_exit())時(shí)將以key所關(guān)聯(lián)的數(shù)據(jù)為參數(shù)調(diào)用destr_function(),以釋放分配的緩沖區(qū)。

  79. 不論哪個(gè)線程調(diào)用pthread_key_create(),所創(chuàng)建的key都是所有線程可訪問(wèn)的,但各個(gè)線程可根據(jù)自己的需要往key中填入不同的值,這就相當(dāng)于提供了一個(gè)同名而不同值的全局變量。在LinuxThreads的實(shí)現(xiàn)中,TSD池用一個(gè)結(jié)構(gòu)數(shù)組表示:

  80. static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };

  81. 創(chuàng)建一個(gè)TSD就相當(dāng)于將結(jié)構(gòu)數(shù)組中的某一項(xiàng)設(shè)置為"in_use",并將其索引返回給*key,然后設(shè)置destructor函數(shù)為destr_function。

  82. 注銷一個(gè)TSD采用如下API:

  83. int pthread_key_delete(pthread_key_t key)

  84. 這個(gè)函數(shù)并不檢查當(dāng)前是否有線程正使用該TSD,也不會(huì)調(diào)用清理函數(shù)(destr_function),而只是將TSD釋放以供下一次調(diào)用pthread_key_create()使用。在LinuxThreads中,它還會(huì)將與之相關(guān)的線程數(shù)據(jù)項(xiàng)設(shè)為NULL(見(jiàn)"訪問(wèn)")。

  85. 三.訪問(wèn)
  86. TSD的讀寫(xiě)都通過(guò)專門的Posix Thread函數(shù)進(jìn)行,其API定義如下:

  87. int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)
  88. void * pthread_getspecific(pthread_key_t key)






  89. 寫(xiě)入(pthread_setspecific())時(shí),將pointer的值(不是所指的內(nèi)容)與key相關(guān)聯(lián),而相應(yīng)的讀出函數(shù)則將與key相關(guān)聯(lián)的數(shù)據(jù)讀出來(lái)。數(shù)據(jù)類型都設(shè)為void *,因此可以指向任何類型的數(shù)據(jù)。

  90. 在LinuxThreads中,使用了一個(gè)位于線程描述結(jié)構(gòu)(_pthread_descr_struct)中的二維void *指針數(shù)組來(lái)存放與key關(guān)聯(lián)的數(shù)據(jù),數(shù)組大小由以下幾個(gè)宏來(lái)說(shuō)明:

  91. #define PTHREAD_KEY_2NDLEVEL_SIZE       32
  92. #define PTHREAD_KEY_1STLEVEL_SIZE   \
  93. ((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)
  94. / PTHREAD_KEY_2NDLEVEL_SIZE)
  95.     其中在/usr/include/bits/local_lim.h中定義了PTHREAD_KEYS_MAX為1024,因此一維數(shù)組大小為32。而具體存放的位置由key值經(jīng)過(guò)以下計(jì)算得到:
  96. idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
  97. idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE





  98. 也就是說(shuō),數(shù)據(jù)存放與一個(gè)32×32的稀疏矩陣中。同樣,訪問(wèn)的時(shí)候也由key值經(jīng)過(guò)類似計(jì)算得到數(shù)據(jù)所在位置索引,再取出其中內(nèi)容返回。

  99. 四.使用范例
  100. 以下這個(gè)例子沒(méi)有什么實(shí)際意義,只是說(shuō)明如何使用,以及能夠使用這一機(jī)制達(dá)到存儲(chǔ)線程私有數(shù)據(jù)的目的。

  101. #include <stdio.h>;
  102. #include <pthread.h>;

  103. pthread_key_t   key;

  104. void echomsg(int t)
  105. {
  106.         printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);
  107. }

  108. void * child1(void *arg)
  109. {
  110.         int tid=pthread_self();
  111.         printf("thread %d enter\n",tid);
  112.         pthread_setspecific(key,(void *)tid);
  113.         sleep(2);
  114.         printf("thread %d returns %d\n",tid,pthread_getspecific(key));
  115.         sleep(5);
  116. }

  117. void * child2(void *arg)
  118. {
  119.         int tid=pthread_self();
  120.         printf("thread %d enter\n",tid);
  121.         pthread_setspecific(key,(void *)tid);
  122.         sleep(1);
  123.         printf("thread %d returns %d\n",tid,pthread_getspecific(key));
  124.         sleep(5);
  125. }

  126. int main(void)
  127. {
  128.         int tid1,tid2;

  129.         printf("hello\n");
  130.         pthread_key_create(&key,echomsg);
  131.         pthread_create(&tid1,NULL,child1,NULL);
  132.         pthread_create(&tid2,NULL,child2,NULL);
  133.         sleep(10);
  134.         pthread_key_delete(key);
  135.         printf("main thread exit\n");
  136.         return 0;
  137. }





  138. 給例程創(chuàng)建兩個(gè)線程分別設(shè)置同一個(gè)線程私有數(shù)據(jù)為自己的線程ID,為了檢驗(yàn)其私有性,程序錯(cuò)開(kāi)了兩個(gè)線程私有數(shù)據(jù)的寫(xiě)入和讀出的時(shí)間,從程序運(yùn)行結(jié)果可以看出,兩個(gè)線程對(duì)TSD的修改互不干擾。同時(shí),當(dāng)線程退出時(shí),清理函數(shù)會(huì)自動(dòng)執(zhí)行,參數(shù)為tid。



  139. Posix線程編程指南(3)   


  140.    
  141. 內(nèi)容:

  142. 一. 互斥鎖
  143. 二. 條件變量
  144. 三. 信號(hào)燈
  145. 四. 異步信號(hào)
  146. 五. 其他同步方式
  147. 關(guān)于作者


  148. 相關(guān)內(nèi)容:

  149. (1) 線程創(chuàng)建與取消  
  150. (2) 線程私有數(shù)據(jù)  




  151. 線程同步
  152. 楊沙洲(pubb@163.net)
  153. 2001 年 10 月

  154. 這是一個(gè)關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細(xì)講述Posix線程庫(kù)API。本文是第三篇,將向您講述線程同步。
  155. 一.互斥鎖
  156. 盡管在Posix Thread中同樣可以使用IPC的信號(hào)量機(jī)制來(lái)實(shí)現(xiàn)互斥鎖mutex功能,但顯然semphore的功能過(guò)于強(qiáng)大了,在Posix Thread中定義了另外一套專門用于線程同步的mutex函數(shù)。

  157. 1. 創(chuàng)建和銷毀
  158. 有兩種方法創(chuàng)建互斥鎖,靜態(tài)方式和動(dòng)態(tài)方式。POSIX定義了一個(gè)宏P(guān)THREAD_MUTEX_INITIALIZER來(lái)靜態(tài)初始化互斥鎖,方法如下:
  159. pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
  160. 在LinuxThreads實(shí)現(xiàn)中,pthread_mutex_t是一個(gè)結(jié)構(gòu),而PTHREAD_MUTEX_INITIALIZER則是一個(gè)結(jié)構(gòu)常量。

  161. 動(dòng)態(tài)方式是采用pthread_mutex_init()函數(shù)來(lái)初始化互斥鎖,API定義如下:
  162. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
  163. 其中mutexattr用于指定互斥鎖屬性(見(jiàn)下),如果為NULL則使用缺省屬性。

  164. pthread_mutex_destroy()用于注銷一個(gè)互斥鎖,API定義如下:
  165. int pthread_mutex_destroy(pthread_mutex_t *mutex)
  166. 銷毀一個(gè)互斥鎖即意味著釋放它所占用的資源,且要求鎖當(dāng)前處于開(kāi)放狀態(tài)。由于在Linux中,互斥鎖并不占用任何資源,因此LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀態(tài)以外(鎖定狀態(tài)則返回EBUSY)沒(méi)有其他動(dòng)作。

  167. 2. 互斥鎖屬性
  168. 互斥鎖的屬性在創(chuàng)建鎖的時(shí)候指定,在LinuxThreads實(shí)現(xiàn)中僅有一個(gè)鎖類型屬性,不同的鎖類型在試圖對(duì)一個(gè)已經(jīng)被鎖定的互斥鎖加鎖時(shí)表現(xiàn)不同。當(dāng)前(glibc2.2.3,linuxthreads0.9)有四個(gè)值可供選擇:

  169. PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當(dāng)一個(gè)線程加鎖以后,其余請(qǐng)求鎖的線程將形成一個(gè)等待隊(duì)列,并在解鎖后按優(yōu)先級(jí)獲得鎖。這種鎖策略保證了資源分配的公平性。
  170. PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個(gè)線程對(duì)同一個(gè)鎖成功獲得多次,并通過(guò)多次unlock解鎖。如果是不同線程請(qǐng)求,則在加鎖線程解鎖時(shí)重新競(jìng)爭(zhēng)。
  171. PTHREAD_MUTEX_ERRORCHECK_NP,檢錯(cuò)鎖,如果同一個(gè)線程請(qǐng)求同一個(gè)鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動(dòng)作相同。這樣就保證當(dāng)不允許多次加鎖時(shí)不會(huì)出現(xiàn)最簡(jiǎn)單情況下的死鎖。
  172. PTHREAD_MUTEX_ADAPTIVE_NP,適應(yīng)鎖,動(dòng)作最簡(jiǎn)單的鎖類型,僅等待解鎖后重新競(jìng)爭(zhēng)。



  173. 3. 鎖操作
  174. 鎖操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測(cè)試加鎖pthread_mutex_trylock()三個(gè),不論哪種類型的鎖,都不可能被兩個(gè)不同的線程同時(shí)得到,而必須等待解鎖。對(duì)于普通鎖和適應(yīng)鎖類型,解鎖者可以是同進(jìn)程內(nèi)任何線程;而檢錯(cuò)鎖則必須由加鎖者解鎖才有效,否則返回EPERM;對(duì)于嵌套鎖,文檔和實(shí)現(xiàn)要求必須由加鎖者解鎖,但實(shí)驗(yàn)結(jié)果表明并沒(méi)有這種限制,這個(gè)不同目前還沒(méi)有得到解釋。在同一進(jìn)程中的線程,如果加鎖后沒(méi)有解鎖,則任何其他線程都無(wú)法再獲得鎖。

  175. int pthread_mutex_lock(pthread_mutex_t *mutex)
  176. int pthread_mutex_unlock(pthread_mutex_t *mutex)
  177. int pthread_mutex_trylock(pthread_mutex_t *mutex)

  178. pthread_mutex_trylock()語(yǔ)義與pthread_mutex_lock()類似,不同的是在鎖已經(jīng)被占據(jù)時(shí)返回EBUSY而不是掛起等待。

  179. 4. 其他
  180. POSIX線程鎖機(jī)制的Linux實(shí)現(xiàn)都不是取消點(diǎn),因此,延遲取消類型的線程不會(huì)因收到取消信號(hào)而離開(kāi)加鎖等待。值得注意的是,如果線程在加鎖后解鎖前被取消,鎖將永遠(yuǎn)保持鎖定狀態(tài),因此如果在關(guān)鍵區(qū)段內(nèi)有取消點(diǎn)存在,或者設(shè)置了異步取消類型,則必須在退出回調(diào)函數(shù)中解鎖。

  181. 這個(gè)鎖機(jī)制同時(shí)也不是異步信號(hào)安全的,也就是說(shuō),不應(yīng)該在信號(hào)處理過(guò)程中使用互斥鎖,否則容易造成死鎖。

  182. 二.條件變量
  183. 條件變量是利用線程間共享的全局變量進(jìn)行同步的一種機(jī)制,主要包括兩個(gè)動(dòng)作:一個(gè)線程等待"條件變量的條件成立"而掛起;另一個(gè)線程使"條件成立"(給出條件成立信號(hào))。為了防止競(jìng)爭(zhēng),條件變量的使用總是和一個(gè)互斥鎖結(jié)合在一起。

  184. 1. 創(chuàng)建和注銷
  185. 條件變量和互斥鎖一樣,都有靜態(tài)動(dòng)態(tài)兩種創(chuàng)建方式,靜態(tài)方式使用PTHREAD_COND_INITIALIZER常量,如下:
  186. pthread_cond_t cond=PTHREAD_COND_INITIALIZER

  187. 動(dòng)態(tài)方式調(diào)用pthread_cond_init()函數(shù),API定義如下:
  188. int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

  189. 盡管POSIX標(biāo)準(zhǔn)中為條件變量定義了屬性,但在LinuxThreads中沒(méi)有實(shí)現(xiàn),因此cond_attr值通常為NULL,且被忽略。

  190. 注銷一個(gè)條件變量需要調(diào)用pthread_cond_destroy(),只有在沒(méi)有線程在該條件變量上等待的時(shí)候才能注銷這個(gè)條件變量,否則返回EBUSY。因?yàn)長(zhǎng)inux實(shí)現(xiàn)的條件變量沒(méi)有分配什么資源,所以注銷動(dòng)作只包括檢查是否有等待線程。API定義如下:
  191. int pthread_cond_destroy(pthread_cond_t *cond)

  192. 2. 等待和激發(fā)
  193. int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
  194. int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)

  195. 等待條件有兩種方式:無(wú)條件等待pthread_cond_wait()和計(jì)時(shí)等待pthread_cond_timedwait(),其中計(jì)時(shí)等待方式如果在給定時(shí)刻前條件沒(méi)有滿足,則返回ETIMEOUT,結(jié)束等待,其中abstime以與time()系統(tǒng)調(diào)用相同意義的絕對(duì)時(shí)間形式出現(xiàn),0表示格林尼治時(shí)間1970年1月1日0時(shí)0分0秒。

  196. 無(wú)論哪種等待方式,都必須和一個(gè)互斥鎖配合,以防止多個(gè)線程同時(shí)請(qǐng)求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競(jìng)爭(zhēng)條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應(yīng)鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調(diào)用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊(duì)列以前,mutex保持鎖定狀態(tài),并在線程掛起進(jìn)入等待前解鎖。在條件滿足從而離開(kāi)pthread_cond_wait()之前,mutex將被重新加鎖,以與進(jìn)入pthread_cond_wait()前的加鎖動(dòng)作對(duì)應(yīng)。

  197. 激發(fā)條件有兩種形式,pthread_cond_signal()激活一個(gè)等待該條件的線程,存在多個(gè)等待線程時(shí)按入隊(duì)順序激活其中一個(gè);而pthread_cond_broadcast()則激活所有等待線程。

  198. 3. 其他
  199. pthread_cond_wait()和pthread_cond_timedwait()都被實(shí)現(xiàn)為取消點(diǎn),因此,在該處等待的線程將立即重新運(yùn)行,在重新鎖定mutex后離開(kāi)pthread_cond_wait(),然后執(zhí)行取消動(dòng)作。也就是說(shuō)如果pthread_cond_wait()被取消,mutex是保持鎖定狀態(tài)的,因而需要定義退出回調(diào)函數(shù)來(lái)為其解鎖。

  200. 以下示例集中演示了互斥鎖和條件變量的結(jié)合使用,以及取消對(duì)于條件等待動(dòng)作的影響。在例子中,有兩個(gè)線程被啟動(dòng),并等待同一個(gè)條件變量,如果不使用退出回調(diào)函數(shù)(見(jiàn)范例中的注釋部分),則tid2將在pthread_mutex_lock()處永久等待。如果使用回調(diào)函數(shù),則tid2的條件等待及主線程的條件激發(fā)都能正常工作。

  201. #include <stdio.h>;
  202. #include <pthread.h>;
  203. #include <unistd.h>;

  204. pthread_mutex_t mutex;
  205. pthread_cond_t  cond;

  206. void * child1(void *arg)
  207. {
  208.         pthread_cleanup_push(pthread_mutex_unlock,&mutex);  /* comment 1 */
  209.         while(1){
  210.                 printf("thread 1 get running \n");
  211.         printf("thread 1 pthread_mutex_lock returns %d\n",
  212. pthread_mutex_lock(&mutex));
  213.         pthread_cond_wait(&cond,&mutex);
  214.                     printf("thread 1 condition applied\n");
  215.         pthread_mutex_unlock(&mutex);
  216.                     sleep(5);
  217.     }
  218.         pthread_cleanup_pop(0);     /* comment 2 */
  219. }

  220. void *child2(void *arg)
  221. {
  222.         while(1){
  223.                 sleep(3);               /* comment 3 */
  224.                 printf("thread 2 get running.\n");
  225.         printf("thread 2 pthread_mutex_lock returns %d\n",
  226. pthread_mutex_lock(&mutex));
  227.         pthread_cond_wait(&cond,&mutex);
  228.         printf("thread 2 condition applied\n");
  229.         pthread_mutex_unlock(&mutex);
  230.         sleep(1);
  231.         }
  232. }

  233. int main(void)
  234. {
  235.         int tid1,tid2;

  236.         printf("hello, condition variable test\n");
  237.         pthread_mutex_init(&mutex,NULL);
  238.         pthread_cond_init(&cond,NULL);
  239.         pthread_create(&tid1,NULL,child1,NULL);
  240.         pthread_create(&tid2,NULL,child2,NULL);
  241.         do{
  242.         sleep(2);                   /* comment 4 */
  243.                 pthread_cancel(tid1);       /* comment 5 */
  244.                 sleep(2);                   /* comment 6 */
  245.         pthread_cond_signal(&cond);
  246.     }while(1);  
  247.         sleep(100);
  248.         pthread_exit(0);
  249. }





  250. 如果不做注釋5的pthread_cancel()動(dòng)作,即使沒(méi)有那些sleep()延時(shí)操作,child1和child2都能正常工作。注釋3和注釋4的延遲使得child1有時(shí)間完成取消動(dòng)作,從而使child2能在child1退出之后進(jìn)入請(qǐng)求鎖操作。如果沒(méi)有注釋1和注釋2的回調(diào)函數(shù)定義,系統(tǒng)將掛起在child2請(qǐng)求鎖的地方;而如果同時(shí)也不做注釋3和注釋4的延時(shí),child2能在child1完成取消動(dòng)作以前得到控制,從而順利執(zhí)行申請(qǐng)鎖的操作,但卻可能掛起在pthread_cond_wait()中,因?yàn)槠渲幸灿猩暾?qǐng)mutex的操作。child1函數(shù)給出的是標(biāo)準(zhǔn)的條件變量的使用方式:回調(diào)函數(shù)保護(hù),等待條件前鎖定,pthread_cond_wait()返回后解鎖。

  251. 條件變量機(jī)制不是異步信號(hào)安全的,也就是說(shuō),在信號(hào)處理函數(shù)中調(diào)用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死鎖。

  252. 三.信號(hào)燈
  253. 信號(hào)燈與互斥鎖和條件變量的主要不同在于"燈"的概念,燈亮則意味著資源可用,燈滅則意味著不可用。如果說(shuō)后兩中同步方式側(cè)重于"等待"操作,即資源不可用的話,信號(hào)燈機(jī)制則側(cè)重于點(diǎn)燈,即告知資源可用;沒(méi)有等待線程的解鎖或激發(fā)條件都是沒(méi)有意義的,而沒(méi)有等待燈亮的線程的點(diǎn)燈操作則有效,且能保持燈亮狀態(tài)。當(dāng)然,這樣的操作原語(yǔ)也意味著更多的開(kāi)銷。

  254. 信號(hào)燈的應(yīng)用除了燈亮/燈滅這種二元燈以外,也可以采用大于1的燈數(shù),以表示資源數(shù)大于1,這時(shí)可以稱之為多元燈。

  255. 1. 創(chuàng)建和注銷
  256. POSIX信號(hào)燈標(biāo)準(zhǔn)定義了有名信號(hào)燈和無(wú)名信號(hào)燈兩種,但LinuxThreads的實(shí)現(xiàn)僅有無(wú)名燈,同時(shí)有名燈除了總是可用于多進(jìn)程之間以外,在使用上與無(wú)名燈并沒(méi)有很大的區(qū)別,因此下面僅就無(wú)名燈進(jìn)行討論。

  257. int sem_init(sem_t *sem, int pshared, unsigned int value)
  258. 這是創(chuàng)建信號(hào)燈的API,其中value為信號(hào)燈的初值,pshared表示是否為多進(jìn)程共享而不僅僅是用于一個(gè)進(jìn)程。LinuxThreads沒(méi)有實(shí)現(xiàn)多進(jìn)程共享信號(hào)燈,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的信號(hào)燈由sem變量表征,用于以下點(diǎn)燈、滅燈操作。

  259. int sem_destroy(sem_t * sem)
  260. 被注銷的信號(hào)燈sem要求已沒(méi)有線程在等待該信號(hào)燈,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的信號(hào)燈注銷函數(shù)不做其他動(dòng)作。

  261. 2. 點(diǎn)燈和滅燈
  262. int sem_post(sem_t * sem)
  263. 點(diǎn)燈操作將信號(hào)燈值原子地加1,表示增加一個(gè)可訪問(wèn)的資源。

  264. int sem_wait(sem_t * sem)
  265. int sem_trywait(sem_t * sem)
  266. sem_wait()為等待燈亮操作,等待燈亮(信號(hào)燈值大于0),然后將信號(hào)燈原子地減1,并返回。sem_trywait()為sem_wait()的非阻塞版,如果信號(hào)燈計(jì)數(shù)大于0,則原子地減1并返回0,否則立即返回-1,errno置為EAGAIN。

  267. 3. 獲取燈值
  268. int sem_getvalue(sem_t * sem, int * sval)
  269. 讀取sem中的燈計(jì)數(shù),存于*sval中,并返回0。

  270. 4. 其他
  271. sem_wait()被實(shí)現(xiàn)為取消點(diǎn),而且在支持原子"比較且交換"指令的體系結(jié)構(gòu)上,sem_post()是唯一能用于異步信號(hào)處理函數(shù)的POSIX異步信號(hào)安全的API。

  272. 四.異步信號(hào)
  273. 由于LinuxThreads是在核外使用核內(nèi)輕量級(jí)進(jìn)程實(shí)現(xiàn)的線程,所以基于內(nèi)核的異步信號(hào)操作對(duì)于線程也是有效的。但同時(shí),由于異步信號(hào)總是實(shí)際發(fā)往某個(gè)進(jìn)程,所以無(wú)法實(shí)現(xiàn)POSIX標(biāo)準(zhǔn)所要求的"信號(hào)到達(dá)某個(gè)進(jìn)程,然后再由該進(jìn)程將信號(hào)分發(fā)到所有沒(méi)有阻塞該信號(hào)的線程中"原語(yǔ),而是只能影響到其中一個(gè)線程。

  274. POSIX異步信號(hào)同時(shí)也是一個(gè)標(biāo)準(zhǔn)C庫(kù)提供的功能,主要包括信號(hào)集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信號(hào)處理函數(shù)安裝(sigaction())、信號(hào)阻塞控制(sigprocmask())、被阻塞信號(hào)查詢(sigpending())、信號(hào)等待(sigsuspend())等,它們與發(fā)送信號(hào)的kill()等函數(shù)配合就能實(shí)現(xiàn)進(jìn)程間異步信號(hào)功能。LinuxThreads圍繞線程封裝了sigaction()何raise(),本節(jié)集中討論LinuxThreads中擴(kuò)展的異步信號(hào)函數(shù),包括pthread_sigmask()、pthread_kill()和sigwait()三個(gè)函數(shù)。毫無(wú)疑問(wèn),所有POSIX異步信號(hào)函數(shù)對(duì)于線程都是可用的。

  275. int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)
  276. 設(shè)置線程的信號(hào)屏蔽碼,語(yǔ)義與sigprocmask()相同,但對(duì)不允許屏蔽的Cancel信號(hào)和不允許響應(yīng)的Restart信號(hào)進(jìn)行了保護(hù)。被屏蔽的信號(hào)保存在信號(hào)隊(duì)列中,可由sigpending()函數(shù)取出。

  277. int pthread_kill(pthread_t thread, int signo)
  278. 向thread號(hào)線程發(fā)送signo信號(hào)。實(shí)現(xiàn)中在通過(guò)thread線程號(hào)定位到對(duì)應(yīng)進(jìn)程號(hào)以后使用kill()系統(tǒng)調(diào)用完成發(fā)送。

  279. int sigwait(const sigset_t *set, int *sig)
  280. 掛起線程,等待set中指定的信號(hào)之一到達(dá),并將到達(dá)的信號(hào)存入*sig中。POSIX標(biāo)準(zhǔn)建議在調(diào)用sigwait()等待信號(hào)以前,進(jìn)程中所有線程都應(yīng)屏蔽該信號(hào),以保證僅有sigwait()的調(diào)用者獲得該信號(hào),因此,對(duì)于需要等待同步的異步信號(hào),總是應(yīng)該在創(chuàng)建任何線程以前調(diào)用pthread_sigmask()屏蔽該信號(hào)的處理。而且,調(diào)用sigwait()期間,原來(lái)附接在該信號(hào)上的信號(hào)處理函數(shù)不會(huì)被調(diào)用。

  281. 如果在等待期間接收到Cancel信號(hào),則立即退出等待,也就是說(shuō)sigwait()被實(shí)現(xiàn)為取消點(diǎn)。

  282. 五.其他同步方式
  283. 除了上述討論的同步方式以外,其他很多進(jìn)程間通信手段對(duì)于LinuxThreads也是可用的,比如基于文件系統(tǒng)的IPC(管道、Unix域Socket等)、消息隊(duì)列(Sys.V或者Posix的)、System V的信號(hào)燈等。只有一點(diǎn)需要注意,LinuxThreads在核內(nèi)是作為共享存儲(chǔ)區(qū)、共享文件系統(tǒng)屬性、共享信號(hào)處理、共享文件描述符的獨(dú)立進(jìn)程看待的。



  284. Posix線程編程指南(4)   


  285.    
  286. 內(nèi)容:

  287. 1. 線程終止方式
  288. 2. 線程終止時(shí)的清理
  289. 3. 線程終止的同步及其返回值
  290. 4. 關(guān)于pthread_exit()和return
  291. 參考資料
  292. 關(guān)于作者


  293. 相關(guān)內(nèi)容:

  294. (1) 線程創(chuàng)建與取消  
  295. (2) 線程私有數(shù)據(jù)  
  296. (3) 線程同步  




  297. 線程終止
  298. 楊沙洲(pubb@163.net)
  299. 2001 年 11 月

  300. 這是一個(gè)關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細(xì)講述Posix線程庫(kù)API。本文是第四篇將向您講述線程中止。
  301. 1.線程終止方式
  302. 一般來(lái)說(shuō),Posix的線程終止有兩種情況:正常終止和非正常終止。線程主動(dòng)調(diào)用pthread_exit()或者從線程函數(shù)中return都將使線程正常退出,這是可預(yù)見(jiàn)的退出方式;非正常終止是線程在其他線程的干預(yù)下,或者由于自身運(yùn)行出錯(cuò)(比如訪問(wèn)非法地址)而退出,這種退出方式是不可預(yù)見(jiàn)的。

  303. 2.線程終止時(shí)的清理
  304. 不論是可預(yù)見(jiàn)的線程終止還是異常終止,都會(huì)存在資源釋放的問(wèn)題,在不考慮因運(yùn)行出錯(cuò)而退出的前提下,如何保證線程終止時(shí)能順利的釋放掉自己所占用的資源,特別是鎖資源,就是一個(gè)必須考慮解決的問(wèn)題。

  305. 最經(jīng)常出現(xiàn)的情形是資源獨(dú)占鎖的使用:線程為了訪問(wèn)臨界資源而為其加上鎖,但在訪問(wèn)過(guò)程中被外界取消,如果線程處于響應(yīng)取消狀態(tài),且采用異步方式響應(yīng),或者在打開(kāi)獨(dú)占鎖以前的運(yùn)行路徑上存在取消點(diǎn),則該臨界資源將永遠(yuǎn)處于鎖定狀態(tài)得不到釋放。外界取消操作是不可預(yù)見(jiàn)的,因此的確需要一個(gè)機(jī)制來(lái)簡(jiǎn)化用于資源釋放的編程。

  306. 在POSIX線程API中提供了一個(gè)pthread_cleanup_push()/pthread_cleanup_pop()函數(shù)對(duì)用于自動(dòng)釋放資源--從pthread_cleanup_push()的調(diào)用點(diǎn)到pthread_cleanup_pop()之間的程序段中的終止動(dòng)作(包括調(diào)用pthread_exit()和取消點(diǎn)終止)都將執(zhí)行pthread_cleanup_push()所指定的清理函數(shù)。API定義如下:

  307. void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
  308. void pthread_cleanup_pop(int execute)






  309. pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的棧結(jié)構(gòu)管理,void routine(void *arg)函數(shù)在調(diào)用pthread_cleanup_push()時(shí)壓入清理函數(shù)棧,多次對(duì)pthread_cleanup_push()的調(diào)用將在清理函數(shù)棧中形成一個(gè)函數(shù)鏈,在執(zhí)行該函數(shù)鏈時(shí)按照壓棧的相反順序彈出。execute參數(shù)表示執(zhí)行到pthread_cleanup_pop()時(shí)是否在彈出清理函數(shù)的同時(shí)執(zhí)行該函數(shù),為0表示不執(zhí)行,非0為執(zhí)行;這個(gè)參數(shù)并不影響異常終止時(shí)清理函數(shù)的執(zhí)行。

  310. pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實(shí)現(xiàn)的,這是pthread.h中的宏定義:

  311. #define pthread_cleanup_push(routine,arg)                                     \
  312.   { struct _pthread_cleanup_buffer _buffer;                                   \
  313.     _pthread_cleanup_push (&_buffer, (routine), (arg));
  314. #define pthread_cleanup_pop(execute)                                          \
  315.     _pthread_cleanup_pop (&_buffer, (execute)); }






  316. 可見(jiàn),pthread_cleanup_push()帶有一個(gè)"{",而pthread_cleanup_pop()帶有一個(gè)"}",因此這兩個(gè)函數(shù)必須成對(duì)出現(xiàn),且必須位于程序的同一級(jí)別的代碼段中才能通過(guò)編譯。在下面的例子里,當(dāng)線程在"do some work"中終止時(shí),將主動(dòng)調(diào)用pthread_mutex_unlock(mut),以完成解鎖動(dòng)作。

  317. pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
  318. pthread_mutex_lock(&mut);
  319. /* do some work */
  320. pthread_mutex_unlock(&mut);
  321. pthread_cleanup_pop(0);






  322. 必須要注意的是,如果線程處于PTHREAD_CANCEL_ASYNCHRONOUS狀態(tài),上述代碼段就有可能出錯(cuò),因?yàn)镃ANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之間發(fā)生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發(fā)生,從而導(dǎo)致清理函數(shù)unlock一個(gè)并沒(méi)有加鎖的mutex變量,造成錯(cuò)誤。因此,在使用清理函數(shù)的時(shí)候,都應(yīng)該暫時(shí)設(shè)置成PTHREAD_CANCEL_DEFERRED模式。為此,POSIX的Linux實(shí)現(xiàn)中還提供了一對(duì)不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴(kuò)展函數(shù),功能與以下代碼段相當(dāng):

  323. { int oldtype;
  324. pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
  325. pthread_cleanup_push(routine, arg);
  326. ...
  327. pthread_cleanup_pop(execute);
  328. pthread_setcanceltype(oldtype, NULL);
  329. }





  330. 3.線程終止的同步及其返回值
  331. 一般情況下,進(jìn)程中各個(gè)線程的運(yùn)行都是相互獨(dú)立的,線程的終止并不會(huì)通知,也不會(huì)影響其他線程,終止的線程所占用的資源也并不會(huì)隨著線程的終止而得到釋放。正如進(jìn)程之間可以用wait()系統(tǒng)調(diào)用來(lái)同步終止并釋放資源一樣,線程之間也有類似機(jī)制,那就是pthread_join()函數(shù)。

  332. void pthread_exit(void *retval)
  333. int pthread_join(pthread_t th, void **thread_return)
  334. int pthread_detach(pthread_t th)






  335. pthread_join()的調(diào)用者將掛起并等待th線程終止,retval是pthread_exit()調(diào)用者線程(線程ID為th)的返回值,如果thread_return不為NULL,則*thread_return=retval。需要注意的是一個(gè)線程僅允許唯一的一個(gè)線程使用pthread_join()等待它的終止,并且被等待的線程應(yīng)該處于可join狀態(tài),即非DETACHED狀態(tài)。

  336. 如果進(jìn)程中的某個(gè)線程執(zhí)行了pthread_detach(th),則th線程將處于DETACHED狀態(tài),這使得th線程在結(jié)束運(yùn)行時(shí)自行釋放所占用的內(nèi)存資源,同時(shí)也無(wú)法由pthread_join()同步,pthread_detach()執(zhí)行之后,對(duì)th請(qǐng)求pthread_join()將返回錯(cuò)誤。

  337. 一個(gè)可join的線程所占用的內(nèi)存僅當(dāng)有線程對(duì)其執(zhí)行了pthread_join()后才會(huì)釋放,因此為了避免內(nèi)存泄漏,所有線程的終止,要么已設(shè)為DETACHED,要么就需要使用pthread_join()來(lái)回收。

  338. 4.關(guān)于pthread_exit()和return
  339. 理論上說(shuō),pthread_exit()和線程宿體函數(shù)退出的功能是相同的,函數(shù)結(jié)束時(shí)會(huì)在內(nèi)部自動(dòng)調(diào)用pthread_exit()來(lái)清理線程相關(guān)的資源。但實(shí)際上二者由于編譯器的處理有很大的不同。

  340. 在進(jìn)程主函數(shù)(main())中調(diào)用pthread_exit(),只會(huì)使主函數(shù)所在的線程(可以說(shuō)是進(jìn)程的主線程)退出;而如果是return,編譯器將使其調(diào)用進(jìn)程退出的代碼(如_exit()),從而導(dǎo)致進(jìn)程及其所有線程結(jié)束運(yùn)行。

  341. 其次,在線程宿主函數(shù)中主動(dòng)調(diào)用return,如果return語(yǔ)句包含在pthread_cleanup_push()/pthread_cleanup_pop()對(duì)中,則不會(huì)引起清理函數(shù)的執(zhí)行,反而會(huì)導(dǎo)致segment fault。



  342. Posix線程編程指南(5)   


  343.    
  344. 內(nèi)容:

  345. 1.獲得本線程ID
  346. 2.判斷兩個(gè)線程是否為同一線程
  347. 3.僅執(zhí)行一次的操作
  348. 4.pthread_kill_other_threads_np()
  349. 關(guān)于作者


  350. 相關(guān)內(nèi)容:

  351. (1) 線程創(chuàng)建與取消  
  352. (2) 線程私有數(shù)據(jù)  
  353. (3) 線程同步  
  354. (4) 線程終止  




  355. 雜項(xiàng)
  356. 楊沙洲(pubb@163.net)
  357. 2001 年 11 月

  358. 這是一個(gè)關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細(xì)講述Posix線程庫(kù)API。本文是第五篇將向您講述pthread_self()、pthread_equal()和pthread_once()等雜項(xiàng)函數(shù)。
  359. 在Posix線程規(guī)范中還有幾個(gè)輔助函數(shù)難以歸類,暫且稱其為雜項(xiàng)函數(shù),主要包括pthread_self()、pthread_equal()和pthread_once()三個(gè),另外還有一個(gè)LinuxThreads非可移植性擴(kuò)展函數(shù)pthread_kill_other_threads_np()。本文就介紹這幾個(gè)函數(shù)的定義和使用。

  360. 1.獲得本線程ID


  361. pthread_t pthread_self(void)

  362. 本函數(shù)返回本線程的標(biāo)識(shí)符。

  363. 在LinuxThreads中,每個(gè)線程都用一個(gè)pthread_descr結(jié)構(gòu)來(lái)描述,其中包含了線程狀態(tài)、線程ID等所有需要的數(shù)據(jù)結(jié)構(gòu),此函數(shù)的實(shí)現(xiàn)就是在線程棧幀中找到本線程的pthread_descr結(jié)構(gòu),然后返回其中的p_tid項(xiàng)。

  364. pthread_t類型在LinuxThreads中定義為無(wú)符號(hào)長(zhǎng)整型。

  365. 2.判斷兩個(gè)線程是否為同一線程
  366. int pthread_equal(pthread_t thread1, pthread_t thread2)

  367. 判斷兩個(gè)線程描述符是否指向同一線程。在LinuxThreads中,線程ID相同的線程必然是同一個(gè)線程,因此,這個(gè)函數(shù)的實(shí)現(xiàn)僅僅判斷thread1和thread2是否相等。

  368. 3.僅執(zhí)行一次的操作
  369. int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

  370. 本函數(shù)使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數(shù)在本進(jìn)程執(zhí)行序列中僅執(zhí)行一次。

  371. #include <stdio.h>;
  372. #include <pthread.h>;

  373. pthread_once_t  once=PTHREAD_ONCE_INIT;

  374. void    once_run(void)
  375. {
  376.         printf("once_run in thread %d\n",pthread_self());
  377. }

  378. void * child1(void *arg)
  379. {
  380.         int tid=pthread_self();
  381.         printf("thread %d enter\n",tid);
  382.         pthread_once(&once,once_run);
  383.         printf("thread %d returns\n",tid);
  384. }

  385. void * child2(void *arg)
  386. {
  387.         int tid=pthread_self();
  388.         printf("thread %d enter\n",tid);
  389.         pthread_once(&once,once_run);
  390.         printf("thread %d returns\n",tid);
  391. }

  392. int main(void)
  393. {
  394.         int tid1,tid2;

  395.         printf("hello\n");
  396.         pthread_create(&tid1,NULL,child1,NULL);
  397.         pthread_create(&tid2,NULL,child2,NULL);
  398.         sleep(10);
  399.         printf("main thread exit\n");
  400.         return 0;
  401. }





  402. once_run()函數(shù)僅執(zhí)行一次,且究竟在哪個(gè)線程中執(zhí)行是不定的,盡管pthread_once(&once,once_run)出現(xiàn)在兩個(gè)線程中。

  403. LinuxThreads使用互斥鎖和條件變量保證由pthread_once()指定的函數(shù)執(zhí)行且僅執(zhí)行一次,而once_control則表征是否執(zhí)行過(guò)。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定義為0),pthread_once()的行為就會(huì)不正常。在LinuxThreads中,實(shí)際"一次性函數(shù)"的執(zhí)行狀態(tài)有三種:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值設(shè)為1,則由于所有pthread_once()都必須等待其中一個(gè)激發(fā)"已執(zhí)行一次"信號(hào),因此所有pthread_once()都會(huì)陷入永久的等待中;如果設(shè)為2,則表示該函數(shù)已執(zhí)行過(guò)一次,從而所有pthread_once()都會(huì)立即返回0。

  404. 4. pthread_kill_other_threads_np()
  405. void pthread_kill_other_threads_np(void)

  406. 這個(gè)函數(shù)是LinuxThreads針對(duì)本身無(wú)法實(shí)現(xiàn)的POSIX約定而做的擴(kuò)展。POSIX要求當(dāng)進(jìn)程的某一個(gè)線程執(zhí)行exec*系統(tǒng)調(diào)用在進(jìn)程空間中加載另一個(gè)程序時(shí),當(dāng)前進(jìn)程的所有線程都應(yīng)終止。由于LinuxThreads的局限性,該機(jī)制無(wú)法在exec中實(shí)現(xiàn),因此要求線程執(zhí)行exec前手工終止其他所有線程。pthread_kill_other_threads_np()的作用就是這個(gè)。

  407. 需要注意的是,pthread_kill_other_threads_np()并沒(méi)有通過(guò)pthread_cancel()來(lái)終止線程,而是直接向管理線程發(fā)"進(jìn)程退出"信號(hào),使所有其他線程都結(jié)束運(yùn)行,而不經(jīng)過(guò)Cancel動(dòng)作,當(dāng)然也不會(huì)執(zhí)行退出回調(diào)函數(shù)。盡管LinuxThreads的實(shí)驗(yàn)結(jié)果與文檔說(shuō)明相同,但代碼實(shí)現(xiàn)中卻是用的__pthread_sig_cancel信號(hào)來(lái)kill線程,應(yīng)該效果與執(zhí)行pthread_cancel()是一樣的,其中原因目前還不清楚。
復(fù)制代碼

論壇徽章:
0
2 [報(bào)告]
發(fā)表于 2004-04-23 22:45 |只看該作者

Posix線程編程指南(轉(zhuǎn)貼)

好像壇子里面玩多線程的蟲(chóng)蟲(chóng)比較少。

論壇徽章:
0
3 [報(bào)告]
發(fā)表于 2004-04-24 07:57 |只看該作者

Posix線程編程指南(轉(zhuǎn)貼)

收藏先!

論壇徽章:
0
4 [報(bào)告]
發(fā)表于 2004-04-24 17:20 |只看該作者

Posix線程編程指南(轉(zhuǎn)貼)

有用過(guò),不過(guò)用的也沒(méi)有那么深入,最多用過(guò)互斥鎖,信號(hào)量很少用

論壇徽章:
0
5 [報(bào)告]
發(fā)表于 2004-04-24 18:12 |只看該作者

Posix線程編程指南(轉(zhuǎn)貼)

我也來(lái)貼幾篇。。。。。。。。。。

國(guó)防科大攻讀博士學(xué)位
2003 年 5 月

共享內(nèi)存可以說(shuō)是最有用的進(jìn)程間通信方式,也是最快的IPC形式。兩個(gè)不同進(jìn)程A、B共享內(nèi)存的意思是,同一塊物理內(nèi)存被映射到進(jìn)程A、B各自的進(jìn)程地址空間。進(jìn)程A可以即時(shí)看到進(jìn)程B對(duì)共享內(nèi)存中數(shù)據(jù)的更新,反之亦然。由于多個(gè)進(jìn)程共享同一塊內(nèi)存區(qū)域,必然需要某種同步機(jī)制,互斥鎖和信號(hào)量都可以。

采用共享內(nèi)存通信的一個(gè)顯而易見(jiàn)的好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫(xiě)內(nèi)存,而不需要任何數(shù)據(jù)的拷貝。對(duì)于像管道和消息隊(duì)列等通信方式,則需要在內(nèi)核和用戶空間進(jìn)行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則只拷貝兩次數(shù)據(jù)[1]:一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。實(shí)際上,進(jìn)程之間在共享內(nèi)存時(shí),并不總是讀寫(xiě)少量數(shù)據(jù)后就解除映射,有新的通信時(shí),再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直到通信完畢為止,這樣,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并沒(méi)有寫(xiě)回文件。共享內(nèi)存中的內(nèi)容往往是在解除映射時(shí)才寫(xiě)回文件的。因此,采用共享內(nèi)存的通信方式效率是非常高的。

Linux的2.2.x內(nèi)核支持多種共享內(nèi)存方式,如mmap()系統(tǒng)調(diào)用,Posix共享內(nèi)存,以及系統(tǒng)V共享內(nèi)存。linux發(fā)行版本如Redhat 8.0支持mmap()系統(tǒng)調(diào)用及系統(tǒng)V共享內(nèi)存,但還沒(méi)實(shí)現(xiàn)Posix共享內(nèi)存,本文將主要介紹mmap()系統(tǒng)調(diào)用及系統(tǒng)V共享內(nèi)存API的原理及應(yīng)用。

一、內(nèi)核怎樣保證各個(gè)進(jìn)程尋址到同一個(gè)共享內(nèi)存區(qū)域的內(nèi)存頁(yè)面

1、page cache及swap cache中頁(yè)面的區(qū)分:一個(gè)被訪問(wèn)文件的物理頁(yè)面都駐留在page cache或swap cache中,一個(gè)頁(yè)面的所有信息由struct page來(lái)描述。struct page中有一個(gè)域?yàn)橹羔榤apping ,它指向一個(gè)struct address_space類型結(jié)構(gòu)。page cache或swap cache中的所有頁(yè)面就是根據(jù)address_space結(jié)構(gòu)以及一個(gè)偏移量來(lái)區(qū)分的。

2、文件與address_space結(jié)構(gòu)的對(duì)應(yīng):一個(gè)具體的文件在打開(kāi)后,內(nèi)核會(huì)在內(nèi)存中為之建立一個(gè)struct inode結(jié)構(gòu),其中的i_mapping域指向一個(gè)address_space結(jié)構(gòu)。這樣,一個(gè)文件就對(duì)應(yīng)一個(gè)address_space結(jié)構(gòu),一個(gè)address_space與一個(gè)偏移量能夠確定一個(gè)page cache 或swap cache中的一個(gè)頁(yè)面。因此,當(dāng)要尋址某個(gè)數(shù)據(jù)時(shí),很容易根據(jù)給定的文件及數(shù)據(jù)在文件內(nèi)的偏移量而找到相應(yīng)的頁(yè)面。

3、進(jìn)程調(diào)用mmap()時(shí),只是在進(jìn)程空間內(nèi)新增了一塊相應(yīng)大小的緩沖區(qū),并設(shè)置了相應(yīng)的訪問(wèn)標(biāo)識(shí),但并沒(méi)有建立進(jìn)程空間到物理頁(yè)面的映射。因此,第一次訪問(wèn)該空間時(shí),會(huì)引發(fā)一個(gè)缺頁(yè)異常。

4、對(duì)于共享內(nèi)存映射情況,缺頁(yè)異常處理程序首先在swap cache中尋找目標(biāo)頁(yè)(符合address_space以及偏移量的物理頁(yè)),如果找到,則直接返回地址;如果沒(méi)有找到,則判斷該頁(yè)是否在交換區(qū)(swap area),如果在,則執(zhí)行一個(gè)換入操作;如果上述兩種情況都不滿足,處理程序?qū)⒎峙湫碌奈锢眄?yè)面,并把它插入到page cache中。進(jìn)程最終將更新進(jìn)程頁(yè)表。
注:對(duì)于映射普通文件情況(非共享映射),缺頁(yè)異常處理程序首先會(huì)在page cache中根據(jù)address_space以及數(shù)據(jù)偏移量尋找相應(yīng)的頁(yè)面。如果沒(méi)有找到,則說(shuō)明文件數(shù)據(jù)還沒(méi)有讀入內(nèi)存,處理程序會(huì)從磁盤讀入相應(yīng)的頁(yè)面,并返回相應(yīng)地址,同時(shí),進(jìn)程頁(yè)表也會(huì)更新。

5、所有進(jìn)程在映射同一個(gè)共享內(nèi)存區(qū)域時(shí),情況都一樣,在建立線性地址與物理地址之間的映射之后,不論進(jìn)程各自的返回地址如何,實(shí)際訪問(wèn)的必然是同一個(gè)共享內(nèi)存區(qū)域?qū)?yīng)的物理頁(yè)面。
注:一個(gè)共享內(nèi)存區(qū)域可以看作是特殊文件系統(tǒng)shm中的一個(gè)文件,shm的安裝點(diǎn)在交換區(qū)上。

上面涉及到了一些數(shù)據(jù)結(jié)構(gòu),圍繞數(shù)據(jù)結(jié)構(gòu)理解問(wèn)題會(huì)容易一些。

二、mmap()及其相關(guān)系統(tǒng)調(diào)用

mmap()系統(tǒng)調(diào)用使得進(jìn)程之間通過(guò)映射同一個(gè)普通文件實(shí)現(xiàn)共享內(nèi)存。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以向訪問(wèn)普通內(nèi)存一樣對(duì)文件進(jìn)行訪問(wèn),不必再調(diào)用read(),write()等操作。

注:實(shí)際上,mmap()系統(tǒng)調(diào)用并不是完全為了用于共享內(nèi)存而設(shè)計(jì)的。它本身提供了不同于一般對(duì)普通文件的訪問(wèn)方式,進(jìn)程可以像讀寫(xiě)內(nèi)存一樣對(duì)普通文件的操作。而Posix或系統(tǒng)V的共享內(nèi)存IPC則純粹用于共享目的,當(dāng)然mmap()實(shí)現(xiàn)共享內(nèi)存也是其主要應(yīng)用之一。

1、mmap()系統(tǒng)調(diào)用形式如下:

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
參數(shù)fd為即將映射到進(jìn)程空間的文件描述字,一般由open()返回,同時(shí),fd可以指定為-1,此時(shí)須指定flags參數(shù)中的MAP_ANON,表明進(jìn)行的是匿名映射(不涉及具體的文件名,避免了文件的創(chuàng)建及打開(kāi),很顯然只能用于具有親緣關(guān)系的進(jìn)程間通信)。len是映射到調(diào)用進(jìn)程地址空間的字節(jié)數(shù),它從被映射文件開(kāi)頭offset個(gè)字節(jié)開(kāi)始算起。prot 參數(shù)指定共享內(nèi)存的訪問(wèn)權(quán)限。可取如下幾個(gè)值的或:PROT_READ(可讀) , PROT_WRITE (可寫(xiě)), PROT_EXEC (可執(zhí)行), PROT_NONE(不可訪問(wèn))。flags由以下幾個(gè)常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。offset參數(shù)一般設(shè)為0,表示從文件頭開(kāi)始映射。參數(shù)addr指定文件應(yīng)被映射到進(jìn)程空間的起始地址,一般被指定一個(gè)空指針,此時(shí)選擇起始地址的任務(wù)留給內(nèi)核來(lái)完成。函數(shù)的返回值為最后文件映射到進(jìn)程空間的地址,進(jìn)程可直接操作起始地址為該值的有效地址。這里不再詳細(xì)介紹mmap()的參數(shù),讀者可參考mmap()手冊(cè)頁(yè)獲得進(jìn)一步的信息。

2、系統(tǒng)調(diào)用mmap()用于共享內(nèi)存的兩種方式:

(1)使用普通文件提供的內(nèi)存映射:適用于任何進(jìn)程之間;此時(shí),需要打開(kāi)或創(chuàng)建一個(gè)文件,然后再調(diào)用mmap();典型調(diào)用代碼如下:

        fd=open(name, flag, mode);
if(fd<0)
        ...
       
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通過(guò)mmap()實(shí)現(xiàn)共享內(nèi)存的通信方式有許多特點(diǎn)和要注意的地方,我們將在范例中進(jìn)行具體說(shuō)明。

(2)使用特殊文件提供匿名內(nèi)存映射:適用于具有親緣關(guān)系的進(jìn)程之間;由于父子進(jìn)程特殊的親緣關(guān)系,在父進(jìn)程中先調(diào)用mmap(),然后調(diào)用fork()。那么在調(diào)用fork()之后,子進(jìn)程繼承父進(jìn)程匿名映射后的地址空間,同樣也繼承mmap()返回的地址,這樣,父子進(jìn)程就可以通過(guò)映射區(qū)域進(jìn)行通信了。注意,這里不是一般的繼承關(guān)系。一般來(lái)說(shuō),子進(jìn)程單獨(dú)維護(hù)從父進(jìn)程繼承下來(lái)的一些變量。而mmap()返回的地址,卻由父子進(jìn)程共同維護(hù)。
對(duì)于具有親緣關(guān)系的進(jìn)程實(shí)現(xiàn)共享內(nèi)存最好的方式應(yīng)該是采用匿名內(nèi)存映射的方式。此時(shí),不必指定具體的文件,只要設(shè)置相應(yīng)的標(biāo)志即可,參見(jiàn)范例2。

3、系統(tǒng)調(diào)用munmap()

int munmap( void * addr, size_t len )
該調(diào)用在進(jìn)程地址空間中解除一個(gè)映射關(guān)系,addr是調(diào)用mmap()時(shí)返回的地址,len是映射區(qū)的大小。當(dāng)映射關(guān)系解除后,對(duì)原來(lái)映射地址的訪問(wèn)將導(dǎo)致段錯(cuò)誤發(fā)生。

4、系統(tǒng)調(diào)用msync()

int msync ( void * addr , size_t len, int flags)
一般說(shuō)來(lái),進(jìn)程在映射空間的對(duì)共享內(nèi)容的改變并不直接寫(xiě)回到磁盤文件中,往往在調(diào)用munmap()后才執(zhí)行該操作?梢酝ㄟ^(guò)調(diào)用msync()實(shí)現(xiàn)磁盤上文件內(nèi)容與共享內(nèi)存區(qū)的內(nèi)容一致。

三、mmap()范例

下面將給出使用mmap()的兩個(gè)范例:范例1給出兩個(gè)進(jìn)程通過(guò)映射普通文件實(shí)現(xiàn)共享內(nèi)存通信;范例2給出父子進(jìn)程通過(guò)匿名映射實(shí)現(xiàn)共享內(nèi)存。系統(tǒng)調(diào)用mmap()有許多有趣的地方,下面是通過(guò)mmap()映射普通文件實(shí)現(xiàn)進(jìn)程間的通信的范例,我們通過(guò)該范例來(lái)說(shuō)明mmap()實(shí)現(xiàn)共享內(nèi)存的特點(diǎn)及注意事項(xiàng)。

范例1:兩個(gè)進(jìn)程通過(guò)映射普通文件實(shí)現(xiàn)共享內(nèi)存通信

范例1包含兩個(gè)子程序:map_normalfile1.c及map_normalfile2.c。編譯兩個(gè)程序,可執(zhí)行文件分別為map_normalfile1及map_normalfile2。兩個(gè)程序通過(guò)命令行參數(shù)指定同一個(gè)文件來(lái)實(shí)現(xiàn)共享內(nèi)存方式的進(jìn)程間通信。map_normalfile2試圖打開(kāi)命令行參數(shù)指定的一個(gè)普通文件,把該文件映射到進(jìn)程的地址空間,并對(duì)映射后的地址空間進(jìn)行寫(xiě)操作。map_normalfile1把命令行參數(shù)指定的文件映射到進(jìn)程地址空間,然后對(duì)映射后的地址空間執(zhí)行讀操作。這樣,兩個(gè)進(jìn)程通過(guò)命令行參數(shù)指定同一個(gè)文件來(lái)實(shí)現(xiàn)共享內(nèi)存方式的進(jìn)程間通信。

下面是兩個(gè)程序代碼:

/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;

main(int argc, char** argv) // map a normal file as shared mem:
{
        int fd,i;
        people *p_map;
        char temp;
       
        fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
        lseek(fd,sizeof(people)*5-1,SEEK_SET);
        write(fd,"",1);
       
        p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
        close( fd );
        temp = 'a';
        for(i=0; i<10; i++)
        {
                temp += 1;
                memcpy( ( *(p_map+i) ).name, &temp,2 );
                ( *(p_map+i) ).age = 20+i;
        }
        printf(" initialize over \n ";
        sleep(10);

        munmap( p_map, sizeof(people)*10 );
        printf( "umap ok \n" );
}

/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;

main(int argc, char** argv)        // map a normal file as shared mem:
{
        int fd,i;
        people *p_map;
        fd=open( argv[1],O_CREAT|O_RDWR,00777 );
        p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        for(i = 0;i<10;i++)
        {
        printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );

        }
        munmap( p_map,sizeof(people)*10 );
}

map_normalfile1.c首先定義了一個(gè)people數(shù)據(jù)結(jié)構(gòu),(在這里采用數(shù)據(jù)結(jié)構(gòu)的方式是因?yàn),共享?nèi)存區(qū)的數(shù)據(jù)往往是有固定格式的,這由通信的各個(gè)進(jìn)程決定,采用結(jié)構(gòu)的方式有普遍代表性)。map_normfile1首先打開(kāi)或創(chuàng)建一個(gè)文件,并把文件的長(zhǎng)度設(shè)置為5個(gè)people結(jié)構(gòu)大小。然后從mmap()的返回地址開(kāi)始,設(shè)置了10個(gè)people結(jié)構(gòu)。然后,進(jìn)程睡眠10秒鐘,等待其他進(jìn)程映射同一個(gè)文件,最后解除映射。

map_normfile2.c只是簡(jiǎn)單的映射一個(gè)文件,并以people數(shù)據(jù)結(jié)構(gòu)的格式從mmap()返回的地址處讀取10個(gè)people結(jié)構(gòu),并輸出讀取的值,然后解除映射。

分別把兩個(gè)程序編譯成可執(zhí)行文件map_normalfile1和map_normalfile2后,在一個(gè)終端上先運(yùn)行./map_normalfile2 /tmp/test_shm,程序輸出結(jié)果如下:

initialize over
umap ok

在map_normalfile1輸出initialize over 之后,輸出umap ok之前,在另一個(gè)終端上運(yùn)行map_normalfile2 /tmp/test_shm,將會(huì)產(chǎn)生如下輸出(為了節(jié)省空間,輸出結(jié)果為稍作整理后的結(jié)果):

name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

在map_normalfile1 輸出umap ok后,運(yùn)行map_normalfile2則輸出如下結(jié)果:

name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;

從程序的運(yùn)行結(jié)果中可以得出的結(jié)論

1、 最終被映射文件的內(nèi)容的長(zhǎng)度不會(huì)超過(guò)文件本身的初始大小,即映射不能改變文件的大;

2、 可以用于進(jìn)程通信的有效地址空間大小大體上受限于被映射文件的大小,但不完全受限于文件大小。打開(kāi)文件被截短為5個(gè)people結(jié)構(gòu)大小,而在map_normalfile1中初始化了10個(gè)people數(shù)據(jù)結(jié)構(gòu),在恰當(dāng)時(shí)候(map_normalfile1輸出initialize over 之后,輸出umap ok之前)調(diào)用map_normalfile2會(huì)發(fā)現(xiàn)map_normalfile2將輸出全部10個(gè)people結(jié)構(gòu)的值,后面將給出詳細(xì)討論。
注:在linux中,內(nèi)存的保護(hù)是以頁(yè)為基本單位的,即使被映射文件只有一個(gè)字節(jié)大小,內(nèi)核也會(huì)為映射分配一個(gè)頁(yè)面大小的內(nèi)存。當(dāng)被映射文件小于一個(gè)頁(yè)面大小時(shí),進(jìn)程可以對(duì)從mmap()返回地址開(kāi)始的一個(gè)頁(yè)面大小進(jìn)行訪問(wèn),而不會(huì)出錯(cuò);但是,如果對(duì)一個(gè)頁(yè)面以外的地址空間進(jìn)行訪問(wèn),則導(dǎo)致錯(cuò)誤發(fā)生,后面將進(jìn)一步描述。因此,可用于進(jìn)程間通信的有效地址空間大小不會(huì)超過(guò)文件大小及一個(gè)頁(yè)面大小的和。

3、 文件一旦被映射后,調(diào)用mmap()的進(jìn)程對(duì)返回地址的訪問(wèn)是對(duì)某一內(nèi)存區(qū)域的訪問(wèn),暫時(shí)脫離了磁盤上文件的影響。所有對(duì)mmap()返回地址空間的操作只在內(nèi)存中有意義,只有在調(diào)用了munmap()后或者msync()時(shí),才把內(nèi)存中的相應(yīng)內(nèi)容寫(xiě)回磁盤文件,所寫(xiě)內(nèi)容仍然不能超過(guò)文件的大小。

范例2:父子進(jìn)程通過(guò)匿名映射實(shí)現(xiàn)共享內(nèi)存

#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;
main(int argc, char** argv)
{
        int i;
        people *p_map;
        char temp;
        p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
        if(fork() == 0)
        {
                sleep(2);
                for(i = 0;i<5;i++)
                        printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);
                (*p_map).age = 100;
                munmap(p_map,sizeof(people)*10); //實(shí)際上,進(jìn)程終止時(shí),會(huì)自動(dòng)解除映射。
                exit();
        }
        temp = 'a';
        for(i = 0;i<5;i++)
        {
                temp += 1;
                memcpy((*(p_map+i)).name, &temp,2);
                (*(p_map+i)).age=20+i;
        }

        sleep(5);
        printf( "parent read: the first people,s age is %d\n",(*p_map).age );
        printf("umap\n";
        munmap( p_map,sizeof(people)*10 );
        printf( "umap ok\n" );
}

考察程序的輸出結(jié)果,體會(huì)父子進(jìn)程匿名共享內(nèi)存:

child read: the 1 people's age is 20
child read: the 2 people's age is 21
child read: the 3 people's age is 22
child read: the 4 people's age is 23
child read: the 5 people's age is 24

parent read: the first people,s age is 100
umap
umap ok

四、對(duì)mmap()返回地址的訪問(wèn)

前面對(duì)范例運(yùn)行結(jié)構(gòu)的討論中已經(jīng)提到,linux采用的是頁(yè)式管理機(jī)制。對(duì)于用mmap()映射普通文件來(lái)說(shuō),進(jìn)程會(huì)在自己的地址空間新增一塊空間,空間大小由mmap()的len參數(shù)指定,注意,進(jìn)程并不一定能夠?qū)θ啃略隹臻g都能進(jìn)行有效訪問(wèn)。進(jìn)程能夠訪問(wèn)的有效地址大小取決于文件被映射部分的大小。簡(jiǎn)單的說(shuō),能夠容納文件被映射部分大小的最少頁(yè)面?zhèn)數(shù)決定了進(jìn)程從mmap()返回的地址開(kāi)始,能夠有效訪問(wèn)的地址空間大小。超過(guò)這個(gè)空間大小,內(nèi)核會(huì)根據(jù)超過(guò)的嚴(yán)重程度返回發(fā)送不同的信號(hào)給進(jìn)程。可用如下圖示說(shuō)明:





注意:文件被映射部分而不是整個(gè)文件決定了進(jìn)程能夠訪問(wèn)的空間大小,另外,如果指定文件的偏移部分,一定要注意為頁(yè)面大小的整數(shù)倍。下面是對(duì)進(jìn)程映射地址空間的訪問(wèn)范例:

#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;

main(int argc, char** argv)
{
        int fd,i;
        int pagesize,offset;
        people *p_map;
       
        pagesize = sysconf(_SC_PAGESIZE);
        printf("pagesize is %d\n",pagesize);
        fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
        lseek(fd,pagesize*2-100,SEEK_SET);
        write(fd,"",1);
        offset = 0;        //此處offset = 0編譯成版本1;offset = pagesize編譯成版本2
        p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
        close(fd);
       
        for(i = 1; i<10; i++)
        {
                (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
                printf("access page %d over\n",i);
                (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
                printf("access page %d edge over, now begin to access page %d\n",i, i+1);
                (*(p_map+pagesize/sizeof(people)*i)).age = 100;
                printf("access page %d over\n",i+1);
        }
        munmap(p_map,sizeof(people)*10);
}

如程序中所注釋的那樣,把程序編譯成兩個(gè)版本,兩個(gè)版本主要體現(xiàn)在文件被映射部分的大小不同。文件的大小介于一個(gè)頁(yè)面與兩個(gè)頁(yè)面之間(大小為:pagesize*2-99),版本1的被映射部分是整個(gè)文件,版本2的文件被映射部分是文件大小減去一個(gè)頁(yè)面后的剩余部分,不到一個(gè)頁(yè)面大小(大小為:pagesize-99)。程序中試圖訪問(wèn)每一個(gè)頁(yè)面邊界,兩個(gè)版本都試圖在進(jìn)程空間中映射pagesize*3的字節(jié)數(shù)。

版本1的輸出結(jié)果如下:

pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
access page 2 over
access page 2 over
access page 2 edge over, now begin to access page 3
Bus error                //被映射文件在進(jìn)程空間中覆蓋了兩個(gè)頁(yè)面,此時(shí),進(jìn)程試圖訪問(wèn)第三個(gè)頁(yè)面

版本2的輸出結(jié)果如下:

pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
Bus error                //被映射文件在進(jìn)程空間中覆蓋了一個(gè)頁(yè)面,此時(shí),進(jìn)程試圖訪問(wèn)第二個(gè)頁(yè)面

結(jié)論:采用系統(tǒng)調(diào)用mmap()實(shí)現(xiàn)進(jìn)程間通信是很方便的,在應(yīng)用層上接口非常簡(jiǎn)潔。內(nèi)部實(shí)現(xiàn)機(jī)制區(qū)涉及到了linux存儲(chǔ)管理以及文件系統(tǒng)等方面的內(nèi)容,可以參考一下相關(guān)重要數(shù)據(jù)結(jié)構(gòu)來(lái)加深理解。在本專題的后面部分,將介紹系統(tǒng)v共享內(nèi)存的實(shí)現(xiàn)。

參考文獻(xiàn):

[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 對(duì)各主題闡述得重點(diǎn)突出,脈絡(luò)清晰。

[2] UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)mmap()有詳細(xì)闡述。

[3] Linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了mmap()相關(guān)的源代碼分析。

[4]mmap()手冊(cè)

關(guān)于作者:

鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式: mlinux@163.com


Linux環(huán)境進(jìn)程間通信(五): 共享內(nèi)存(下)   


   
內(nèi)容:

1、系統(tǒng)V共享內(nèi)存原理
2、系統(tǒng)V共享內(nèi)存API
3、系統(tǒng)V共享內(nèi)存限制
4、系統(tǒng)V共享內(nèi)存范例
結(jié)論
參考資料
關(guān)于作者


相關(guān)內(nèi)容:

Linux 環(huán)境進(jìn)程間通信(一): 管道及有名管道  
Linux 環(huán)境進(jìn)程間通信(二):信號(hào)(上)
Linux 環(huán)境進(jìn)程間通信(二):信號(hào)(下)
Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列
Linux環(huán)境進(jìn)程間通信(四):信號(hào)燈
Linux環(huán)境進(jìn)程間通信(五):共享內(nèi)存(上)


在 Linux 專區(qū)還有:

教程
工具與產(chǎn)品
代碼與組件
項(xiàng)目
文章




鄭彥興 (mlinux@163.com)
國(guó)防科大攻讀博士學(xué)位
2003 年 5 月

在共享內(nèi)存(上)中,主要圍繞著系統(tǒng)調(diào)用mmap()進(jìn)行討論的,本部分將討論系統(tǒng)V共享內(nèi)存,并通過(guò)實(shí)驗(yàn)結(jié)果對(duì)比來(lái)闡述兩者的異同。系統(tǒng)V共享內(nèi)存指的是把所有共享數(shù)據(jù)放在共享內(nèi)存區(qū)域(IPC shared memory region),任何想要訪問(wèn)該數(shù)據(jù)的進(jìn)程都必須在本進(jìn)程的地址空間新增一塊內(nèi)存區(qū)域,用來(lái)映射存放共享數(shù)據(jù)的物理內(nèi)存頁(yè)面。

系統(tǒng)調(diào)用mmap()通過(guò)映射一個(gè)普通文件實(shí)現(xiàn)共享內(nèi)存。系統(tǒng)V則是通過(guò)映射特殊文件系統(tǒng)shm中的文件實(shí)現(xiàn)進(jìn)程間的共享內(nèi)存通信。也就是說(shuō),每個(gè)共享內(nèi)存區(qū)域?qū)?yīng)特殊文件系統(tǒng)shm中的一個(gè)文件(這是通過(guò)shmid_kernel結(jié)構(gòu)聯(lián)系起來(lái)的),后面還將闡述。

1、系統(tǒng)V共享內(nèi)存原理

進(jìn)程間需要共享的數(shù)據(jù)被放在一個(gè)叫做IPC共享內(nèi)存區(qū)域的地方,所有需要訪問(wèn)該共享區(qū)域的進(jìn)程都要把該共享區(qū)域映射到本進(jìn)程的地址空間中去。系統(tǒng)V共享內(nèi)存通過(guò)shmget獲得或創(chuàng)建一個(gè)IPC共享內(nèi)存區(qū)域,并返回相應(yīng)的標(biāo)識(shí)符。內(nèi)核在保證shmget獲得或創(chuàng)建一個(gè)共享內(nèi)存區(qū),初始化該共享內(nèi)存區(qū)相應(yīng)的shmid_kernel結(jié)構(gòu)注同時(shí),還將在特殊文件系統(tǒng)shm中,創(chuàng)建并打開(kāi)一個(gè)同名文件,并在內(nèi)存中建立起該文件的相應(yīng)dentry及inode結(jié)構(gòu),新打開(kāi)的文件不屬于任何一個(gè)進(jìn)程(任何進(jìn)程都可以訪問(wèn)該共享內(nèi)存區(qū))。所有這一切都是系統(tǒng)調(diào)用shmget完成的。

注:每一個(gè)共享內(nèi)存區(qū)都有一個(gè)控制結(jié)構(gòu)struct shmid_kernel,shmid_kernel是共享內(nèi)存區(qū)域中非常重要的一個(gè)數(shù)據(jù)結(jié)構(gòu),它是存儲(chǔ)管理和文件系統(tǒng)結(jié)合起來(lái)的橋梁,定義如下:

struct shmid_kernel /* private to the kernel */
{       
        struct kern_ipc_perm        shm_perm;
        struct file *                shm_file;
        int                        id;
        unsigned long                shm_nattch;
        unsigned long                shm_segsz;
        time_t                        shm_atim;
        time_t                        shm_dtim;
        time_t                        shm_ctim;
        pid_t                        shm_cprid;
        pid_t                        shm_lprid;
};

該結(jié)構(gòu)中最重要的一個(gè)域應(yīng)該是shm_file,它存儲(chǔ)了將被映射文件的地址。每個(gè)共享內(nèi)存區(qū)對(duì)象都對(duì)應(yīng)特殊文件系統(tǒng)shm中的一個(gè)文件,一般情況下,特殊文件系統(tǒng)shm中的文件是不能用read()、write()等方法訪問(wèn)的,當(dāng)采取共享內(nèi)存的方式把其中的文件映射到進(jìn)程地址空間后,可直接采用訪問(wèn)內(nèi)存的方式對(duì)其訪問(wèn)。

這里我們采用[1]中的圖表給出與系統(tǒng)V共享內(nèi)存相關(guān)數(shù)據(jù)結(jié)構(gòu):





正如消息隊(duì)列和信號(hào)燈一樣,內(nèi)核通過(guò)數(shù)據(jù)結(jié)構(gòu)struct ipc_ids shm_ids維護(hù)系統(tǒng)中的所有共享內(nèi)存區(qū)域。上圖中的shm_ids.entries變量指向一個(gè)ipc_id結(jié)構(gòu)數(shù)組,而每個(gè)ipc_id結(jié)構(gòu)數(shù)組中有個(gè)指向kern_ipc_perm結(jié)構(gòu)的指針。到這里讀者應(yīng)該很熟悉了,對(duì)于系統(tǒng)V共享內(nèi)存區(qū)來(lái)說(shuō),kern_ipc_perm的宿主是shmid_kernel結(jié)構(gòu),shmid_kernel是用來(lái)描述一個(gè)共享內(nèi)存區(qū)域的,這樣內(nèi)核就能夠控制系統(tǒng)中所有的共享區(qū)域。同時(shí),在shmid_kernel結(jié)構(gòu)的file類型指針shm_file指向文件系統(tǒng)shm中相應(yīng)的文件,這樣,共享內(nèi)存區(qū)域就與shm文件系統(tǒng)中的文件對(duì)應(yīng)起來(lái)。

在創(chuàng)建了一個(gè)共享內(nèi)存區(qū)域后,還要將它映射到進(jìn)程地址空間,系統(tǒng)調(diào)用shmat()完成此項(xiàng)功能。由于在調(diào)用shmget()時(shí),已經(jīng)創(chuàng)建了文件系統(tǒng)shm中的一個(gè)同名文件與共享內(nèi)存區(qū)域相對(duì)應(yīng),因此,調(diào)用shmat()的過(guò)程相當(dāng)于映射文件系統(tǒng)shm中的同名文件過(guò)程,原理與mmap()大同小異。

2、系統(tǒng)V共享內(nèi)存API

對(duì)于系統(tǒng)V共享內(nèi)存,主要有以下幾個(gè)API:shmget()、shmat()、shmdt()及shmctl()。

#include <sys/ipc.h>;
#include <sys/shm.h>;

shmget()用來(lái)獲得共享內(nèi)存區(qū)域的ID,如果不存在指定的共享區(qū)域就創(chuàng)建相應(yīng)的區(qū)域。shmat()把共享內(nèi)存區(qū)域映射到調(diào)用進(jìn)程的地址空間中去,這樣,進(jìn)程就可以方便地對(duì)共享區(qū)域進(jìn)行訪問(wèn)操作。shmdt()調(diào)用用來(lái)解除進(jìn)程對(duì)共享內(nèi)存區(qū)域的映射。shmctl實(shí)現(xiàn)對(duì)共享內(nèi)存區(qū)域的控制操作。這里我們不對(duì)這些系統(tǒng)調(diào)用作具體的介紹,讀者可參考相應(yīng)的手冊(cè)頁(yè)面,后面的范例中將給出它們的調(diào)用方法。

注:shmget的內(nèi)部實(shí)現(xiàn)包含了許多重要的系統(tǒng)V共享內(nèi)存機(jī)制;shmat在把共享內(nèi)存區(qū)域映射到進(jìn)程空間時(shí),并不真正改變進(jìn)程的頁(yè)表。當(dāng)進(jìn)程第一次訪問(wèn)內(nèi)存映射區(qū)域訪問(wèn)時(shí),會(huì)因?yàn)闆](méi)有物理頁(yè)表的分配而導(dǎo)致一個(gè)缺頁(yè)異常,然后內(nèi)核再根據(jù)相應(yīng)的存儲(chǔ)管理機(jī)制為共享內(nèi)存映射區(qū)域分配相應(yīng)的頁(yè)表。

3、系統(tǒng)V共享內(nèi)存限制

在/proc/sys/kernel/目錄下,記錄著系統(tǒng)V共享內(nèi)存的一下限制,如一個(gè)共享內(nèi)存區(qū)的最大字節(jié)數(shù)shmmax,系統(tǒng)范圍內(nèi)最大共享內(nèi)存區(qū)標(biāo)識(shí)符數(shù)shmmni等,可以手工對(duì)其調(diào)整,但不推薦這樣做。

在[2]中,給出了這些限制的測(cè)試方法,不再贅述。

4、系統(tǒng)V共享內(nèi)存范例

本部分將給出系統(tǒng)V共享內(nèi)存API的使用方法,并對(duì)比分析系統(tǒng)V共享內(nèi)存機(jī)制與mmap()映射普通文件實(shí)現(xiàn)共享內(nèi)存之間的差異,首先給出兩個(gè)進(jìn)程通過(guò)系統(tǒng)V共享內(nèi)存通信的范例:

/***** testwrite.c *******/
#include <sys/ipc.h>;
#include <sys/shm.h>;
#include <sys/types.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int age;
} people;
main(int argc, char** argv)
{
        int shm_id,i;
        key_t key;
        char temp;
        people *p_map;
        char* name = "/dev/shm/myshm2";
        key = ftok(name,0);
        if(key==-1)
                perror("ftok error";
        shm_id=shmget(key,4096,IPC_CREAT);       
        if(shm_id==-1)
        {
                perror("shmget error";
                return;
        }
        p_map=(people*)shmat(shm_id,NULL,0);
        temp='a';
        for(i = 0;i<10;i++)
        {
                temp+=1;
                memcpy((*(p_map+i)).name,&temp,1);
                (*(p_map+i)).age=20+i;
        }
        if(shmdt(p_map)==-1)
                perror(" detach error ";
}
/********** testread.c ************/
#include <sys/ipc.h>;
#include <sys/shm.h>;
#include <sys/types.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int age;
} people;
main(int argc, char** argv)
{
        int shm_id,i;
        key_t key;
        people *p_map;
        char* name = "/dev/shm/myshm2";
        key = ftok(name,0);
        if(key == -1)
                perror("ftok error";
        shm_id = shmget(key,4096,IPC_CREAT);       
        if(shm_id == -1)
        {
                perror("shmget error";
                return;
        }
        p_map = (people*)shmat(shm_id,NULL,0);
        for(i = 0;i<10;i++)
        {
        printf( "name:%s\n",(*(p_map+i)).name );
        printf( "age %d\n",(*(p_map+i)).age );
        }
        if(shmdt(p_map) == -1)
                perror(" detach error ";
}

testwrite.c創(chuàng)建一個(gè)系統(tǒng)V共享內(nèi)存區(qū),并在其中寫(xiě)入格式化數(shù)據(jù);testread.c訪問(wèn)同一個(gè)系統(tǒng)V共享內(nèi)存區(qū),讀出其中的格式化數(shù)據(jù)。分別把兩個(gè)程序編譯為testwrite及testread,先后執(zhí)行./testwrite及./testread 則./testread輸出結(jié)果如下:

name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

通過(guò)對(duì)試驗(yàn)結(jié)果分析,對(duì)比系統(tǒng)V與mmap()映射普通文件實(shí)現(xiàn)共享內(nèi)存通信,可以得出如下結(jié)論:

1、 系統(tǒng)V共享內(nèi)存中的數(shù)據(jù),從來(lái)不寫(xiě)入到實(shí)際磁盤文件中去;而通過(guò)mmap()映射普通文件實(shí)現(xiàn)的共享內(nèi)存通信可以指定何時(shí)將數(shù)據(jù)寫(xiě)入磁盤文件中。注:前面講到,系統(tǒng)V共享內(nèi)存機(jī)制實(shí)際是通過(guò)映射特殊文件系統(tǒng)shm中的文件實(shí)現(xiàn)的,文件系統(tǒng)shm的安裝點(diǎn)在交換分區(qū)上,系統(tǒng)重新引導(dǎo)后,所有的內(nèi)容都丟失。

2、 系統(tǒng)V共享內(nèi)存是隨內(nèi)核持續(xù)的,即使所有訪問(wèn)共享內(nèi)存的進(jìn)程都已經(jīng)正常終止,共享內(nèi)存區(qū)仍然存在(除非顯式刪除共享內(nèi)存),在內(nèi)核重新引導(dǎo)之前,對(duì)該共享內(nèi)存區(qū)域的任何改寫(xiě)操作都將一直保留。

3、 通過(guò)調(diào)用mmap()映射普通文件進(jìn)行進(jìn)程間通信時(shí),一定要注意考慮進(jìn)程何時(shí)終止對(duì)通信的影響。而通過(guò)系統(tǒng)V共享內(nèi)存實(shí)現(xiàn)通信的進(jìn)程則不然。注:這里沒(méi)有給出shmctl的使用范例,原理與消息隊(duì)列大同小異。

結(jié)論:

共享內(nèi)存允許兩個(gè)或多個(gè)進(jìn)程共享一給定的存儲(chǔ)區(qū),因?yàn)閿?shù)據(jù)不需要來(lái)回復(fù)制,所以是最快的一種進(jìn)程間通信機(jī)制。共享內(nèi)存可以通過(guò)mmap()映射普通文件(特殊情況下還可以采用匿名映射)機(jī)制實(shí)現(xiàn),也可以通過(guò)系統(tǒng)V共享內(nèi)存機(jī)制實(shí)現(xiàn)。應(yīng)用接口和原理很簡(jiǎn)單,內(nèi)部機(jī)制復(fù)雜。為了實(shí)現(xiàn)更安全通信,往往還與信號(hào)燈等同步機(jī)制共同使用。

共享內(nèi)存涉及到了存儲(chǔ)管理以及文件系統(tǒng)等方面的知識(shí),深入理解其內(nèi)部機(jī)制有一定的難度,關(guān)鍵還要緊緊抓住內(nèi)核使用的重要數(shù)據(jù)結(jié)構(gòu)。系統(tǒng)V共享內(nèi)存是以文件的形式組織在特殊文件系統(tǒng)shm中的。通過(guò)shmget可以創(chuàng)建或獲得共享內(nèi)存的標(biāo)識(shí)符。取得共享內(nèi)存標(biāo)識(shí)符后,要通過(guò)shmat將這個(gè)內(nèi)存區(qū)映射到本進(jìn)程的虛擬地址空間。

參考文獻(xiàn):

[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 對(duì)各主題闡述得重點(diǎn)突出,脈絡(luò)清晰。

[2] UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)mmap()有詳細(xì)闡述。

[3] Linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了mmap()相關(guān)的源代碼分析。

[4]shmget、shmat、shmctl、shmdt手冊(cè)

關(guān)于作者:

鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式: mlinux@163.com

論壇徽章:
0
6 [報(bào)告]
發(fā)表于 2004-04-24 18:16 |只看該作者

Posix線程編程指南(轉(zhuǎn)貼)

上篇是關(guān)于share memory的,下面是關(guān)于semaphore的。!

Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列     


   
內(nèi)容:

一、消息隊(duì)列基本概念
二、操作消息隊(duì)列
三、消息隊(duì)列的限制
四、消息隊(duì)列應(yīng)用實(shí)例
小結(jié):
參考資料
關(guān)于作者


相關(guān)內(nèi)容:

Linux 環(huán)境進(jìn)程間通信(一): 管道及有名管道  
Linux 環(huán)境進(jìn)程間通信(二):信號(hào)(上)
Linux 環(huán)境進(jìn)程間通信(二):信號(hào)(下)




鄭彥興 (mlinux@163.com)
國(guó)防科大攻讀博士學(xué)位
2003 年 1 月

本系列文章中的前兩部分,我們探討管道及信號(hào)兩種通信機(jī)制,本文將深入第三部分,介紹系統(tǒng) V 消息隊(duì)列及其相應(yīng) API。
消息隊(duì)列(也叫做報(bào)文隊(duì)列)能夠克服早期unix通信機(jī)制的一些缺點(diǎn)。作為早期unix通信機(jī)制之一的信號(hào)能夠傳送的信息量有限,后來(lái)雖然POSIX 1003.1b在信號(hào)的實(shí)時(shí)性方面作了拓廣,使得信號(hào)在傳遞信息量方面有了相當(dāng)程度的改進(jìn),但是信號(hào)這種通信方式更像"即時(shí)"的通信方式,它要求接受信號(hào)的進(jìn)程在某個(gè)時(shí)間范圍內(nèi)對(duì)信號(hào)做出反應(yīng),因此該信號(hào)最多在接受信號(hào)進(jìn)程的生命周期內(nèi)才有意義,信號(hào)所傳遞的信息是接近于隨進(jìn)程持續(xù)的概念(process-persistent),見(jiàn)附錄 1;管道及有名管道及有名管道則是典型的隨進(jìn)程持續(xù)IPC,并且,只能傳送無(wú)格式的字節(jié)流無(wú)疑會(huì)給應(yīng)用程序開(kāi)發(fā)帶來(lái)不便,另外,它的緩沖區(qū)大小也受到限制。

消息隊(duì)列就是一個(gè)消息的鏈表。可以把消息看作一個(gè)記錄,具有特定的格式以及特定的優(yōu)先級(jí)。對(duì)消息隊(duì)列有寫(xiě)權(quán)限的進(jìn)程可以向中按照一定的規(guī)則添加新消息;對(duì)消息隊(duì)列有讀權(quán)限的進(jìn)程則可以從消息隊(duì)列中讀走消息。消息隊(duì)列是隨內(nèi)核持續(xù)的(參見(jiàn)附錄 1)。

目前主要有兩種類型的消息隊(duì)列:POSIX消息隊(duì)列以及系統(tǒng)V消息隊(duì)列,系統(tǒng)V消息隊(duì)列目前被大量使用。考慮到程序的可移植性,新開(kāi)發(fā)的應(yīng)用程序應(yīng)盡量使用POSIX消息隊(duì)列。

在本系列專題的序(深刻理解Linux進(jìn)程間通信(IPC))中,提到對(duì)于消息隊(duì)列、信號(hào)燈、以及共享內(nèi)存區(qū)來(lái)說(shuō),有兩個(gè)實(shí)現(xiàn)版本:POSIX的以及系統(tǒng)V的。Linux內(nèi)核(內(nèi)核2.4.18)支持POSIX信號(hào)燈、POSIX共享內(nèi)存區(qū)以及POSIX消息隊(duì)列,但對(duì)于主流Linux發(fā)行版本之一redhad8.0(內(nèi)核2.4.18),還沒(méi)有提供對(duì)POSIX進(jìn)程間通信API的支持,不過(guò)應(yīng)該只是時(shí)間上的事。

因此,本文將主要介紹系統(tǒng)V消息隊(duì)列及其相應(yīng)API。在沒(méi)有聲明的情況下,以下討論中指的都是系統(tǒng)V消息隊(duì)列。

一、消息隊(duì)列基本概念

系統(tǒng)V消息隊(duì)列是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者顯示刪除一個(gè)消息隊(duì)列時(shí),該消息隊(duì)列才會(huì)真正被刪除。因此系統(tǒng)中記錄消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)(struct ipc_ids msg_ids)位于內(nèi)核中,系統(tǒng)中的所有消息隊(duì)列都可以在結(jié)構(gòu)msg_ids中找到訪問(wèn)入口。
消息隊(duì)列就是一個(gè)消息的鏈表。每個(gè)消息隊(duì)列都有一個(gè)隊(duì)列頭,用結(jié)構(gòu)struct msg_queue來(lái)描述(參見(jiàn)附錄 2)。隊(duì)列頭中包含了該消息隊(duì)列的大量信息,包括消息隊(duì)列鍵值、用戶ID、組ID、消息隊(duì)列中消息數(shù)目等等,甚至記錄了最近對(duì)消息隊(duì)列讀寫(xiě)進(jìn)程的ID。讀者可以訪問(wèn)這些信息,也可以設(shè)置其中的某些信息。
下圖說(shuō)明了內(nèi)核與消息隊(duì)列是怎樣建立起聯(lián)系的:
其中:struct ipc_ids msg_ids是內(nèi)核中記錄消息隊(duì)列的全局?jǐn)?shù)據(jù)結(jié)構(gòu);struct msg_queue是每個(gè)消息隊(duì)列的隊(duì)列頭。

從上圖可以看出,全局?jǐn)?shù)據(jù)結(jié)構(gòu) struct ipc_ids msg_ids 可以訪問(wèn)到每個(gè)消息隊(duì)列頭的第一個(gè)成員:struct kern_ipc_perm;而每個(gè)struct kern_ipc_perm能夠與具體的消息隊(duì)列對(duì)應(yīng)起來(lái)是因?yàn)樵谠摻Y(jié)構(gòu)中,有一個(gè)key_t類型成員key,而key則唯一確定一個(gè)消息隊(duì)列。kern_ipc_perm結(jié)構(gòu)如下:

struct kern_ipc_perm{   //內(nèi)核中記錄消息隊(duì)列的全局?jǐn)?shù)據(jù)結(jié)構(gòu)msg_ids能夠訪問(wèn)到該結(jié)構(gòu);
            key_t   key;    //該鍵值則唯一對(duì)應(yīng)一個(gè)消息隊(duì)列
            uid_t   uid;
            gid_t   gid;
uid_t   cuid;
gid_t   cgid;
mode_t  mode;
unsigned long seq;
}





二、操作消息隊(duì)列

對(duì)消息隊(duì)列的操作無(wú)非有下面三種類型:

1、 打開(kāi)或創(chuàng)建消息隊(duì)列
消息隊(duì)列的內(nèi)核持續(xù)性要求每個(gè)消息隊(duì)列都在系統(tǒng)范圍內(nèi)對(duì)應(yīng)唯一的鍵值,所以,要獲得一個(gè)消息隊(duì)列的描述字,只需提供該消息隊(duì)列的鍵值即可;

注:消息隊(duì)列描述字是由在系統(tǒng)范圍內(nèi)唯一的鍵值生成的,而鍵值可以看作對(duì)應(yīng)系統(tǒng)內(nèi)的一條路經(jīng)。

2、 讀寫(xiě)操作

消息讀寫(xiě)操作非常簡(jiǎn)單,對(duì)開(kāi)發(fā)人員來(lái)說(shuō),每個(gè)消息都類似如下的數(shù)據(jù)結(jié)構(gòu):

struct msgbuf{
long mtype;
char mtext[1];
};




mtype成員代表消息類型,從消息隊(duì)列中讀取消息的一個(gè)重要依據(jù)就是消息的類型;mtext是消息內(nèi)容,當(dāng)然長(zhǎng)度不一定為1。因此,對(duì)于發(fā)送消息來(lái)說(shuō),首先預(yù)置一個(gè)msgbuf緩沖區(qū)并寫(xiě)入消息類型和內(nèi)容,調(diào)用相應(yīng)的發(fā)送函數(shù)即可;對(duì)讀取消息來(lái)說(shuō),首先分配這樣一個(gè)msgbuf緩沖區(qū),然后把消息讀入該緩沖區(qū)即可。

3、 獲得或設(shè)置消息隊(duì)列屬性:

消息隊(duì)列的信息基本上都保存在消息隊(duì)列頭中,因此,可以分配一個(gè)類似于消息隊(duì)列頭的結(jié)構(gòu)(struct msqid_ds,見(jiàn)附錄 2),來(lái)返回消息隊(duì)列的屬性;同樣可以設(shè)置該數(shù)據(jù)結(jié)構(gòu)。






消息隊(duì)列API

1、文件名到鍵值

#include <sys/types.h>;
#include <sys/ipc.h>;
key_t ftok (char*pathname, char proj);





它返回與路徑pathname相對(duì)應(yīng)的一個(gè)鍵值。該函數(shù)不直接對(duì)消息隊(duì)列操作,但在調(diào)用ipc(MSGGET,…)或msgget()來(lái)獲得消息隊(duì)列描述字前,往往要調(diào)用該函數(shù)。典型的調(diào)用代碼是:

   key=ftok(path_ptr, 'a');
    ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
    …





2、linux為操作系統(tǒng)V進(jìn)程間通信的三種方式(消息隊(duì)列、信號(hào)燈、共享內(nèi)存區(qū))提供了一個(gè)統(tǒng)一的用戶界面:
int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);

第一個(gè)參數(shù)指明對(duì)IPC對(duì)象的操作方式,對(duì)消息隊(duì)列而言共有四種操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分別代表向消息隊(duì)列發(fā)送消息、從消息隊(duì)列讀取消息、打開(kāi)或創(chuàng)建消息隊(duì)列、控制消息隊(duì)列;first參數(shù)代表唯一的IPC對(duì)象;下面將介紹四種操作。

int ipc(MSGGET, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgget( (key_t)first,second)。
int ipc(MSGCTL, int first, int second, int third, void *ptr, long fifth)
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgctl( first,second, (struct msqid_ds*) ptr)。
int ipc(MSGSND, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgsnd( first, (struct msgbuf*)ptr, second, third)。
int ipc(MSGRCV, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),


注:本人不主張采用系統(tǒng)調(diào)用ipc(),而更傾向于采用系統(tǒng)V或者POSIX進(jìn)程間通信API。原因如下:

雖然該系統(tǒng)調(diào)用提供了統(tǒng)一的用戶界面,但正是由于這個(gè)特性,它的參數(shù)幾乎不能給出特定的實(shí)際意義(如以first、second來(lái)命名參數(shù)),在一定程度上造成開(kāi)發(fā)不便。
正如ipc手冊(cè)所說(shuō)的:ipc()是linux所特有的,編寫(xiě)程序時(shí)應(yīng)注意程序的移植性問(wèn)題;
該系統(tǒng)調(diào)用的實(shí)現(xiàn)不過(guò)是把系統(tǒng)V IPC函數(shù)進(jìn)行了封裝,沒(méi)有任何效率上的優(yōu)勢(shì);
系統(tǒng)V在IPC方面的API數(shù)量不多,形式也較簡(jiǎn)潔。


3.系統(tǒng)V消息隊(duì)列API
系統(tǒng)V消息隊(duì)列API共有四個(gè),使用時(shí)需要包括幾個(gè)頭文件:

#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/msg.h>;





1)int msgget(key_t key, int msgflg)

參數(shù)key是一個(gè)鍵值,由ftok獲得;msgflg參數(shù)是一些標(biāo)志位。該調(diào)用返回與健值key相對(duì)應(yīng)的消息隊(duì)列描述字。

在以下兩種情況下,該調(diào)用將創(chuàng)建一個(gè)新的消息隊(duì)列:

如果沒(méi)有消息隊(duì)列與健值key相對(duì)應(yīng),并且msgflg中包含了IPC_CREAT標(biāo)志位;
key參數(shù)為IPC_PRIVATE;


參數(shù)msgflg可以為以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或結(jié)果。

調(diào)用返回:成功返回消息隊(duì)列描述字,否則返回-1。

注:參數(shù)key設(shè)置成常數(shù)IPC_PRIVATE并不意味著其他進(jìn)程不能訪問(wèn)該消息隊(duì)列,只意味著即將創(chuàng)建新的消息隊(duì)列。

2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
該系統(tǒng)調(diào)用從msgid代表的消息隊(duì)列中讀取一個(gè)消息,并把消息存儲(chǔ)在msgp指向的msgbuf結(jié)構(gòu)中。

msqid為消息隊(duì)列描述字;消息返回后存儲(chǔ)在msgp指向的地址,msgsz指定msgbuf的mtext成員的長(zhǎng)度(即消息內(nèi)容的長(zhǎng)度),msgtyp為請(qǐng)求讀取的消息類型;讀消息標(biāo)志msgflg可以為以下幾個(gè)常值的或:

IPC_NOWAIT 如果沒(méi)有滿足條件的消息,調(diào)用立即返回,此時(shí),errno=ENOMSG
IPC_EXCEPT 與msgtyp>;0配合使用,返回隊(duì)列中第一個(gè)類型不為msgtyp的消息
IPC_NOERROR 如果隊(duì)列中滿足條件的消息內(nèi)容大于所請(qǐng)求的msgsz字節(jié),則把該消息截?cái),截(cái)嗖糠謱G失。


msgrcv手冊(cè)中詳細(xì)給出了消息類型取不同值時(shí)(>;0; <0; =0),調(diào)用將返回消息隊(duì)列中的哪個(gè)消息。

msgrcv()解除阻塞的條件有三個(gè):

消息隊(duì)列中有了滿足條件的消息;
msqid代表的消息隊(duì)列被刪除;
調(diào)用msgrcv()的進(jìn)程被信號(hào)中斷;


調(diào)用返回:成功返回讀出消息的實(shí)際字節(jié)數(shù),否則返回-1。

3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
向msgid代表的消息隊(duì)列發(fā)送一個(gè)消息,即將發(fā)送的消息存儲(chǔ)在msgp指向的msgbuf結(jié)構(gòu)中,消息的大小由msgze指定。

對(duì)發(fā)送消息來(lái)說(shuō),有意義的msgflg標(biāo)志為IPC_NOWAIT,指明在消息隊(duì)列沒(méi)有足夠空間容納要發(fā)送的消息時(shí),msgsnd是否等待。造成msgsnd()等待的條件有兩種:

當(dāng)前消息的大小與當(dāng)前消息隊(duì)列中的字節(jié)數(shù)之和超過(guò)了消息隊(duì)列的總?cè)萘浚?
當(dāng)前消息隊(duì)列的消息數(shù)(單位"個(gè)")不小于消息隊(duì)列的總?cè)萘浚▎挝?quot;字節(jié)數(shù)"),此時(shí),雖然消息隊(duì)列中的消息數(shù)目很多,但基本上都只有一個(gè)字節(jié)。

msgsnd()解除阻塞的條件有三個(gè):
不滿足上述兩個(gè)條件,即消息隊(duì)列中有容納該消息的空間;
msqid代表的消息隊(duì)列被刪除;
調(diào)用msgsnd()的進(jìn)程被信號(hào)中斷;


調(diào)用返回:成功返回0,否則返回-1。

4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
該系統(tǒng)調(diào)用對(duì)由msqid標(biāo)識(shí)的消息隊(duì)列執(zhí)行cmd操作,共有三種cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。

IPC_STAT:該命令用來(lái)獲取消息隊(duì)列信息,返回的信息存貯在buf指向的msqid結(jié)構(gòu)中;
IPC_SET:該命令用來(lái)設(shè)置消息隊(duì)列的屬性,要設(shè)置的屬性存儲(chǔ)在buf指向的msqid結(jié)構(gòu)中;可設(shè)置屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同時(shí),也影響msg_ctime成員。
IPC_RMID:刪除msqid標(biāo)識(shí)的消息隊(duì)列;


調(diào)用返回:成功返回0,否則返回-1。

三、消息隊(duì)列的限制
每個(gè)消息隊(duì)列的容量(所能容納的字節(jié)數(shù))都有限制,該值因系統(tǒng)不同而不同。在后面的應(yīng)用實(shí)例中,輸出了redhat 8.0的限制,結(jié)果參見(jiàn)附錄 3。

另一個(gè)限制是每個(gè)消息隊(duì)列所能容納的最大消息數(shù):在redhad 8.0中,該限制是受消息隊(duì)列容量制約的:消息個(gè)數(shù)要小于消息隊(duì)列的容量(字節(jié)數(shù))。

注:上述兩個(gè)限制是針對(duì)每個(gè)消息隊(duì)列而言的,系統(tǒng)對(duì)消息隊(duì)列的限制還有系統(tǒng)范圍內(nèi)的最大消息隊(duì)列個(gè)數(shù),以及整個(gè)系統(tǒng)范圍內(nèi)的最大消息數(shù)。一般來(lái)說(shuō),實(shí)際開(kāi)發(fā)過(guò)程中不會(huì)超過(guò)這個(gè)限制。

四、消息隊(duì)列應(yīng)用實(shí)例
消息隊(duì)列應(yīng)用相對(duì)較簡(jiǎn)單,下面實(shí)例基本上覆蓋了對(duì)消息隊(duì)列的所有操作,同時(shí),程序輸出結(jié)果有助于加深對(duì)前面所講的某些規(guī)則及消息隊(duì)列限制的理解。

#include <sys/types.h>;
#include <sys/msg.h>;
#include <unistd.h>;
void msg_stat(int,struct msqid_ds );
main()
{
int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf{
        int mtype;
        char mtext[1];
    }msg_sbuf;
struct msgmbuf
    {
    int mtype;
    char mtext[10];
    }msg_rbuf;
struct msqid_ds msg_ginfo,msg_sinfo;
char* msgpath="/unix/msgqueue";
key=ftok(msgpath,'a');
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666);
if(msgid==-1)
{
    printf("msg create error\n";
    return;
}
//創(chuàng)建一個(gè)消息隊(duì)列后,輸出消息隊(duì)列缺省屬性
msg_stat(msgid,msg_ginfo);
sflags=IPC_NOWAIT;
msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='a';
reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);
if(reval==-1)
{
    printf("message send error\n";
}
//發(fā)送一個(gè)消息后,輸出消息隊(duì)列屬性
msg_stat(msgid,msg_ginfo);
rflags=IPC_NOWAIT|MSG_NOERROR;
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
if(reval==-1)
    printf("read msg error\n";
else
    printf("read from msg queue %d bytes\n",reval);
//從消息隊(duì)列中讀出消息后,輸出消息隊(duì)列屬性
msg_stat(msgid,msg_ginfo);
msg_sinfo.msg_perm.uid=8;//just a try
msg_sinfo.msg_perm.gid=8;//
msg_sinfo.msg_qbytes=16388;
//此處驗(yàn)證超級(jí)用戶可以更改消息隊(duì)列的缺省msg_qbytes
//注意這里設(shè)置的值大于缺省值
reval=msgctl(msgid,IPC_SET,&msg_sinfo);
if(reval==-1)
{
    printf("msg set info error\n";
    return;
}
msg_stat(msgid,msg_ginfo);
//驗(yàn)證設(shè)置消息隊(duì)列屬性
reval=msgctl(msgid,IPC_RMID,NULL);//刪除消息隊(duì)列
if(reval==-1)
{
    printf("unlink msg queue error\n";
    return;
}
}
void msg_stat(int msgid,struct msqid_ds msg_info)
{
int reval;
sleep(1);//只是為了后面輸出時(shí)間的方便
reval=msgctl(msgid,IPC_STAT,&msg_info);
if(reval==-1)
{
    printf("get msg info error\n";
    return;
}
printf("\n";
printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
printf("number of messages in queue is %d\n",msg_info.msg_qnum);
printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
//每個(gè)消息隊(duì)列的容量(字節(jié)數(shù))都有限制MSGMNB,值的大小因系統(tǒng)而異。在創(chuàng)建新的消息隊(duì)列時(shí),//msg_qbytes的缺省值就是MSGMNB
printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime)));
printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime)));
printf("last change time is %s", ctime(&(msg_info.msg_ctime)));
printf("msg uid is %d\n",msg_info.msg_perm.uid);
printf("msg gid is %d\n",msg_info.msg_perm.gid);
}



程序輸出結(jié)果見(jiàn)附錄 3。

小結(jié):
消息隊(duì)列與管道以及有名管道相比,具有更大的靈活性,首先,它提供有格式字節(jié)流,有利于減少開(kāi)發(fā)人員的工作量;其次,消息具有類型,在實(shí)際應(yīng)用中,可作為優(yōu)先級(jí)使用。這兩點(diǎn)是管道以及有名管道所不能比的。同樣,消息隊(duì)列可以在幾個(gè)進(jìn)程間復(fù)用,而不管這幾個(gè)進(jìn)程是否具有親緣關(guān)系,這一點(diǎn)與有名管道很相似;但消息隊(duì)列是隨內(nèi)核持續(xù)的,與有名管道(隨進(jìn)程持續(xù))相比,生命力更強(qiáng),應(yīng)用空間更大。

附錄 1:在參考文獻(xiàn)[1]中,給出了IPC隨進(jìn)程持續(xù)、隨內(nèi)核持續(xù)以及隨文件系統(tǒng)持續(xù)的定義:

隨進(jìn)程持續(xù):IPC一直存在到打開(kāi)IPC對(duì)象的最后一個(gè)進(jìn)程關(guān)閉該對(duì)象為止。如管道和有名管道;
隨內(nèi)核持續(xù):IPC一直持續(xù)到內(nèi)核重新自舉或者顯示刪除該對(duì)象為止。如消息隊(duì)列、信號(hào)燈以及共享內(nèi)存等;
隨文件系統(tǒng)持續(xù):IPC一直持續(xù)到顯示刪除該對(duì)象為止。


附錄 2:
結(jié)構(gòu)msg_queue用來(lái)描述消息隊(duì)列頭,存在于系統(tǒng)空間:

struct msg_queue {
    struct kern_ipc_perm q_perm;
    time_t q_stime;         /* last msgsnd time */
    time_t q_rtime;         /* last msgrcv time */
    time_t q_ctime;         /* last change time */
    unsigned long q_cbytes;     /* current number of bytes on queue */
    unsigned long q_qnum;       /* number of messages in queue */
    unsigned long q_qbytes;     /* max number of bytes on queue */
    pid_t q_lspid;          /* pid of last msgsnd */
    pid_t q_lrpid;          /* last receive pid */
    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
};





結(jié)構(gòu)msqid_ds用來(lái)設(shè)置或返回消息隊(duì)列的信息,存在于用戶空間;

struct msqid_ds {
    struct ipc_perm msg_perm;
    struct msg *msg_first;      /* first message on queue,unused  */
    struct msg *msg_last;       /* last message in queue,unused */
    __kernel_time_t msg_stime;  /* last msgsnd time */
    __kernel_time_t msg_rtime;  /* last msgrcv time */
    __kernel_time_t msg_ctime;  /* last change time */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;  /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};



//可以看出上述兩個(gè)結(jié)構(gòu)很相似。

附錄 3:消息隊(duì)列實(shí)例輸出結(jié)果:

current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384
pid of last msgsnd is 0
pid of last msgrcv is 0
last msgsnd time is Thu Jan  1 08:00:00 1970
last msgrcv time is Thu Jan  1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
//上面剛剛創(chuàng)建一個(gè)新消息隊(duì)列時(shí)的輸出
current number of bytes on queue is 1
number of messages in queue is 1
max number of bytes on queue is 16384
pid of last msgsnd is 2510
pid of last msgrcv is 0
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Thu Jan  1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
read from msg queue 1 bytes
//實(shí)際讀出的字節(jié)數(shù)
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384   //每個(gè)消息隊(duì)列最大容量(字節(jié)數(shù))
pid of last msgsnd is 2510
pid of last msgrcv is 2510
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16388   //可看出超級(jí)用戶可修改消息隊(duì)列最大容量
pid of last msgsnd is 2510
pid of last msgrcv is 2510  //對(duì)操作消息隊(duì)列進(jìn)程的跟蹤
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:23 2002    //msgctl()調(diào)用對(duì)msg_ctime有影響
msg uid is 8
msg gid is 8





參考文獻(xiàn):

UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)POSIX以及系統(tǒng)V消息隊(duì)列都有闡述,對(duì)Linux環(huán)境下的程序開(kāi)發(fā)有極大的啟發(fā)意義。
linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了系統(tǒng)V消息隊(duì)列相關(guān)的源代碼分析。
http://www.fanqiang.com/a4/b2/20010508/113315.html,主要闡述linux下對(duì)文件的操作,詳細(xì)介紹了對(duì)文件的存取權(quán)限位,對(duì)IPC對(duì)象的存取權(quán)限同樣具有很好的借鑒意義。
msgget、msgsnd、msgrcv、msgctl手冊(cè)

關(guān)于作者:
鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式: mlinux@163.com

//////////////////////////////////////////////////////////////////////////
國(guó)防科大
2003 年 4 月

一、信號(hào)燈概述

信號(hào)燈與其他進(jìn)程間通信方式不大相同,它主要提供對(duì)進(jìn)程間共享資源訪問(wèn)控制機(jī)制。相當(dāng)于內(nèi)存中的標(biāo)志,進(jìn)程可以根據(jù)它判定是否能夠訪問(wèn)某些共享資源,同時(shí),進(jìn)程也可以修改該標(biāo)志。除了用于訪問(wèn)控制外,還可用于進(jìn)程同步。信號(hào)燈有以下兩種類型:

二值信號(hào)燈:最簡(jiǎn)單的信號(hào)燈形式,信號(hào)燈的值只能取0或1,類似于互斥鎖。
注:二值信號(hào)燈能夠?qū)崿F(xiàn)互斥鎖的功能,但兩者的關(guān)注內(nèi)容不同。信號(hào)燈強(qiáng)調(diào)共享資源,只要共享資源可用,其他進(jìn)程同樣可以修改信號(hào)燈的值;互斥鎖更強(qiáng)調(diào)進(jìn)程,占用資源的進(jìn)程使用完資源后,必須由進(jìn)程本身來(lái)解鎖。
計(jì)算信號(hào)燈:信號(hào)燈的值可以取任意非負(fù)值(當(dāng)然受內(nèi)核本身的約束)。
二、Linux信號(hào)燈

linux對(duì)信號(hào)燈的支持狀況與消息隊(duì)列一樣,在red had 8.0發(fā)行版本中支持的是系統(tǒng)V的信號(hào)燈。因此,本文將主要介紹系統(tǒng)V信號(hào)燈及其相應(yīng)API。在沒(méi)有聲明的情況下,以下討論中指的都是系統(tǒng)V信號(hào)燈。

注意,通常所說(shuō)的系統(tǒng)V信號(hào)燈指的是計(jì)數(shù)信號(hào)燈集。

三、信號(hào)燈與內(nèi)核

1、系統(tǒng)V信號(hào)燈是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者顯示刪除一個(gè)信號(hào)燈集時(shí),該信號(hào)燈集才會(huì)真正被刪除。因此系統(tǒng)中記錄信號(hào)燈的數(shù)據(jù)結(jié)構(gòu)(struct ipc_ids sem_ids)位于內(nèi)核中,系統(tǒng)中的所有信號(hào)燈都可以在結(jié)構(gòu)sem_ids中找到訪問(wèn)入口。

2、下圖說(shuō)明了內(nèi)核與信號(hào)燈是怎樣建立起聯(lián)系的:

其中:struct ipc_ids sem_ids是內(nèi)核中記錄信號(hào)燈的全局?jǐn)?shù)據(jù)結(jié)構(gòu);描述一個(gè)具體的信號(hào)燈及其相關(guān)信息。





其中,struct sem結(jié)構(gòu)如下:

struct sem{
int semval;                // current value
int sempid                // pid of last operation
}

從上圖可以看出,全局?jǐn)?shù)據(jù)結(jié)構(gòu)struct ipc_ids sem_ids可以訪問(wèn)到struct kern_ipc_perm的第一個(gè)成員:struct kern_ipc_perm;而每個(gè)struct kern_ipc_perm能夠與具體的信號(hào)燈對(duì)應(yīng)起來(lái)是因?yàn)樵谠摻Y(jié)構(gòu)中,有一個(gè)key_t類型成員key,而key則唯一確定一個(gè)信號(hào)燈集;同時(shí),結(jié)構(gòu)struct kern_ipc_perm的最后一個(gè)成員sem_nsems確定了該信號(hào)燈在信號(hào)燈集中的順序,這樣內(nèi)核就能夠記錄每個(gè)信號(hào)燈的信息了。kern_ipc_perm結(jié)構(gòu)參見(jiàn)《Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列》。struct sem_array見(jiàn)附錄1。

四、操作信號(hào)燈

對(duì)消息隊(duì)列的操作無(wú)非有下面三種類型:

1、 打開(kāi)或創(chuàng)建信號(hào)燈
與消息隊(duì)列的創(chuàng)建及打開(kāi)基本相同,不再詳述。

2、 信號(hào)燈值操作
linux可以增加或減小信號(hào)燈的值,相應(yīng)于對(duì)共享資源的釋放和占有。具體參見(jiàn)后面的semop系統(tǒng)調(diào)用。

3、 獲得或設(shè)置信號(hào)燈屬性:
系統(tǒng)中的每一個(gè)信號(hào)燈集都對(duì)應(yīng)一個(gè)struct sem_array結(jié)構(gòu),該結(jié)構(gòu)記錄了信號(hào)燈集的各種信息,存在于系統(tǒng)空間。為了設(shè)置、獲得該信號(hào)燈集的各種信息及屬性,在用戶空間有一個(gè)重要的聯(lián)合結(jié)構(gòu)與之對(duì)應(yīng),即union semun。





聯(lián)合semun數(shù)據(jù)結(jié)構(gòu)各成員意義參見(jiàn)附錄2

信號(hào)燈API

1、文件名到鍵值

#include <sys/types.h>;
#include <sys/ipc.h>;
key_t ftok (char*pathname, char proj);

它返回與路徑pathname相對(duì)應(yīng)的一個(gè)鍵值,具體用法請(qǐng)參考《Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列》。

2、 linux特有的ipc()調(diào)用:

int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);

參數(shù)call取不同值時(shí),對(duì)應(yīng)信號(hào)燈的三個(gè)系統(tǒng)調(diào)用:
當(dāng)call為SEMOP時(shí),對(duì)應(yīng)int semop(int semid, struct sembuf *sops, unsigned nsops)調(diào)用;
當(dāng)call為SEMGET時(shí),對(duì)應(yīng)int semget(key_t key, int nsems, int semflg)調(diào)用;
當(dāng)call為SEMCTL時(shí),對(duì)應(yīng)int semctl(int semid,int semnum,int cmd,union semun arg)調(diào)用;
這些調(diào)用將在后面闡述。

注:本人不主張采用系統(tǒng)調(diào)用ipc(),而更傾向于采用系統(tǒng)V或者POSIX進(jìn)程間通信API。原因已在Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列中給出。

3、系統(tǒng)V信號(hào)燈API

系統(tǒng)V消息隊(duì)列API只有三個(gè),使用時(shí)需要包括幾個(gè)頭文件:

#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/sem.h>;

1)int semget(key_t key, int nsems, int semflg)
參數(shù)key是一個(gè)鍵值,由ftok獲得,唯一標(biāo)識(shí)一個(gè)信號(hào)燈集,用法與msgget()中的key相同;參數(shù)nsems指定打開(kāi)或者新創(chuàng)建的信號(hào)燈集中將包含信號(hào)燈的數(shù)目;semflg參數(shù)是一些標(biāo)志位。參數(shù)key和semflg的取值,以及何時(shí)打開(kāi)已有信號(hào)燈集或者創(chuàng)建一個(gè)新的信號(hào)燈集與msgget()中的對(duì)應(yīng)部分相同,不再祥述。
該調(diào)用返回與健值key相對(duì)應(yīng)的信號(hào)燈集描述字。
調(diào)用返回:成功返回信號(hào)燈集描述字,否則返回-1。
注:如果key所代表的信號(hào)燈已經(jīng)存在,且semget指定了IPC_CREAT|IPC_EXCL標(biāo)志,那么即使參數(shù)nsems與原來(lái)信號(hào)燈的數(shù)目不等,返回的也是EEXIST錯(cuò)誤;如果semget只指定了IPC_CREAT標(biāo)志,那么參數(shù)nsems必須與原來(lái)的值一致,在后面程序?qū)嵗羞要進(jìn)一步說(shuō)明。

2)int semop(int semid, struct sembuf *sops, unsigned nsops);
semid是信號(hào)燈集ID,sops指向數(shù)組的每一個(gè)sembuf結(jié)構(gòu)都刻畫(huà)一個(gè)在特定信號(hào)燈上的操作。nsops為sops指向數(shù)組的大小。
sembuf結(jié)構(gòu)如下:

struct sembuf {
        unsigned short          sem_num;                /* semaphore index in array */
        short                        sem_op;                /* semaphore operation */
        short                        sem_flg;                /* operation flags */
};



sem_num對(duì)應(yīng)信號(hào)集中的信號(hào)燈,0對(duì)應(yīng)第一個(gè)信號(hào)燈。sem_flg可取IPC_NOWAIT以及SEM_UNDO兩個(gè)標(biāo)志。如果設(shè)置了SEM_UNDO標(biāo)志,那么在進(jìn)程結(jié)束時(shí),相應(yīng)的操作將被取消,這是比較重要的一個(gè)標(biāo)志位。如果設(shè)置了該標(biāo)志位,那么在進(jìn)程沒(méi)有釋放共享資源就退出時(shí),內(nèi)核將代為釋放。如果為一個(gè)信號(hào)燈設(shè)置了該標(biāo)志,內(nèi)核都要分配一個(gè)sem_undo結(jié)構(gòu)來(lái)記錄它,為的是確保以后資源能夠安全釋放。事實(shí)上,如果進(jìn)程退出了,那么它所占用就釋放了,但信號(hào)燈值卻沒(méi)有改變,此時(shí),信號(hào)燈值反映的已經(jīng)不是資源占有的實(shí)際情況,在這種情況下,問(wèn)題的解決就靠?jī)?nèi)核來(lái)完成。這有點(diǎn)像僵尸進(jìn)程,進(jìn)程雖然退出了,資源也都釋放了,但內(nèi)核進(jìn)程表中仍然有它的記錄,此時(shí)就需要父進(jìn)程調(diào)用waitpid來(lái)解決問(wèn)題了。
sem_op的值大于0,等于0以及小于0確定了對(duì)sem_num指定的信號(hào)燈進(jìn)行的三種操作。具體請(qǐng)參考linux相應(yīng)手冊(cè)頁(yè)。
這里需要強(qiáng)調(diào)的是semop同時(shí)操作多個(gè)信號(hào)燈,在實(shí)際應(yīng)用中,對(duì)應(yīng)多種資源的申請(qǐng)或釋放。semop保證操作的原子性,這一點(diǎn)尤為重要。尤其對(duì)于多種資源的申請(qǐng)來(lái)說(shuō),要么一次性獲得所有資源,要么放棄申請(qǐng),要么在不占有任何資源情況下繼續(xù)等待,這樣,一方面避免了資源的浪費(fèi);另一方面,避免了進(jìn)程之間由于申請(qǐng)共享資源造成死鎖。
也許從實(shí)際含義上更好理解這些操作:信號(hào)燈的當(dāng)前值記錄相應(yīng)資源目前可用數(shù)目;sem_op>;0對(duì)應(yīng)相應(yīng)進(jìn)程要釋放sem_op數(shù)目的共享資源;sem_op=0可以用于對(duì)共享資源是否已用完的測(cè)試;sem_op<0相當(dāng)于進(jìn)程要申請(qǐng)-sem_op個(gè)共享資源。再聯(lián)想操作的原子性,更不難理解該系統(tǒng)調(diào)用何時(shí)正常返回,何時(shí)睡眠等待。
調(diào)用返回:成功返回0,否則返回-1。

3) int semctl(int semid,int semnum,int cmd,union semun arg)
該系統(tǒng)調(diào)用實(shí)現(xiàn)對(duì)信號(hào)燈的各種控制操作,參數(shù)semid指定信號(hào)燈集,參數(shù)cmd指定具體的操作類型;參數(shù)semnum指定對(duì)哪個(gè)信號(hào)燈操作,只對(duì)幾個(gè)特殊的cmd操作有意義;arg用于設(shè)置或返回信號(hào)燈信息。
該系統(tǒng)調(diào)用詳細(xì)信息請(qǐng)參見(jiàn)其手冊(cè)頁(yè),這里只給出參數(shù)cmd所能指定的操作。
IPC_STAT 獲取信號(hào)燈信息,信息由arg.buf返回;
IPC_SET 設(shè)置信號(hào)燈信息,待設(shè)置信息保存在arg.buf中(在manpage中給出了可以設(shè)置哪些信息);
GETALL 返回所有信號(hào)燈的值,結(jié)果保存在arg.array中,參數(shù)sennum被忽略;
GETNCNT 返回等待semnum所代表信號(hào)燈的值增加的進(jìn)程數(shù),相當(dāng)于目前有多少進(jìn)程在等待semnum代表的信號(hào)燈所代表的共享資源;
GETPID 返回最后一個(gè)對(duì)semnum所代表信號(hào)燈執(zhí)行semop操作的進(jìn)程ID;
GETVAL 返回semnum所代表信號(hào)燈的值;
GETZCNT 返回等待semnum所代表信號(hào)燈的值變成0的進(jìn)程數(shù);
SETALL 通過(guò)arg.array更新所有信號(hào)燈的值;同時(shí),更新與本信號(hào)集相關(guān)的semid_ds結(jié)構(gòu)的sem_ctime成員;
SETVAL 設(shè)置semnum所代表信號(hào)燈的值為arg.val;


調(diào)用返回:調(diào)用失敗返回-1,成功返回與cmd相關(guān):

Cmd return value
GETNCNT Semncnt
GETPID Sempid
GETVAL Semval
GETZCNT Semzcnt


五、信號(hào)燈的限制

1、 一次系統(tǒng)調(diào)用semop可同時(shí)操作的信號(hào)燈數(shù)目SEMOPM,semop中的參數(shù)nsops如果超過(guò)了這個(gè)數(shù)目,將返回E2BIG錯(cuò)誤。SEMOPM的大小特定與系統(tǒng),redhat 8.0為32。

2、 信號(hào)燈的最大數(shù)目:SEMVMX,當(dāng)設(shè)置信號(hào)燈值超過(guò)這個(gè)限制時(shí),會(huì)返回ERANGE錯(cuò)誤。在redhat 8.0中該值為32767。

3、 系統(tǒng)范圍內(nèi)信號(hào)燈集的最大數(shù)目SEMMNI以及系統(tǒng)范圍內(nèi)信號(hào)燈的最大數(shù)目SEMMNS。超過(guò)這兩個(gè)限制將返回ENOSPC錯(cuò)誤。redhat 8.0中該值為32000。

4、 每個(gè)信號(hào)燈集中的最大信號(hào)燈數(shù)目SEMMSL,redhat 8.0中為250。 SEMOPM以及SEMVMX是使用semop調(diào)用時(shí)應(yīng)該注意的;SEMMNI以及SEMMNS是調(diào)用semget時(shí)應(yīng)該注意的。SEMVMX同時(shí)也是semctl調(diào)用應(yīng)該注意的。

六、競(jìng)爭(zhēng)問(wèn)題

第一個(gè)創(chuàng)建信號(hào)燈的進(jìn)程同時(shí)也初始化信號(hào)燈,這樣,系統(tǒng)調(diào)用semget包含了兩個(gè)步驟:創(chuàng)建信號(hào)燈;初始化信號(hào)燈。由此可能導(dǎo)致一種競(jìng)爭(zhēng)狀態(tài):第一個(gè)創(chuàng)建信號(hào)燈的進(jìn)程在初始化信號(hào)燈時(shí),第二個(gè)進(jìn)程又調(diào)用semget,并且發(fā)現(xiàn)信號(hào)燈已經(jīng)存在,此時(shí),第二個(gè)進(jìn)程必須具有判斷是否有進(jìn)程正在對(duì)信號(hào)燈進(jìn)行初始化的能力。在參考文獻(xiàn)[1]中,給出了繞過(guò)這種競(jìng)爭(zhēng)狀態(tài)的方法:當(dāng)semget創(chuàng)建一個(gè)新的信號(hào)燈時(shí),信號(hào)燈結(jié)構(gòu)semid_ds的sem_otime成員初始化后的值為0。因此,第二個(gè)進(jìn)程在成功調(diào)用semget后,可再次以IPC_STAT命令調(diào)用semctl,等待sem_otime變?yōu)榉?值,此時(shí)可判斷該信號(hào)燈已經(jīng)初始化完畢。下圖描述了競(jìng)爭(zhēng)狀態(tài)產(chǎn)生及解決方法:





實(shí)際上,這種解決方法也是基于這樣一個(gè)假定:第一個(gè)創(chuàng)建信號(hào)燈的進(jìn)程必須調(diào)用semop,這樣sem_otime才能變?yōu)榉橇阒。另外,因(yàn)榈谝粋(gè)進(jìn)程可能不調(diào)用semop,或者semop操作需要很長(zhǎng)時(shí)間,第二個(gè)進(jìn)程可能無(wú)限期等待下去,或者等待很長(zhǎng)時(shí)間。

七、信號(hào)燈應(yīng)用實(shí)例

本實(shí)例有兩個(gè)目的:1、獲取各種信號(hào)燈信息;2、利用信號(hào)燈實(shí)現(xiàn)共享資源的申請(qǐng)和釋放。并在程序中給出了詳細(xì)注釋。


#include <linux/sem.h>;
#include <stdio.h>;
#include <errno.h>;
#define SEM_PATH "/unix/my_sem"
#define max_tries 3

int semid;
main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg;                         //union semun: 請(qǐng)參考附錄2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL|00666;
flag2=IPC_CREAT|00666;
key=ftok(SEM_PATH,'a');
//error handling for ftok here;
init_ok=0;
semid=semget(key,1,flag1);//create a semaphore set that only includes one semphore.
if(semid<0)
{
        tmperrno=errno;
        perror("semget";
if(tmperrno==EEXIST)
//errno is undefined after a successful library call( including perror call) so it is saved //in tmperrno.
                {
                semid=semget(key,1,flag2);
//flag2 只包含了IPC_CREAT標(biāo)志, 參數(shù)nsems(這里為1)必須與原來(lái)的信號(hào)燈數(shù)目一致
                arg.buf=&sem_info;
                for(i=0; i<max_tries; i++)
                {
                        if(semctl(semid, 0, IPC_STAT, arg)==-1)
                        {        perror("semctl error"; i=max_tries;}
                        else
                        {
                                if(arg.buf->;sem_otime!=0){ i=max_tries;  init_ok=1;}
                                else         sleep(1);       
                        }
                }
                if(!init_ok)
  // do some initializing, here we assume that the first process that creates the sem will
  // finish initialize the sem and run semop in max_tries*1 seconds. else it will not run
  // semop any more.
                {
                        arg.val=1;
                        if(semctl(semid,0,SETVAL,arg)==-1) perror("semctl setval error";
                }
        }
        else
        {perror("semget error, process exit");        exit();        }
}
else //semid>;=0; do some initializing        
{
        arg.val=1;
        if(semctl(semid,0,SETVAL,arg)==-1)
                perror("semctl setval error");
}
//get some information about the semaphore and the limit of semaphore in redhat8.0
        arg.buf=&sem_info;
        if(semctl(semid, 0, IPC_STAT, arg)==-1)
                perror("semctl IPC STAT");               
        printf("owner's uid is %d\n",         arg.buf->;sem_perm.uid);
        printf("owner's gid is %d\n",         arg.buf->;sem_perm.gid);
        printf("creater's uid is %d\n",         arg.buf->;sem_perm.cuid);
        printf("creater's gid is %d\n",         arg.buf->;sem_perm.cgid);

        arg.__buf=&sem_info2;
        if(semctl(semid,0,IPC_INFO,arg)==-1)
                perror("semctl IPC_INFO");
        printf("the number of entries in semaphore map is %d \n",                         arg.__buf->;semmap);
        printf("max number of semaphore identifiers is %d \n",                          arg.__buf->;semmni);
        printf("mas number of semaphores in system is %d \n",                                 arg.__buf->;semmns);
        printf("the number of undo structures system wide is %d \n",                 arg.__buf->;semmnu);
        printf("max number of semaphores per semid is %d \n",                                 arg.__buf->;semmsl);
        printf("max number of ops per semop call is %d \n",                                 arg.__buf->;semopm);
        printf("max number of undo entries per process is %d \n",                          arg.__buf->;semume);
        printf("the sizeof of struct sem_undo is %d \n",                                            arg.__buf->;semusz);
        printf("the maximum semaphore value is %d \n",                                         arg.__buf->;semvmx);
       
//now ask for available resource:       
        askfor_res.sem_num=0;
        askfor_res.sem_op=-1;
        askfor_res.sem_flg=SEM_UNDO;               
               
                if(semop(semid,&askfor_res,1)==-1)//ask for resource
                        perror("semop error");
       
        sleep(3); //do some handling on the sharing resource here, just sleep on it 3 seconds
        printf("now free the resource\n");       
       
//now free resource       
        free_res.sem_num=0;
        free_res.sem_op=1;
        free_res.sem_flg=SEM_UNDO;

        if(semop(semid,&free_res,1)==-1)//free the resource.
                if(errno==EIDRM)
                        printf("the semaphore set was removed\n");
//you can comment out the codes below to compile a different version:                       
        if(semctl(semid, 0, IPC_RMID)==-1)
                perror("semctl IPC_RMID");
        else printf("remove sem ok\n");
}



注:讀者可以嘗試一下注釋掉初始化步驟,進(jìn)程在運(yùn)行時(shí)會(huì)出現(xiàn)何種情況(進(jìn)程在申請(qǐng)資源時(shí)會(huì)睡眠),同時(shí)可以像程序結(jié)尾給出的注釋那樣,把該程序編譯成兩個(gè)不同版本。下面是本程序的運(yùn)行結(jié)果(操作系統(tǒng)redhat8.0):


owner's uid is 0
owner's gid is 0
creater's uid is 0
creater's gid is 0
the number of entries in semaphore map is 32000
max number of semaphore identifiers is 128
mas number of semaphores in system is 32000
the number of undo structures system wide is 32000
max number of semaphores per semid is 250
max number of ops per semop call is 32
max number of undo entries per process is 32
the sizeof of struct sem_undo is 20
the maximum semaphore value is 32767
now free the resource
remove sem ok



Summary:信號(hào)燈與其它進(jìn)程間通信方式有所不同,它主要用于進(jìn)程間同步。通常所說(shuō)的系統(tǒng)V信號(hào)燈實(shí)際上是一個(gè)信號(hào)燈的集合,可用于多種共享資源的進(jìn)程間同步。每個(gè)信號(hào)燈都有一個(gè)值,可以用來(lái)表示當(dāng)前該信號(hào)燈代表的共享資源可用(available)數(shù)量,如果一個(gè)進(jìn)程要申請(qǐng)共享資源,那么就從信號(hào)燈值中減去要申請(qǐng)的數(shù)目,如果當(dāng)前沒(méi)有足夠的可用資源,進(jìn)程可以睡眠等待,也可以立即返回。當(dāng)進(jìn)程要申請(qǐng)多種共享資源時(shí),linux可以保證操作的原子性,即要么申請(qǐng)到所有的共享資源,要么放棄所有資源,這樣能夠保證多個(gè)進(jìn)程不會(huì)造成互鎖。Linux對(duì)信號(hào)燈有各種各樣的限制,程序中給出了輸出結(jié)果。另外,如果讀者想對(duì)信號(hào)燈作進(jìn)一步的理解,建議閱讀sem.h源代碼,該文件不長(zhǎng),但給出了信號(hào)燈相關(guān)的重要數(shù)據(jù)結(jié)構(gòu)。

附錄1: struct sem_array如下:


/*系統(tǒng)中的每個(gè)信號(hào)燈集對(duì)應(yīng)一個(gè)sem_array 結(jié)構(gòu) */
struct sem_array {
        struct kern_ipc_perm        sem_perm;                /* permissions .. see ipc.h */
        time_t                        sem_otime;                        /* last semop time */
        time_t                        sem_ctime;                        /* last change time */
        struct sem                *sem_base;                        /* ptr to first semaphore in array */
        struct sem_queue        *sem_pending;                /* pending operations to be processed */
        struct sem_queue        **sem_pending_last;         /* last pending operation */
        struct sem_undo                *undo;                        /* undo requests on this array */
        unsigned long                sem_nsems;                /* no. of semaphores in array */
};



其中,sem_queue結(jié)構(gòu)如下:


/* 系統(tǒng)中每個(gè)因?yàn)樾盘?hào)燈而睡眠的進(jìn)程,都對(duì)應(yīng)一個(gè)sem_queue結(jié)構(gòu)*/
struct sem_queue {
        struct sem_queue *        next;                 /* next entry in the queue */
        struct sem_queue **        prev;                 /* previous entry in the queue, *(q->;prev) == q */
        struct task_struct*        sleeper;         /* this process */
        struct sem_undo *        undo;                 /* undo structure */
        int   pid;                                                 /* process id of requesting process */
        int   status;                                         /* completion status of operation */
        struct sem_array *        sma;                         /* semaphore array for operations */
        int        id;                                                         /* internal sem id */
        struct sembuf *        sops;                         /* array of pending operations */
        int        nsops;                                                 /* number of operations */
        int        alter;                                                 /* operation will alter semaphore */
};



附錄2:union semun是系統(tǒng)調(diào)用semctl中的重要參數(shù):


union semun {
        int val;                                        /* value for SETVAL */
        struct semid_ds *buf;                /* buffer for IPC_STAT & IPC_SET */
        unsigned short *array;                /* array for GETALL & SETALL */
        struct seminfo *__buf;                /* buffer for IPC_INFO */   //test!!
        void *__pad;
};
struct  seminfo {
        int semmap;
        int semmni;
        int semmns;
        int semmnu;
        int semmsl;
        int semopm;
        int semume;
        int semusz;
        int semvmx;
        int semaem;
};



參考文獻(xiàn):

[1] UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)POSIX以及系統(tǒng)V信號(hào)燈都有闡述,對(duì)Linux環(huán)境下的程序開(kāi)發(fā)有極大的啟發(fā)意義。

[2] linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了系統(tǒng)V信號(hào)燈相關(guān)的源代碼分析,尤其在闡述保證操作原子性方面,以及闡述undo標(biāo)志位時(shí),討論的很深刻。

[3]GNU/Linux編程指南,第二版,Kurt Wall等著,張輝譯

[4]semget、semop、semctl手冊(cè)

關(guān)于作者:

鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式: mlinux@163.com

論壇徽章:
0
7 [報(bào)告]
發(fā)表于 2004-04-24 18:18 |只看該作者

Posix線程編程指南(轉(zhuǎn)貼)

怎么搞的,圖片都沒(méi)有貼上啊,隨告訴我怎么把圖片貼上。。!

論壇徽章:
0
8 [報(bào)告]
發(fā)表于 2009-10-27 23:24 |只看該作者
好東西,收藏了,學(xué)習(xí)!

論壇徽章:
0
9 [報(bào)告]
發(fā)表于 2009-10-28 10:37 |只看該作者

找資料的確辛苦的 頂你

論壇徽章:
0
10 [報(bào)告]
發(fā)表于 2013-04-10 12:22 |只看該作者
d登陸一下就為了回你的帖子
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號(hào)-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號(hào):11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專區(qū)
中國(guó)互聯(lián)網(wǎng)協(xié)會(huì)會(huì)員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過(guò)ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請(qǐng)注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP