Linux核心中的的原子變數分析

來源:互聯網
上載者:User

 

Linux核心中的的原子變數分析

Linux核心中有幾個東西是比較常見的,原子變數就是其中之一。其實之前我也沒有專門去折騰過這些東西,畢竟就那麼幾行代碼,只要知道這個意思就行了。也就僅限於知道有一條彙編指令叫lock。用這個可以保證資料操作的原子性。但前幾天因為某些原因翻了一下RCU的代碼,RCU的代碼在指標賦值的時候並未lock。所以有些奇怪。經過一翻折騰,終於有了一些答案。現寫出來與大家一起分享,高手就不用看了,屬於小菜級的文章。

首先我們從一小段代碼入手:

2       int atomic_set()

3       {

4               int a = 0x1111;

5               int b = a;

6               return 0;

7       }

簡單吧?

反組譯碼後:

0x00000000 <atomic_set+0>:      push   %ebp

0x00000001 <atomic_set+1>:      mov    %esp,%ebp

0x00000003 <atomic_set+3>:      sub    $0x10,%esp

0x00000006 <atomic_set+6>:      movl   $0x1111,0xfffffff8(%ebp)

0x0000000d <atomic_set+13>:     mov    0xfffffff8(%ebp),%eax

0x00000010 <atomic_set+16>:     mov    %eax,0xfffffffc(%ebp)

0x00000013 <atomic_set+19>:     mov    $0x0,%eax

0x00000018 <atomic_set+24>:     leave 

0x00000019 <atomic_set+25>:     ret

 

上面這段彙編是未加任何最佳化選項的。

         大家不用看函數壓棧的代碼,就可以看出核心的指令是:

0x00000006 <atomic_set+6>:      movl   $0x1111,0xfffffff8(%ebp)

0x0000000d <atomic_set+13>:     mov    0xfffffff8(%ebp),%eax

0x00000010 <atomic_set+16>:     mov    %eax,0xfffffffc(%ebp)

         第一條就是簡單的賦值。

         第二條是為第三條操作做準備的,因為不能記憶體到記憶體,這個如果不知道的話可以回去看看彙編的書籍。

         第三條也就是簡單的賦值。

         現在的主要問題變成了mov這條指令的原子性了。OK。

         翻intel手冊可以看到有如下一句話:

         對以下記憶體操作的執行總是原子的:

讀或寫一個位元組.

讀或寫一個16位邊界對齊的的字。

讀或寫一個32位邊界對齊的雙字。

事實上Linux核心中也是這麼做的:

 

 

 

static inline void atomic_set(atomic_t *v, int i)

{

         v->counter = i;

}

  

  OK,讓我們來看看這個:

static inline void atomic_inc(atomic_t *v)

{

         asm volatile(LOCK_PREFIX "incl %0"

                       : "+m" (v->counter));

}

至於LOCK_PREFIX大家直接當這個就是一條lock指令就成了,當不是SMP的情況下直接就當它不存在就得了。

為什麼要加lock呢?這裡有個權威解釋,取自IA-32手冊:

The LOCK prefix can be prepended only to the following instructions and only to those

forms of the instructions where the destination operand is a memory operand: ADD,

ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB,

SUB, XOR, XADD, and XCHG. If the LOCK prefix is used with one of these instructions

and the source operand is a memory operand, an undefined opcode exception (#UD)

may be generated. An undefined opcode exception will also be generated if the LOCK

prefix is used with any instruction not in the above list. The XCHG instruction always

asserts the LOCK# signal regardless of the presence or absence of the LOCK prefix.

所以這個你得無條件接受,沒有什麼好說的。

但是在單核的情況下不用lock也是沒關係的。

OK,看完這段話,相當於整個原子操作都搞明白了。

LOCK_PREFIX這個玩意我也看了一下:

#define LOCK_PREFIX_HERE \

                   ".section .smp_locks,\"a\"\n"         \

                   ".balign 4\n"                       \

                   ".long 671f - .\n" /* offset */   \

                   ".previous\n"                     \

                   "671:"

 

#define LOCK_PREFIX LOCK_PREFIX_HERE "\n\tlock; "

 

搞來搞去還就是lock指令,不過真不知道作者他搞這麼一堆東西是為了啥。上面那一堆的意思就是為每一個lock操作追加一個變數,至於追加的這個變數到底是用來幹嘛的,讓我們來看一看。

先給出一條函數調用路線:

init_module->load_module->post_relocation->module_finalize->alternatives_smp_module_add->alternatives_smp_unlock

這個我們簡單分析一下就行了:

 

 

static void alternatives_smp_unlock(const s32 *start, const s32 *end,

                                         u8 *text, u8 *text_end)

{

         const s32 *poff;

 

         if (noreplace_smp)

                   return;

 

         mutex_lock(&text_mutex);

         for (poff = start; poff < end; poff++) {

                   u8 *ptr = (u8 *)poff + *poff;

 

                   if (!*poff || ptr < text || ptr >= text_end)

                            continue;

                   /* turn lock prefix into DS segment override prefix */

                   if (*ptr == 0xf0)

                            text_poke(ptr, ((unsigned char []){0x3E}), 1);

         };

         mutex_unlock(&text_mutex);

}

關鍵落在這個地方:

/* turn lock prefix into DS segment override prefix */

                   if (*ptr == 0xf0)

                            text_poke(ptr, ((unsigned char []){0x3E}), 1);

根據我自已看代碼的上下文,因為我上面給出的只是個函數調用關係,具體大家可以自行查看代碼,我的理解是:

當你編譯核心增加SMP選項的時候會在操作前面加了lock指令,這個前面已經說過了,但是在啟動並執行時候,也就是執行module_init的時候會調用上面給出那個函數鏈,在這個函數鏈中會根據探測的結果判斷你是否真的是多核或多CPU,如果是就不管了,繼續加lock指令,如果不是,就調用最終那一句text_poke去掉text段中那個lock鎖的代碼。

感覺這個技巧還是有用的,尤其是在單核的CPU上,linux核心中本來用原子操作就比較多,能省一點是一點。

Linux核心中的技巧太多啦,已經見怪不怪了。

抽根煙先,下回見。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.