内核代码阅读(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的时候了。
上一篇:内核代码阅读(24) - 调度


下一篇:内核代码阅读(18) - 时钟中断的上半部do_timer和下半部timer_bh