1. 程式人生 > >linux核心之程序管理詳解

linux核心之程序管理詳解

1、程序描述符

(1)程序與執行緒

         程序是處於執行期的程式以及相關資源的總稱。執行緒在linux上稱為輕量級程序,沒有獨立的地址空間,一個程序下的所有執行緒共享地址空間、檔案系統資源、檔案描述符、訊號處理程式等。

(2)程序描述符task_struct

         核心把程序的列表存放在叫做任務佇列的雙向迴圈連結串列中。連結串列中的每一個項都是型別為task_struct(即程序描述符的結構),它包含了一個具體程序的所有相關資訊。通過slab分配器分配task_struct結構,這樣方便物件複用和快取著色。該結構體有些大,下圖只是指出其中比較重要的成員部分。

A.state狀態

TASK_RUNNING表示程序要麼正在執行,要麼正要準備執行。

    TASK_INTERRUPTIBLE表示程序被阻塞(睡眠),直到某個條件變為真。條件一旦達成,程序的狀態就被設定為TASK_RUNNING

    TASK_UNINTERRUPTIBLE的意義與TASK_INTERRUPTIBLE類似,除了不能通過接受一個訊號來喚醒以外。

    __TASK_STOPPED表示程序被停止執行。

    __TASK_TRACED表示程序被debugger等程序監視。

    EXIT_ZOMBIE表示程序的執行被終止,但是其父程序還沒有使用wait()等系統呼叫來獲知它的終止資訊。

    EXIT_DEAD表示程序的最終狀態。

    EXIT_ZOMBIEEXIT_DEAD也可以存放在exit_state成員中

B.程序識別符號

         核心通過一個唯一的程序標示值或PID來標識每一個程序。

C.核心堆疊stack

核心通過thread_union聯合體來表示程序的核心棧,其中THREAD_SIZE巨集的大小為8192(首地址按照8192對齊),包含thread_info大小,實際可用棧小於8192位元組(見下節分析)。

當程序從使用者態切換到核心態時,程序的核心棧總是空的,所以ARMsp暫存器指向這個棧的頂端。因此,核心能夠輕易地通過sp暫存器(因為對齊,低

13位置0即首地址)獲得當前正在CPU上執行的程序。

D.程序親屬關係成員

在Linux系統中,所有程序之間都有著直接或間接地聯絡,每個程序都有其父程序,也可能有零個或多個子程序。擁有同一父程序的所有程序具有兄弟關係。

    real_parent指向其父程序,如果建立它的父程序不再存在,則指向PID為1的init程序。

    parent指向其父程序,當它終止時,必須向它的父程序傳送訊號。它的值通常與real_parent相同。

    children表示連結串列的頭部,連結串列中的所有元素都是它的子程序。

    sibling用於把當前程序插入到兄弟連結串列中。

    group_leader指向其所在程序組的領頭程序

E.程序排程

實時優先順序範圍是0到MAX_RT_PRIO-1(即99),而普通程序的靜態優先順序範圍是從MAX_RT_PRIO到MAX_PRIO-1(即100到139)。值越大靜態優先順序越低。 

  static_prio用於儲存靜態優先順序,可以通過nice系統呼叫來進行修改。

  rt_priority用於儲存實時優先順序。

  normal_prio的值取決於靜態優先順序和排程策略。

  prio用於儲存動態優先順序。

  policy表示程序的排程策略,目前主要有以下五種: 

SCHED_NORMAL用於普通程序,通過CFS排程器實現。

SCHED_BATCH用於非互動的處理器消耗型程序

SCHED_IDLE是在系統負載很低時使用。

SCHED_FIFO(先入先出排程演算法)和SCHED_RR(輪流排程演算法)都是實時排程策略

F.程序地址空間

mm指向程序所擁有的記憶體描述符,而active_mm指向程序執行時所使用的記憶體描述符。對於普通程序而言,這兩個指標變數的值相同。但是,核心執行緒不擁有任何記憶體描述符,所以它們的mm成員總是為NULL。當核心執行緒得以執行時,它的active_mm成員被初始化為前一個執行程序的active_mm值。

G.訊號處理

signal指向程序的訊號描述符。

   sighand指向程序的訊號處理程式描述符。

   blocked表示被阻塞訊號的掩碼,real_blocked表示臨時掩碼。

   pending存放私有掛起訊號的資料結構

   sas_ss_sp是訊號處理程式備用堆疊的地址,sas_ss_size表示堆疊的大小。

  裝置驅動程式常用notifier指向的函式來阻塞程序的某些訊號(notifier_mask是這些訊號的位掩碼),notifier_data指的是notifier所指向的函式可能使用的資料

(3)thread_info

         在2.6以前的核心中,各個程序的task_struct存放在它們核心棧的尾端,目的是為了讓那些像X86那樣暫存器較少的硬體體系結構只要通過棧指標就能計算出它的位置,而避免使用額外的暫存器專門記錄。現在用slab分配器動態生成task_struct,所以只需在棧底(對於向下增長的棧來說)或棧頂(向上增長的棧來說)建立一個thread_info,通過它指向該程序的task_struct。核心棧的實現在上面已經提到,如下:

union thread_union {                                                                                                                              

   struct thread_info thread_info;                                                                                                              

   unsigned long stack[THREAD_SIZE/sizeof(long)];                                                                                               

};

總大小8192,其中包含了thread_info大小,實際可用棧<8192位元組。

/*

 *low level task data that entry.S needs immediate access to.

 *__switch_to() assumes cpu_context follows immediately after cpu_domain.

 */

struct thread_info {

   unsigned long       flags;      /* low level flags */

   int         preempt_count;  /* 0 => preemptable, <0 => bug */

   mm_segment_t        addr_limit; /*address limit */

    struct task_struct *task;      /* main task structure*/指向task_struct

   struct exec_domain *exec_domain;   /* executiondomain */

   __u32           cpu;        /* cpu */

   __u32           cpu_domain; /* cpudomain */

    struct cpu_context_save  cpu_context;   /* cpu context */存放CPU暫存器內容,在任務切換時載入或儲存

   __u32           syscall;    /* syscall number */

   __u8            used_cp[16];    /* thread used copro */

unsignedlong       tp_value;

#ifdef CONFIG_CRUNCH

structcrunch_state crunchstate;

#endif

   union fp_state      fpstate__attribute__((aligned(8)));

   union vfp_state     vfpstate;

#ifdef CONFIG_ARM_THUMBEE

   unsigned long      thumbee_state;  /* ThumbEE HandlerBase register */

#endif

   struct restart_block   restart_block;

};      

struct cpu_context_save {       //各個暫存器的值

   __u32   r4;

   __u32   r5;

   __u32   r6;

   __u32   r7;

   __u32   r8;

   __u32   r9;

   __u32   sl;

   __u32   fp;

   __u32   sp;

   __u32   pc;

   __u32   extra[2];       /* Xscale 'acc' register, etc */

};

初始的task_struct和thread_info見下一章節分析

2、程序建立

(1)普通程序

         Linux建立程序分解到兩個單獨的函式中去執行:fork()和exec()。首先,fork()通過拷貝當前程序建立一個子程序,子程序與父程序的區別僅僅在於PID、PPID和某些資源和統計量。exec()函式負責讀取可執行檔案並載入地址空間開始執行。

         fork()使用寫時拷貝頁實現,那麼實際開銷就是複製父程序的頁表以及子程序建立唯一的程序描述符。

         vfork()與fork()功能相同,唯一的不同是vfork不拷貝父程序的頁表項(理想情況下,儘量不要用vfork防止exec失敗情況)。

         fork()、vfork()和__clone()庫函式都根據各自需要的引數標誌呼叫clone()系統呼叫,然後由clone呼叫核心do_fork()介面。

         do_fork(kernel/fork.c)完成了建立中的大部分工作,主體實現函式copy_process()。copy_process主要完成以下工作:

A.呼叫dup_task_struct()為新程序建立一個核心棧、thread_info結構和task_struct,這些值與當前程序值相同。此時,子程序和父程序的描述符完全相同。

B.檢查並確保新建立這個子程序後,當前使用者擁有的程序數目沒有超出給它分配的資源限制。

C.子程序著手是自己與父程序區別開來。

D.子程序狀態被設定為TASK_UNINTERRUPTIBLE,防止它投入執行

E.呼叫copy_flags()更新task_struct的flags成員

F.呼叫alloc_pid()為新程序分配一個有效的PID

G.根據引數標誌,拷貝或共享開啟的檔案、檔案系統資訊、訊號處理函式、程序地址空間和名稱空間等。

H.掃尾工作並返回一個指向子程序的指標。

再回到do_fork()函式,如果copy_process()函式成功返回,新建立的子程序被喚醒並讓其投入執行,核心有意選擇子程式首先執行。一般子程序都會馬上呼叫exec()函式,這樣可以避免寫時拷貝的額外開銷。

(2)核心執行緒

         核心經常需要在後臺執行一些操作(如刷磁碟,空閒頁回收等),這種任務可以交給核心執行緒。核心執行緒有一下幾點不同:

         沒有獨立的地址空間,所有核心執行緒共享地址空間。

         只執行在核心態,從不切換到使用者空間

         只能使用大於PAGE_OFFSET的線性地址空間

但是和普通程序一樣,可以被排程,可以被搶佔。

         核心執行緒可以通過kernel_thread和kthread_create建立,兩者主要不同點:

A.kthread_create建立的核心執行緒有乾淨的上下文環境,適合於驅動模組或使用者空間的程式建立核心執行緒使用,不會把某些核心資訊暴露給使用者程式。

B.二者建立的父程序不同:kernel_thread建立的程序可以是init或其他核心執行緒,其中kernel_init(1號程序)和kthreadd(2號程序)就是該方法建立;kthread_ctreate建立的程序的父程序被指定kthreadd。

C.kthread_create是kthread_create_on_node的巨集定義,該函式主要是將要建立的執行緒放到佇列裡,然後喚醒kthreadd核心執行緒呼叫create_kthread介面建立,在完成建立之前kthread_create_on_node一直等待。

(3)程序0、1、2

所有程序的祖先叫做程序0(idle程序),它是在linux的初始化階段從無到有建立的一個核心執行緒,唯一使用靜態分配資料結構的程序(所有其他程序的資料結構都是動態分配),所以這些靜態分配的資料結構也順其自然的變成連結串列的頭。在init/init_task.c定義:

struct task_struct init_task =INIT_TASK(init_task)                  //程序描述符

.stack     = &init_thread_info

.mm    = NULL //核心執行緒沒有獨立記憶體地址空間

.active_mm = &init_mm

.tasks     = LIST_HEAD_INIT(tsk.tasks) 自己指向自己

.real_parent    = &tsk

.parent    = &tsk

.children  = LIST_HEAD_INIT(tsk.children)

.sibling   = LIST_HEAD_INIT(tsk.sibling)

.group_leader   = &tsk

.fs    = &init_fs

.files     = &init_files

.signal    = &init_signals

.sighand   = &init_sighand

union thread_union init_thread_union__init_task_data = { INIT_THREAD_INFO(init_task) }; 在《linux核心之啟動過程詳解》一文中講過在進入start_kernel之前的彙編程式碼,將sp堆疊指向這個靜態核心堆疊。

#define INIT_THREAD_INFO(tsk)                       \                                                                                            

{                                   \                                                                                                            

   .task       = &tsk,  //指向程序描述符            \                                                                                                    

   .exec_domain    =&default_exec_domain,            \                                                                                         

   .flags      = 0,                        \                                                                                                    

   .preempt_count  = INIT_PREEMPT_COUNT,               \                                                                                        

   .addr_limit = KERNEL_DS,                    \                                                                                                

   .cpu_domain = domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \                                                                                    

              domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER) |   \                                                                                     

              domain_val(DOMAIN_IO,DOMAIN_CLIENT),     \                                                                                        

   .restart_block  = {                     \                                                                                                     

       .fn = do_no_restart_syscall,           \                                                                                                

   },                              \                                                                                                            

}

主核心頁全域性目錄存放在swapper_pg_dir中,該變數arch/arm/kernel/head.S中開頭出定義,細節見《linux核心之啟動過程詳解》一文。

         Start_kernel完成核心相關的所有初始化之後,最後執行rest_init函式,該函式會建立兩個執行緒:

kernel_thread(kernel_init, NULL, CLONE_FS |CLONE_SIGHAND);

pid = kernel_thread(kthreadd, NULL,CLONE_FS | CLONE_FILES);

同進程0共享所有的核心資料結構。

kernel_init執行緒呼叫run_init_process—>do_execve系統呼叫裝入可執行程式/sbin/init(ps命令看到的1號程序就是這個init),完成核心態到使用者態的切換,擁有自己的地址空間和資料結構,執行/etc下面的各種指令碼,實現程式啟動。

Kthreadd執行緒不停檢視核心執行緒建立佇列,看是否有核心執行緒需要建立,這也就有了上面kthread_create核心執行緒的父程序都指向它。

         在多處理器系統中,每個CPU都有一個程序0。開機時先啟動CPU0,同時禁用其他CPU,在CPU0上初始化好後,再啟用其他的CPU。

3、程序切換

(1)arm簡介

ARM微處理器共有37個32位暫存器,其中31個為通用暫存器,6個為狀態暫存器。但是這些暫存器不能被同時訪問,具體哪些暫存器是可以訪問的,取決ARM處理器的工作狀態及具體的執行模式。但在任何時候,通用暫存器R14~R0、程式計數器PC、一個狀態暫存器都是可訪問的。

通用暫存器:不分組暫存器(R0-R7) 、分組暫存器(R8-R14)(r13-sp,r14-lr) 、程式計數器R15(PC)。如下圖所示:

Arm在linux下一般執行在兩個模式:usr(使用者態)和svc(核心態)。從上圖可知usr和svc有各自獨立的r13(sp)、r14(lr)、SPSR暫存器,這樣使用者空間和核心空間可以儲存各自的堆疊,方便程序切換。

程序切換隻在核心態進行,可以通過系統呼叫、異常、中斷方式進入核心態。

(2)程序間切換

         程序切換:為了控制程序的執行,核心必須有能力掛起正在CPU上執行的程序,並恢復以前掛起的某個程序的執行。儘管每個程序可以擁有屬於自己的地址空間,但所有程序必須共享CPU暫存器。因此,在恢復一個程序的執行前,必須確保每個暫存器裝入掛起程序時的值,這組暫存器資料稱為硬體上下文。

         從本質上說,每個程序切換由兩部組成:

A.切換頁全域性目錄以安裝一個新的地址空間(在程序地址空間一節會有講述)

B.切換核心態堆疊和硬體上下文,因為硬體上下文提供了核心執行新程序所需的所有資訊,包含CPU暫存器。

         程序切換的核心函式:schedule()—>__schedule()—>context_switch()—>switch_to()(由各自平臺實現)—>__switch_to()(在arm上這是一個彙編程式碼)原始碼如下。

__switch_to(prev, task_thread_info(prev),task_thread_info(next))

r0=prev    //任務

r1= task_thread_info(prev)  //核心棧

r2= task_thread_info(next)  //核心棧

#define task_thread_info(task)  ((struct thread_info *)(task)->stack)

/*

 *Register switch for ARMv3 and ARMv4 processors

 * r0= previous task_struct, r1 = previous thread_info, r2 = next thread_info

 *previous and next are guaranteed not to be the same.

 */

ENTRY(__switch_to)

UNWIND(.fnstart    )

UNWIND(.cantunwind )

   add ip, r1, #TI_CPU_SAVE //IP指向上一個執行緒thread_infocpu_context成員地址

   ldr r3, [r2, #TI_TP_VALUE] //r3=下一個執行緒tp_value的值(即TLS暫存器)

 //儲存現場,儲存r4 - sl,fp, sp, lr到上一個執行緒的cpu_context裡。

ARM(   stmia   ip!, {r4 - sl, fp, sp, lr} )    @ Store most regs on stack

 THUMB( stmia  ip!, {r4 - sl, fp}     )    @ Store most regs on stack

 THUMB( str sp, [ip], #4           )

 THUMB( str lr, [ip], #4           )

#ifdef CONFIG_CPU_USE_DOMAINS       //未定義

   ldr r6, [r2, #TI_CPU_DOMAIN]

#endif

   set_tls r3, r4, r5 //設定TLS暫存器,TLS執行緒區域性儲存

#if defined(CONFIG_CC_STACKPROTECTOR)&& !defined(CONFIG_SMP) //未定義

   ldr r7, [r2, #TI_TASK]

   ldr r8, =__stack_chk_guard

   ldr r7, [r7, #TSK_STACK_CANARY]

#endif

#ifdef CONFIG_CPU_USE_DOMAINS       //未定義

   mcr p15, 0, r6, c3, c0, 0       @Set domain register

#endif

   mov r5, r0  //臨時儲存上一個執行緒的task_struct

   add r4, r2, #TI_CPU_SAVE //r4指向下一個執行緒的cpu_context

    // thread_notify_head通知鏈

ldr r0,=thread_notify_head

   mov r1, #THREAD_NOTIFY_SWITCH

   bl  atomic_notifier_call_chain

#if defined(CONFIG_CC_STACKPROTECTOR)&& !defined(CONFIG_SMP) //未定義

   str r7, [r8]

#endif

 THUMB( mov ip, r4             ) //ip=r4:指向下一個執行緒的cpu_context

mov r0, r5 //恢復r0重新指向上一個執行緒的task_struct(未使用)

//恢復現場,將下一個執行緒暫存器載入到CPUpc=cpu_context->pc,剛好對應上面儲存現場時的lr(即下一個執行緒要執行的地方)

 ARM(  ldmia   r4, {r4 - sl, fp, sp,pc}  )   @ Load all regs saved previously

 THUMB( ldmia  ip!, {r4 - sl, fp}     )    @ Load all regs saved previously

 THUMB( ldr sp, [ip], #4           )

 THUMB( ldr pc, [ip]           )

 UNWIND(.fnend      )

ENDPROC(__switch_to)

(3)同一程序使用者態和核心態切換

         這裡主要介紹系統呼叫引發的使用者態和核心態切換,其他方式類似。

         每個程序會有兩個棧,一個使用者棧,存在於使用者空間,一個核心棧,存在於核心空間。當程序在使用者空間執行時,CPU堆疊指標暫存器裡面的內容是使用者堆疊地址;當程序在核心空間時,CPU堆疊指標暫存器裡的內容是核心棧空間地址,使用核心棧。

         當程序通過系統呼叫陷入核心態時,程序使用的堆疊也要從使用者棧轉到核心棧。程序陷入核心態後,先把使用者態堆疊的地址儲存在核心棧之中,然後設定堆疊指標暫存器的內容為核心棧的地址,這樣就完成了使用者棧向核心棧的轉換;當程序從核心態恢復到使用者態之行時,在核心態之行的最後將儲存在核心棧裡面的使用者棧的地址恢復到堆疊指標暫存器即可。這樣就實現了核心棧和使用者棧的互轉。

         在程序從使用者態轉到核心態的時候,程序的核心棧總是空的。這是因為,當程序在使用者態執行時,使用的是使用者棧,當程序陷入到核心態時,核心棧儲存程序在核心態執行的相關資訊,但是一旦程序返回到使用者態後,核心棧中儲存的資訊無效,會全部恢復,因此每次程序從使用者態陷入核心的時候得到的核心棧都是空的。所以在程序陷入核心的時候,直接把核心棧的棧頂地址給堆疊指標暫存器就可以。

         程序呼叫系統呼叫通過swi指令產生中斷髮起核心服務請求,從而陷入核心。核心的相應入口點為ENTRY(vector_swi),執行這個函式前,硬體已經完成了如下事情:

A.CPSR暫存器儲存到SPSR_svc暫存器中,將返回地址(使用者空間執行swi指令的下一條指令)儲存在lr_svc

B.設定CPSR暫存器的值。具體包括:CPSR.M = '10011'svc mode),CPSR.I = '1'disable IRQ),CPSR.IT = '00000000'TODO),CPSR.J = '0'()等

CPC設定為swi異常向量的地址

原始碼(去掉無關巨集)如下:

   .align  5                                                                                                                                    

ENTRY(vector_swi)                                                                                                                                

   sub sp, sp, #S_FRAME_SIZE   //前面已經提到過使用者態進入核心態,核心堆疊是空的,sp指向核心堆疊棧頂(這裡sp_svc,不是sp_usr所以不會衝突,thread_info在遠離棧頂的低地址)                                                                                                                 

   stmia   sp, {r0 - r12}          @ Calling r0 - r12 //r0-r12使用者和核心共用,將使用者空間的暫存器r0-r12壓棧;而上一節核心執行緒切換CPU資訊則儲存thread_info>cpu_context                                                                                         

 ARM(  add r8, sp, #S_PC       )                                                                                                                

 ARM(  stmdb   r8, {sp, lr}^       )  @ Calling sp, lr                                                                                         

 THUMB( mov r8, sp          )                                                                                                                    

 THUMB( store_user_sp_lr r8, r10, S_SP  )   @calling sp, lr                                                                                     

   mrs r8, spsr            @ calledfrom non-FIQ mode, so ok.                                                                                    

   str lr, [sp, #S_PC]         @ Savecalling PC //儲存使用者空間的lr即返回的pc地址到核心棧                                                                                                

   str r8, [sp, #S_PSR]        @ Save CPSR    //儲存儲存cpsr到核心堆疊                                                                                                  

   str r0, [sp, #S_OLD_R0]     @ SaveOLD_R0                                                                                                    

   zero_fp                                                                                                                                      

#ifdef CONFIG_ALIGNMENT_TRAP                                                                                                                     

   ldr ip, __cr_alignment                                                                                                                       

   ldr ip, [ip]                                                                                                                                 

   mcr p15, 0, ip, c1, c0      @update control register                                                                                        

#endif                                                                                                                                           

   enable_irq                                                                                                                                   

   ct_user_exit                                                                                                                                 

get_thread_info tsk        //核心棧按照8192對齊,thread_info在核心棧的首地址,所以通過sp的低13位設定為0程式設計首地址即thread_info存放的地方。

//獲取系統呼叫號                                                                                                                                                 

#if defined(CONFIG_OABI_COMPAT)

   /*

    * If we have CONFIG_OABI_COMPAT then we need to look at the swi

     * value to determine if it is an EABI or anold ABI call.

    */

#ifdef CONFIG_ARM_THUMB

   tst r8, #PSR_T_BIT

   movne   r10, #0             @ no thumb OABI emulation

 USER( ldreq   r10, [lr, #-4]      )  @ get SWI instruction

#endif

#endif

    adrtbl, sys_call_table     @ load syscalltable pointer 獲取syscall表首地址

#if defined(CONFIG_OABI_COMPAT)

   /*

    * If the swi argument is zero, this is an EABI call and we do nothing.

    *

    * If this is an old ABI call, get the syscall number into scno and

    * get the old ABI syscall table address.

    */

   bics    r10, r10, #0xff000000

   eorne   scno, r10,#__NR_OABI_SYSCALL_BASE

   ldrne   tbl, =sys_oabi_call_table

#endif

local_restart:

   ldr r10, [tsk, #TI_FLAGS]       @check for syscall tracing

   stmdb   sp!, {r4, r5}           @ push fifth and sixth args swi後面的引數入棧

   tst r10, #_TIF_SYSCALL_WORK     @are we tracing syscalls?

   bne __sys_trace

   cmp scno, #NR_syscalls      @check upper syscall limit

   adr lr, BSYM(ret_fast_syscall)  @ return address

   ldrcc   pc, [tbl, scno, lsl#2]     @ call sys_* routine //執行相應的系統呼叫

   add r1, sp, #S_OFF

2: mov why, #0             @ nolonger a real syscall

   cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)

   eor r0, scno, #__NR_SYSCALL_BASE   @ put OS number back

   bcs arm_syscall

   b   sys_ni_syscall          @ not private func

#if defined(CONFIG_OABI_COMPAT) ||!defined(CONFIG_AEABI)

   /*

    * We failed to handle a fault trying to access the page

    * containing the swi instruction, but we're not really in a

    * position to return -EFAULT. Instead, return back to the

    * instruction and re-enter the user fault handling path trying

    * to page it in. This will likely result in sending SEGV to the

    * current task.

    */

9001:

   sub lr, lr, #4

   str lr, [sp, #S_PC]

   b   ret_fast_syscall       //系統呼叫結束返回處理

#endif

ENDPROC(vector_swi)

   .align  5

/* 

 *This is the fast syscall return path.  Wedo as little as

 *possible here, and this includes saving r0 back into the SVC

 *stack.

 */

ret_fast_syscall:

 UNWIND(.fnstart    )

 UNWIND(.cantunwind )

   disable_irq             @ disableinterrupts

   ldr r1, [tsk, #TI_FLAGS]

   tst r1, #_TIF_WORK_MASK

   bne fast_work_pending

   asm_trace_hardirqs_on

   /* perform architecture specific actions before user return */

   arch_ret_to_user r1, lr   //未實現

   ct_user_enter

    restore_user_regs fast = 1, offset = S_OFF    //恢復使用者空間的CPU資訊

 UNWIND(.fnend      )

         //恢復使用者空間CPU暫存器資訊巨集定義

    .macro restore_user_regs, fast = 0, offset = 0

   ldr r1, [sp, #\offset + S_PSR]  @get calling cpsr //獲取使用者的CPSR

   ldr lr, [sp, #\offset + S_PC]!  @get pc //獲取使用者空間PC

   msr spsr_cxsf, r1           @ savein spsr_svc

   clrex                   @ clearthe exclusive monitor

   .if \fast

   ldmdb   sp, {r1 - lr}^          @ get calling r1 – lr //將之前壓棧的使用者暫存器出棧到暫存器

   .else

   ldmdb   sp, {r0 - lr}^          @ get calling r0 - lr

   .endif

   mov r0, r0              @ ARMv5Tand earlier require a nop

                        @ after ldm {}^

   add sp, sp, #S_FRAME_SIZE - S_PC //核心堆疊指標sp_svc重新指向棧頂

   movs    pc, lr              @ return & move spsr_svc intocpsr //切換到usr返回到使用者空間執行

   .endm

 

程序切換隻能發生在核心,切換時CPU的資訊存放在thread_info結構題的cpu_context中;程序從使用者態到核心態,程序的使用者態CPU資訊則存放在核心態堆疊空間pt_regs地方。


4、程序銷燬

         程序終止的一般方式是呼叫exit()系統呼叫,即可能顯式地呼叫,也可能隱式的從某個主函式返回;當程序接收到它既不能處理也不能忽略的訊號或異常時,還可能被動終結。不管如何,在核心都是通過do_exit完成工作。

         呼叫了do_exit()之後,儘管執行緒已經僵死不能再執行,但是系統還保留了它的程序描述符,這樣可以讓系統有辦法在子程序終結後仍能獲得它的資訊。所以,程序終結時所需的清理工作和程序描述符的刪除被分開執行。在父程序獲得已終結的子程序的資訊後,或者通知核心它並不關注那些資訊後,子程序的task_struct結構才被釋放。

         如果父程序在子程序之前退出,必須有機制來保證子程序能找到一個新的父程序,否則這些程序退出時永遠處於僵死狀態,白白耗費記憶體。對於這個問題,解決方法是給子程序在當前執行緒組內找一個執行緒作為父親,如果不行,就讓init做它們的父程序。

相關推薦

linux核心程序管理

1、程序描述符 (1)程序與執行緒          程序是處於執行期的程式以及相關資源的總稱。執行緒在linux上稱為輕量級程序,沒有獨立的地址空間,一個程序下的所有執行緒共享地址空間、檔案系統資源、檔案描述符、訊號處理程式等。 (2)程序描述符task_struct

Linux核心定時器

static struct pin_desc *irq_pd; /* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 */ /* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 */ static unsigned char key_val; struct pin_desc{u

#21 在Linux裏進程管理,與pstree、ps、pgrep、pkill、pidof、top命令的應用

在linux裏進程管理詳解 與pstree、ps、pgrep、pkill、pidof、top命令的應用 進程管理: 所謂進程:process,一個活動的程序的實體的副本; 生命周期; 可能包含一個或多個執行流; 創建進程: 每個進程的組織結構是一致的: 內核在正常啟動並且全

(轉)Linux命令Ethtool用法

如果 size ram phy 基本設置 速度 終端 網卡驅動 sed Linux命令之Ethtool用法詳解 原文:http://www.linuxidc.com/Linux/2012-01/52669.htm Linux/Unix命令之Ethtool描述:Eth

1.12-linux三劍客awk用法

-a proc == 行號 oss url oldboyedu rap oai 1.12linux三劍客之awk用法詳解內容:1. awk執行過程2. awk命令格式3. awk用法4. awk數組第1章 awk執行過程 一直讀取到文件的最後一行第2章 awk ‘找誰{幹啥

linux三劍客sed入門

linux 三劍客 sed sed介紹sed流編輯器(stream editor),在三劍客中排行老二,是一款簡單的文本編輯語言。sed並不直接處理源文件,而是逐行讀取源文件的內容到內存(稱模式空間)中,然後在模式空間中使用sed命令處理,再打印模式空間處理後的內容到標準輸出。sed的能夠實現的功

Linux三劍客awk命令

awk簡單入門 awk是一個強大的文字分析工具,相對於grep的查詢,sed的編輯,awk在其對資料分析並生成報告時,顯得尤為強大。簡單來說awk就是把檔案逐行的讀入,以空格為預設分隔符將每行切片,切開的部分再進行各種分析處理。 使用方法: awk '{pattern + a

Linux核心原始碼目錄結構

    3.1 Linux核心原始碼目錄如下:         /arch:目錄包括了所有和體系結構相關的核心程式碼。它下面的每一個子目錄都代表一種Linux支援的體系結構,例如i386就是Intel

Linux核心模組(驅動)編譯

本文主要說說如何編譯自己開發的核心模組。由於驅動通常也被編譯成核心模組,因此文章的內容也適用於驅動的編譯。 由於在下能力相當有限,有不當之處,還望大家批評指正^_^ 一、準備工作 準備工作如何做,

Linux核心的Oops問題

參考:Linux:  How To Locate An Oops 什麼是Oops?從語言學的角度說,Oops應該是一個擬聲詞。當出了點小事故,或者做了比較尷尬的事之後,你可以說"Oops",翻譯成中國話就叫做“哎呦”。“哎呦,對不起,對不起,我真不是故意打碎

編譯U-boot和Linux核心的步驟和

1、準備材料 linux核心和uboot的原始碼包—- 6818GEC.tar.gz 環境:VMware12.0 Ubuntu16.04(64位) (1)先將 6818GEC.tar.gz 放在Ubuntu的共享目錄下,然後將 68

Linux 使用者和組管理

使用者與組的分類    Linux系統對使用者分配如下:    -系統管理員:root    -普通使用者:普通使用者分為以下兩種               系統使用者:系統使用者通常是不可登陸的,執行某些服務及程序的帳號              

Linux系列 tar 命令

tar 是 unix/linux下的打包器 【解壓】 輸入命令: # tar  -zxvf  filename.tar.gz 引數解釋: z :表示 tar 包是被 gzip 壓縮過的 (字尾是.tar.gz),所以解壓時需要用 gu

Linux命令——Date命令

date命令的幫助資訊  [[email protected] source]# date --help 用法:date [選項]... [+格式]  或:date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]] 以

linux命令dd命令和/dev/zero、/dev/null

主要內容來自:http://www.cnblogs.com/dkblog/archive/2009/09/18/1980715.html dd 是 Linux/UNIX 下的一個非常有用的命令,作用是用指定大小的塊拷貝一個檔案,並在拷貝的同時進行指定的轉換(convert

LinuxLinux 下多程序程式設計

一.多程序程式的特點    程序是一個具有獨立功能的程式關於某個資料集合的一次可以併發執行的執行活動,是處 於活動狀態的計算機程式。    程序作為構成系統的基本細胞, 不僅是系統內部獨立執行的實體, 而且是獨立競爭資源的基本實體。    程序

Linux核心程序排程

一些概念   排程程式負責決定哪個程序投入執行,何時執行及執行多長時間。程序排程程式就是在可執行態程序之間分配有限的處理器時間資源的核心子系統。 多工系統可分為兩類:非搶佔式多工和搶佔式多工。Linux提供了搶佔式多工。 I/O消耗型程序就是大部分時間用來

linux核心kobject事件處理

/*所有事件的名稱列表*/ static const char *kobject_actions[] = { [KOBJ_ADD] = "add", [KOBJ_REMOVE] = "remove", [KOBJ_CHANGE] = "change", [KO

Linux 命令 scp 命令

Linux 命令之 scp 命令詳解 一、scp 簡介 scp 命令用於不同主機之間複製檔案和目錄。 scp 是 secure copy 的縮寫,是 基於 ssh 協議進行安全的遠端檔案拷貝命令。 scp 想要免密進行復制,需要傳送祕鑰給相應的節點。 scp 是加密的,rcp 是不加密的,scp 是 rcp

Linux核心 記憶體管理

前面幾篇介紹了程序的一些知識,從這篇開始介紹記憶體、檔案、IO等知識,發現更不好寫哈哈。但還是有必要記錄下自己的所學所思。供後續翻閱,同時寫作也是一個鞏固的過程。 這些知識以前有文件涉及過,但是角度不同,這個系列站的角度更底層,基本都是從Linux核心出發,會更深入。所以當你都讀完,然後再次審視這些功能的實現