进程的数据结构
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, ®s, 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, ®s, 0); }
asmlinkage int sys_vfork(struct pt_regs regs) { return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0); }
fork和clone最终调用到了do_fork。 do_fork是一个复杂的函数,留做下一篇慢慢分析。 突然发现这样一节一节的代码学习挺无趣的,到change的时候了。