第十章 內核同步的方法

原子操作

1. 原子操作可以保證指令以原子的方式執行——執行過程不被打斷。

2. 兩個原子操作絕對不可能併發地訪問同一個變量。大多數體繫結構會提供支持原子操作的簡單算數指令,即使沒有,也會爲單步執行提供鎖內存總線的指令,確保其他改變內存的操作不會同時發生。

3. 原子操作分爲兩種,一種是針對整數的,另一種是針對單獨的位。

4. 針對單獨位的操作,提供了一套原子操作和一套非原子操作,非原子操作函數的特點函數名字前綴多了兩個下劃線。比如test_bit()對應的非原子形式是__test_bit()。

5. 在編寫代碼時,能使用原子操作時,就儘量不要使用複雜的加鎖機制。這樣給系統帶來的開銷小,對高速緩存行(cache-line)的影響也小。

6. 原子性和順序性

  • 原子性:如一個整數的初始值是42,然後又置爲365,那麼讀取這個整數肯定會返回42或者365,而絕不會是二者的混合。即讀總是返回一個完整的字,這或者發生在寫操作前,或者之後,絕不可能發生在寫的過程中。原子性確保指令執行期間不被打斷,要麼全部執行完,要麼根本不執行。
  • 順序性:比如要求讀必須在特定的寫之前完成。即確保即使兩條或多條指令出現再獨立的執行線程中,甚至獨立的處理器上,它們本該的執行順序卻依然要保持。通過屏障(barrier)指令實現。

7. atomic_t類型即便在64位體繫結構下也是32位的,若要使用64爲的原子變量,則要使用atomic64_t。但是多數的32位體系機構不支持atomic64_t類型。故爲了便於在Linux支持的各種體繫結構之間移植代碼,開發者應該使用32位的atomic_t類型。把64位的atomic64_t類型留給那些特殊體繫結構和需要64位的代碼

8. 從指定的地址開始搜索第一個被設置(或未被設置)的位:

  • int find_first_bit(unsigned long *addr, unsigned int size)
  • int find_first_zero_bit(unsigned long *addr, unsigned int size)
  • 上面的兩個函數,第二個參數是要搜索的總位數,返回值位第一個被設置(或沒被設置的)位的位號
  • 如果搜索範圍僅限與一個字,則使用_ffs()和ffz(),它們只需要給定一個要搜索的地址做參數

自旋鎖

9. 一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用時自旋(特別浪費處理器時間),故自旋鎖不應被長時間持有

10. 自旋鎖設計的初衷:在短期間內進行輕量級加鎖,持有自旋鎖的時間最好小於完成兩次上下文切換的耗時(跟信號量對比)。

11. Linux內核實現的自旋鎖不可遞歸。

12. 自旋鎖爲SMP機器提供了防止併發訪問所需的保護機制。

13. 而在單處理器機器上,編譯的時候不會加入自旋鎖,僅被當作一個設置內核搶佔機制是否被啓用的開關。如果禁止內核搶佔,那麼在編譯時自旋鎖會被完全剔除出內核。

14. 自旋鎖可以在中斷處理函數中使用,而信號量不行。但是需要注意:

  • 在中斷處理函數中使用自旋鎖的時候,一定要在獲取鎖之前,首先禁止本地中斷(當前CPU的中斷請求,不用管其他CPU上的中斷)。原因是:如果不禁止本地中斷,那麼中斷處理程序會打斷正持有鎖的內核代碼,有可能會試圖爭用這個已經被持有的自旋鎖,這樣,中斷處理程序就會自旋,等待鎖重新可用,由於此時都在同一個CPU上,鎖的持有者在這個中斷處理程序執行完畢前不可能執行,這就是雙重請求死鎖。如果中斷發生在不同的CPU上,即使中斷處理程序在同一個鎖上自旋,也不會妨礙鎖的持有者(在不同的CPU上)最終釋放鎖。
  • 內核提供了spin_lock_irq_save和spin_unlock_irqrestore來實現這一目的
  • 對於單處理器系統,雖然在編譯時拋棄了鎖機制(退化爲只開啓或者關閉內核搶佔),如果在中斷處理程序中也使用了自旋鎖,同樣需要在獲取自旋鎖之前關閉本地中斷,以禁止中斷處理程序訪問共享數據。

15. spin_lock_irq和spin_unlock_irq

  • 如果可以確定本地中斷在加鎖前是激活的,那就不用在解鎖後恢復中斷以前的狀態了。就可以使用spin_unlock_irq解鎖時無條件地激活中斷
  • 在不確定當前本地中斷狀態的情況下,不推薦使用上面的兩個函數。

16. 內核配置宏CONFIG_DEBUG_SPINLOCK可以用來調試使用自旋鎖的代碼

17. 完整的自旋鎖操作列表

方法 描述
spin_lock() 獲取指定的自旋鎖
spin_unlock() 禁止本地中斷並獲取指定的鎖
spin_lock_irqsave() 保存本地中斷的當前狀態,禁止本地中斷,並獲取指定的鎖
spin_unlock() 釋放指定的鎖
spin_unlock_irq() 釋放指定的鎖,並激活本地中斷
spin_unlock_irqstore() 釋放指定的鎖,並讓本地中斷恢復到以前的狀態
spin_lock_init() 動態初始化指定的spinlock_t
spin_trylock() 試圖獲取指定的鎖,若未獲取,返回非0
spin_is_locked() 若指定的鎖當前正在被獲取,返回非0,否則返回0

18. 自旋鎖和下半部

  • spin_lock_bh 獲取指定的鎖,並禁止所有下半部的執行。spin_unlock_bh執行相反的操作
  • 由於下半部可以搶佔進程上下文的代碼,故當下半部和進程上下文共享數據時,必須對進程上下文中的共享數據進行保護,所以需要在進程上下文加鎖的同時還要禁止下半部的執行
  • 同樣,由於中斷處理程序可以搶佔下半部,所以若中斷處理程序和下半部共享數據,那麼就必須在下半部中獲取恰當的鎖的同時還要禁止中斷
  • 由於同類的tasklet不會在不同的CPU上同時執行,所以對於同類的tasklet中共享數據不需要保護;
  • 由於tasklet不會在同一個CPU上發生互相搶佔,故當數據在不同種類的tasklet共享時,只需要在訪問下半部的數據前先獲得一個普通的自旋鎖即可,而不需要禁止禁止下半部
  • 對於軟中斷,由於同一個軟中斷可以在不同的CPU上同時執行,而且在同一個CPU上不會發生軟中斷互相搶佔,所以無論是否同種類的軟中斷,如果數據被軟中斷共享,就必須得到鎖的保護,而沒有必要禁止下半部的執行。

19. read_lock和write_lock

  • 一個或多個讀任務可以併發地持有讀者鎖
  • 用於寫的鎖最多只能被一個寫任務持有,而且此時不能有併發的讀操作
  • 若在中斷處理函數中只有讀操作而沒有寫操作,就可以使用read_lock而不是read_lock_irqsave。但是此時write_lock_irqsave來禁止在寫操作時發生中斷,因爲此時如果不禁止本地中斷,中斷處理函數裏的讀操作就有可能死鎖在寫鎖上。
  • 同理,若讀者正在進行操作,包含寫操作的中斷發生了,由於讀鎖還沒有全部被釋放,故中斷裏的寫操作就會自旋,而讀操作只能在包含寫操作的中斷返回後才能繼續,纔可能釋放讀鎖,此時死鎖就會發生。如果有這種情況,讀者應該使用read_lock_irqsave而不是read_lock
  • 讀寫自旋鎖方法列表
方法 描述
read_lock() 獲得指定的讀鎖
read_lock_irq() 禁止本地中斷並獲得指定讀鎖
read_lock_irqsave() 存儲本地中斷的當前狀態,禁止本地中斷並獲得指定讀鎖
read_unlock() 釋放指定的讀鎖
read_unlock_irq() 釋放指定的讀鎖並激活本地中斷
read_unlock_irqstore() 釋放指定的讀鎖並將本地中斷恢復到指定的前狀態
write_lock() 獲得指定的寫鎖
write_lock_irq() 禁止本地中斷並獲得指定的寫鎖
write_lock_irqsave() 存儲本地中斷的當前狀態,禁止本地中斷並獲得指定寫鎖
write_unlock() 釋放指定的寫鎖
write_unlock_irq() 釋放指定的寫鎖並激活本地中斷
write_unlock_irqstore() 釋放指定的寫鎖並將本地中斷恢復到指定的前狀態
write_trylock() 試圖獲得指定的寫鎖;若寫鎖不可用,返回非0值
rwlock_init() 初始化指定的rwlock_t

信號量 smeaphone

20. Linux信號量是一種睡眠鎖,不會禁止內核搶佔,故持有信號量的代碼可以被搶佔。這意味着信號量不會對調度的等待時間帶來負面影響

21. 讀寫信號量的睡眠都不會被信號打斷

22. 跟讀寫自旋鎖一樣,除非代碼中的讀和寫可以明白無誤地分割開來,否則最好不使用它。

互斥體 mutex

23. 是一種實現了互斥的特定睡眠鎖,即互斥體是一種信號量

24. 任何時刻只有一個任務可以持有mutex

25. 在同一上下文中上鎖和解鎖

26. 遞歸地上鎖和解鎖是不允許的,同樣也不能再去解鎖一個已經被解開的mutex

27. 當持有一個mutex時,進程不可以退出

28. mutex不能在中斷或者下半部中使用,即使mutex_trylock也不行

29. 可以打開內核宏CONFIG_DEBUG_MUTEXES來檢查互斥體的使用

30. 除非mutex的某個約束妨礙你使用,否則相比信號量要優先使用mutex

31. 互斥體和自旋鎖的比較

需求 建議的加鎖方法
低開銷加鎖 優先使用自旋鎖
短期加鎖 優先使用自旋鎖
長期加鎖 優先使用互斥體
中斷上下文加鎖 使用自旋鎖
持有鎖需要睡眠 使用互斥體

完成變量 completion variable

32. 如果在內核中一個任務需要發出信號通知另一個任務發生了某個特定事件,利用完成變量是使兩個任務得以同步的簡單方法。

33. 通常用法:

A common usage is to have a completion variable dynamically created as a member of a

data structure. Kernel code waiting for the initialization of the data structure calls

wait_for_completion().When the initialization is complete, the waiting tasks are awakened via a call to completion().

順序鎖 (seq鎖)

34. 用於讀寫共享數據。

35. 實現方法主要依靠一個序列計數器,當有數據被寫入時,會得到一個鎖,並且序列值會增加。在讀取數據之前和之後,序列號都會被讀取。如果讀取的序列號值相同,意味着在讀操作進行的過程中沒有寫操作打斷過。由於序列號初始值是0,在獲得和釋放寫順序鎖的時候都會使序列號加一,故如果讀取的值是偶數,那麼表明當前沒有寫操作在進行,否則如果當前有寫操作正在進行,那麼read_seqbegin不會返回,知道讀到的是偶數爲止。

To define a seq lock:

  1. seqlock_t mr_seq_lock = DEFINE_SEQLOCK(mr_seq_lock);

The write path is then

  1. write_seqlock(&mr_seq_lock);
  2. /* write lock is obtained... */
  3. write_sequnlock(&mr_seq_lock);

This looks like normal spin lock code.The oddness comes in with the read path, which is quite a bit different:

  1. unsigned long seq;
  2. do {
  3. seq = read_seqbegin(&mr_seq_lock);
  4. /* read data here ... */
  5. } while (read_seqretry(&mr_seq_lock, seq));

上面的函數read_seqretry會將當前的序列號跟seq比較,如果不相等,那麼就返回非0.

36. 使用seq鎖的建議

  • 你的數據存在很多讀者
  • 你的數據寫着很少
  • 雖然寫者很少,但是你希望寫優先與讀,而且不允許讀者讓寫者飢餓
  • 你的數據很簡單,如簡單結構,甚至是簡單的整形——在某些場合,你是不能使用原子量的

37. 使用seq鎖的經典案例就是jiffies。

禁止搶佔

38. 內核搶佔代碼使用自旋鎖作爲非搶佔區域的標記。即如果一個自旋鎖被持有,內核變不能進行搶佔。

39.  只需要禁止內核搶佔,而不需要自旋鎖的情況:per-processor data。因爲per-processor data對每個CPU都是唯一的,這樣per-processor data就不需要擔心SMP併發,也就不需要持有自旋鎖,但是因爲內核是搶佔的,那麼當前CPU完全可能會調度一個新的任務訪問同一個變量,這種情況即便是單CPU系統上也會存在針對這種問題,就可以通過preempt_disable禁止內核搶佔。(我們知道,如果搶佔計數非0,就不會調度下一個task運行)

完。

来自为知笔记(Wiz)

时间: 2024-10-17 21:27:41

第十章 內核同步的方法的相关文章

第九章 內核同步介紹

1. 隨着2.6版內核的出現,Linux內核已經發展成搶佔式內核,如果不加保護,調度程序可以在任何時刻搶佔正在運行的內核代碼,重新調度其他的進程執行 2. 臨界區或者臨界段:訪問和操作共享數據的代碼段 3. 如果兩個執行線程(指代的是任何正在執行的代碼,如一個在內核執行進程.一個中斷處理程序或者內核線程)處於同一個臨界區中同時執行,就成它是競爭條件(race conditions) 4. 避免併發和防止競爭條件稱爲同步(synchronization). 5. 忙等待:反覆處於一個循環中,不斷檢

第二章 從內核出發

1. 內核源碼樹的根目錄描述 目錄 描述 arch 特定體繫結構的代碼 block 塊設備IO層 crypto 加密API Documentation 內核源碼文檔 drivers 設備驅動程序 firmware 使用某些驅動程序而需要的設備固件 fs VFS和各種文件系統 include 內核頭文件 init 內核引導和初始化 ipc 進程間通訊代碼 kernel 像調度程序這樣的核心子系統 lib 通用內核函數 mm 內存管理子系統和VM net 網絡子系統 samples 示例,示範代碼

线上一例主从不同步解决方法

首先,在从库上执行:show slave status\G ,发现SQL显示:NO,并且报错删除日志子类的. 解决方法:才从库上连续执行若干次如下命令即可解决: mysql>slave stop;              mysql>SET GLOBAL SQL_SLAVE_SKIP_COUNTER =1;     #跳过一个事务              mysql>slave start; 线上一例主从不同步解决方法,布布扣,bubuko.com

同步方法 sleep和wait 线程同步的方法

当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 分两种情况 1):进入此对象的非同步方法 答案:可以 2):进入此对象的同步方法 答案:不可以 sleep指线程被调用时,占着CPU不工作,形象地说明为"占着CPU睡觉",此时,系统的CPU部分资源被占用,其他线程无法进入,会增加时间限制.wait指线程处于进入等待状态,形象地说明为"等待使用CPU",此时线程不占用任何资源,不增加时间限制.所以sleep(100L)意

SQLServer2000数据同步复制技术方法

一. 预备工作1.发布服务器,订阅服务器都创建一个同名的windows用户,并设置相同的密码,做为发布快照文件夹的有效访问用户--管理工具--计算机管理--用户和组--右键用户--新建用户--建立一个隶属于administrator组的登陆windows的用户(SynUser)2.在发布服务器上,新建一个共享目录,做为发布的快照文件的存放目录,操作:我的电脑--D: 新建一个目录,名为: PUB--右键这个新建的目录--属性--共享--选择"共享该文件夹"--通过"权限&qu

CVE-2016-8655,af_packet Linux 內核通殺提權漏洞淺析

簡單寫一下思路 這個東西需要namespace方面的支援, 首先open socket , 一連串路徑(packet_set_ring()->init_prb_bdqc()->prb_setup_retire_blk_timer()->prb_init_blk_timer()->prb_init_blk_timer()->init_timer())後產生 timer object, 搶著在socket close.之前控制po->tp_version 使其走其他路徑搶先

oracle数据表数据同步公用方法

自己写了个数据同步的方法,两个数据库之间的数据同步,自己可以通过调用存储过程,添加作业实现定时同步数据. CREATE OR REPLACE PROCEDURE Data_sync_Common(tableName in varchar2) is v_sql VARCHAR2(20000); --????SQL pk_col_name VARCHAR2(800); --主键SQL insert_col_name_A VARCHAR2(20000); --A表字段 insert_col_name_

JAVA中线程同步的方法(7种)汇总

一.同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态. 注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类. 二.同步代码块 即有synchronized关键字修饰的语句块. 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如: synchronized(object){ } 注:同步是一种高开销

Linux下安裝Oracle database內核參數設置

參考:1529864.1 ************************************************** RAM                                  Swap Space Between 1 GB and 2 GB       1.5 times the size of RAM Between 2 GB and 16 GB      Equal to the size of RAM More than 16 GB