进程---------进程描述符(1)

来源:互联网 发布:知乎复制不了 编辑:程序博客网 时间:2024/06/07 20:08

       进程是程序执行时的一个实例,它是描述程序已经执行到何种程度的数据结构的汇集。

       父子进程有相同的代码,共享正文页,但是它们有着独立的数据拷贝(堆和栈),因此子进程对一个内存单元的修改对于父进程是不可见的(反之亦然)。但是现在的unix系统,它们支持多线程应用程序-----拥有很多相对独立执行流的用户程序共享应用程序的大部分数据结构。现在绝大部分应用程序都是用pthread(POSIX thread)库的标准函数库编写的。

       linux采用轻量级进程对多线程应用程序提供更好的支持。两个轻量级进程基本上可以共享一些资源,如地址空间/打开文件等等。只要其中一个修改共享资源,另一个就可以立即查看这种修改,当然它们访问共享资源时必须同步它们自己。实现多线程应用程序的一个简单方式就是把轻量级进程与每个线程关联起来,这样线程之间就可以通过简单的共享同一地址空间、同一打开文件等来访问相同的数据结构集;同时每个线程都可以由内核独立调度,以便一个睡眠的时候另一个仍然可以时运行的。POSIX兼容的多线程应用程序由支持“线程组”的内核,在linux中,一个线程组就是实现了多线程应用程序的一组轻量级进程。对于像getpid(),kill()和_exit()这样的一些系统调用,它像一个组织,起整体的作用。

        进程描述符都是task_struct类型结构,它的字段包含了与一个进程相关的所有信息,在include\linux\sched.h文件中定义。


  1. //进程描述符task_struct  
  2.   
  3. struct task_struct {  
  4.   
  5. /* * offsets of these are hardcoded elsewhere - touch with care 
  6.  
  7. */ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ //-1 不能运行 0 运行 >0 停止  
  8.   
  9. unsigned long flags; /* per process flags, defined below *///进程标志,在下面定义  
  10.   
  11. int sigpending; //进程上是否有待处理的信号  
  12.   
  13. mm_segment_t addr_limit; /* thread address space:进程地址空间 
  14.  
  15. 0-0xBFFFFFFF for user-thead 
  16.  
  17. 0-0xFFFFFFFF for kernel-thread 
  18.  
  19. */  
  20.   
  21.   
  22. volatile long need_resched; //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度  
  23.   
  24.   
  25. int lock_depth; /* Lock depth *///锁深度  
  26.   
  27. /* * offset 32 begins here on 32-bit platforms. We keep 
  28.  
  29. * all fields in a single cacheline that are needed for 
  30.  
  31. * the goodness() loop in schedule(). 
  32.  
  33. */ long counter; //进程可运行的时间量  
  34.   
  35. long nice; //进程的基本时间片  
  36.   
  37. unsigned long policy; //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR;分时进程:SCHED_OTHER;  
  38.   
  39. struct mm_struct *mm; //进程内存管理信息  
  40.   
  41. int processor;  
  42.   
  43. /* * cpus_runnable is ~0 if the process is not running on any 
  44.  
  45. * CPU. It's (1 << cpu) if it's running on a CPU. This mask 
  46.  
  47. * is updated under the runqueue lock. 
  48.  
  49. * * To determine whether a process might run on a CPU, this 
  50.  
  51. * mask is AND-ed with cpus_allowed. 
  52.  
  53. * 若进程不在任何CPU上运行,cpus_runnable 的值是0,否则是1。这个值在运行 *队列被锁时更新;*/  
  54.   
  55. unsigned long cpus_runnable, cpus_allowed;  
  56.   
  57. /* * (only the 'next' pointer fits into the cacheline, but 
  58.  
  59. * that's just fine.) 
  60.  
  61. */  
  62.   
  63. struct list_head run_list; //指向运行队列的指针  
  64.   
  65. unsigned long sleep_time; //进程的睡眠时间  
  66.   
  67. struct task_struct *next_task, *prev_task; //用于将系统中所有的进程连成一个双向循环链表,其根是init_task.  
  68.   
  69. struct mm_struct *active_mm;  
  70.   
  71. struct list_head local_pages; //指向本地页面  
  72.   
  73. unsigned int allocation_order, nr_local_pages;  
  74.   
  75. /* task state */  
  76.   
  77. struct linux_binfmt *binfmt; //进程所运行的可执行文件的格式  
  78.   
  79. int exit_code, exit_signal;  
  80.   
  81. int pdeath_signal; /* The signal sent when the parent dies *///父进程终止是向子进程发送的信号  
  82.   
  83. /* ??? */  
  84.   
  85. unsigned long personality; //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序  
  86.   
  87. int did_exec:1; //按POSIX要求设计的布尔量,区分进程正在执行从父进程中继承的代码,还是执行由execve装入的新程序代码  
  88.   
  89. pid_t pid; //进程标识符,用来代表一个进程  
  90.   
  91. pid_t pgrp; //进程组标识,表示进程所属的进程组  
  92.   
  93. pid_t tty_old_pgrp; //进程控制终端所在的组标识  
  94.   
  95. pid_t session; //进程的会话标识  
  96.   
  97. pid_t tgid;  
  98.   
  99. /* boolean value for session group leader */  
  100.   
  101. int leader; //标志,表示进程是否为会话主管  
  102.   
  103. /*  
  104.  
  105. * pointers to (original) parent process, youngest child, younger sibling, 
  106.  
  107. * older sibling, respectively. (p->father can be replaced with  
  108.  
  109. * p->p_pptr->pid) 
  110.  
  111. *///指针指向(原始的)父进程,孩子进程,比自己年轻的兄弟进程,比自己年长的兄弟进程  
  112.   
  113. //(p->father能被p->p_pptr->pid代替)  
  114.   
  115. struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;  
  116.   
  117. struct list_head thread_group; //线程链表  
  118.   
  119. /* PID hash table linkage. *///进程散列表指针  
  120.   
  121. struct task_struct *pidhash_next; //用于将进程链入HASH表pidhash  
  122.   
  123. struct task_struct **pidhash_pprev;  
  124.   
  125. wait_queue_head_t wait_chldexit; /* for wait4() *///wait4()使用  
  126.   
  127. struct completion *vfork_done; /* for vfork() */// vfork() 使用  
  128.   
  129. unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值  
  130.   
  131. //it_real_value,it_real_incr用于REAL定时器,单位为jiffies。系统根据it_real_value //设置定时器的第一个终止时间。在定时器到期时,向进程发送SIGALRM信号,同时根据it_real_incr重置终止时间  
  132.   
  133. //it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。当进程运行时,不管在何种状态下,每个tick都使  
  134.   
  135. //it_prof_value值减一,当减到0时,向进程发送信号SIGPROF,并根据it_prof_incr重置时间  
  136.   
  137. //it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种状态下,每个tick都使  
  138.   
  139. //it_virt_value值减一,当减到0时,向进程发送信号SIGVTALRM,根据it_virt_incr重置初值。  
  140.   
  141. //Real定时器根据系统时间实时更新,不管进程是否在运行  
  142.   
  143. //Virtual定时器只在进程运行时,根据进程在用户态消耗的时间更新  
  144.   
  145. //Profile定时器在进程运行时,根据进程消耗的时(不管在用户态还是内核态)更新  
  146.   
  147. unsigned long it_real_value, it_prof_value, it_virt_value;  
  148.   
  149. unsigned long it_real_incr, it_prof_incr, it_virt_value;  
  150.   
  151. struct timer_list real_timer;//指向实时定时器的指针  
  152.   
  153. struct tms times; //记录进程消耗的时间,  
  154.   
  155. unsigned long start_time;//进程创建的时间  
  156.   
  157. long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; //记录进程在每个CPU上所消耗的用户态时间和核心态时间  
  158.   
  159. /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */  
  160.   
  161. //内存缺页和交换信息:  
  162.   
  163. //min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换设备读入的页面数);  
  164.   
  165. //nswap记录进程累计换出的页面数,即写到交换设备上的页面数。  
  166.   
  167. //cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。在父进程  
  168.   
  169. //回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中  
  170.   
  171. unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;  
  172.   
  173. int swappable:1; //表示进程的虚拟地址空间是否允许换出   
  174.   
  175. /* process credentials *////进程认证信息  
  176.   
  177. //uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid //euid,egid为有效uid,gid  
  178.   
  179. //fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件系统的访问权限时使用他们。  
  180.   
  181. //suid,sgid为备份uid,gid  
  182.   
  183. uid_t uid,euid,suid,fsuid;  
  184.   
  185. gid_t gid,egid,sgid,fsgid;  
  186.   
  187. int ngroups; //记录进程在多少个用户组中  
  188.   
  189. gid_t groups[NGROUPS]; //记录进程所在的组  
  190.   
  191. kernel_cap_t cap_effective, cap_inheritable, cap_permitted;//进程的权能,分别是有效位集合,继承位集合,允许位集合  
  192.   
  193. int keep_capabilities:1;  
  194.   
  195. struct user_struct *user;  
  196.   
  197. /* limits */  
  198.   
  199. struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息  
  200.   
  201. unsigned short used_math; //是否使用FPU  
  202.   
  203. char comm[16]; //进程正在运行的可执行文件名   
  204.   
  205. /* file system info *///文件系统信息  
  206.   
  207. int link_count, total_link_count;  
  208.   
  209. struct tty_struct *tty; /* NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空*/  
  210.   
  211. unsigned int locks; /* How many file locks are being held */  
  212.   
  213. /* ipc stuff *///进程间通信信息  
  214.   
  215. struct sem_undo *semundo; //进程在信号灯上的所有undo操作  
  216.   
  217. struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作  
  218.   
  219. /* CPU-specific state of this task *///进程的CPU状态,切换时,要保存到停止进程的  
  220.   
  221. task_struct中  
  222.   
  223. struct thread_struct thread;  
  224.   
  225. /* filesystem information文件系统信息*/  
  226.   
  227. struct fs_struct *fs;  
  228.   
  229. /* open file information *///打开文件信息  
  230.   
  231. struct files_struct *files;  
  232.   
  233. /* signal handlers *///信号处理函数  
  234.   
  235. spinlock_t sigmask_lock; /* Protects signal and blocked */  
  236.   
  237. struct signal_struct *sig; //信号处理函数,  
  238.   
  239. sigset_t blocked; //进程当前要阻塞的信号,每个信号对应一位  
  240.   
  241. struct sigpending pending; //进程上是否有待处理的信号  
  242.   
  243. unsigned long sas_ss_sp;  
  244.   
  245. size_t sas_ss_size;  
  246.   
  247. int (*notifier)(void *priv);  
  248.   
  249. void *notifier_data;  
  250.   
  251. sigset_t *notifier_mask;  
  252.   
  253. /* Thread group tracking */  
  254.   
  255. u32 parent_exec_id;  
  256.   
  257. u32 self_exec_id;  
  258.   
  259. /* Protection of (de-)allocation: mm, files, fs, tty */  
  260.   
  261. spinlock_t alloc_lock;  
  262.   
  263. /* journalling filesystem info */  
  264.   
  265. void *journal_info;  
  266.   
  267. };  
进程状态:

      在当前的linux版本中这些状态时互斥的,只能设置一种状态。

      可运行状态(TASK—RUNNING),进程要么在cpu上执行,要么准备执行;

      可中断的等待状态(TASK—INTERRUPTIBLE),进程在等待某个条件,一旦该条件成立可立即变为TASK—RUNNING

      不可中断的等待状态TASK—UNINTERRUPTIBLE),即使等待的条件为真,该睡眠进程也不能改变它的状态,如设备驱动程序正在探测相应的硬件设备时,在设备探测完成前,设备驱动程序不可被中断,否则设备会处于不可预知的状态;

      暂停状态(TASK—STOPPED),进程接受到SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU;

      跟踪状态(TASK—TRACED),进程的执行由debugger程序暂停,当一个进程被另一个进程监控(如被另一个进程执行ptrace);

      当进程的执行被终止时,进程的状态会变为以下两种状态中的一种,它可以存放在进程描述符的state字段中,也可以存放在exit—state字段中。

      僵死状态(TASK—ZOMBIE),进程的执行被终止,但是父进程还没有发布wait4()或者waitpid()系统调用来返回有关死亡进程的信息。此时内核不能丢弃包含在死进程描述符中的数据,因为父进程可能还需要它。

       僵死撤销状态(EXIT—DEAD),父进程发出wait4()或waitpid()系统调用。可以防止其他执行线程在同一个进程上执行wait类系统调用。

       state字段的值通常用一个简单的赋值语句设置,如:p->state=TASK_—RUNNING,内核也可以用宏设置,可以确保编译程序不把赋值语句和其他指令混合,防止灾难性的后果。

标示一个进程:

       一般使用32位进程描述符地址标示一个进程,进程描述符指针指向这些地址,内核对进程的大部分引用都是通过进程描述符指针进行的。

      在类unix系统中,允许用户使用一个叫做进程标识符processID的数来标识进程,PID存放在进程描述符的pid字段中,且pid编号循环使用,内核通过一个pidmap—array位图来表示当前已分配的pid号和闲置的pid号。一个页框包含32768位,在32位体系结构中只需一页,在64位体系结构中,pid的上限可以扩展到4194303,此时需要为pid位图增加多个页.系统管理员可以通过往/proc/sys/kernel/pid—max这个文件中写入值来决定pid的上限。

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

进程描述符处理:

       内核要同时处理很多进程,并把进程描述符放在动态内存中。对每个进程,linux都是把两个不同的数据结构紧凑的放在一个单独为进程分配的存储区域中:1内核态的进程堆栈;2紧挨进程描述符的小数据结构thread—info,叫做线程描述符,这块存储区域大小通常位8192字节(2个页框),通常这块存储区连续,且第一个页框的起始地址是2的13次方的倍数。线程描述符驻留在该内存区的开始,而栈从末端向下增长。


    esp寄存器是cpu栈指针,用来存放栈顶单元的地址。从用户态刚切换为内核态以后,进程的内核栈总是空的,esp寄存器指向这个栈的顶端,一旦写入数据,esp值就减小。由于thread_info结构是52字节,因此内核栈能扩展到8196-52=8144字节。

    c语言使用下列的联合结构标示一个进程的线程描述符和内核栈

   union thread_union{

struct thread_info thread_info;

unsigned long stack[2048];// 对4k的栈数组下标示1024

};


标示当前进程:

    thread_info结构与内核态对战之间紧密结合的好处是:内核很容易从esp寄存器的值获得当前在cpu上正在运行进程的thread_info结构的地址。如果thread_union结构长度是8k,则内核屏蔽esp的低13位有效数字就可以获得thread_info结构的基地址。

    进程常用的是进程描述符的地址而不是thread_info的地址,为此内核调用current宏,该宏本质上等价于current_thread_info->task,它产生如下汇编语言指令:

movl $0xffffe000,%ecx

andl %esp,%ecx

movl %ecx,p

    因为task字段在thread_info结构中的偏移量为0,执行这三条指令后,p就是在cpu上运行进程的描述符指针。

    对每个硬件处理器,仅通过检查栈就可以获得当前正确的进程。

进程链表:

    进程链表把所有进程的描述符链接起来,它的本质是一个双向链表。每个task_struct结构都包含一个list_head结构的tasks字段,这个类型的prev和next字段分别指向前面和后面的task_struct元素。进程链表的头是init_task描述符,它是所谓的0进程(process 0)或者swapper进程的进程描述符。

    有一个宏for_each_process,它的功能是扫描整个进程链表,

#define for_eack_process

for(p=&init_task;(p=list_entry(p->tasks.next,struct task_struct,tasks))!=&init_task)

选择进程运行:

    实质是选择一个处于TASK_RUNNING状态的进程,linux 2.6开始建立多个可运行进程链表,每种进程优先权对应一个链表。每个task_struct描述符包含一个list_head类型的字段run_list,进程描述符的prio字段存放进程的动态优先权。





原创粉丝点击