为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块 PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个 PCB,在创建进程时,建立 PCB,伴随进程运行的全过程,直到进程撤消而撤消。PCB 记录了操作系统所需的,用于描述进程的当前情况以及控制进程运行的全部信息(如打开的文件、挂起的信号、进程状态、地址空间等等)。
在 Linux 中每一个进程都由 task_struct 数据结构来定义。task_struct 就是我们通常所说的 PCB。它是对进程控制的唯一手段也是最有效的手段。当我们调用 fork() 时,系统会为我们产生一个 task_struct 结构。然后从父进程,那里继承一些数据,并把新的进程插入到进程树中,以待进行进程管理。因此了解 task_struct 的结构对于我们理解任务调度(在 Linux 中任务和进程是同一概念)的关键。
在进行剖析 task_struct 的定义之前,我们先按照我们的理论推一下它的结构:
1、进程状态,将纪录进程在等待,运行,或死锁;
2、调度信息,由哪个调度函数调度,怎样调度等;
3、进程的通讯状况;
4、因为要插入进程树,必须有联系父子兄弟的指针,当然是 task_struct 型;
5、时间信息,比如计算好执行的时间,以便cpu 分配;
6、标号,决定改进程归属;
7、可以读写打开的一些文件信息;
8、 进程上下文和内核上下文;
9、处理器上下文;
10、内存信息;
因为每一个PCB都是这样的,只有这些结构,才能满足一个进程的所有要求。
task_struct 结构体包含了一个进程所需的所有信息。它定义在 linux-2.6.38.8/include/linux/sched.h 文件中。
在 Linux 内核编程中常用的 CURRENT 宏可以非常简单地获取到指向 task_struct 的指针。
struct task_struct {/* 1. state: 进程执行时,它会根据具体情况改变状态。进程状态是进程调度和对换的依据。Linux中的进程主要有如下状态:1) TASK_RUNNING: 可运行处于这种状态的进程,只有两种状态:1.1) 正在运行正在运行的进程就是当前进程(由current所指向的进程)1.2) 正准备运行准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源,系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行 2) TASK_INTERRUPTIBLE: 可中断的等待状态,是针对等待某事件或其他资源的睡眠进程设置的,在内核发送信号给该进程表明事件已经发生时,进程状态变为TASK_RUNNING,它只要调度器选中该进程即可恢复执行 3) TASK_UNINTERRUPTIBLE: 不可中断的等待状态处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中,处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等 它们不能由外部信号唤醒,只能由内核亲自唤醒 4) TASK_ZOMBIE: 僵死进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。5) TASK_STOPPED: 暂停此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态6) TASK_TRACED从本质上来说,这属于TASK_STOPPED状态,用于从停止的进程中,将当前被调试的进程与常规的进程区分开来7) TASK_DEAD父进程wait系统调用发出后,当子进程退出时,父进程负责回收子进程的全部资源,子进程进入TASK_DEAD状态8) TASK_SWAPPING: 换入/换出*/volatile long state;/*2. stack进程内核栈,进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈*/ void *stack;/*3. usage进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态*/atomic_t usage;/*4. flagsflags是进程当前的状态标志(注意和运行状态区分)1) #define PF_ALIGNWARN 0x00000001: 显示内存地址未对齐警告2) #define PF_PTRACED 0x00000010: 标识是否是否调用了ptrace3) #define PF_TRACESYS 0x00000020: 跟踪系统调用4) #define PF_FORKNOEXEC 0x00000040: 已经完成fork,但还没有调用exec5) #define PF_SUPERPRIV 0x00000100: 使用超级用户(root)权限6) #define PF_DUMPCORE 0x00000200: dumped core 7) #define PF_SIGNALED 0x00000400: 此进程由于其他进程发送相关信号而被杀死 8) #define PF_STARTING 0x00000002: 当前进程正在被创建9) #define PF_EXITING 0x00000004: 当前进程正在关闭10) #define PF_USEDFPU 0x00100000: Process used the FPU this quantum(SMP only) #define PF_DTRACE 0x00200000: delayed trace (used on m68k) */unsigned int flags; /*5. ptraceptrace系统调用,成员ptrace被设置为0时表示不需要被跟踪,它的可能取值如下: linux-2.6.38.8/include/linux/ptrace.h 1) #define PT_PTRACED 0x000000012) #define PT_DTRACE 0x00000002: delayed trace (used on m68k, i386) 3) #define PT_TRACESYSGOOD 0x000000044) #define PT_PTRACE_CAP 0x00000008: ptracer can follow suid-exec 5) #define PT_TRACE_FORK 0x000000106) #define PT_TRACE_VFORK 0x000000207) #define PT_TRACE_CLONE 0x000000408) #define PT_TRACE_EXEC 0x000000809) #define PT_TRACE_VFORK_DONE 0x0000010010) #define PT_TRACE_EXIT 0x00000200*/unsigned int ptrace;unsigned long ptrace_message;siginfo_t *last_siginfo; /*6. lock_depth用于表示获取大内核锁的次数,如果进程未获得过锁,则置为-1*/int lock_depth; /*7. oncpu在SMP上帮助实现无加锁的进程切换(unlocked context switches)*/ #ifdef CONFIG_SMP #ifdef __ARCH_WANT_UNLOCKED_CTXSWint oncpu; #endif #endif/*8. 进程调度1) prio: 调度器考虑的优先级保存在prio,由于在某些情况下内核需要暂时提高进程的优先级,因此需要第三个成员来表示(除了static_prio、normal_prio之外),由于这些改变不是持久的,因此静态(static_prio)和普通(normal_prio)优先级不受影响2) static_prio: 用于保存进程的"静态优先级",静态优先级是进程"启动"时分配的优先级,它可以用nice、sched_setscheduler系统调用修改,否则在进程运行期间会一直保持恒定3) normal_prio: 表示基于进程的"静态优先级"和"调度策略"计算出的优先级,因此,即使普通进程和实时进程具有相同的静态优先级(static_prio),其普通优先级(normal_prio)也是不同的。进程分支时(fork),新创建的子进程会集成普通优先级 */int prio, static_prio, normal_prio;/*4) rt_priority: 表示实时进程的优先级,需要明白的是,"实时进程优先级"和"普通进程优先级"有两个独立的范畴,实时进程即使是最低优先级也高于普通进程,最低的实时优先级为0,最高的优先级为99,值越大,表明优先级越高*/unsigned int rt_priority;/*5) sched_class: 该进程所属的调度类,目前内核中有实现以下四种: 5.1) static const struct sched_class fair_sched_class;5.2) static const struct sched_class rt_sched_class;5.3) static const struct sched_class idle_sched_class;5.4) static const struct sched_class stop_sched_class; */const struct sched_class *sched_class;/*6) se: 用于普通进程的调用实体 调度器不限于调度进程,还可以处理更大的实体,这可以实现"组调度",可用的CPU时间可以首先在一般的进程组(例如所有进程可以按所有者分组)之间分配,接下来分配的时间在组内再次分配这种一般性要求调度器不直接操作进程,而是处理"可调度实体",一个实体有sched_entity的一个实例标识在最简单的情况下,调度在各个进程上执行,由于调度器设计为处理可调度的实体,在调度器看来各个进程也必须也像这样的实体,因此se在task_struct中内嵌了一个sched_entity实例,调度器可据此操作各个task_struct*/struct sched_entity se;/*7) rt: 用于实时进程的调用实体 */struct sched_rt_entity rt;#ifdef CONFIG_PREEMPT_NOTIFIERS /*9. preempt_notifierpreempt_notifiers结构体链表 */struct hlist_head preempt_notifiers; #endif/*10. fpu_counterFPU使用计数 */unsigned char fpu_counter;#ifdef CONFIG_BLK_DEV_IO_TRACE/*11. btrace_seqblktrace是一个针对Linux内核中块设备I/O层的跟踪工具*/unsigned int btrace_seq; #endif/*12. policypolicy表示进程的调度策略,目前主要有以下五种:1) #define SCHED_NORMAL 0: 用于普通进程,它们通过完全公平调度器来处理2) #define SCHED_FIFO 1: 先来先服务调度,由实时调度类处理3) #define SCHED_RR 2: 时间片轮转调度,由实时调度类处理4) #define SCHED_BATCH 3: 用于非交互、CPU使用密集的批处理进程,通过完全公平调度器来处理,调度决策对此类进程给与"冷处理",它们绝不会抢占CFS调度器处理的另一个进程,因此不会干扰交互式进程,如果不打算用nice降低进程的静态优先级,同时又不希望该进程影响系统的交互性,最适合用该调度策略5) #define SCHED_IDLE 5: 可用于次要的进程,其相对权重总是最小的,也通过完全公平调度器来处理。要注意的是,SCHED_IDLE不负责调度空闲进程,空闲进程由内核提供单独的机制来处理只有root用户能通过sched_setscheduler()系统调用来改变调度策略 */unsigned int policy;/*13. cpus_allowedcpus_allowed是一个位域,在多处理器系统上使用,用于控制进程可以在哪里处理器上运行*/cpumask_t cpus_allowed;/*14. RCU同步原语 */ #ifdef CONFIG_TREE_PREEMPT_RCUint rcu_read_lock_nesting;char rcu_read_unlock_special;struct rcu_node *rcu_blocked_node;struct list_head rcu_node_entry; #endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)/*15. sched_info用于调度器统计进程的运行信息*/struct sched_info sched_info; #endif/*16. tasks通过list_head将当前进程的task_struct串联进内核的进程列表中,构建;linux进程链表*/struct list_head tasks;/*17. pushable_taskslimit pushing to one attempt */struct plist_node pushable_tasks;/*18. 进程地址空间 1) mm: 指向进程所拥有的内存描述符 2) active_mm: active_mm指向进程运行时所使用的内存描述符对于普通进程而言,这两个指针变量的值相同。但是,内核线程不拥有任何内存描述符,所以它们的mm成员总是为NULL。当内核线程得以运行时,它的active_mm成员被初始化为前一个运行进程的active_mm值*/struct mm_struct *mm, *active_mm;/*19. exit_state进程退出状态码*/int exit_state;/*20. 判断标志1) exit_codeexit_code用于设置进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)2) exit_signalexit_signal被置为-1时表示是某个线程组中的一员。只有当线程组的最后一个成员终止时,才会产生一个信号,以通知线程组的领头进程的父进程*/int exit_code, exit_signal; /*3) pdeath_signalpdeath_signal用于判断父进程终止时发送信号*/int pdeath_signal; /*4) personality用于处理不同的ABI,它的可能取值如下: enum {PER_LINUX = 0x0000,PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT,PER_LINUX_FDPIC = 0x0000 | FDPIC_FUNCPTRS,PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE,PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS |WHOLE_SECONDS | SHORT_INODE,PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS,PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE,PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS,PER_BSD = 0x0006,PER_SUNOS = 0x0006 | STICKY_TIMEOUTS,PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE,PER_LINUX32 = 0x0008,PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB,PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS, PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS, PER_IRIX64 = 0x000b | STICKY_TIMEOUTS, PER_RISCOS = 0x000c,PER_SOLARIS = 0x000d | STICKY_TIMEOUTS,PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,PER_OSF4 = 0x000f, PER_HPUX = 0x0010,PER_MASK = 0x00ff,};*/unsigned int personality;/*5) did_execdid_exec用于记录进程代码是否被execve()函数所执行*/unsigned did_exec:1;/*6) in_execvein_execve用于通知LSM是否被do_execve()函数所调用*/unsigned in_execve:1; /*7) in_iowaitin_iowait用于判断是否进行iowait计数*/unsigned in_iowait:1;/*8) sched_reset_on_forksched_reset_on_fork用于判断是否恢复默认的优先级或调度策略*/unsigned sched_reset_on_fork:1;/*21. 进程标识符(PID)在CONFIG_BASE_SMALL配置为0的情况下,PID的取值范围是0到32767,即系统中的进程数最大为32768个#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000) 在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用 返回的是当前进程的tgid值而不是pid值。*/pid_t pid;pid_t tgid;#ifdef CONFIG_CC_STACKPROTECTOR /*22. stack_canary防止内核堆栈溢出,在GCC编译内核时,需要加上-fstack-protector选项*/unsigned long stack_canary; #endif/*23. 表示进程亲属关系的成员 1) real_parent: 指向其父进程,如果创建它的父进程不再存在,则指向PID为1的init进程2) parent: 指向其父进程,当它终止时,必须向它的父进程发送信号。它的值通常与real_parent相同 */struct task_struct *real_parent; struct task_struct *parent; /*3) children: 表示链表的头部,链表中的所有元素都是它的子进程(子进程链表)4) sibling: 用于把当前进程插入到兄弟链表中(连接到父进程的子进程链表(兄弟链表))5) group_leader: 指向其所在进程组的领头进程*/struct list_head children; struct list_head sibling; struct task_struct *group_leader; struct list_head ptraced;struct list_head ptrace_entry; struct bts_context *bts;/*24. pidsPID散列表和链表 */struct pid_link pids[PIDTYPE_MAX];/*25. thread_group线程组中所有进程的链表*/struct list_head thread_group;/*26. do_fork函数 1) vfork_done在执行do_fork()时,如果给定特别标志,则vfork_done会指向一个特殊地址2) set_child_tid、clear_child_tid如果copy_process函数的clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID,则会把child_tidptr参数的值分别复制到set_child_tid和clear_child_tid成员。这些标志说明必须改变子 进程用户态地址空间的child_tidptr所指向的变量的值。*/struct completion *vfork_done; int __user *set_child_tid; int __user *clear_child_tid; /*27. 记录进程的I/O计数(时间)1) utime用于记录进程在"用户态"下所经过的节拍数(定时器)2) stime用于记录进程在"内核态"下所经过的节拍数(定时器)3) utimescaled用于记录进程在"用户态"的运行时间,但它们以处理器的频率为刻度4) stimescaled用于记录进程在"内核态"的运行时间,但它们以处理器的频率为刻度*/cputime_t utime, stime, utimescaled, stimescaled;/*5) gtime以节拍计数的虚拟机运行时间(guest time)*/cputime_t gtime;/*6) prev_utime、prev_stime是先前的运行时间*/cputime_t prev_utime, prev_stime; /*7) nvcsw自愿(voluntary)上下文切换计数8) nivcsw非自愿(involuntary)上下文切换计数*/unsigned long nvcsw, nivcsw; /*9) start_time进程创建时间10) real_start_time进程睡眠时间,还包含了进程睡眠时间,常用于/proc/pid/stat,*/struct timespec start_time; struct timespec real_start_time;/*11) cputime_expires用来统计进程或进程组被跟踪的处理器时间,其中的三个成员对应着cpu_timers[3]的三个链表*/struct task_cputime cputime_expires;struct list_head cpu_timers[3];#ifdef CONFIG_DETECT_HUNG_TASK /*12) last_switch_countnvcsw和nivcsw的总和*/unsigned long last_switch_count;#endifstruct task_io_accounting ioac; #if defined(CONFIG_TASK_XACCT)u64 acct_rss_mem1; u64 acct_vm_mem1; cputime_t acct_timexpd; #endif/*28. 缺页统计 */ unsigned long min_flt, maj_flt; /*29. 进程权能 */const struct cred *real_cred; const struct cred *cred; struct mutex cred_guard_mutex; struct cred *replacement_session_keyring; /*30. comm[TASK_COMM_LEN]相应的程序名 */char comm[TASK_COMM_LEN]; /*31. 文件 1) fs用来表示进程与文件系统的联系,包括当前目录和根目录2) files表示进程当前打开的文件*/int link_count, total_link_count; struct fs_struct *fs; struct files_struct *files;#ifdef CONFIG_SYSVIPC /*32. sysvsem进程通信(SYSVIPC)*/struct sysv_sem sysvsem; #endif/*33. 处理器特有数据*/struct thread_struct thread; /*34. nsproxy命名空间 */struct nsproxy *nsproxy; /*35. 信号处理 1) signal: 指向进程的信号描述符2) sighand: 指向进程的信号处理程序描述符*/struct signal_struct *signal;struct sighand_struct *sighand;/*3) blocked: 表示被阻塞信号的掩码4) real_blocked: 表示临时掩码*/sigset_t blocked, real_blocked;sigset_t saved_sigmask; /*5) pending: 存放私有挂起信号的数据结构*/struct sigpending pending;/*6) sas_ss_sp: 信号处理程序备用堆栈的地址7) sas_ss_size: 表示堆栈的大小*/unsigned long sas_ss_sp;size_t sas_ss_size;/*8) notifier设备驱动程序常用notifier指向的函数来阻塞进程的某些信号9) otifier_data指的是notifier所指向的函数可能使用的数据。10) otifier_mask标识这些信号的位掩码*/int (*notifier)(void *priv);void *notifier_data;sigset_t *notifier_mask;/*36. 进程审计 */struct audit_context *audit_context; #ifdef CONFIG_AUDITSYSCALLuid_t loginuid;unsigned int sessionid; #endif/*37. secure computing */seccomp_t seccomp;/*38. 用于copy_process函数使用CLONE_PARENT标记时 */u32 parent_exec_id;u32 self_exec_id;/*39. alloc_lock用于保护资源分配或释放的自旋锁 */spinlock_t alloc_lock;/*40. 中断 */ #ifdef CONFIG_GENERIC_HARDIRQS struct irqaction *irqaction; #endif #ifdef CONFIG_TRACE_IRQFLAGSunsigned int irq_events;int hardirqs_enabled;unsigned long hardirq_enable_ip;unsigned int hardirq_enable_event;unsigned long hardirq_disable_ip;unsigned int hardirq_disable_event;int softirqs_enabled;unsigned long softirq_disable_ip;unsigned int softirq_disable_event;unsigned long softirq_enable_ip;unsigned int softirq_enable_event;int hardirq_context;int softirq_context; #endif/*41. pi_locktask_rq_lock函数所使用的锁 */spinlock_t pi_lock;#ifdef CONFIG_RT_MUTEXES /*42. 基于PI协议的等待互斥锁,其中PI指的是priority inheritance/9优先级继承)*/struct plist_head pi_waiters; struct rt_mutex_waiter *pi_blocked_on; #endif#ifdef CONFIG_DEBUG_MUTEXES /*43. blocked_on死锁检测*/struct mutex_waiter *blocked_on; #endif/*44. lockdep, */ #ifdef CONFIG_LOCKDEP # define MAX_LOCK_DEPTH 48ULu64 curr_chain_key;int lockdep_depth;unsigned int lockdep_recursion;struct held_lock held_locks[MAX_LOCK_DEPTH];gfp_t lockdep_reclaim_gfp; #endif/*45. journal_infoJFS文件系统*/void *journal_info;/*46. 块设备链表*/struct bio *bio_list, **bio_tail; /*47. reclaim_state内存回收*/struct reclaim_state *reclaim_state;/*48. backing_dev_info存放块设备I/O数据流量信息*/struct backing_dev_info *backing_dev_info;/*49. io_contextI/O调度器所使用的信息 */struct io_context *io_context;/*50. CPUSET功能 */ #ifdef CONFIG_CPUSETSnodemask_t mems_allowed; int cpuset_mem_spread_rotor; #endif/*51. Control Groups */ #ifdef CONFIG_CGROUPS struct css_set *cgroups; struct list_head cg_list; #endif/*52. robust_listfutex同步机制 */ #ifdef CONFIG_FUTEXstruct robust_list_head __user *robust_list; #ifdef CONFIG_COMPATstruct compat_robust_list_head __user *compat_robust_list; #endifstruct list_head pi_state_list;struct futex_pi_state *pi_state_cache; #endif #ifdef CONFIG_PERF_EVENTSstruct perf_event_context *perf_event_ctxp;struct mutex perf_event_mutex;struct list_head perf_event_list; #endif/*53. 非一致内存访问(NUMA Non-Uniform Memory Access)*/ #ifdef CONFIG_NUMAstruct mempolicy *mempolicy; /* Protected by alloc_lock */short il_next; #endif/*54. fs_excl文件系统互斥资源*/atomic_t fs_excl;/*55. rcuRCU链表 */ struct rcu_head rcu;/*56. splice_pipe管道*/struct pipe_inode_info *splice_pipe;/*57. delays延迟计数*/ #ifdef CONFIG_TASK_DELAY_ACCTstruct task_delay_info *delays; #endif/*58. make_it_failfault injection*/ #ifdef CONFIG_FAULT_INJECTIONint make_it_fail; #endif/*59. dirtiesFLoating proportions */struct prop_local_single dirties;/*60. Infrastructure for displayinglatency */ #ifdef CONFIG_LATENCYTOPint latency_record_count;struct latency_record latency_record[LT_SAVECOUNT]; #endif/*61. time slack values,常用于poll和select函数 */unsigned long timer_slack_ns;unsigned long default_timer_slack_ns;/*62. scm_work_listsocket控制消息(control message)*/struct list_head *scm_work_list;/*63. ftrace跟踪器*/ #ifdef CONFIG_FUNCTION_GRAPH_TRACER int curr_ret_stack; struct ftrace_ret_stack *ret_stack; unsigned long long ftrace_timestamp; atomic_t trace_overrun; atomic_t tracing_graph_pause; #endif #ifdef CONFIG_TRACING unsigned long trace; unsigned long trace_recursion; #endif };