进程的数据结构
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的时候了。