天天看點

核心代碼閱讀(20) - 程序

程序的資料結構

TSS段

雖然Linux跳過了TSS,還是有必要了解這個硬體的。

Intel在32位上考慮了程序的管理和排程,添加了TSS段,記錄了程序相關的關鍵性的資訊。

同樣的,TSS段也要有段描述表項,但是隻能在GDT中。如果在LDT中則會産生一次GP。

TSS段所對應的寄存器是TR,和CS,DS一樣,TR也有一個不可見的影子,每當一個段選擇碼加載到TR中,CPU會找到TR所選中的TSS段,并裝入影子裡。

Linux對TSS的使用

TSS的問題是每次切換程序都要重新裝載TR和更新TSS段的影子,雖然代碼中一條指令就完成了程序的切換,但是性能和靈活性不高。
Linux隻在系統初始化一次TSS,程序切換并不切換TR。是以TSS是全局的資源,在SMP中每個CPU有一個TR值,一經初始化就不改變TR。
當系統進入核心态時,需要棧切換,核心的棧SS和ESP正好存儲在目前程序的TSS中。由于核心運作在0級,是以對應TSS中的SS0和ESP0.      

TSS初始化

#define INIT_TSS  {                                                \
        0,0, /* back_link, __blh */                                \
        sizeof(init_stack) + (long) &init_stack, /* esp0 */        \
        __KERNEL_DS, 0, /* ss0 */                                \
        0,0,0,0,0,0, /* stack1, stack2 */                        \
        0, /* cr3 */                                                \
        0,0, /* eip,eflags */                                        \
        0,0,0,0, /* eax,ecx,edx,ebx */                                \
        0,0,0,0, /* esp,ebp,esi,edi */                                \
        0,0,0,0,0,0, /* es,cs,ss */                                \
        0,0,0,0,0,0, /* ds,fs,gs */                                \
        __LDT(0),0, /* ldt */                                        \
        0, INVALID_IO_BITMAP_OFFSET, /* tace, bitmap */                \
        {~0, } /* ioperm */                                        \
    }      
1) sizeof(init_stack) + (long) &init_stack
   esp0 指向init_statck的頂端。
2) __KERNEL_DS, 0,
   ss0 指向__KERNEL_DS
   
init_statck的定義:      
#define init_stack        (init_task_union.stack)      
union task_union init_task_union 
        __attribute__((__section__(".data.init_task"))) =
                { INIT_TASK(init_task_union.task) };      
struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };      
1) init_tss 數組大小是CPU的個數,其中每個tss_struct的内容都是一樣的。      

task_union和核心空間的棧

union task_union {
        struct task_struct task;
        unsigned long stack[INIT_TASK_SIZE/sizeof(long)];
    };      
1) task_union
   這個union很關鍵,核心在配置設定一個task_struct的時候,實際上配置設定2個頁面。
   頁面的開始部分是task_struct結構體,剩下的部分是核心棧。
2) task_struct大小約1k,核心棧大小約7k。核心棧的大小是固定的。      

和task_struct,核心棧相關的操作

task_struct的配置設定

#define alloc_task_struct() ((struct task_struct *) __get_free_pages(GFP_KERNEL,1))
    #define free_task_struct(p) free_pages((unsigned long) (p), 1)      
1) alloc_task_struct
   實際上配置設定了2兩個頁面。
2) free_task_struct
   實際上調用了free_pages      

current宏

static inline struct task_struct * get_current(void)
    {
        struct task_struct *current;
        __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
        return current;
    }      
__asm__("andl %%esp,%0; " : "=r"(current) : "0"(~8191UL));
核心需要擷取目前程序的結構體的指針時,把目前的esp和8k對齊就得到了8k的起始位址,也就是task_struct的位址。
current宏隻使用了一條andl就得到了目前程序的結構體指針,而不是把task_struct指針存放在全局變量中,然後從記憶體取讀。妙!!!      

task_struct

struct task_struct {
        volatile long state;
        unsigned long flags;
        int sigpending;
        mm_segment_t addr_limit;        /* thread address space:
                                                 0-0xBFFFFFFF for user-thead
                                                0-0xFFFFFFFF for kernel-thread
                                        */
        struct exec_domain *exec_domain;
        volatile long need_resched;
        unsigned long ptrace;
        int lock_depth;
        long counter;
        long nice;
        unsigned long policy;
        struct mm_struct *mm;
        int has_cpu, processor;
        unsigned long cpus_allowed;
        struct list_head run_list;
        unsigned long sleep_time;
        struct task_struct *next_task, *prev_task;
        struct mm_struct *active_mm;
        struct linux_binfmt *binfmt;
        int exit_code, exit_signal;
        struct rlimit rlim[RLIM_NLIMITS];
        ...
    }      
1) state
   程序目前的狀态
   #define TASK_RUNNING                0
       這個程序可以被排程執行,并不是說這個程序正在執行
   #define TASK_INTERRUPTIBLE        1
       淺度睡眠 interruptible_sleep_on/wake_up_interruptible
   #define TASK_UNINTERRUPTIBLE        2
       深度睡眠 sleep_on/wake_up
   #define TASK_ZOMBIE                4
   #define TASK_STOPPED                8
      用于調試 SIGSTOP SIGCONT
2) flags
   使用者程序管理的一些狀态
3) sigpending
   表示程序收到了信号,但尚未處理。
4) couter
   用于程序排程
5) need_resched
   CPU從系統空間傳回使用者空間前夕需要進行一次排程。
6) addr_limit
   虛拟位址的上限。
7) struct rlimit rlim[RLIM_NLIMITS];
   程序的資源限制。
   #define RLIMIT_CPU        0                /* CPU time in ms */
   #define RLIMIT_FSIZE        1                /* Maximum filesize */
   #define RLIMIT_DATA        2                /* max data size */
   #define RLIMIT_STACK        3                /* max stack size */
   #define RLIMIT_CORE        4                /* max core file size */
   #define RLIMIT_RSS        5                /* max resident set size */
   #define RLIMIT_NPROC        6                /* max number of processes */
   #define RLIMIT_NOFILE        7                /* max number of open files */
   #define RLIMIT_MEMLOCK        8                /* max locked-in-memory address space */
   #define RLIMIT_AS        9                /* address space limit */
   #define RLIMIT_LOCKS        10                /* maximum file locks held */
   #define RLIM_NLIMITS        11      

程序的建立和退出

Linux程序建立必須從一個已有的程序分裂出來。

fork + execve

fork和clone

fork原型      
pid_t fork(void);      
clone原型      
in clone(int(*fn)(void *arg), void *child_stack, int flags, void *arg)      
asmlinkage int sys_fork(struct pt_regs regs)
    {
        return do_fork(SIGCHLD, regs.esp, &regs, 0);
    }      
asmlinkage int sys_clone(struct pt_regs regs)
    {
        unsigned long clone_flags;
        unsigned long newsp;
    
        clone_flags = regs.ebx;
        newsp = regs.ecx;
        if (!newsp)
        newsp = regs.esp;
        return do_fork(clone_flags, newsp, &regs, 0);
    }      
asmlinkage int sys_vfork(struct pt_regs regs)
    {
        return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
    }      
fork和clone最終調用到了do_fork。
do_fork是一個複雜的函數,留做下一篇慢慢分析。
突然發現這樣一節一節的代碼學習挺無趣的,到change的時候了。      

繼續閱讀