進程上下文與中斷上下文


1、前言

  最近在學習linux內核方面的知識,經常會看到用戶空間與內核空間及進程上下文與中斷上下文。看着很熟悉,半天又說不出到底是怎么回事,有什么區別。看書過程經常被感覺欺騙,似懂非懂的感覺,很是不爽,今天好好結合書和網上的資料總結一下,加深理解。

2、用戶空間與內核空間  

  我們知道現在操作系統都是采用虛擬存儲器,那么對32位操作系統而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。操心系統的核心是內核,獨立於普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證用戶進程不能直接操作內核,保證內核的安全,操心系統將虛擬空間划分為兩部分,一部分為內核空間,一部分為用戶空間。針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。於是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。空間分配如下圖所示:

  有了用戶空間和內核空間,整個linux內部結構可以分為三部分,從最底層到最上層依次是:硬件-->內核空間-->用戶空間。如下圖所示:

  需要注意的細節問題:

(1) 內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。不管是內核空間還是用戶空間,它們都處於虛擬空間中。 

(2) Linux使用兩級保護機制:0級供內核使用,3級供用戶程序使用。

  內核態與用戶態:

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

(2)當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態)。此時處理器在特權級最低的(3級)用戶代碼中運行。當正在執行用戶程序而突然被中斷程序中斷時,此時用戶程序也可以象征性地稱為處於進程的內核態。因為中斷處理程序將使用當前進程的內核棧。

 

3、進程上下文與中斷上下文

  我在看《linux內核設計與實現》這本書的第三章進程管理時候,看到進程上下文。書中說當一個程序執行了系統調用或者觸發某個異常(軟中斷),此時就會陷入內核空間,內核此時代表進程執行,並處於進程上下文中。看后還是沒有弄清楚,什么是進程上下文,如何上google上面狂搜一把,總結如下:

  程序在執行過程中通常有用戶態和內核態兩種狀態,CPU對處於內核態根據上下文環境進一步細分,因此有了下面三種狀態:

(1)內核態,運行於進程上下文,內核代表進程運行於內核空間。
(2)內核態,運行於中斷上下文,內核代表硬件運行於內核空間。
(3)用戶態,運行於用戶空間。

  上下文context: 上下文簡單說來就是一個環境。

  用戶空間的應用程序,通過系統調用,進入內核空間。這個時候用戶空間的進程要傳遞 很多變量、參數的值給內核,內核態運行的時候也要保存用戶進程的一些寄存 器值、變量等。所謂的“進程上下文”,可以看作是用戶進程傳遞給內核的這些參數以及內核要保存的那一整套的變量和寄存器值和當時的環境等。

  相對於進程而言,就是進程執行時的環境。具體來說就是各個變量和數據,包括所有的寄存器變量、進程打開的文件、內存信息等。一個進程的上下文可以分為三個部分:用戶級上下文、寄存器上下文以及系統級上下文。

(1)用戶級上下文: 正文、數據、用戶堆棧以及共享存儲區;
(2)寄存器上下文: 通用寄存器、程序寄存器(IP)、處理器狀態寄存器(EFLAGS)、棧指針(ESP);
(3)系統級上下文: 進程控制塊task_struct、內存管理信息(mm_struct、vm_area_struct、pgd、pte)、內核棧。

    當發生進程調度時,進行進程切換就是上下文切換(context switch).操作系統必須對上面提到的全部信息進行切換,新調度的進程才能運行。而系統調用進行的模式切換(mode switch)。模式切換與進程切換比較起來,容易很多,而且節省時間,因為模式切換最主要的任務只是切換進程寄存器上下文的切換。

  硬件通過觸發信號,導致內核調用中斷處理程序,進入內核空間。這個過程中,硬件的 一些變量和參數也要傳遞給內核,內核通過這些參數進行中斷處理。所謂的“ 中斷上下文”,其實也可以看作就是硬件傳遞過來的這些參數和內核需要保存的一些其他環境(主要是當前被打斷執行的進程環境)。中斷時,內核不代表任何進程運行,它一般只訪問系統空間,而不會訪問進程空間,內核在中斷上下文中執行時一般不會阻塞。

摘錄Linux注釋的內容如下:

Process Context
-------------------------------------------
    One of the most important parts of a process is the executing program code. This code is read in from an executable file and executed within the program's address space. Normal program execution occurs in user-space. When a program executes a system call or triggers an exception, it enters kernel-space. At this point, the kernel is said to be "executing on behalf of the process" and is in process context. When in process context, the current macro is valid[7]. Upon exiting the kernel, the process resumes execution in user-space, unless a higher-priority process has become runnable in the interim(過渡期), in which case the scheduler is invoked to select the higher priority process.

    Other than process context there is interrupt context, In interrupt context, the system is not running on behalf of a process, but is executing an interrupt handler. There is no process tied to interrupt handlers and consequently no process context. 

    System calls and exception handlers are well-defined interfaces into the kernel. A process can begin executing in kernel-space only through one of these interfaces -- all access to the kernel is through these interfaces.

-------------------------------------------

Interrupt Context
-------------------------------------------
    When executing an interrupt handler or bottom half, the kernel is in interrupt context. Recall that process context is the mode of operation the kernel is in while it is executing on behalf of a process -- for example, executing a system call or running a kernel thread. In process context, the current macro points to the associated task. Furthermore, because a process is coupled to the kernel in process context(因為進程是以進程上文的形式連接到內核中的), process context can sleep or otherwise invoke the scheduler.

    Interrupt context, on the other hand, is not associated with a process. The current macro is not relevant (although it points to the interrupted process). Without a backing process(由於沒有進程的背景),interrupt context cannot sleep -- how would it ever reschedule?(否則怎么再對它重新調度?) Therefore, you cannot call certain functions from interrupt context. If a function sleeps, you cannot use it from your interrupt handler -- this limits the functions that one can call from an interrupt handler.(這是對什么樣的函數可以在中斷處理程序中使用的限制)

    Interrupt context is time critical because the interrupt handler interrupts other code. Code should be quick and simple. Busy looping is discouraged. This is a very important point; always keep in mind that your interrupt handler has interrupted other code (possibly even another interrupt handler on a different line!). Because of this asynchronous nature, it is imperative(必須) that all interrupt handlers be as quick and as simple as possible. As much as possible, work should be pushed out from the interrupt handler and performed in a bottom half, which runs at a more convenient time.

    The setup of an interrupt handler's stacks is a configuration option. Historically, interrupt handlers did not receive(擁有) their own stacks. Instead, they would share the stack of the process that they interrupted[1]. The kernel stack is two pages in size; typically, that is 8KB on 32-bit architectures and 16KB on 64-bit architectures. Because in this setup interrupt handlers share the stack, they must be exceptionally frugal(必須非常節省) with what data they allocate there. Of course, the kernel stack is limited to begin with, so all kernel code should be cautious.

   A process is always running. When nothing else is schedulable, the idle task runs. 

-------------------------------------------

LINUX完全注釋中的一段話:

  當一個進程在執行時,CPU的所有寄存器中的值、進程的狀態以及堆棧中的內容被稱為該進程的上下文。當內核需要切換到另一個進程時,它需要保存當前進程的所有狀態,即保存當前進程的上下文,以便在再次執行該進程時,能夠必得到切換時的狀態執行下去。在LINUX中,當前進程上下文均保存在進程的任務數據結構中。在發生中斷時,內核就在被中斷進程的上下文中,在內核態下執行中斷服務例程。但同時會保留所有需要用到的資源,以便中繼服務結束時能恢復被中斷進程的執行。

 

 

linux內核空間和用戶空間詳解

linux驅動程序一般工作在內核空間,但也可以工作在用戶空間。下面我們將詳細解析,什么是內核空間,什么是用戶空間,以及如何判斷他們。
Linux簡化了分段機制,使得虛擬地址與線性地址總是一致,因此,Linux的虛擬地址空間也為0~4G。Linux內核將這4G字節的空間分為兩部分。將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為“內核空間”。而將較低的3G字節(從虛擬地址 0x00000000到0xBFFFFFFF),供各個進程使用,稱為“用戶空間)。因為每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。於是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。

    Linux使用兩級保護機制:0級供內核使用,3級供用戶程序使用。從圖中可以看出(這里無法表示圖),每個進程有各自的私有用戶空間(0~3G),這個空間對系統中的其他進程是不可見的。最高的1GB字節虛擬內核空間則為所有進程以及內核所共享。

  內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。不管是內核空間還是用戶空間,它們都處於虛擬空間中。
雖然內核空間占據了每個虛擬空間中的最高1GB字節,但映射到物理內存卻總是從最低地址(0x00000000)開始。對內核空間來說,其地址映射是很簡單的線性映射,0xC0000000就是物理地址與線性地址之間的位移量,在Linux代碼中就叫做PAGE_OFFSET。


內核空間和用戶空間之間如何進行通訊?
內核空間和用戶空間一般通過系統調用進行通信。


如何判斷一個驅動是用戶模式驅動還是內核模式驅動?  判斷的標准是什么?

用戶空間模式的驅動一般通過系統調用來完成對硬件的訪問,如通過系統調用將驅動的io空間映射到用戶空間等。因此,主要的判斷依據就是系統調用。
內核空間和用戶空間上不同太多了,說不完,比如用戶態的鏈表和內核鏈表不一樣;用戶態用printf,內核態用printk;用戶態每個應用程序空間是虛擬的,相對獨立的,內核態中卻不是獨立的,所以編程要非常小心。等等。

還有用戶態和內核態程序通訊的方法很多,不單單是系統調用,實際上系統調用是個不好的選擇,因為需要系統調用號,這個需要統一分配。
可以通過ioctl、sysfs、proc等來完成。
 
 
內核態和用戶態
 
當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處於內核運行態(或簡稱為內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每個進程都有自己的內核棧。當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態)。即此時處理器在特權級最低的(3級)用戶代碼中運行。當正在執行用戶程序而突然被中斷程序中斷時,此時用戶程序也可以象征性地稱為處於進程的內核態。因為中斷處理程序將使用當前進程的內核棧。這與處於內核態的進程的狀態有些類似。


進程上下文和中斷上下文
 
處理器總處於以下狀態中的一種:

1、內核態,運行於進程上下文,內核代表進程運行於內核空間;

2、內核態,運行於中斷上下文,內核代表硬件運行於內核空間;

3、用戶態,運行於用戶空間。

用戶空間的應用程序,通過系統調用,進入內核空間。這個時候用戶空間的進程要傳遞很多變量、參數的值給內核,內核態運行的時候也要保存用戶進程的一些寄存器值、變量等。所謂的“進程上下文”,可以看作是用戶進程傳遞給內核的這些參數以及內核要保存的那一整套的變量和寄存器值和當時的環境等。

硬件通過觸發信號,導致內核調用中斷處理程序,進入內核空間。這個過程中,硬件的一些變量和參數也要傳遞給內核,內核通過這些參數進行中斷處理。所謂的“中斷上下文”,其實也可以看作就是硬件傳遞過來的這些參數和內核需要保存的一些其他環境(主要是當前被打斷執行的進程環境)。
 
 
 
前面幾節主要對Linux的外在體系結構做了一些介紹,在這一節里,將分析一下Linux的內部結構,初略可以將這個內部體系划分為三層:Hardware => Kernel Space => User Space
 
 
1. 為什么要划分為內核空間和用戶空間?
Linux Kernel是操作系統的核心,獨立於普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。
 
對於Kernel這么一個高安全級別的東西,顯然是不容許其它的應用程序隨便調用或訪問的,所以需要對Kernel提供一定的保護機制,這個保護機制用來告訴那些應用程序,你只可以訪問某些許可的資源,不許可的資源是拒絕被訪問的,於是就把Kernel和上層的應用程序抽像的隔離開,分別稱之為Kernel Space和User Space。
 
2. 用戶空間的程序如何對內核空間進行訪問?
上面說到用戶態和內核態是兩個隔離的空間,雖然從邏輯上被抽像的隔離,但無可避免的是,總是會有那么一些用戶空間需要訪問內核空間的資源,怎么辦呢?

從上圖結構中可以看出,Kernel Space層從下至上包括:
Arch:對應Kernel里arch目錄,含有諸如x86, ia64, arm, s390等體系結構的支持;
Device Driver:對應Kernel里drivers目錄,含有block, char, net, usb等不同硬件驅動的支持;
在Arch和Driver之上,是對內存,進程,文件系統,網絡協議棧等的支持;

最上一層是System Call Interface,系統調用接口,正如其名,這層就是用戶空間與內核空間的橋梁,用戶空間的應用程序通過System Call這個統一入口來訪問系統中的硬件資源,通過此接口,所有的資源訪問都是在內核的控制下執行,以免導致對用戶程序對系統資源的越權訪問,從而保障了系統的安全和穩定。

3. glibc庫的作用
在用戶空間一層,可以看到有glibc庫的存在,這里之所以把它單獨列出來強調,是因為一般用戶空間的程序不會直接調用內核的System Call去訪問系統資源,而是由glibc這樣的庫間接去調用System Call,換言之,glibc對Kernel的System Call做了一層封裝。

4. Kernel的C庫與glibc的C庫
之前強調過,Kernel是相對獨立的存在,不依賴於任何用戶空間的程序或庫,而大家都知道Kernel除了少量的匯編代碼,大多數都是由C語言編寫,glibc庫屬於用戶空間,沒有glibc庫的支持,Kernel又是如何去處理C語言相關的代碼呢?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM