程序的資料結構
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的時候了。