天天看點

嵌入式之linux使用者空間與核心空間,程序上下文與中斷上下文

文章目錄

    • 前言
    • 使用者空間與核心空間
      • 核心态與使用者态
    • 程序上下文和中斷上下文
      • 上下文
      • 原子
      • 程序上下文
      • 中斷上下文
      • 程序上下文VS中斷上下文
      • 原子上下文

前言

  • 之前在學習嵌入式linux系統的時候,一直對于中斷上下文,程序上下文很迷,聽着都很熟悉,但是強行讓自己去解釋着寫概念總是很難去說清,是以奔着弄清楚這幾個概念的目的,在網上看了好多的資料。對于這些概念的學習是一個不斷累積的過程。

使用者空間與核心空間

  • 我們知道現在作業系統都是采用虛拟存儲器,那麼對32位作業系統而言,它的尋址空間(虛拟存儲空間)為4G(2的32次方)。操心系統的核心是核心,獨立于普通的應用程式,可以通路受保護的記憶體空間,也有通路底層硬體裝置的所有權限。為了保證使用者程序不能直接操作核心,保證核心的安全,操心系統将虛拟空間劃分為兩部分,一部分為核心空間,一部分為使用者空間。針對linux作業系統而言,将**最高的1G位元組(從虛拟位址0xC0000000到0xFFFFFFFF),供核心使用,稱為核心空間,而将較低的3G位元組(從虛拟位址0x00000000到0xBFFFFFFF),供各個程序使用,稱為使用者空間。**每個程序可以通過系統調用進入核心,是以,Linux核心由系統内的所有程序共享。于是,從具體程序的角度來看,每個程序可以擁有4G位元組的虛拟空間。空間配置設定如下圖所示:
    嵌入式之linux使用者空間與核心空間,程式上下文與中斷上下文
  • 有了使用者空間和核心空間,整個linux内部結構可以分為三部分,從最底層到最上層依次是:硬體–>核心空間–>使用者空間。如下圖所示:
    嵌入式之linux使用者空間與核心空間,程式上下文與中斷上下文
  • 需要注意的是:

    (1) 核心空間中存放的是核心代碼和資料,而程序的使用者空間中存放的是使用者程式的代碼和資料。不管是核心空間還是使用者空間,它們都處于虛拟空間中。

    (2) Linux使用兩級保護機制:0級供核心使用,3級供使用者程式使用。

核心态與使用者态

(1)當一個任務(程序)執行系統調用而陷入核心代碼中執行時,稱程序處于核心運作态(核心态)。此時處理器處于特權級最高的(0級)核心代碼中執行。當程序處于核心态時,執行的核心代碼會使用目前程序的核心棧。每個程序都有自己的核心棧。

(2)當程序在執行使用者自己的代碼時,則稱其處于使用者運作态(使用者态)。此時處理器在特權級最低的(3級)使用者代碼中運作。當正在執行使用者程式而突然被中斷程式中斷時,此時使用者程式也可以象征性地稱為處于程序的核心态。因為中斷處理程式将使用目前程序的核心棧。

程序上下文和中斷上下文

上下文

  • 上下文是從英文 context 翻譯過來的。指的是一種環境。相對于程序而言,就是程序執行時的環境;具體來說就是各個變量和資料,包括所有的寄存器變量、程序打開的檔案、記憶體資訊等。

原子

  • 原子(atom)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation)意為"不可被中斷的一個或一系列操作" 。

而為什麼會有上下文這種概念呢?

  • 核心空間和使用者空間是現代作業系統的兩種工作模式,核心子產品運作在核心空間,而使用者态應用程式運作在使用者空間。它們代表不同的級别,而對系統資源具有不同的通路權限。核心子產品運作在最進階别(核心态),這個級下所有的操作都受系統信任,而應用程式運作在較低級别(使用者态)。在這個級别,處理器控制着對硬體的直接通路以及對記憶體的非授權通路。核心态和使用者态有自己的記憶體映射,即自己的位址空間。
  • 其中處理器總處于以下狀态中的一種:

    (1)核心态,運作于程序上下文,核心代表程序運作于核心空間;核心态,運作于中斷上下文,核心代表硬體運作于核心空間;

    (2)使用者态,運作于使用者空間。

  • 系統的兩種不同運作狀态,才有了上下文的概念。使用者空間的應用程式,如果想請求系統服務,比如操作某個實體裝置,映射裝置的位址到使用者空間,必須通過系統調用來實作。(系統調用是作業系統提供給使用者空間的接口函數)。
  • 通過系統調用,使用者空間的應用程式就會進入核心空間,由核心代表該程序運作于核心空間,這就涉及到上下文的切換,使用者空間和核心空間具有不同的 位址映射,通用或專用的寄存器組,而使用者空間的程序要傳遞很多變量、參數給核心,核心也要儲存使用者程序的一些寄存器、變量等,以便系統調用結束後回到使用者 空間繼續執行。

程序上下文

所謂的程序上下文,就是一個程序在執行的時候,CPU的所有寄存器中的值、程序的狀态以及堆棧上的内容,當核心需要切換到另一個程序時,它 需要儲存目前程序的所有狀态,即儲存目前程序的程序上下文,以便再次執行該程序時,能夠恢複切換時的狀态,繼續執行。

  • 一個程序的上下文可以分為三個部分:使用者級上下文、寄存器上下文以及系統級上下文。

    使用者上下文:正文、資料、使用者堆棧以及共享存儲區;

    寄存器上下文: 通用寄存器、程式寄存器(IP)、處理器狀态寄存器(EFLAGS)、棧指針(ESP);

    系統級上下文: 程序控制塊task_struct、記憶體管理資訊(mm_struct、vm_area_struct、pgd、pte)、核心棧。

    當發生程序排程時,進行程序切換就是上下文切換(context switch)。

  • 作業系統必須對上面提到的全部資訊進行切換,新排程的程序才能運作。而系統調用進行的是模式切換(mode switch)。模式切換與程序切換比較起來,容易很多,而且節省時間,因為模式切換最主要的任務隻是切換程序寄存器上下文的切換。
  • 程序上下文主要是異常處理程式和核心線程。核心之是以進入程序上下文是因為程序自身的一些工作需要在核心中做。例如,系統調用是為目前程序服務的,異常通常是處理程序導緻的錯誤狀态等。是以在程序上下文中引用current是有意義的。

中斷上下文

  • 硬體通過觸發信号,向CPU發送中斷信号,導緻核心調用中斷處理程式,進入核心空間。這個過程中,硬體的一些變量和參數也要傳遞給核心, 核心通過這些參數進行中斷處理。

    是以,“中斷上下文”就可以了解為硬體傳遞過來的這些參數和核心需要儲存的一些環境,主要是被中斷的程序的環境。

    核心進入中斷上下文是因為中斷信号而導緻的中斷處理或軟中斷。而中斷信号的發生是随機的,中斷處理程式及軟中斷并不能事先預測發生中斷時目前運作的是哪個程序,是以在中斷上下文中引用current是可以的,但沒有意義。

    事實上,對于A程序希望等待的中斷信号,可能在B程序執行期間發生。例如,A程序啟動寫磁盤操作,A程序睡眠後B程序在運作,當磁盤寫完後磁盤中斷信号打斷的是B程序,在中斷處理時會喚醒A程序。

程序上下文VS中斷上下文

  • 核心可以處于兩種上下文:程序上下文和中斷上下文。

    在系統調用之後,使用者應用程式進入核心空間,此後核心空間針對使用者空間相應程序的代表就運作于程序上下文。

  • 異步發生的中斷會引發中斷處理程式被調用,中斷處理程式就運作于中斷上下文。

    中斷上下文和程序上下文不可能同時發生。

    運作于程序上下文的核心代碼是可搶占的,但中斷上下文則會一直運作至結束,不會被搶占。是以,核心會限制中斷上下文的工作,不允許其執行如下操作:

  1. 進入睡眠狀态或主動放棄CPU

    由于中斷上下文不屬于任何程序,它與current沒有任何關系(盡管此時current指向被中斷的程序),是以中斷上下文一旦睡眠或者放棄CPU,将無法被喚醒。是以也叫原子上下文(atomic context)。

  2. 占用互斥體

    為了保護中斷句柄臨界區資源,不能使用mutexes。如果獲得不到信号量,代碼就會睡眠,會産生和上面相同的情況,如果必須使用鎖,則使用spinlock。

  3. 執行耗時的任務

    中斷處理應該盡可能快,因為核心要響應大量服務和請求,中斷上下文占用CPU時間太長會嚴重影響系統功能。在中斷處理例程中執行耗時任務時,應該交由中斷處理例程底半部來處理。

  4. 通路使用者空間虛拟記憶體

    因為中斷上下文是和特定程序無關的,它是核心代表硬體運作在核心空間,是以在中斷上下文無法通路使用者空間的虛拟位址

  5. 中斷處理例程不應該設定成reentrant(可被并行或遞歸調用的例程)

    因為中斷發生時,preempt和irq都被disable,直到中斷傳回。是以中斷上下文和程序上下文不一樣,中斷處理例程的不同執行個體,是不允許在SMP上并發運作的。

  6. 中斷處理例程可以被更進階别的IRQ中斷

    如果想禁止這種中斷,可以将中斷處理例程定義成快速處理例程,相當于告訴CPU,該例程運作時,禁止本地CPU上所有中斷請求。這直接導緻的結果是,由于其他中斷被延遲響應,系統性能下降。

原子上下文

核心的一個基本原則就是:在中斷或者說原子上下文中,核心不能通路使用者空間,而且核心是不能睡眠的。也就是說在這種情況下,核心是不能調用有可能引起睡眠的任何函數。一般來講原子上下文指的是在中斷或軟中斷中,以及在持有自旋鎖的時候。核心提供 了四個宏來判斷是否處于這幾種情況裡:

  • 這四個宏所通路的count都是thread_info->preempt_count。這個變量其實是一個位掩碼。最低8位表示搶占計數,通常由spin_lock/spin_unlock修改,或程式員強制修改,同時表明核心容許的最大搶占深度是256。
  • 8-15位是軟中斷計數,通常由local_bh_disable/local_bh_enable修改,同時表明核心容許的最大軟中斷深度是256。
  • 16-27位是硬中斷計數,通常由enter_irq/exit_irq修改,同時表明核心容許的最大硬中斷深度是4096。
  • 第28位是PREEMPT_ACTIVE标志。用代碼表示就是:
PREEMPT_MASK: 0x000000ff
    SOFTIRQ_MASK: 0x0000ff00
    HARDIRQ_MASK: 0x0fff0000
           

凡是上面4個宏傳回1得到地方都是原子上下文,是**不容許核心通路使用者空間,不容許核心睡眠的,不容許調用任何可能引起睡眠的函數。**而且代表thread_info->preempt_count不是0,這就告訴核心,在這裡面搶占被禁用。

但 是,對于in_atomic()來說,在啟用搶占的情況下,它工作的很好,可以告訴核心目前是否持有自旋鎖,是否禁用搶占等。但是,在沒有啟用搶占的情況 下,spin_lock根本不修改preempt_count,是以即使核心調用了spin_lock,持有了自旋鎖,in_atomic()仍然會傳回 0,錯誤的告訴核心目前在非原子上下文中。是以凡是依賴in_atomic()來判斷是否在原子上下文的代碼,在禁搶占的情況下都是有問題的。

部落格内容參考:https://mp.weixin.qq.com/s?src=11&timestamp=1563710917&ver=1742&signature=QR4lcgtbcKzOvDFLwoqFNplQTjSK7GewpVDBCvNpaXRozYEwcUOrFTqWKf5XsDAp00ySWlARSnQsnzf01DCZW4ANXbnv0iDVukCI4NLwjfqfXrqxwXEwoEGjfqzOoY6B&new=1

繼續閱讀