Chinaunix首页 | 论坛 | 博客
  • 博客访问: 712091
  • 博文数量: 130
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 2198
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-29 12:48
个人简介

每一个“丑得人神共愤”的泡妞高 手都有一颗坚忍的心,这证明了人类 在绝境中毫不妥协的求生精神,反正丑都丑了,索性放开手脚大干一场,这就叫“无产阶级失去的是锁链,得到的是全世界”

文章分类

全部博文(130)

文章存档

2013年(130)

我的朋友

分类: LINUX

2013-04-22 08:59:19

进程管理:(一)进程描述符和进程的组织

概述

一个进程就是处于执行期的程序,但进程不仅仅局限于一段可执行程序代码,通常进程还要包括其他资源,如打开的文件、挂起的信号、内和内部数据、处理器状态、地址空间以及一个或多个执行线程(thread of execution)等,当然还包括用来存储全局变量的数据段。
执行线程,简称线程(thread),是在进程中的活动对象,每个线程拥有一个独立的程序计数器(即IP),进程栈和一组进程寄存器。内核调度的对象是线程而不是进程。
在现代操作系统中,进程提供两种虚拟机制:虚拟处理器和虚拟内存。虽然实际上可能是多进程正在分享一个处理器,但虚拟处理器给进程一种假象,让进程觉得自己在独享处理器;而虚拟内存让进程在获取和使用内存时感觉自己拥有整个系统所有的内存资源。
无疑,进程在它被创建的时刻开始存活,在Linux系统中欧你,这通常是调用fork()系统调用的结果,该系统调用通过复制一个现有进程来创建一个全新的进程。在该调用结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行。fork()系统调用从内核返回两次:一次回到父进程,另一次回到新诞生的子进程。通常新创建的进程都是为了立即执行新的、不同的程序,而接着调用exec*()家族函数就可以创建新的地址空间,将新程序载入。最终,程序通过exit()系统调用退出执行,这个函数会终结进程并将其占用的资源释放掉。父进程可以通过wait4()系统调用查询子进程是否终结,这其实使得进程拥有了等待特定进程执行完毕的能力。进程推出执行后被设置为僵死状态,知道父进程调用wait()或waitpid()为止。

Linux内核通常把进程也叫做任务(task)

进程描述符

内核把进程存放在叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct、称为进程描述符的结构,它包含一个具体进程的所有信息。

task_struct的完整结构可点此task_struct.zip进行下载,在此我们将列出task_struct的部分成员进行分析和讲解。

  1. struct task_struct {
  2.     volatile long state;
  3.     void *stack;
  4.     int prio, static_prio, normal_prio;
  5.     unsigned int policy;
  6.     struct list_head tasks;
  7.     int exit_state;
  8.     int exit_code, exit_signal;
  9.     pid_t pid;
  10.     pid_t tgid;
  11.     struct task_struct *parent;
  12.     struct list_head children;
  13.     struct list_head sibling;
  14.     .........................
  15. }


linux通过slab分配器分配task_struct结构,这样能够达到对象复用和缓存着色(cache coloring)的目的,通过预先分配和重复使用task_struct,可以避免动态分配和释放所带来的资源消耗。在内核中,访问任务通常需要获得指向其task_struct的指针。实际上,内核中大部分处理进程的代码都是直接通过task_struct进行的。内核中current宏指向当前进程,因此通过current宏查找到当前正在运行的进程的进程描述符的速度就显得尤为重要。硬件体系结构不同哦,该宏的实现也不同,它必须针对专门的硬件体系结构做处理。有的硬件体系结构可以拿出一个专门寄存器来存放指向当前进程task_struct的指针,用于加快访问速度。而像x86这样的体系结构,其寄存器并不富余,就只能在内核栈的尾端创建thread_info结构,通过计算偏移间接地查找task_struct结构。其中关系如图所示:



学习内核最好的方式是阅读源码,下面将相关源码贴出来,方便对实现感兴趣的同学们考究。
以下thread_union函数源码摘自2.6.34.14版本内核源码的include/linux/sched.h文件,这是对内核栈的定义。
  1. union thread_union {
  2.     struct thread_info thread_info;
  3.     unsigned long stack[THREAD_SIZE/sizeof(long)];
  4. };
以下struct thread_info结构源码摘自2.6.34.14版本内核源码的arch/x86/include/asm/thread_info.h文件,thread_info的成员task指向当前进程。
  1. struct thread_info {
  2.     struct task_struct *task; /* main task structure */
  3.     struct exec_domain *exec_domain; /* execution domain */
  4.     __u32 flags; /* low level flags */
  5.     __u32 status; /* thread synchronous flags */
  6.     __u32 cpu; /* current CPU */
  7.     int preempt_count; /* 0 => preemptable,
  8.                            <0 => BUG */
  9.     mm_segment_t addr_limit;
  10.     struct restart_block restart_block;
  11.     void __user *sysenter_return;
  12. #ifdef CONFIG_X86_32
  13.     unsigned long previous_esp; /* ESP of the previous stack in
  14.                            case of nested (IRQ) stacks
  15.                         */
  16.     __u8 supervisor_stack[0];
  17. #endif
  18.     int uaccess_err;
  19. };
以下current_thread_info函数源码摘自2.6.34.14版本内核源码的arch/x86/include/asm/thread_info.h文件,current_thread_info获得当前thread_info
  1. /* how to get the current stack pointer from C */
  2. register unsigned long current_stack_pointer asm("esp") __used;

  3. /* how to get the thread information struct from C */
  4. static inline struct thread_info *current_thread_info(void)
  5. {
  6.     return (struct thread_info *)
  7.         (current_stack_pointer & ~(THREAD_SIZE - 1));
  8. }

以下current宏源码摘自2.6.34.14版本内核源码的include/asm-generic/current.h文件,current调用current_thread_info从而获得指向当前进进程的指针

  1. #define get_current() (current_thread_info()->task)
  2. #define current get_current()

注意,程序一般在用户空间执行,当一个程序执行了系统调用或者触发了某个异常时,它就陷入了内核空间,此时,我们称内核“代表进程执行”并处于进程上下文中。只有在此上下文中current宏才是有效的。

进程的组织

要理解进程的组织必须对内核的链表设施有个基本的了解,还不了解内核链表的同学可以看看我的另外一片文章《内核链表分析 》。进程的组织主要涉及到task_struct中的tasks、parent、children和sibling成员。这里我们将这4个成员分成三部分来讲。

首先要谈到的是tasks成员,它是struct list_head类型的,它将进程组织成下图所表示的形式:

其中init_task是静态分配的init进程的进程描述符。进程的遍历可使用for_each_process,其定义如下(源码摘自2.6.34.14版本内核源码的include/linux/shced.h文件):

  1. #define next_task(p) \
  2.     list_entry_rcu((p)->tasks.next, struct task_struct, tasks)

  3. #define for_each_process(p) \
  4.     for (p = &init_task ; (p = next_task(p)) != &init_task ; )
然后是parent成员,它是struct task_struct类型指针,它将进程组织成下图所表示的形式:


通过parent指针任意一个进程可以一直向上追溯到它们共同的祖先init进程。

最后是children和sibling成员,它们是struct list_head类型,它们将进程组织成下图所表示的形式:


子进程的遍历可使用如下代码:

  1. struct task_struct *task;
  2. struct list_head *list;

  3. list_for_each(list, &current->children) {
  4.         task = list_entry(list, struct task_parent, sibling);
  5.         /* task 现在指向当前某个子进程 */
  6.         ............
  7. }



补充说明



如果你看到了这里,非常感谢你阅读了我的文章,不过针对有些同学看文章没有耐心我想强调一下, 看文章一定要仔仔细细的看,争取搞懂里面说的每一个知识点,这样你才能学到东西,至少我相信你认真看我的文章就一定能学到东西,不管你是大牛还是菜鸟。我 的文章里面会贴出相关源码和参考资料,一是为了方便大家搞懂每一个知识点的;二是有些地方我可能解释得不是很清楚或者跟你的理解不一样,源码就是最好的解 释。如果我理解错了,欢迎大家指正;如果大家有什么问题也欢迎大家评论、留言。在此向大家推荐一个非常方便的内核源码分析工具lxr,其地址为



参考资料

  1. 《LINUX内核设计与实现》第二版第三章

原文链接:http://blog.chinaunix.net/uid-26497520-id-3608803.html,看着作者熬夜工作的份上转载请注明出处


阅读(2802) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~