Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1722993
  • 博文数量: 199
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 6186
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-30 11:01
个人简介

Linuxer.

文章存档

2015年(4)

2014年(28)

2013年(167)

分类: LINUX

2013-07-12 16:36:29


  1. 一、进程概念
  2. 进程是程序执行的一个实例。
  3. 从内核观点看,进程的目的就是担当分配系统资源(cpu时间、内存等)的实体。

  4. 二、进程描述符
  5. 1.进程描述符包含了与一个进程相关的所有信息。
  6. struct task_struct {
  7.     volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
  8.     /*
  9.     表示进程的当前状态:
  10.     TASK_RUNNING:正在运行或在就绪队列run-queue中准备运行的进程,实际参与进程调度。
  11.     TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程通过信号(signal)或定时中断唤醒后进入就绪队列run-queue。
  12.     TASK_UNINTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,不可由其它进程通过信号(signal)或定时中断唤醒。
  13.     TASK_ZOMBIE:表示进程结束但尚未消亡的一种状态(僵死状态)。此时,进程已经结束运行且释放大部分资源,但尚未释放进程控制块。
  14.     TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。导致这种状态的原因有二,或者是对收到SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU信号的反应,或者是受其它进程的ptrace系统调用的控制而暂时将CPU交给控制进程。
  15.     TASK_SWAPPING: 进程页面被交换出内存的进程。
  16.     */    
  17.     unsigned long flags;//进程标志,与管理有关,在调用fork()时给出
  18.     int sigpending;//进程上是否有待处理的信号
  19.     mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
  20.     /*0-0xBFFFFFFF for user-thead
  21.      0-0xFFFFFFFF for kernel-thread
  22.     */
  23.     struct exec_domain *exec_domain;//进程执行域
  24.     volatile long need_resched;//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
  25.     unsigned long ptrace;
  26.     int lock_depth;//锁深度
  27.     long counter;//进程的基本时间片,在轮转法调度时表示进程当前还可运行多久,在进程开始运行是被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调 度。重新调度将从run_queue队列选出counter值最大的就绪进程并给予CPU使用权,因此counter起到了进程的动态优先级的作用
  28.     long nice;//静态优先级
  29.     unsigned long policy;//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR,分时进程:SCHED_OTHER
  30.     struct mm_struct *mm;//进程内存管理信息
  31.     int has_cpu, processor;
  32.     unsigned long cpus_allowed;
  33.     struct list_head run_list;//指向运行队列的指针
  34.     unsigned long sleep_time; //进程的睡眠时间
  35.     //用于将系统中所有的进程连成一个双向循环链表,其根是init_task
  36.     struct task_struct *next_task, *prev_task;
  37.     struct mm_struct *active_mm;
  38.     struct linux_binfmt *binfmt;//进程所运行的可执行文件的格式
  39.     int exit_code, exit_signal;
  40.     int pdeath_signal;//父进程终止是向子进程发送的信号
  41.     unsigned long personality;
  42.     int dumpable:1;
  43.     int did_exec:1;
  44.     pid_t pid; //进程标识符,用来代表一个进程
  45.     pid_t pgrp; //进程组标识,表示进程所属的进程组
  46.     pid_t tty_old_pgrp; //进程控制终端所在的组标识
  47.     pid_t session;//进程的会话标识
  48.     pid_t tgid;
  49.     int leader;//表示进程是否为会话主管
  50.     //指向最原始的进程任务指针,父进程任务指针,子进程任务指针,新兄弟进程任务指针,旧兄弟进程任务指针。
  51.     struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
  52.     struct list_head thread_group; //线程链表
  53.     //用于将进程链入HASH表
  54.     struct task_struct *pidhash_next;
  55.     struct task_struct **pidhash_pprev;    
  56.     wait_queue_head_t wait_chldexit; //供wait4()使用
  57.     struct semaphore *vfork_sem;//供vfork()使用
  58.     unsigned long rt_priority;//实时优先级,用它计算实时进程调度时的weight值
  59.     //it_real_value,it_real_incr用于REAL定时器,单位为jiffies,系统根据it_real_value
  60.     //设置定时器的第一个终止时间.在定时器到期时,向进程发送SIGALRM信号,同时根据
  61.     //it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
  62.     //当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送信号SIGPROF,并根据it_prof_incr重置时间.
  63.     //it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
  64.     //状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据it_virt_incr重置初值
  65.     unsigned long it_real_value, it_prof_value, it_virt_value;
  66.     unsigned long it_real_incr, it_prof_incr, it_virt_incr;
  67.     struct timer_list real_timer;//指向实时定时器的指针
  68.     struct tms times; //记录进程消耗的时间
  69.     unsigned long start_time;//进程创建的时间
  70.     long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];//记录进程在每个CPU上所消耗的用户态时间和核心态时间
  71.     //内存缺页和交换信息:
  72.     //min_flt, maj_flt累计进程的次缺页数(Copyon Write页和匿名页)和主缺页数(从映射文件或交换
  73.     //设备读入的页面数);nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
  74.     //cmin_flt, cmaj_flt,cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
  75.     //在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
  76.     unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
  77.     int swappable:1; //表示进程的虚拟地址空间是否允许换出
  78.     //进程认证信息
  79.     //uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid,euid,egid为有效uid,gid
  80.     //fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件系统的访问权限时使用他们。
  81.     //suid,sgid为备份uid,gid
  82.     uid_t uid,euid,suid,fsuid;
  83.     gid_t gid,egid,sgid,fsgid;
  84.     int ngroups;//记录进程在多少个用户组中
  85.     gid_t groups[NGROUPS];//记录进程所在的组
  86.     kernel_cap_t cap_effective, cap_inheritable, cap_permitted;//进程的权能,分别是有效位集合,继承位集合,允许位集合
  87.     int keep_capabilities:1;
  88.     struct user_struct *user;//代表进程所属的用户
  89.     struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息
  90.     unsigned short used_math; //是否使用FPU
  91.     char comm[16]; //进程正在运行的可执行文件名
  92.      //文件系统信息
  93.     int link_count;
  94.     struct tty_struct *tty;//进程所在的控制终端,如果不需要控制终端,则该指针为空
  95.     unsigned int locks; /* How many file locks are being held */
  96.     //进程间通信信息
  97.     struct sem_undo *semundo;//进程在信号量上的所有undo操作
  98.     struct sem_queue *semsleeping;//当进程因为信号量操作而挂起时,他在该队列中记录等待的操作
  99.     struct thread_struct thread;//进程的CPU状态,切换时,要保存到停止进程的task_struct中
  100.     struct fs_struct *fs; //文件系统信息
  101.     struct files_struct *files;//打开文件信息
  102.      //信号处理函数
  103.     spinlock_t sigmask_lock; /* Protects signal and blocked */
  104.     struct signal_struct *sig;//信号处理函数
  105.     sigset_t blocked;//进程当前要阻塞的信号,每个信号对应一位
  106.     struct sigpending pending; //进程上是否有待处理的信号
  107.     unsigned long sas_ss_sp;
  108.     size_t sas_ss_size;
  109.     int (*notifier)(void *priv);
  110.     void *notifier_data;
  111.     sigset_t *notifier_mask;
  112.     /* Thread group tracking */
  113.     u32 parent_exec_id;
  114.     u32 self_exec_id;
  115.     spinlock_t alloc_lock;
  116. };

  117. 2.进程描述符处理
  118. 对于每一个进程而言,内核为其单独分配了一个内存区域,这个区域存储的是内核栈和该进程所对应的一个小型进程描述符——thread_info结构。
  119. struct thread_info {
  120.     struct task_struct *task;//指向进程描述符
  121.   struct exec_domain *exec_domain; /* execution domain */
  122.   unsigned long flags; /* low level flags */
  123.   unsigned long status; /* thread-synchronous flags */
  124.   __u32 cpu; /* current CPU */
  125.   __s32 preempt_count; /* 0 => preemptable, <0 => BUG */
  126.   mm_segment_t addr_limit;
  127.   struct restart_block restart_block;
  128.   unsigned long previous_esp;
  129.   __u8 supervisor_stack[0];
  130. };
  131. 之所以将thread_info结构称之为小型的进程描述符,是因为在这个结构中并没有直接包含与进程相关的字段,而是通过task字段指向具体某个进程描述符。通常这块内存区域的大小是8KB,也就是两个页的大小(有时候也使用一个页来存储,即4KB)。一个进程的内核栈和thread_info结构之间的逻辑关系如下图所示:
  132. 从上图可知,内核栈是从该内存区域的顶层向下(从高地址到低地址)增长的,而thread_info结构则是从该区域的开始处向上(从低地址到高地址)增长。内核栈的栈顶地址存储在esp寄存器中。所以,当进程从用户态切换到内核态后,esp寄存器指向这个区域的末端。从代码的角度来看,内核栈和thread_info结构是被定义在一个联合体当中的:
  133. //定义在linux/include/linux/sched.h中
  134. union thread_union {
  135.     struct thread_info thread_info;
  136.     unsigned long stack[THREAD_SIZE/sizeof(long)];
  137. };
  138. 其中,THREAD_SIZE的值取8192时,stack数组的大小为2048;THREAD_SIZE的值取4096时,stack数组的大小为1024。现在我们应该思考,为何要将内核栈和thread_info(其实也就相当于task_struct,只不过使用thread_info结构更节省空间)紧密的放在一起?最主要的原因就是内核可以很容易的通过esp寄存器的值获得当前正在运行进程的thread_info结构的地址,进而获得当前进程描述符的地址。
  139. static inline struct thread_info *current_thread_info(void)
  140. {
  141.     struct thread_info *ti;
  142.     __asm__(\"andl %%esp,%0; \":\"=r\" (ti) : \"0\" (~(THREAD_SIZE - 1)));
  143.     return ti;
  144. }
  145. 这条内联汇编语句会屏蔽掉esp寄存器中的内核栈顶地址的低13位(或12位,当THREAD_SIZE为4096时)。此时ti所指的地址就是这片内存区域的起始地址,也就刚好是thread_info结构的地址。因为这块区域的大小通常是8192个字节(两个页框),考虑到效率,内核让这8K空间占据连续的两个页框并让第一个页框的起始地址是2^13的倍数,所以屏蔽掉低13位就正好是起始地址。但是,thread_info结构的地址并不会对我们直接有用。我们通常可以轻松的通过current宏获得当前进程的task_struct结构。
  146. static inline struct task_struct * get_current(void)
  147. {
  148.     return current_thread_info()->task;
  149. }
  150. #define current get_current()
  151. 通过上述源码可以发现,current宏返回的是thread_info结构task字段。而task正好指向与thread_info结构关联的那个进程描述符。得到current后,我们就可以获得当前正在运行进程的描述符中任何一个字段了,比如我们通常所做的:current->pid。

  152. .进程间关系
  153. 进程之间有父子关系,如果一个进程创建多个子进程,那这些子进程之间就有了兄弟关系。Linux中,进程0和进程1由内核创建,进程1(init)是其他所有进程的祖先。
  154. 在进程描述符表task_struct结构中,以下字段表示进程间的关系:
  155. real_parent:指向创建进程P的进程的描述符,如果P的父进程不存在,就指向进程1的描述符。
  156. parent:指向P的当前父进程,往往与real_parent一致。当出现Q进程向P发出跟踪调试ptrace()系统调用时,该字段指向Q进程描述符。
  157. children:一个链表头,链表中所有元素都是进程P创建的子进程。
  158. sibling:指向兄弟进程链表的下一个元素或前一个元素的指针。

  159. 另外,进程间还存在其他关系:登录会话关系、进程组关系、线程组关系、跟踪调试关系。
  160. 在task_struct结构中,以下字段表示这些关系(假设当前进程为P):
  161. group_leader:P所在进程组的领头进程的描述符指针
  162. signal->pgrp:P所在进程组的领头进程的PID
  163. tgid:P所在线程组的领头进程的PID
  164. signal->session:P所在登录会话领头进程的PID
  165. ptrace_children:一个链表头,链表中的所有元素是被调试器程序跟踪的P的子进程
  166. ptrace_list:当P被调试跟踪时,指向调试跟踪进程的父进程链表的前一个和下一个元素

  167. 四、进程与等待队列
  168. 等待队列(wait queue)用于使进程带等待某一特定的事件发生,而无需频繁的轮询操作,进程在等待时间内睡眠,在等待的事件发生时由内核自动唤醒。
  169. 1.等待队列相关数据结构
  170. 每一个等待队列都由两部分组成:等待队列头(struct wait_queue_head_t)和等待队列成员(struct wait_queue)
  171. struct __wait_queue_head {
  172.     spinlock_t lock; /*因为等待队列可以在中断时随时修改,因此设置一个自旋锁保证一致性*/
  173.     struct list_head task_list;
  174. };
  175. typedef struct __wait_queue_head wait_queue_head_t;

  176. struct __wait_queue {
  177.     unsigned int flags; /*指明等待的进程是互斥进程还是非互斥进程*/
  178.     struct task_struct *task; /*指向任务的task_struct*/
  179.     wait_queue_func_t func;
  180.     struct list_head task_list;
  181. };
  182. typedef struct __wait_queue_head wait_queue_head_t;
  183. 最后形成的结果就是一个等待队列头串起多个等待队列成员,如图所示:


  184. 2.等待队列的使用分为以下两部分:
  185. (1)为使当前进程在一个等待队列中睡眠,需要调用wait_event(或某个等价函数),此后,进程进入睡眠,将控制权交给调度器。以块设备为例,当内核向块设备发出请求后,因为数据传输不会立即发生,因此进程睡眠
  186. (2)相对应的,是当数据到达后,必须调用wake_up函数(或某个等价函数)来唤醒等待队列中睡眠的进程

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