您的位置:首页 > 运维架构 > Linux

linux 2.6源代码情景分析笔记之进程2

2009-12-25 02:55 489 查看
能被独立调度的每个执行上下文都必须拥有自己的进程描述符。进程和进程描述符之间有严格的对应关系,使用32位进程描述符地址标识,进程描述符指针指向这些地址,内核对进程的大部分引用是通过进程描述符指针进行的。

可以使用pid(进程标识符)来标记进程,存放在字段pid中,PID被顺序编号,新创建的进程PID通常是前一个进程的PID加一。

#define PID_MAX_DEFAULT 0x8000

一个线程租中的所有线程使用和该线程组的领头线程(thread group leader)相同的PID,也就是该组中第一个轻量级进程的PID,被存入进程描述符的tgid字段中。getpid()系统调用返回当前进程的tgid值而不是pid的值,因此一个多线程应用的所有线程共享相同的PID。绝大多数进程都属于一个线程组,包含单一的成员;线程组领头线程其tgid的值与pid的相同,因而getpid()系统调用对这类进程所其的作用和一般进程是一样的。

对每个进程来说,linux把两个不同的数据结构紧凑地存放在一个单独为进程分配的存储区域内:一个内核态的进程堆栈,另一个是紧挨进程描述符的小数据结构thread_info,叫做线程描述符,这块存储区域的大小通常为8192个字节(两个页框)。内核让这8k空间占据连续的两个页框并让第一个页框的起始地址是2^13的倍数。当几乎没有可用的动态内存空间时,就会很难找到这样的两个连续页框,因为空闲空间可能存在大量碎片。内核态的进程访问处于内核数据段的栈,这个栈不同于用户态的进程所用的栈。因为内核控制路径使用很少的栈,因此只需要几千个字节的内核态堆栈。使用task和thread_info字段使thread_info结构与task_struct结构互相关联。

esp寄存器是cpu栈指针,用来存放栈顶单元的地址。栈起始于末端,并朝这个内存区开始的方向增长。从用户态刚切换到内核态之后,进程的内核栈总是空的,因此,esp寄存器指向这个栈的顶端。一旦数据写入堆栈,esp的数值就递减。(thread_info结构是52个字节长)

union thread_union {

struct thread_info thread_info;

unsigned long stack[THREAD_SIZE/sizeof(long)];

};

使用以上联合结构表示一个进程的线程描述符和内核栈。

struct thread_info {

struct task_struct *task; /* main task structure */

struct exec_domain *exec_domain; /* execution domain */

unsigned long flags; /* low level flags */

unsigned long status; /* thread-synchronous flags */

__u32 cpu; /* current CPU */

__s32 preempt_count; /* 0 => preemptable, <0 => BUG */

mm_segment_t addr_limit; /* thread address space:0-0xBFFFFFFF for user-thead,0-0xFFFFFFFF for kernel-thread*/

struct restart_block restart_block;

unsigned long previous_esp; /* ESP of the previous stack in case of nested (IRQ) stacks*/

__u8 supervisor_stack[0];

};

thread_info结构从0x015fa000地址开始存放,而栈从0x015fc000地址开始存放。esp寄存器的值指向地址为0x015fa878的当前栈顶。

内核使用alloc_thread_info和free_thread_info宏分配和释放存储thread_info结构和内核栈的内存区。

#ifdef CONFIG_DEBUG_STACK_USAGE

#define alloc_thread_info(tsk) /

({ /

struct thread_info *ret; /

/

ret = kmalloc(THREAD_SIZE, GFP_KERNEL); /

if (ret) /

memset(ret, 0, THREAD_SIZE); /

ret; /

})

#else

#define alloc_thread_info(tsk) kmalloc(THREAD_SIZE, GFP_KERNEL)

#endif

#define free_thread_info(info) kfree(info)

如果thread_union结构长度是8k(2^13),则内核屏蔽掉esp的低13位有效位就可以获得thread_info结构的基地址;而如果thread_union结构长度是4k(2^12),内核需要屏蔽调esp的低12位有效位。下面的函数就是来完成这个工作的。执行完后,就包含在执行指令的cpu上运行的进程的thread_info结构的指针。

/* how to get the thread information struct from C */

static inline struct thread_info *current_thread_info(void)

{

struct thread_info *ti;

__asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));

return ti;

}

进程常用的是进程描述符的地址而不是thread_info结构的地址。为了获得当前在cpu上运行进程的描述符指针,内核调用以下宏(current),看return:current_thread_info()->task;task字段在thread_info结构中的偏移量为0,所以执行完这三条指令之后,p就包含在cpu上运行进程的描述符指针。

static inline struct task_struct * get_current(void)

{

return current_thread_info()->task;

}

#define current get_current()

current经常作为进程描述符字段的前缀出现在内核代码中。current->pid就是返回在cpu上正在执行的进程的PID.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: