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

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
1234下一頁
最近訪問板塊 發(fā)新帖
查看: 8161 | 回復: 39
打印 上一主題 下一主題

Unreliable Guide to Locking -by Rusty Russell-中文版 [復制鏈接]

論壇徽章:
0
跳轉到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2005-11-25 17:45 |只看該作者 |倒序瀏覽
Kernel Locking 中文版


Unreliable Guide To Locking
Rusty Russell

      <rusty@rustcorp.com.au>
翻譯:

      albcamus <albcamus@163.com>


Copyright © 2003 Rusty Russell

This documentation is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

For more details see the file COPYING in the source distribution of Linux.

論壇徽章:
0
2 [報告]
發(fā)表于 2005-11-25 17:46 |只看該作者
第1章. 介紹


歡迎進入Rusty優(yōu)秀的《Unreliable Guide to Kernel Locking》細節(jié)。本文檔描述了Linux 2.6內核的鎖系統(tǒng)。

隨著Linux內核引入了超線程與 搶占 ,每一個Hacking內核的人都應該了解并發(fā)性與為SMP加鎖的基礎知識。

論壇徽章:
0
3 [報告]
發(fā)表于 2005-11-25 17:46 |只看該作者
第2章. 并發(fā)帶來的問題


(如果你已知道并發(fā)是什么,請略過本章).

在一個普通的程序里,你可以這樣為一個計數(shù)器增加計數(shù):


        very_important_count++;


下面是你期望的結果:

table21.png (14.68 KB, 下載次數(shù): 192)

期望的結果

期望的結果

論壇徽章:
0
4 [報告]
發(fā)表于 2005-11-25 17:47 |只看該作者
事情還可能這樣發(fā)生:

Table 2-2. 可能的結果

table22.png (15.04 KB, 下載次數(shù): 172)

可能的結果

可能的結果

論壇徽章:
0
5 [報告]
發(fā)表于 2005-11-25 17:48 |只看該作者
2.1. 競態(tài)條件與臨界區(qū)


這種情況,結果依賴于多個任務的相對執(zhí)行順序,叫做競態(tài)條件。包含并發(fā)問題的代碼,叫做臨界區(qū)。尤其是Linux開始支持SMP機器以來,競態(tài)條件和臨界區(qū)就成為內核設計與實現(xiàn)的主要問題。

搶占具有相同的影響,即使只有一個CPU也是這樣的:搶占了正在臨界區(qū)中執(zhí)行的任務,我們就重現(xiàn)了上面描述的情況。在這種情況下,搶占了其他代碼的線程必須在它的臨界區(qū)獨自運行(排斥其它線程)。

解決的方法是:認識到何時會發(fā)生同時訪問,使用鎖來保證在任意時刻只有一個實例能夠進入臨界區(qū)。在Linux內核中有很多友好的原語(primitives)來幫助你做到這點──也有不友好的原語,但我將當作它們并不存在。

論壇徽章:
0
6 [報告]
發(fā)表于 2005-11-25 17:49 |只看該作者
第3章. 在Linux內核中加鎖


我多么希望能給你一句忠告:不要與比你更不理性的人同眠;而如果是關于鎖的忠告,我會這樣給出:保持簡單。

不要試圖引入新類型的鎖。


真是夠奇怪的,最后一條竟然恰恰與我的忠告相反──因為這時你已經與不理性的人同眠了,你需要考慮弄只看門狗。(譯者案:Watchdog是一種以NMI方式檢查CPU死鎖的機制)


3.1. 兩種主要類型的鎖:自旋鎖與信號量


內核中主要有兩種類型的鎖。最基本的類型是自旋鎖(include/asm/spinlock.h),它只允許一個持有者;如果你獲取自旋鎖時不成功,你將一直在自旋中嘗試獲得它,直到成功。自旋鎖又小又快,可以在任何地方使用。

第二種類型是信號量(include/asm/spinlock.h),它可以在任何時刻被多個持有者同時持有(允許的持有者的數(shù)量是在信號量初試化時規(guī)定的),但是在通常情況下信號量只允許一個持有者(這時它也叫互斥鎖,mutex)。如果你獲取信號量時不成功,任務就會把自身放到一個隊列中睡眠,一直到信號量被釋放時才被喚醒。這意味著在你等待的時候,CPU將做其他的事情。但是,很多情況是不允許睡眠的(參考 第9章 ),這時你應該用自旋鎖而不是信號量。

這兩種鎖的任何一種都是不允許遞歸的:參考 7.1小節(jié).


3.2. 單CPU內核與鎖


在單CPU機器上,對于編譯時既沒打開CONFIG_SMP、也沒打開CONFIG_PREEMPT的內核來說,自旋鎖根本不存在。這是一個出色的設計策略:既然沒有別人能在同時刻執(zhí)行,就沒理由加鎖。

(譯案:感謝max2005兄的指正)

如果編譯時同時打開了CONFIG_SMP和CONFIG_PREEMPT,自旋鎖的作用就僅僅是禁止搶占,這就足以防止任何競態(tài)了。多數(shù)情況下,我們可以把搶占等同于SMP來考慮其可能帶來的競態(tài)問題,不必單獨對付它。

當測試加鎖代碼時,即使你沒有SMP測試環(huán)境,總應該打開CONFIG_SMP和CONFIG_PREEMPT選項,因為這會重現(xiàn)關于鎖的某些類型的BUG。

信號量依然存在,因為它們之所以被引入,乃是為了同步用戶上下文。我們將馬上看到這一點。


3.3. 只在用戶上下文加鎖


如果你的數(shù)據(jù)結構只可能被用戶上下文訪問,你可以用信號量(linux/asm/semaphore.h)來保護它。這是最簡單的情況了:把信號量初試化為可用資源的數(shù)量(通常是1),就可以調用down_interruptible()來獲取信號量,調用up()來釋放它。還有一個down()函數(shù),但應該避免使用它,因為如果有信號到來它不會返回。

例如:linux/net/core/netfilter.c允許用nf_register_sockopt()注冊新的setsockopt()和getsockopt()調用。注冊和注銷只有在模塊加載與卸載時才會發(fā)生(是在引導時執(zhí)行的,沒有并發(fā)問題),注冊了的鏈表只有setsockopt()或getsockopt()系統(tǒng)調用才會查閱。因此,nf_sockopt_mutex 就可以完美的保護住這些,這是因為setsockopt和getsockopt允許睡眠。


3.4. 在用戶上下文與Softirqs之間加鎖


如果一個softirq與用戶上下文共享數(shù)據(jù),就有兩個問題:首先,當前的用戶上下文可能被softirq中斷;其次,臨界區(qū)可能會在別的CPU進入。這時spin_lock_bh() (include/linux/spinlock.h)就有了用武之地。它會在那個CPU上禁止softirqs,然后獲取鎖。spin_unlock_bh()做相反的工作。(由于歷史原因,后綴‘bh’成為對各種下半部的通稱,后者是software interrupts的舊稱。其實spin_lock_bh本應叫作spin_lock_softirq才貼切)

注意這里你也可以用spin_lock_irq()或者spin_lock_irqsave(),這樣不單會禁止softirqs,還會禁止硬件中斷:參考第4章。

這在UP 上也能很好地工作:自旋鎖消失了,該宏變成了只是local_bh_disable() (include/linux/interrupt.h),它會禁止softirqs運行。


3.5. 在用戶上下文與Tasklets之間加鎖


這種情況與上面的情況(譯者案,上面“在用戶上下文與softirqs之間加鎖”的情況)完全一致,因為tasklets本來就是作為一種softirq運行的。


3.6. 在用戶上下文與Timers之間加鎖


這種情況也和上面情況完全一致,因為timers本來就是一種softirq(譯者案:Timer是時鐘中斷的下半部)。從加鎖觀點來看,tasklets和timers的地位是等同的。


3.7. 在Tasklets/Timers之間加鎖


有時一個tasklet或timer會與另一個tasklet或timer共享數(shù)據(jù)。


3.7.1. 同一個Tasklet/Timer


由于同一時刻,一個tasklet決不會在兩個CPU上執(zhí)行,即使在SMP機器上,你也不必擔心你的tasklet會發(fā)生重入問題(同時運行了兩個實例)


3.7.2. 不同的Tasklets/Timers


如果你的tasklet或timer想要同另一個tasklet或timer共享數(shù)據(jù),你需要對它們二者都使用spin_lock()和spin_unlock()。沒必要使用spin_lock_bh(),因為你已經處在tasklet中了,而同一個CPU不會同時再執(zhí)行其他的tasklet。


3.8. 在Softirqs之間加鎖


一個softirq經常需要與自己或其它的tasklet/timer共享數(shù)據(jù)。


3.8.1. 同一個Softirq


同一個softirq可能在別的CPU上執(zhí)行:你可以使用per-CPU數(shù)據(jù)(參考 8.3小節(jié))以獲得更好的性能。如果你打算這樣使用softirq,通常是為了獲得可伸縮的高性能而帶來額外的復雜性。

你需要用spin_lock()和spin_unlock()保護共享數(shù)據(jù)。


3.8.2. 不同的 Softirqs


你需要使用spin_lock()和spin_unlock()保護共享數(shù)據(jù),不管是timer,tasklet,還是不同的/同一個/另一個的softirq:它們中的任何一個都可以在另一個CPU上運行。

[ 本帖最后由 albcamus 于 2006-10-12 11:36 編輯 ]

論壇徽章:
0
7 [報告]
發(fā)表于 2005-11-25 17:50 |只看該作者
第4章. 硬中斷上下文


硬中斷通常與一個tasklet或softirq通信。這通常涉及到把一個任務放到某個隊列中,再由softirq取出來。


4.1. 在硬中斷與Softirqs/Tasklets之間加鎖


如果一個硬件中斷服務例程與一個softirq共享數(shù)據(jù),就有兩點需要考慮。第一,softirq的執(zhí)行過程可能會被硬件中斷打斷;第二,臨界區(qū)可能會被另一個CPU上的硬件中斷進入。這正是spin_lock_irq()派上用場的地方。它在那個CPU上禁止中斷,然后獲取鎖。spin_unlock_irq()做相反的工作。

硬件中斷服務例程中不需要使用spin_lock_irq(),因為當它在執(zhí)行的時候softirq是不可能執(zhí)行的:它可以使用spin_lock(),這個會更快一些。唯一的例外是另一個硬件中斷服務例程使用了相同的鎖:spin_lock_irq()會禁止那個硬件中斷。

這在UP機器上也能很好的工作:自旋鎖消失了,spin_lock_irq()變成了僅僅是local_irq_disable() (include/asm/smp.h),這將會禁止sofirq/tasklet/BH的運行。

spin_lock_irqsave()(include/linux/spinlock.h)是spin_lock_irq()的變體,它把當前的中斷開啟與否的狀態(tài)保存在一個狀態(tài)字中,用以將來傳遞給spin_unlock_restore()。這意味著同樣的代碼既可以在硬件中斷服務例程中(這時中斷已關閉)使用,也可以在softirqs中(這時需要主動禁止中斷)使用。

注意,softirqs(包括tasklets和timers)是在硬件中斷返回時得到運行的,因此spin_lock_irq()同樣也會禁止掉它們。從這個意義上說,spin_lock_irqsave()是最通用和最強大的加鎖函數(shù)。


4.2. 在兩個硬中斷服務例程之間加鎖


很少有兩個硬件中斷服務例程共享數(shù)據(jù)的情況。如果你真的需要這樣做,可以使用spin_lock_irqsave() :在進入中斷服務時是否自動關閉中斷,這件事是體系結構相關的。

論壇徽章:
0
8 [報告]
發(fā)表于 2005-11-25 17:50 |只看該作者
第5章. 關于鎖的使用的圖表


Pete Zaitcev 提供了這樣的總結:

    *

      如果你處在進程上下文(任何系統(tǒng)調用),想把其他進程排擠出臨界區(qū),使用信號量。你可以獲得信號量之后去睡眠(例如調用copy_from_user或kmalloc(x, GFP_KERNEL)之類的函數(shù))。
    *

      否則(亦即:數(shù)據(jù)可能被中斷訪問),使用spin_lock_irqsave()和spin_lock_irqrestore()。
    *

      避免任何持有自旋鎖超過5行代碼的情況,避免任何跨越函數(shù)調用的只有自旋鎖的情況。(有種情況例外,比方象readb這樣的原子訪問)

5.1. 加鎖最低要求表


下面的表列出了在不同上下文中加鎖時的最低要求。在一些情況下,同一個上下文在給定時刻只能在一個CPU上執(zhí)行,因此不需要鎖(例如,某個線程在給定時刻只可能在一個CPU上運行,但如果它需要跟另一個線程共享數(shù)據(jù),就需要鎖了)

記住上面的建議:你可以總是只用spin_lock_irqsave(),它是其它加鎖原語的超集。


表 5-1. 加鎖的要求

table51.png (39.29 KB, 下載次數(shù): 173)

加鎖要求表

加鎖要求表

論壇徽章:
0
9 [報告]
發(fā)表于 2005-11-25 17:53 |只看該作者
第6章. 常見的例子


讓我們一步步看一下一個簡單的例子:一個對映射號的緩存(譯者案:原文“a cache of number to name mappings”,虛存子系統(tǒng)不熟,恐怕翻譯不確)。該緩存保存了對象的使用有多頻繁這個數(shù)據(jù),當它滿了,拋出最少使用的那個。


6.1. 都在用戶上下文


在第一個例子中,我們假定所有的操作都發(fā)生在用戶上下文(也就是,都在系統(tǒng)調用中),所以允許睡眠。這意味著我們可以使用信號量來保護cache和其中的所有對象。下面是代碼:


  1. #include <linux/list.h>
  2. #include <linux/slab.h>
  3. #include <linux/string.h>
  4. #include <asm/semaphore.h>
  5. #include <asm/errno.h>

  6. struct object
  7. {
  8.         struct list_head list;
  9.         int id;
  10.         char name[32];
  11.         int popularity;
  12. };


  13. /* 保護緩存、緩存號和其中的對象 */
  14. static DECLARE_MUTEX(cache_lock);
  15. static LIST_HEAD(cache);
  16. static unsigned int cache_num = 0;
  17. #define MAX_CACHE_SIZE 10

  18. /* 必須持有cache_lock信號量 */
  19. static struct object *__cache_find(int id)
  20. {
  21.         struct object *i;

  22.         list_for_each_entry(i, &cache, list)
  23.                 if (i->id == id) {
  24.                         i->popularity++;
  25.                         return i;
  26.                 }
  27.         return NULL;
  28. }

  29. /* 必須持有cache_lock信號量 */
  30. static void __cache_delete(struct object *obj)
  31. {
  32.         BUG_ON(!obj);
  33.         list_del(&obj->list);
  34.         kfree(obj);
  35.         cache_num--;
  36. }


  37. /* 必須持有cache_lock信號量 */
  38. static void __cache_add(struct object *obj)
  39. {
  40.         list_add(&obj->list, &cache);
  41.         if (++cache_num > MAX_CACHE_SIZE) {
  42.                 struct object *i, *outcast = NULL;
  43.                 list_for_each_entry(i, &cache, list) {
  44.                         if (!outcast || i->popularity < outcast->popularity)
  45.                                 outcast = i;
  46.                 }
  47.                 __cache_delete(outcast);
  48.         }
  49. }

  50. /* 對上面函數(shù)的調用,這才是信號量發(fā)揮作用的地方──譯注 */
  51. int cache_add(int id, const char *name)
  52. {
  53.         struct object *obj;

  54.         if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
  55.                 return -ENOMEM;

  56.         strlcpy(obj->name, name, sizeof(obj->name));
  57.         obj->id = id;
  58.         obj->popularity = 0;

  59.         down(&cache_lock);
  60.         __cache_add(obj);
  61.         up(&cache_lock);
  62.         return 0;
  63. }

  64. void cache_delete(int id)
  65. {
  66.         down(&cache_lock);
  67.         __cache_delete(__cache_find(id));
  68.         up(&cache_lock);
  69. }

  70. int cache_find(int id, char *name)
  71. {
  72.         struct object *obj;
  73.         int ret = -ENOENT;

  74.         down(&cache_lock);
  75.         obj = __cache_find(id);
  76.         if (obj) {
  77.                 ret = 0;
  78.                 strcpy(name, obj->name);
  79.         }
  80.         up(&cache_lock);

  81. return ret;

  82. }

復制代碼


注意我們總是保證在持有cache_lock信號量的情況下對緩存添加、刪除和查找操作:緩存結構自身與其中的對象都被該信號量保護起來。這種情況很簡單,因為我們?yōu)橛脩艨截悢?shù)據(jù),而不允許用戶直接訪問對象。

這里有一個輕量(而且很常見)的優(yōu)化:在cache_add中,我們先設置對象的各個域,然后再獲取鎖。這是安全的,因為沒有人能夠在我們把對象加入到cache之前就訪問它。


6.2. 從中斷上下文中訪問


現(xiàn)在我們來考慮一下cache_find會在中斷上下文中被調用的情況:這個“中斷上下文”或者是硬件中斷,或者是softirq。我們使用一個timer來從cache中刪除對象。

改寫了的代碼在下面,用標準的補丁形式給出:以-開始的行是被刪除了的,以+開始的行是新添加了的。


  1. --- cache.c.usercontext 2003-12-09 13:58:54.000000000 +1100
  2. +++ cache.c.interrupt   2003-12-09 14:07:49.000000000 +1100
  3. @@ -12,7 +12,7 @@
  4.          int popularity;
  5. };

  6. -static DECLARE_MUTEX(cache_lock);
  7. +static spinlock_t cache_lock = SPIN_LOCK_UNLOCKED;
  8. static LIST_HEAD(cache);
  9. static unsigned int cache_num = 0;
  10. #define MAX_CACHE_SIZE 10
  11. @@ -55,6 +55,7 @@
  12. int cache_add(int id, const char *name)
  13. {
  14.          struct object *obj;
  15. +        unsigned long flags;

  16.          if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
  17.                  return -ENOMEM;
  18. @@ -63,30 +64,33 @@
  19.          obj->id = id;
  20.          obj->popularity = 0;

  21. -        down(&cache_lock);
  22. +        spin_lock_irqsave(&cache_lock, flags);
  23.          __cache_add(obj);
  24. -        up(&cache_lock);
  25. +        spin_unlock_irqrestore(&cache_lock, flags);
  26.          return 0;
  27. }

  28. void cache_delete(int id)
  29. {
  30. -        down(&cache_lock);
  31. +        unsigned long flags;
  32. +
  33. +        spin_lock_irqsave(&cache_lock, flags);
  34.          __cache_delete(__cache_find(id));
  35. -        up(&cache_lock);
  36. +        spin_unlock_irqrestore(&cache_lock, flags);
  37. }

  38. int cache_find(int id, char *name)
  39. {
  40.          struct object *obj;
  41.          int ret = -ENOENT;
  42. +        unsigned long flags;

  43. -        down(&cache_lock);
  44. +        spin_lock_irqsave(&cache_lock, flags);
  45.          obj = __cache_find(id);
  46.          if (obj) {
  47.                  ret = 0;
  48.                  strcpy(name, obj->name);
  49.          }
  50. -        up(&cache_lock);
  51. +        spin_unlock_irqrestore(&cache_lock, flags);
  52.          return ret;
  53. }
復制代碼


注意一下,如果中斷原來是開啟著的,spin_lock_irqsave會關掉它們;否則(我們已經處在中斷服務例程中)就什么也不做。這些函數(shù)可以安全地在任何上下文中調用。

不幸的是,cache_add調用了帶有GFP_KERNEL標志的kmalloc,這只是在用戶上下文才是合法的。我假定cache_add仍然只會在用戶上下文中被調用,否則就應該給cache_add添加參數(shù)了。(譯注:我覺得作者的意思是說,如果打算讓cache_add能在中斷和用戶兩種上下文中使用,應該添加一個參數(shù)來表達究竟是在哪種上下文中調用的。FIXME)


6.3. 把對象暴露在文件之外


如果對象包含更多的信息,僅僅提供拷貝函數(shù)是不夠的:其它代碼可能會需要保持一個指向對象的指針,例如,不想每次訪問它都要先查找。這就產生了兩個問題。

第一個問題是,我們采用cache_lock來保護對象,我們必須把這些函數(shù)定義為非static的,這樣其它文件中的代碼才能使用。這使得鎖的使用更加技巧化,再也不是在同一地方了。

第二個問題是生命期問題。如果其它結構保留了一個指向我們的對象的指針,它多半期望該指針總是有效的。很不幸,這只在你持有鎖時才會得到保證,否則其它人可能調用cache_delete來刪除對象──還可能更糟糕,在刪除之后又添加了另一個對象,還在原來的地址上。

但是由于只有一把鎖,你也不能總持有它,否則其它人就完全無法工作了。

該問題的解決是使用引用計數(shù):每個持有指向對象的指針的人,在他們第一次獲得該指針時,遞增一次引用計數(shù);當他們使用完畢,遞減一次引用計數(shù)。誰把引用計數(shù)減到了零,就知道可以刪除了,于是執(zhí)行刪除操作。

下面是代碼:


  1. --- cache.c.interrupt   2003-12-09 14:25:43.000000000 +1100
  2. +++ cache.c.refcnt      2003-12-09 14:33:05.000000000 +1100
  3. @@ -7,6 +7,7 @@
  4. struct object
  5. {
  6.          struct list_head list;
  7. +        unsigned int refcnt;
  8.          int id;
  9.          char name[32];
  10.          int popularity;
  11. @@ -17,6 +18,35 @@
  12. static unsigned int cache_num = 0;
  13. #define MAX_CACHE_SIZE 10

  14. +static void __object_put(struct object *obj)
  15. +{
  16. +        if (--obj->refcnt == 0)
  17. +                kfree(obj);
  18. +}
  19. +
  20. +static void __object_get(struct object *obj)
  21. +{
  22. +        obj->refcnt++;
  23. +}
  24. +
  25. +void object_put(struct object *obj)
  26. +{
  27. +        unsigned long flags;
  28. +
  29. +        spin_lock_irqsave(&cache_lock, flags);
  30. +        __object_put(obj);
  31. +        spin_unlock_irqrestore(&cache_lock, flags);
  32. +}
  33. +
  34. +void object_get(struct object *obj)
  35. +{
  36. +        unsigned long flags;
  37. +
  38. +        spin_lock_irqsave(&cache_lock, flags);
  39. +        __object_get(obj);
  40. +        spin_unlock_irqrestore(&cache_lock, flags);
  41. +}
  42. +
  43. /* Must be holding cache_lock */
  44. static struct object *__cache_find(int id)
  45. {
  46. @@ -35,6 +65,7 @@
  47. {
  48.          BUG_ON(!obj);
  49.          list_del(&obj->list);
  50. +        __object_put(obj);
  51.          cache_num--;
  52. }

  53. @@ -63,6 +94,7 @@
  54.          strlcpy(obj->name, name, sizeof(obj->name));
  55.          obj->id = id;
  56.          obj->popularity = 0;
  57. +        obj->refcnt = 1; /* The cache holds a reference */

  58.          spin_lock_irqsave(&cache_lock, flags);
  59.          __cache_add(obj);
  60. @@ -79,18 +111,15 @@
  61.          spin_unlock_irqrestore(&cache_lock, flags);
  62. }

  63. -int cache_find(int id, char *name)
  64. +struct object *cache_find(int id)
  65. {
  66.          struct object *obj;
  67. -        int ret = -ENOENT;
  68.          unsigned long flags;

  69.          spin_lock_irqsave(&cache_lock, flags);
  70.          obj = __cache_find(id);
  71. -        if (obj) {
  72. -                ret = 0;
  73. -                strcpy(name, obj->name);
  74. -        }
  75. +        if (obj)
  76. +                __object_get(obj);
  77.          spin_unlock_irqrestore(&cache_lock, flags);
  78. -        return ret;
  79. +        return obj;
  80. }
復制代碼


我們把引用計數(shù)操作封裝進標準的‘get’和‘put’函數(shù)中。現(xiàn)在我們可以用cache_find返回對象的指針了,它具有引用計數(shù)功能。這樣,持有對象指針的用戶就可以睡眠了(例如,調用copy_to_user把名字拷貝到用戶空間。)(譯注:最后一句的意思是說,持有對象指針的用戶不必象以前那樣為了保證指針的合法性就一直持有鎖了。引用計數(shù)本身跟鎖以及是否可以睡眠一點關系都沒有)

另一點要注意的是,我說“每個指向對象的指針都應該有一個引用計數(shù)”──因此當對象剛被插入到cache中時,其引用計數(shù)應當為1。有些不同版本的實現(xiàn)并不使用引用計數(shù),但它們更加復雜。


6.3.1. 為引用計數(shù)使用原子操作


實踐中,refcnt的類型應該是atomic_t。在include/asm/atomic.h中定義了幾個原子操作:這些操作保證了從系統(tǒng)的所有CPU的角度看,都是原子的。因此不需要鎖。這種情況下,原子操作要比自旋鎖簡單的多,盡管復雜情況下使用自旋鎖要來得清晰。使用atomic_inc和atomic_dec_and_test來取代標準的加減操作符,再也不用為引用計數(shù)上鎖了。


  1. --- cache.c.refcnt      2003-12-09 15:00:35.000000000 +1100
  2. +++ cache.c.refcnt-atomic       2003-12-11 15:49:42.000000000 +1100
  3. @@ -7,7 +7,7 @@
  4. struct object
  5. {
  6.          struct list_head list;
  7. -        unsigned int refcnt;
  8. +        atomic_t refcnt;
  9.          int id;
  10.          char name[32];
  11.          int popularity;
  12. @@ -18,33 +18,15 @@
  13. static unsigned int cache_num = 0;
  14. #define MAX_CACHE_SIZE 10

  15. -static void __object_put(struct object *obj)
  16. -{
  17. -        if (--obj->refcnt == 0)
  18. -                kfree(obj);
  19. -}
  20. -
  21. -static void __object_get(struct object *obj)
  22. -{
  23. -        obj->refcnt++;
  24. -}
  25. -
  26. void object_put(struct object *obj)
  27. {
  28. -        unsigned long flags;
  29. -
  30. -        spin_lock_irqsave(&cache_lock, flags);
  31. -        __object_put(obj);
  32. -        spin_unlock_irqrestore(&cache_lock, flags);
  33. +        if (atomic_dec_and_test(&obj->refcnt))
  34. +                kfree(obj);
  35. }

  36. void object_get(struct object *obj)
  37. {
  38. -        unsigned long flags;
  39. -
  40. -        spin_lock_irqsave(&cache_lock, flags);
  41. -        __object_get(obj);
  42. -        spin_unlock_irqrestore(&cache_lock, flags);
  43. +        atomic_inc(&obj->refcnt);
  44. }

  45. /* Must be holding cache_lock */
  46. @@ -65,7 +47,7 @@
  47. {
  48.          BUG_ON(!obj);
  49.          list_del(&obj->list);
  50. -        __object_put(obj);
  51. +        object_put(obj);
  52.          cache_num--;
  53. }

  54. @@ -94,7 +76,7 @@
  55.          strlcpy(obj->name, name, sizeof(obj->name));
  56.          obj->id = id;
  57.          obj->popularity = 0;
  58. -        obj->refcnt = 1; /* The cache holds a reference */
  59. +        atomic_set(&obj->refcnt, 1); /* The cache holds a reference */

  60.          spin_lock_irqsave(&cache_lock, flags);
  61.          __cache_add(obj);
  62. @@ -119,7 +101,7 @@
  63.          spin_lock_irqsave(&cache_lock, flags);
  64.          obj = __cache_find(id);
  65.          if (obj)
  66. -                __object_get(obj);
  67. +                object_get(obj);
  68.          spin_unlock_irqrestore(&cache_lock, flags);
  69.          return obj;
  70. }
復制代碼


6.4. 保護對象自身


在上面的例子中,我們都假定對象(除了引用計數(shù))自從創(chuàng)建之后就不會被更改。如果我們希望允許改變name(譯者案,指struct object中的name域),有三種可能:

    *

      你可以把cache_lock變成非static的,告訴人們改變name之前先獲得鎖。
    *

      你也可以提供一個cache_obj_rename函數(shù),該函數(shù)先獲得鎖,然后為調用者改變name。你需要告訴每一個需要改變name的人使用這個函數(shù)。
    *

      你也可以用cache_lock只保護cache自身,而使用另一把鎖來保護name域。

理論上,你可以把鎖的使用細粒度化(fine-grained)到這樣的程度:為每個域維持一把鎖。實踐中,最通用的變體是:

    *

      一把用來保護基礎設施(infrastructure.本例中是cache鏈表)和所有對象的鎖。至今為止我們在示例中看到的便是這種情況。
    *

      一把用來保護基礎設施(包括對象結構中的鏈表指針)的鎖,另一把放在對象內部,用來保護對象的其它域.
    *

      多把鎖來保護基礎設施(例如,Hash表的每一個鏈都用一把鎖保護),可能配合使用每個對象一把的鎖。


下面是“每個對象一把的鎖”的實現(xiàn):


  1. --- cache.c.refcnt-atomic       2003-12-11 15:50:54.000000000 +1100
  2. +++ cache.c.perobjectlock       2003-12-11 17:15:03.000000000 +1100
  3. @@ -6,11 +6,17 @@

  4. struct object
  5. {
  6. +         /* 下面兩個域,由cache_lock來保護 */
  7.          struct list_head list;
  8. +        int popularity;
  9. +
  10.          atomic_t refcnt;
  11. +
  12. +        /* Doesn't change once created. */
  13.          int id;
  14. +
  15. +        spinlock_t lock; /* Protects the name */
  16.          char name[32];
  17. -        int popularity;
  18. };

  19. static spinlock_t cache_lock = SPIN_LOCK_UNLOCKED;
  20. @@ -77,6 +84,7 @@
  21.          obj->id = id;
  22.          obj->popularity = 0;
  23.          atomic_set(&obj->refcnt, 1); /* cache本身占一個引用計數(shù) */
  24. +        spin_lock_init(&obj->lock);

  25.          spin_lock_irqsave(&cache_lock, flags);
  26.          __cache_add(obj);
復制代碼

注意我的策略是popularity域由cache_lock來保護,而不是每個對象一把的鎖。這是因為它(就象對象內部的struct list_head域一樣)在邏輯上是屬于基礎設施的一部分的。這樣,當在__cache_add查找最少使用的對象時,我就不必去獲取每個對象一把的鎖了。

這個策略還有一處要注意:id域是不可更改的,這樣我就不必在__cache_find()時去獲取每個對象一把的鎖來保護它。每個對象一把的鎖,在這里,只是被調用者使用來保護name域的讀和寫。

另外,注意我在注釋里說明了哪些數(shù)據(jù)由哪些鎖來保護。這是非常重要的,因為它描述了代碼運行時的行為,(倘若不加注釋)僅僅靠閱讀代碼是很難獲得認識的。這正如Alan Cox所說,“鎖是用來保護數(shù)據(jù)的,而非代碼。”

論壇徽章:
0
10 [報告]
發(fā)表于 2005-11-25 17:55 |只看該作者
第7章. 常見問題


7.1. 死鎖: 簡單與高級


當一段代碼試圖兩次獲取同一把自旋鎖時,就出現(xiàn)了BUG:它將永遠自旋,等待著鎖被釋放(自旋鎖、讀寫自旋鎖和信號量在linux中皆是非遞歸的)。這很容易診斷:它并非是一個類似“呆上五夜來談清楚毛絨絨的兔子身上有多少根毛的編碼問題”(譯者案:原文是stay-up-five-nights-talk-to-fluffy-code-bunnies ,沒法翻譯啊)之類的問題。

試想一個稍微復雜一點的情況,假設你有一個softirq和用戶上下文共享一片區(qū)域。如果你使用了spin_lock()來保護它,很可能用戶上下文持有鎖時被這個softirq打斷,然后softirq嘗試獲得該鎖──但它永遠也不會成功。

這些情形稱為死鎖。如同上面展示的那樣,它甚至會在單CPU機器上發(fā)生(盡管不會在編譯時沒選上SMP的機器上發(fā)生。因為如果編譯時CONFIG_SMP=n,自旋鎖就消失了。這種情況下你會造成數(shù)據(jù)腐敗)。

這種死鎖容易診斷:在SMP機器上,看門狗(watchdog)timer或者編譯時加入DEBUG_SPINLOCKS (include/linux/spinlock.h)會在發(fā)生死鎖時立即顯示它。

更復雜的問題是一種叫做“致命擁抱”的死鎖,它涉及到兩把或更多的鎖。比方你有一個Hash表:表中的每一個項是一個自旋鎖和一個對象鏈表。在softirq處理函數(shù)中,你可能會想更改某個對象在Hash表中的位置,從某個位置到另一個:你獲得了舊的Hash鏈表的自旋鎖和新的鏈表的自旋鎖,然后從舊的鏈表中刪除對象,插入到新的鏈表中。

這里有兩個問題。第一,如果你的代碼試圖把對象移動到相同的鏈表中,它就會因兩次嘗試獲取同一把鎖而死鎖;第二,如果在別的CPU上運行的同一個softirq試圖把另一個對象從你的目的鏈表移動到你的源鏈表中,下面的事情就發(fā)生了:


Table 7-1. 順序

CPU 1
       

CPU 2

獲取鎖 A -> OK
       

獲取鎖 B -> OK

試圖獲取 B -> spin
       

試圖獲取 A -> spin

這兩個CPU將永遠自旋,等待著對方釋放自旋鎖。它會使系統(tǒng)看起來象崩潰了一樣。


7.2. 防備死鎖


教科書告訴你說:總是以相同的順序獲取鎖,你就不會造成死鎖了。而實踐則會告訴你,這種方法不具有擴展性:當我創(chuàng)建一把鎖的時候,我對內核的理解還真不足以計算出在5000種鎖的層次中,哪一種合適。

最好的鎖是被封裝了的:它們不會暴露在頭文件中,不會被它所在文件之外的函數(shù)持有。你可以讀一下這種代碼,來看看為什么它永遠不會死鎖。因為它持有一把鎖時,是決不會試圖去獲取另一把的。使用你的代碼的人甚至不需要知道你曾經使用了鎖。

一個經典問題是當你提供了回調函數(shù)或鉤子函數(shù):如果你在持有鎖的情況下調用它們,你將冒死鎖的危險:簡單死鎖或致命擁抱(誰能預知回調函數(shù)將會干些什么?)。記住,別的程序員是極度想得到你的(原文:the other programmers are out to get you,實在不懂如何翻譯才好;-( ),所以不要這樣做。


7.2.1. 對死鎖的防備過當


死鎖誠然會帶來問題,然而不如數(shù)據(jù)腐敗(data corruption)之甚。試想這樣的一段代碼,它獲取一把讀鎖,搜索一個鏈表,如果沒找到想要的數(shù)據(jù),就釋放掉讀鎖,然后獲取一把寫鎖,把對象插入到鏈表:這樣的代碼存在競態(tài)問題。

如果你看不出為什么,那就請離我的代碼他媽的遠點兒。


7.3. 競態(tài)Timers: 一次內核游戲


Timers會造成它們獨有的競態(tài)條件?紤]一個對象集合(鏈表,Hash表等),每個對象都有一個timer,該timer負責銷毀它所屬的對象。

如果你打算銷毀整個對象集合(例如模塊卸載的時候),你可能會這樣做:


  1.         /* 這段代碼太太太太太糟糕了:如果想更糟糕的話,你還可以使用“匈牙利命名法”
  2.          */
  3.         spin_lock_bh(&list_lock);

  4.         while (list) {
  5.                 struct foo *next = list->next;
  6.                 del_timer(&list->timer);
  7.                 kfree(list);
  8.                 list = next;
  9.         }

  10.         spin_unlock_bh(&list_lock);
復制代碼

在SMP機器上,這段代碼早晚要導致崩潰。因為一個timer可能在spin_lock_bh()之前已經gone off了,它將只有在我們調用spin_unlock_bh()之后才成功的獲取鎖,然后試圖去釋放掉響應的元素(而元素已經釋放掉了!)

這個問題可以通過檢查del_timer()的返回值來預防:如果返回1,說明timer已經被刪除了;如果返回0,說明(在這個例子中)它正在運行。所以我們應當這樣:


  1.         retry:  
  2.                 spin_lock_bh(&list_lock);

  3.                 while (list) {
  4.                         struct foo *next = list->next;
  5.                         if (!del_timer(&list->timer)) {
  6.                                 /* Give timer a chance to delete this */
  7.                                 spin_unlock_bh(&list_lock);
  8.                                 goto retry;
  9.                         }
  10.                         kfree(list);
  11.                         list = next;
  12.                 }

  13.                 spin_unlock_bh(&list_lock);
  14.    
復制代碼

另一個常見問題是那些自啟動的(通過在timer的函數(shù)的最后調用add_timer()就會實現(xiàn)定時器的自啟動 )timers的刪除操作。這是一個很常見的易導致競態(tài)的情形,你該使用del_timer_sync() (include/linux/timer.h)來處理這種情況。該函數(shù)的返回值是:我們要刪除的timer最終被停止自啟動之前,刪除動作應該執(zhí)行的次數(shù)。


7.4. 混亂的Sparc處理器


Alan Cox說:“在sparc機器上,中斷的禁止/激活動作處在寄存器窗口中”。Andi Kleen說“當你從另一個函數(shù)用標志(譯注:flag, 指如spin_lock_irqsave時的第二個參數(shù))恢復寄存器狀態(tài)時,就會弄亂所有的寄存器窗口”

所以永遠不要把由spin_lock_irqsave()保存的標志傳遞給另一個函數(shù)(除非它被聲明為inline的)。通常沒人會這么干,但你能記住這點最好。David Miller在直接方式下無法做任何事情。(我敢這么說,是因為我有印象他和一位PowverPC維護者在對待折衷點問題上態(tài)度是什么樣子的。)
您需要登錄后才可以回帖 登錄 | 注冊

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

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP