task_struct 构造描述

在 Linux 中每个历程都是由一个 task_struct 构造来停止描述的。凡是我们常说的 PBC (历程控造块)就是指 task_struct。

task-struct 构造包罗了历程的所有信息,它是系统对历程停止控造的有效手段。

task_struct 构造停止详细描述如下

struct task_struct { /* 历程施行时,它会按照详细情况改动形态,Linux 中的历程次要有如下形态 TASK_RUNNING 可运行 TASK_INTERRUPTIBLE 可中断的期待形态 TASK_UNINTERRUPTIBLE 不成中断的期待形态 TASK_ZOMBIE 僵死 TASK_STOPPED 暂停 */ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ unsigned long flags; /* per PRocess flags, defined below */ int sigpending; mm_segment_t addr_limit; /* thread address space: 0-0xBFFFFFFF for user-thead 0-0xFFFFFFFF for kernel-thread */ struct exec_domain *exec_domain; volatile long need_resched; unsigned long ptrace; //上下文切换时内核锁的深度 int lock_depth; /* Lock depth */ long counter; //历程剩余的时间片 long nice; /*历程调度战略,有3种 SCHED_OTHER 其他调度, 通俗历程调度战略 SCHED_FIFO 先来先办事调度 实时历程调度战略 SCHED_RR 时间片轮转调度 实时历程调度战略 */ unsigned long policy; //描述历程的地址空间 struct mm_struct *mm; int processor; //历程当前正在利用的CPU struct list_head run_list; //运行队列的链表 unsigned long sleep_time; //历程在双向轮回链表中的链接 struct task_struct *next_task, *prev_task; /* active_mm,那是为内核线程而引入的。因为内核线程没有本身的地址空间,为了让内核线程与通俗历程具有同一的上下文 切换体例,当内核线程停止上下文切换时,让切换进来的线程的active_mm 指向刚被调度进来的历程的 active_mm(若是历程的mm 域不为空,则其active_mm 域与mm 域不异)。 */ struct mm_struct *active_mm; //内核线程所借用的地址空间 struct list_head local_pages; unsigned int allocation_order, nr_local_pages;/* task state */ //指向历程所属的全局施行文件格局构造,共有a.out、script、elf、java 等4 种 struct linux_binfmt *binfmt; //法式的返回代码以及法式异常末行产生的信号,那些数据由父历程(子历程完成后)轮流查询 int exit_code, exit_signal; int pdeath_signal; /* The signal sent when the parent dies */ /* ??? */ unsigned long personality; //按POSIX 要求设想的布尔量,区分历程正在施行老法式代码,仍是用系统挪用execve()拆入一个新的法式 int did_exec:1; pid_t pid; //历程标识符 /* boolean value for session group leader */ int leader; /* * pointers to (original) parent process, YOUngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->p_pptr->pid) */ /*\ p_opptr Original parent 祖先 p_pptr Parent 父历程 p_cptr Child 子历程 p_ysptr Younger sibling 弟历程 p_osptr Older sibling 兄历程 */ struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct list_head thread_group; /* PID hash table linkage. */ //历程在哈希表中的链接 struct task_struct *pidhash_next; struct task_struct **pidhash_pprev; /*在历程完毕时,或发出系统挪用wait4 时,为了期待子历程的完毕,而将本身(父历程) 睡眠在该期待队列上,设置形态标记为TASK_INTERRUPTIBLE,而且把控造权转给调度法式*/ wait_queue_head_t wait_chldexit; /* for wait4() */ struct completion *vfork_done; /* for vfork() */ unsigned long rt_priority; //实时优先级 /*历程有3 品种型的按时器: 实时按时器: 实时更新,即不管该历程能否运行 it_real_value、it_real_incr、real_timer 虚拟按时器: 只在历程运行于用户态时更新 it_virt_value、it_virt_incr 概略按时器: 历程运行于用户态和系统态时更新 it_prof_value、it_prof_incr */ unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; struct tms times; unsigned long start_time; //历程创建时间 /* per_cpu_utime 历程在某个CPU 上运行时在用户态下消耗的时间 per_cpu_stime 历程在某个CPU 上运行时在系统态下消耗的时间 */ long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ /* min_flat,maj_flt,nswap 历程累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数 cmin_flat,cmaj_flt,cnswap 本历程做为祖先辈程,其所有条理子历程的累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数 */ unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; //历程占用的内存页面能否可换出/* process credentials */ /* Uid、gid 用户标识符、组标识符 Euid、egid 有效用户标识符、有效组标识符 Suid、sgid 备份用户标识符、备份组标识符 Fsuid、fsgid 文件系统用户标识符、文件系统组标识符 */ uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; .../* limits */ //每一个历程能够通过系统挪用setlimit 和getlimit 来限造它资本的利用 struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; //那个域存储历程施行的法式的名字,那个名字用在调试中 char comm[16]; struct sem_undo *semundo; //为制止死锁而在信号量上设置的打消操做 struct sem_queue *semsleeping; //与信号量操做相关的期待队列/* CPU-specific state of this task */ struct thread_struct thread;/* filesystem information */ struct fs_struct *fs; //历程的可施行映像所在的文件系统/* open file information */ struct files_struct *files; //历程翻开的文件/* signal handlers */ //信号掩码的自旋锁 spinlock_t sigmask_lock; /* Protects signal and blocked */ struct signal_struct *sig; //信号处置函数 sigset_t blocked; //信号掩码 ...};

历程就是处于施行期的法式,但历程不单单局限于一段可施行法式代码(也就是所谓的代码段,text section),从上面的数据构造能够看到,历程还包罗其他的资本,好比翻开的文件,挂起的信号,处置器形态,内核数据构造,内存映射地址空间等。

在操做系统中,内核的调度对象时线程,而不是历程。线程时历程中的活动对象。每个线程都拥有一个独立的法式计数器、历程栈和一组历程程存放器。

在传统的 Unix 系统中,一个历程只包罗一个线程,但是在现代操做系统中,一个历程能够包罗多个线程。在 Linux 系统中线程的实现十分出格:它对线程和历程其实不出格区分。关于 Linux 而言,线程只不外是一种特殊的历程罢了。后续的文章专门介绍历程和线程的创建过程停止阐发。

在操做系统中,内核把历程的列表存放在一个叫使命队列的双向轮回链表中,链表中的每个元素类型就是上述的数据构造 task_struct, 称为历程描述符的构造。该构造中包罗了详细历程的所有信息。task_struct 在32位机器上,大约有1.7KB的大小。

task_struct 构造在内存中的存放

在阐发之前,需要领会下一个概念 -- 内核栈。

我们晓得一个在32系统中,历程的虚拟地址空间大小为4G。在那4G虚拟机造空间中有一段虚拟地址空间为栈的区域,该栈的区域为用户态栈。该栈记录的是在用户态历程的函数挪用过程。

Linux 进程管理之基础知识  第1张

但是当历程停止系统挪用时进入内核态时,在内核中利用的栈不再是上述的用户态的栈了,而是零丁的内核空间的栈,称为内核栈。每个历程都有各自的内核栈。该栈是在历程创建时生成的。

当历程从用户态进入内核态时,CPU 就主动地设置该历程的内核栈,也就是说,CPU 从使命形态段 TSS 中拆入内核栈指针 esp。

在 Intel 系统中,栈起始于末端,并朝那个内存区起头的标的目的增长。从用户态刚切换到内核态以后,历程的内核栈老是空的,后续有数据起头写入栈中时,esp 的值就递加。

内核栈在差别的版本中暗示的也不大不异,在2.4版本中内核栈空间和 task_struct 构造时存放到一块的。

//2.4内核栈暗示体例union task_union {struct task_struct task;unsigned long stack[INIT_TASK_SIZE/sizeof(long)];};

从那个构造能够看出,内核栈占8KB 的内存区。现实上,历程的task_struct 构造所占的内存是由内核动态分配的,更切当地说,内核底子不给 task_struct 分配内存,而仅仅给内核栈分配8KB 的内存,并把此中的一部门给 task_struct 利用。

task_struct 构造大约占1K 字节摆布,其详细数字与内核版本有关,因为差别的版本其域稍有差别。因而,内核栈的大小不克不及超越7KB,不然,内核栈会笼盖 task_struct 构造,从而招致内核瓦解。不外,7KB 大小对内核栈已足够。

把 task_struct 构造与内核栈放在一路具有以下益处:

内核能够便利而快速地找到那个构造,用伪代码描述如下:task_struct = (struct task_struct *) STACK_POINTER & 0xffffe000制止在创建历程时动态分配额外的内存。task_struct 构造的起始地址老是起头于页大小(PAGE_SIZE)的鸿沟。

内核栈的散布图如下:

Linux 进程管理之基础知识  第2张

获取当前历程指针时,操做如下:

static inline struct task_struct * get_current(void){struct task_struct *current;__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));return current;}#define current get_current()

现实上,那段代码相当于如下一组汇编指令(设p 是指向当前历程task_struc 构造的指针):

movl $0xffffe000, %ecxandl %esp, %ecxmovl %ecx, p

换句话说,仅仅只需查抄栈指针的值,而底子无需存取内存,内核就能够导出

task_struct 构造的地址。

在2.6以前,各个历程的 task_struct 存放在他们内核栈的尾端。如许做的目标是为了让那些像X86那样存放器较少的硬件系统构造只要通过栈指针就能计算出它的位置,而制止利用额外的存放器专门记录。

而在2.6中利用的 slab 分配器动态的生成 task_struct 构造,所以只需在栈底(关于向下增长的栈而言)或栈顶(关于向上增长的栈而言)创建一个新的数据构造 thread_info。那个新建的数据构造在汇编代码入彀算器偏移量变得十分容易。

其构造如下:

//2.6 内核栈暗示体例union thread_union {struct thread_info thread_info;unsigned long stack[THREAD_SIZE/sizeof(long)];};thread_info 构造如下:struct thread_info {struct task_struct *task; /* main task structure */__u32 cpu; /* current CPU */...};

在2.6中的 task_struct 中,存在一个stack 字段指向 thread_info

struct task_struct {volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */void *stack; // 指向 thread_info 的指针...}

内核栈的散布图如下:

Linux 进程管理之基础知识  第3张

当获取当前历程信息时,可通过 current_thread_info() 获取。#define get_current() (current_thread_info()->task)#define current get_current()

在X86系统上,current 都是把栈指针的后13个有效位屏障掉,用来计算偏移停止获取的。,汇编代码如下:

movl $-8192, %eaxandl %esp, %eax

最初,current 再从 thread_info 的 task 域中提取并返回 task_struct 的地址

current_thread_info()->task

历程形态

在 task_struct 中 state 描述了历程的当前形态。系统中的每个历程必然处于如下5中形态中的一种,因而 state 也必为如下5种形态标记之一。

TASK_RUNNING(可运行): 历程是可施行的,或者正在施行,或者在运行队列中期待施行。正在运行的历程就是当前历程(由 current 所指向的历程),而筹办运行的历程只要得到CPU 就能够立即投入运行,CPU 是那些历程独一期待的系统资本。系统中有一个运行队列,用来包容所有处于可运行形态的历程,调度法式施行时,从中选择一个历程投入运行TASK_INTERRUPTIBLE(可中断): 历程正在睡眠(阻塞),在期待某些前提的达成,一旦那些前提达成,内核就把历程形态设置为可运行。处于此形态的历程也会因为领受到信号而提早被唤醒并随时筹办投入运行。TASK_UNINTERRUPTIBLE(不成中断):除了就算是领受到信号也不会被唤醒或者筹办投入运行外,那个形态与可打断形态不异。该形态凡是在历程必需在期待时不受干扰或者期待事务很快就会发作时呈现。因为处于此形态的使命对信号不做响应,所以较之可中断形态,利用的较少。TASK_ZOMBIE(僵死):历程固然已经末行,但因为某种原因,父历程还没有施行wait()系统挪用,末行历程的信息也还没有收受接管。望文生义,处于该形态的历程就是死历程,那种历程现实上是系统中的垃圾,必需停止响应处置以释放其占用的资本。TASK_STOPPED(暂停):历程停行施行,历程没有投入运行也不克不及投入运行。凡是那种形态发作在领受到 SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 等信号的时候,此外,在调试期间领受到的任何信号,城市是历程进入那种形态。

形态之间的切换关系如图:

Linux 进程管理之基础知识  第4张

本文只介绍了历程的内核根底常识,后续通过源码进一步阐发相关历程办理的常识。