精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux內核同步機制:引入Per-CPU變量的意義

454398 ? 來源:linuxer ? 作者:蝸窩科技 ? 2020-10-11 11:37 ? 次閱讀

一、源由:為何引入Per-CPU變量?

1、lock bus帶來的性能問題

ARM平臺上,ARMv6之前,SWP和SWPB指令被用來支持對shared memory的訪問:

SWP , , []

Rn中保存了SWP指令要操作的內存地址,通過該指令可以將Rn指定的內存數據加載到Rt寄存器,同時將Rt2寄存器中的數值保存到Rn指定的內存中去。

我們在原子操作那篇文檔中描述的read-modify-write的問題本質上是一個保持對內存read和write訪問的原子性的問題。也就是說對內存的讀和寫的訪問不能被打斷。對該問題的解決可以通過硬件、軟件或者軟硬件結合的方法來進行。早期的ARM CPU給出的方案就是依賴硬件:SWP這個匯編指令執行了一次讀內存操作、一次寫內存操作,但是從程序員的角度看,SWP這條指令就是原子的,讀寫之間不會被任何的異步事件打斷。具體底層的硬件是如何做的呢?這時候,硬件會提供一個lock signal,在進行memory操作的時候設定lock信號,告訴總線這是一個不可被中斷的內存訪問,直到完成了SWP需要進行的兩次內存訪問之后再clear lock信號。

lock memory bus對多核系統的性能造成嚴重的影響(系統中其他的processor對那條被lock的memory bus的訪問就被hold住了),如何解決這個問題?最好的鎖機制就是不使用鎖,因此解決這個問題可以使用釜底抽薪的方法,那就是不在系統中的多個processor之間共享數據,給每一個CPU分配一個不就OK了嗎。

當然,隨著技術的發展,在ARMv6之后的ARM CPU已經不推薦使用SWP這樣的指令,而是提供了LDREX和STREX這樣的指令。這種方法是使用軟硬件結合的方法來解決原子操作問題,看起來代碼比較復雜,但是系統的性能可以得到提升。其實,從硬件角度看,LDREX和STREX這樣的指令也是采用了lock-free的做法。OK,由于不再lock bus,看起來Per-CPU變量存在的基礎被打破了。不過考慮cache的操作,實際上它還是有意義的。

2、cache的影響

在The Memory Hierarchy文檔中,我們已經了解了關于memory一些基礎的知識,一些基礎的內容,這里就不再重復了。我們假設一個多核系統中的cache如下:

每個CPU都有自己的L1 cache(包括data cache和instruction cache),所有的CPU共用一個L2 cache。L1、L2以及main memory的訪問速度之間的差異都是非常大,最高的性能的情況下當然是L1 cache hit,這樣就不需要訪問下一階memory來加載cache line。

我們首先看在多個CPU之間共享內存的情況。這種情況下,任何一個CPU如果修改了共享內存就會導致所有其他CPU的L1 cache上對應的cache line變成invalid(硬件完成)。雖然對性能造成影響,但是系統必須這么做,因為需要維持cache的同步。將一個共享memory變成Per-CPU memory本質上是一個耗費更多memory來解決performance的方法。當一個在多個CPU之間共享的變量變成每個CPU都有屬于自己的一個私有的變量的時候,我們就不必考慮來自多個CPU上的并發,僅僅考慮本CPU上的并發就OK了。當然,還有一點要注意,那就是在訪問Per-CPU變量的時候,不能調度,當然更準確的說法是該task不能調度到其他CPU上去。目前的內核的做法是在訪問Per-CPU變量的時候disable preemptive,雖然沒有能夠完全避免使用鎖的機制(disable preemptive也是一種鎖的機制),但毫無疑問,這是一種代價比較小的鎖。

二、接口

1、靜態聲明和定義Per-CPU變量的API如下表所示:

聲明和定義Per-CPU變量的API 描述
DECLARE_PER_CPU(type, name)
DEFINE_PER_CPU(type, name)
普通的、沒有特殊要求的per cpu變量定義接口函數。沒有對齊的要求
DECLARE_PER_CPU_FIRST(type, name)
DEFINE_PER_CPU_FIRST(type, name)
通過該API定義的per cpu變量位于整個per cpu相關section的最前面。
DECLARE_PER_CPU_SHARED_ALIGNED(type, name)
DEFINE_PER_CPU_SHARED_ALIGNED(type, name)
通過該API定義的per cpu變量在SMP的情況下會對齊到L1 cache line ,對于UP,不需要對齊到cachine line
DECLARE_PER_CPU_ALIGNED(type, name)
DEFINE_PER_CPU_ALIGNED(type, name)
無論SMP或者UP,都是需要對齊到L1 cache line
DECLARE_PER_CPU_PAGE_ALIGNED(type, name)
DEFINE_PER_CPU_PAGE_ALIGNED(type, name)
為定義page aligned per cpu變量而設定的API接口
DECLARE_PER_CPU_READ_MOSTLY(type, name)
DEFINE_PER_CPU_READ_MOSTLY(type, name)
通過該API定義的per cpu變量是read mostly的

看到這樣“豐富多彩”的Per-CPU變量的API,你是不是已經醉了。這些定義使用在不同的場合,主要的factor包括:

-該變量在section中的位置

-該變量的對齊方式

-該變量對SMP和UP的處理不同

-訪問per cpu的形態

例如:如果你準備定義的per cpu變量是要求按照page對齊的,那么在定義該per cpu變量的時候需要使用DECLARE_PER_CPU_PAGE_ALIGNED。如果只要求在SMP的情況下對齊到cache line,那么使用DECLARE_PER_CPU_SHARED_ALIGNED來定義該per cpu變量。

2、訪問靜態聲明和定義Per-CPU變量的API

靜態定義的per cpu變量不能象普通變量那樣進行訪問,需要使用特定的接口函數,具體如下:

get_cpu_var(var)

put_cpu_var(var)

上面這兩個接口函數已經內嵌了鎖的機制(preempt disable),用戶可以直接調用該接口進行本CPU上該變量副本的訪問。如果用戶確認當前的執行環境已經是preempt disable(例如持有spinlock),那么可以使用lock-free版本的Per-CPU變量的API:__get_cpu_var。

3、動態分配Per-CPU變量的API如下表所示:

動態分配和釋放Per-CPU變量的API 描述
alloc_percpu(type) 分配類型是type的per cpu變量,返回per cpu變量的地址(注意:不是各個CPU上的副本)
void free_percpu(void __percpu *ptr) 釋放ptr指向的per cpu變量空間

4、訪問動態分配Per-CPU變量的API如下表所示:

訪問Per-CPU變量的API 描述
get_cpu_ptr 這個接口是和訪問靜態Per-CPU變量的get_cpu_var接口是類似的,當然,這個接口是for 動態分配Per-CPU變量
put_cpu_ptr 同上
per_cpu_ptr(ptr, cpu) 根據per cpu變量的地址和cpu number,返回指定CPU number上該per cpu變量的地址

三、實現

1、靜態Per-CPU變量定義

我們以DEFINE_PER_CPU的實現為例子,描述linux kernel中如何實現靜態Per-CPU變量定義。具體代碼如下:

#define DEFINE_PER_CPU(type, name) \
DEFINE_PER_CPU_SECTION(type, name, "")

type就是變量的類型,name是per cpu變量符號。DEFINE_PER_CPU_SECTION宏可以把一個per cpu變量放到指定的section中,具體代碼如下:

#define DEFINE_PER_CPU_SECTION(type, name, sec) \
__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES \-----安排section
__typeof__(type) name----------------------定義變量

在這里具體arch specific的percpu代碼中(arch/arm/include/asm/percpu.h)可以定義PER_CPU_DEF_ATTRIBUTES,以便控制該per cpu變量的屬性,當然,如果arch specific的percpu代碼不定義,那么在general arch-independent的代碼中(include/asm-generic/percpu.h)會定義為空。這里可以順便提一下Per-CPU變量的軟件層次:

(1)arch-independent interface。在include/linux/percpu.h文件中,定義了內核其他模塊要使用per cpu機制使用的接口API以及相關數據結構的定義。內核其他模塊需要使用per cpu變量接口的時候需要include該頭文件

(2)arch-general interface。在include/asm-generic/percpu.h文件中。如果所有的arch相關的定義都是一樣的,那么就把它抽取出來,放到asm-generic目錄下。毫無疑問,這個文件定義的接口和數據結構是硬件相關的,只不過軟件抽象各個arch-specific的內容,形成一個arch general layer。一般來說,我們不需要直接include該頭文件,include/linux/percpu.h會include該頭文件。

(3)arch-specific。這是和硬件相關的接口,在arch/arm/include/asm/percpu.h,定義了ARM平臺中,具體和per cpu相關的接口代碼。

我們回到正題,看看__PCPU_ATTRS的定義:

#define __PCPU_ATTRS(sec) \
__percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \
PER_CPU_ATTRIBUTES

PER_CPU_BASE_SECTION 定義了基礎的section name symbol,定義如下:

#ifndef PER_CPU_BASE_SECTION
#ifdef CONFIG_SMP
#define PER_CPU_BASE_SECTION ".data..percpu"
#else
#define PER_CPU_BASE_SECTION ".data"
#endif
#endif

雖然有各種各樣的靜態Per-CPU變量定義方法,但是都是類似的,只不過是放在不同的section中,屬性不同而已,這里就不看其他的實現了,直接給出section的安排:

(1)普通per cpu變量的section安排

SMP UP
Build-in kernel ".data..percpu" section ".data" section
defined in module ".data..percpu" section ".data" section

(2)first per cpu變量的section安排

SMP UP
Build-in kernel ".data..percpu..first" section ".data" section
defined in module ".data..percpu..first" section ".data" section

(3)SMP shared aligned per cpu變量的section安排

SMP UP
Build-in kernel ".data..percpu..shared_aligned" section ".data" section
defined in module ".data..percpu" section ".data" section

(4)aligned per cpu變量的section安排

SMP UP
Build-in kernel ".data..percpu..shared_aligned" section ".data..shared_aligned" section
defined in module ".data..percpu" section ".data..shared_aligned" section

(5)page aligned per cpu變量的section安排

SMP UP
Build-in kernel ".data..percpu..page_aligned" section ".data..page_aligned" section
defined in module ".data..percpu..page_aligned" section ".data..page_aligned" section

(6)read mostly per cpu變量的section安排

SMP UP
Build-in kernel ".data..percpu..readmostly" section ".data..readmostly" section
defined in module ".data..percpu..readmostly" section ".data..readmostly" section

了解了靜態定義Per-CPU變量的實現,但是為何要引入這么多的section呢?對于kernel中的普通變量,經過了編譯和鏈接后,會被放置到.data或者.bss段,系統在初始化的時候會準備好一切(例如clear bss),由于per cpu變量的特殊性,內核將這些變量放置到了其他的section,位于kernel address space中__per_cpu_start和__per_cpu_end之間,我們稱之Per-CPU變量的原始變量(我也想不出什么好詞了)。

只有Per-CPU變量的原始變量還是不夠的,必須為每一個CPU建立一個副本,怎么建?直接靜態定義一個NR_CPUS的數組?NR_CPUS定義了系統支持的最大的processor的個數,并不是實際中系統processor的數目,這樣的定義非常浪費內存。此外,靜態定義的數據在內存中連續,對于UMA系統而言是OK的,對于NUMA系統,每個CPU上的Per-CPU變量的副本應該位于它訪問最快的那段memory上,也就是說Per-CPU變量的各個CPU副本可能是散布在整個內存地址空間的,而這些空間之間是有空洞的。本質上,副本per cpu內存的分配歸屬于內存管理子系統,因此,分配per cpu變量副本的內存本文不會詳述,大致的思路如下:

內存管理子系統會根據當前的內存配置為每一個CPU分配一大塊memory,對于UMA,這個memory也是位于main memory,對于NUMA,有可能是分配最靠近該CPU的memory(也就是說該cpu訪問這段內存最快),但無論如何,這些都是內存管理子系統需要考慮的。無論靜態還是動態per cpu變量的分配,其機制都是一樣的,只不過,對于靜態per cpu變量,需要在系統初始化的時候,對應per cpu section,預先動態分配一個同樣size的per cpu chunk。在vmlinux.lds.h文件中,定義了percpu section的排列情況:

#define PERCPU_INPUT(cacheline) \
VMLINUX_SYMBOL(__per_cpu_start) = .; \
*(.data..percpu..first) \
. = ALIGN(PAGE_SIZE); \
*(.data..percpu..page_aligned) \
. = ALIGN(cacheline); \
*(.data..percpu..readmostly) \
. = ALIGN(cacheline); \
*(.data..percpu) \
*(.data..percpu..shared_aligned) \
VMLINUX_SYMBOL(__per_cpu_end) = .;

對于build in內核的那些per cpu變量,必然位于__per_cpu_start和__per_cpu_end之間的per cpu section。在系統初始化的時候(setup_per_cpu_areas),分配per cpu memory chunk,并將per cpu section copy到每一個chunk中。

2、訪問靜態定義的per cpu變量

代碼如下:

#define get_cpu_var(var) (*({ \
preempt_disable(); \
&__get_cpu_var(var); }))

再看到get_cpu_var和__get_cpu_var這兩個符號,相信廣大人民群眾已經相當的熟悉,一個持有鎖的版本,一個lock-free的版本。為防止當前task由于搶占而調度到其他的CPU上,在訪問per cpu memory的時候都需要使用preempt_disable這樣的鎖的機制。我們來看__get_cpu_var:

#define __get_cpu_var(var) (*this_cpu_ptr(&(var)))

#define this_cpu_ptr(ptr) __this_cpu_ptr(ptr)

對于ARM平臺,我們沒有定義__this_cpu_ptr,因此采用asm-general版本的:

#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)

SHIFT_PERCPU_PTR這個宏定義從字面上就可以看出它是可以從原始的per cpu變量的地址,通過簡單的變換(SHIFT)轉成實際的per cpu變量副本的地址。實際上,per cpu內存管理模塊可以保證原始的per cpu變量的地址和各個CPU上的per cpu變量副本的地址有簡單的線性關系(就是一個固定的offset)。__my_cpu_offset這個宏定義就是和offset相關的,如果arch specific沒有定義,那么可以采用asm general版本的,如下:

#define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())

raw_smp_processor_id可以獲取本CPU的ID,如果沒有arch specific沒有定義__per_cpu_offset這個宏,那么offset保存在__per_cpu_offset的數組中(下面只是數組聲明,具體定義在mm/percpu.c文件中),如下:

#ifndef __per_cpu_offset
extern unsigned long __per_cpu_offset[NR_CPUS];

#define per_cpu_offset(x) (__per_cpu_offset[x])
#endif

對于ARMV6K和ARMv7版本,offset保存在TPIDRPRW寄存器中,這樣是為了提升系統性能。

3、動態分配per cpu變量

這部分內容留給內存管理子系統吧。
編輯:hfy

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • ARM
    ARM
    +關注

    關注

    134

    文章

    9057

    瀏覽量

    366877
  • 寄存器
    +關注

    關注

    31

    文章

    5325

    瀏覽量

    120052
  • cpu
    cpu
    +關注

    關注

    68

    文章

    10829

    瀏覽量

    211194
  • Linux
    +關注

    關注

    87

    文章

    11232

    瀏覽量

    208952
收藏 人收藏

    評論

    相關推薦

    詳解linux內核中的mutex同步機制

    linux內核中,互斥量(mutex,即mutual exclusion)是一種保證串行化的睡眠鎖機制。和spinlock的語義類似,都是允許一個執行線索進入臨界區,不同的是當無法獲得鎖的時候
    的頭像 發表于 05-13 08:56 ?6776次閱讀
    詳解<b class='flag-5'>linux</b><b class='flag-5'>內核</b>中的mutex<b class='flag-5'>同步機制</b>

    Linux內核同步機制mutex詳解

    linux內核中,互斥量mutex是一種保證CPU串行運行的睡眠鎖機制。和spinlock類似,都是同一個時刻只有一個線程進入臨界資源,不同的是,當無法獲取鎖的時候,spinlock
    發表于 06-26 16:05 ?1054次閱讀

    Linux內核同步機制

    在現代操作系統里,同一時間可能有多個內核執行流在執行,因此內核其實象多進程多線程編程一樣也需要一些同步機制同步各執行單元對共享數據的訪問。尤其是在多處理器系統上,更需要一些
    發表于 08-06 07:08

    Vulkan同步機制和圖形轉換的風險

    Vulkan同步機制和圖形-計算-圖形轉換的風險(一)
    發表于 01-21 06:17

    RTT中的消息同步機制是如何實現的?

    RTT中的消息同步機制是如何實現的
    發表于 11-02 07:00

    Linux內核同步機制的自旋鎖原理

    一、自旋鎖 自旋鎖是專為防止多處理器并發而引入的一種鎖,它在內核中大量應用于中斷處理等部分(對于單處理器來說,防止中斷處理中的并發可簡單采用關閉中
    發表于 06-08 14:50 ?1304次閱讀

    linux內核機制有哪些

    路徑(進程)以交錯的方式運行。對于這些交錯路徑執行的內核路徑,如不采取必要的同步措施,將會對一些關鍵數據結構進行交錯訪問和修改,從而導致這些數據結構狀態的不一致,進而導致系統崩潰。因此,為了確保系統高效穩定有序地運行,linux
    發表于 11-14 15:25 ?5541次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內核</b><b class='flag-5'>機制</b>有哪些

    linux內核機制

    在現代操作系統里,同一時間可能有多個內核執行流在執行,因此內核其實象多進程多線程編程一樣也需要一些同步機制同步各執行單元對共享數據的訪問。尤其是在多處理器系統上,更需要一些
    發表于 11-14 15:52 ?7069次閱讀

    你知道linux 同步機制的complete?

    Linux內核中,completion是一種簡單的同步機制,標志"things may proceed"。 要使用completion,必須在文件中包含,同時創建一個類型為struct completion的
    發表于 04-24 11:45 ?1256次閱讀

    你了解Linux內核同步機制

    在現代操作系統里,同一時間可能有多個內核執行流在執行,因此內核其實象多進程多線程編程一樣也需要一些同步機制同步各執行單元對共享數據的訪問。
    發表于 05-12 08:26 ?626次閱讀

    可以了解并學習Linux 內核同步機制

    Linux內核同步機制,挺復雜的一個東西,常用的有自旋鎖,信號量,互斥體,原子操作,順序鎖,RCU,內存屏障等。
    發表于 05-14 14:10 ?690次閱讀

    Linux內核中有哪些鎖

    LInux操作系統里,同一時間可能有多個內核執行流在執行,因此內核其實象多進程多線程編程一樣也需要一些同步機制同步各執行單元對共享數據的
    的頭像 發表于 02-24 15:26 ?3426次閱讀

    Linux內核同步機制

    在現代操作系統里,同一時間可能有多個內核執行流在執行,因此內核其實像多進程多線程編程一樣也需要一些同步機制同步各執行單元對共享數據的訪問,尤其是在多處理器系統上,更需要一些
    的頭像 發表于 09-22 09:46 ?2261次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>的<b class='flag-5'>同步機制</b>

    關于Linux kernel同步機制的這些知識點你不得不知道

    同步就是進程與進程之間,進程與系統資源之間的交互。由于 Linux內核采用的是多任務,所以在多個進程之間,必須要有同步機制來保證彼此協調。
    的頭像 發表于 04-21 14:42 ?796次閱讀

    淺談Linux kernel中的同步機制

    同步就是進程與進程之間,進程與系統資源之間的交互。由于 Linux內核采用的是多任務,所以在多個進程之間,必須要有同步機制來保證彼此協調。
    的頭像 發表于 05-04 17:06 ?873次閱讀