contributed by < 404allen404
>
測驗 3 的程式碼先定義了許多巨集和函數,在此先說明它們的功能。
BIT_MASK(nr) 這個巨集用來產生第 nr 個位元為 1 的 mask , mask 的長度為 BITS_PER_LONG 個位元。要加上 % 的原因是因為要避免產生的 mask 變為 0 。
BIT_WORD(nr) 這個巨集用來換算 nr 是幾個 word ,所以直接把 nr 除以 BITS_PER_LONG 就好。
small_const_nbits(nbits) 這個巨集透過 __buildin_constant_p 確認 nbits 是一個常數而不是變數,並且確認 nbits 是一個大於零的數字且小於等於 BITS_PER_LONG 。
想問一個問題,為什麼需要使用 __buildin_constant_p 去確認 nbits 是否為常數呢?這樣會幫助編譯器去最佳化我們的程式嗎?
GENMASK(h, l) 這個巨集可以分成兩部份來解釋,第一個部份是 ((~0UL) - (1UL << (l)) + 1) ,這一個部份是用來確保第 0 個位元到第 l - 1 個位元是 0 ,而另外一個部份是 (~0UL >> (BITS_PER_LONG - 1 - (h))) ,這個部份用來確保第 l + 1 個位元到最後一個位元的值都是 0 ,最後把這兩個部份做 & 運算,就可以得到從第 l 個位元到第 h 個位元的值都是 1 ,且剩餘的位元都是 0 的 mask 。
__const_hweight8(w) 這個巨集是用來計算 w 的低 8 個位元有幾個 1 。
__const_hweight16(w) 這個巨集使用了 __const_hweight8(w) 這個巨集去算出 w 的低 16 個位元有幾個 1 。
__const_hweight32(w) 這個巨集使用了 __const_hweight16(w) 這個巨集去算出 w 的低 32 個位元有幾個 1 。
__const_hweight64(w) 這個巨集使用了 __const_hweight32(w) 這個巨集用來計算 w 總共有幾個位元是 1 。
hweight_long 這個函數用來計算 w 總共有幾個位元是 1 。
min(x, y) 這個巨集用來得到 x 和 y 之中比較小的那個值。
BITMAP_LAST_WORD_MASK(nbits) 這個巨集主要是用來得到低 nbits 位元都是 1 的 mask ,剩餘的位元都是 0 。接著做更詳細的討論,假設 nbits 是非負整數,當 , 時,可以得到低 nbits 位元都是 1 的 mask ,而當 時,可以得到低 nbits % 64 個位元都是 1 的 mask 。
FIND_NTH_BIT 這個巨集用來找從 FETCH 這個位置開始尋找第 num 個位元是 1 的位置。首先先來解釋 for 迴圈的作用,當 size 大於 BITS_PER_LONG 的時候,代表有可能改變 idx 用來得到更後面記憶體中的值,所以才需要這個 for 迴圈找到合適的 idx 值。在 for 迴圈內,透過 hweight_long 得到 tmp 有幾個位元是 1 , 就可以透過 w 是否大於 nr ,知道是否要讓 for 迴圈繼續下一回合,因為如果 w 小於等於 nr 就代表在 FETCH[idx] 的值已經可以找到第 num 的 1 了,可以直接用 goto 跳至 found 這個標籤開始尋找。最後解釋一下 for 迴圈出來後的 if ,這個 if 是用來檢查 sz 是不是 BITS_PER_LONG 的倍數,如果不是的話,就產生一個低 sz % 64 位元都是 1 的 mask ,讓 tmp 只保留低 sz % 64 位元的值,剩下的位元都變成 0 ,這樣才能確保 fns 不會檢查超過 size 的範圍。
__ffs 這個函數是用來找到一個第一個值為 1 的位元是哪一個。如果 if 裡面檢查到非零,代表在這個檢查的範圍裡面就有 1 出現了,不用再把值往右移檢查,只需要縮小檢查的範圍為當前檢查的範圍的一半就好。所以 AAAA 要填入 0xffffffff 。
__clear_bit 這個函數用來把第 nr 個位元變成 0 ,所以 BBBB 要是 ~mask 才對,這樣才能確保只有 第 nr 個位元會變成 0 ,剩下的位元都維持原樣。而第 4 行後面要加 BIT_WORD(nr) 是因為如果 nr 大於等於 BITS_PER_LONG ,就需要加 offset ,因為 p 這個指標變數一次只能操作寬度為 BITS_PER_LONG 位元的值。
fns 這個函數用來找到一個 word 裡面第 n 個位元是 1 的位置,透過 __ffs 和 __clear_bit 這兩個函數不斷的尋找和清除,直到 n 變成 0 ,就找到第 n 個位元是 1 的位置了。
find_nth_bit 這個函數從 addr 這個位置開始尋找第 n 個位元是 1 的位置,而 size 代表要搜尋的最大範圍。當 的時候,代表不可能找到,所以直接 return size 。接著透過 small_const_nbits 確認 size 的範圍是 ,這樣就可以透過 GENMASK 產生一個只有要搜尋的範圍的位元是 1 的 mask ,將 mask 和 *addr 做 & 運算,就可以得到一個把要搜尋的範圍以外的位元都變成 0 的 val ,才可以透過 fns 尋找我們要的位置,如果沒找到,一樣 return size ,如果找到了,就 return fns 的結果。會用到 FIND_NTH_BIT 的狀況是當 的時候,因為上面的方法只能處理 的狀況。
了解了 find_nth_bit 這個函數的作用後,我開始尋找在 linux 核心的程式碼哪裡有應用到這個函數。
在 include/linux/cpumask.h
內的 cpumask_nth 函數有使用到 find_nth_bit 這個函數。