什么是进程?什么是task_struct?
首先站在用户的角度来看:进程就是运行中的程序,这是一个比较抽象化的概念。
站在操作系统的角度来看:进程就是操作系统对进程的描述,这个描述信息就是对进程的具象化描述- - -这个描述信息就是进程,这个描述信息就是pcb(process control block)- - -进程控制块或者进程描述符。在linux下就是task_struct这个结构体。
在创建新进程时,Linux就从系统内存中分配一个task_struct结构,并把它的首地址加入 task 数组中,这个 task 数组是系统中的一个向量数组,该数组的长度默认是 512B,该数组的元素是指向task_struct的。
在《操作系统精髓与设计原理》这本书中对进程作了如下定义:
- 一个正在执行中的程序
- 一个正在计算机上执行的程序实例
- 能分配给处理器并由处理器处理的实体
- 一个具有以下特征的活动单元:1、一组指令序列的执行。2、一个当前状态和相关的系统资源集合
那么 task_struct 中都有什么内容呢?
- 标识符:描述本进程的唯一标识符,用于区别其他进程
- 状态:任务状态,退出信号,退出代码等
- 优先级:相对于其他进程的优先级
- 程序计数器:程序中即将被执行的下一条指令的地址
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据
- IO状态信息:包括显示的 I/O 请求,分配给进程的 I/O 设备和被进程使用的文件列表。
- 记账信息(审计信息):可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 那么还有其他的信息如下介绍:
调度数据成员
//进程的状态,能否执行(-1, 0, >1)
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
volatile这个关键字是保持内存可见性,防止代码过度优化,就是state变量从内存当中读取数据,而不是将从寄存器中读取,这也是为了保证对操作系统状态实时访问的稳定性。
/* 状态取值
192 * Task state bitmask. NOTE! These bits are also
193 * encoded in fs/proc/array.c: get_task_state().
194 *
195 * We have two separate sets of flags: task->state
196 * is about runnability, while task->exit_state are
197 * about the task exiting. Confusing, but this way
198 * modifying one set can't modify the other one by
199 * mistake.
200 */
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_PARKED 512
#define TASK_STATE_MAX 1024
/* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
/* Convenience macros for the sake of wake_up */
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
#define TASK_ALL (TASK_NORMAL | __TASK_STOPPED | __TASK_TRACED)
//用于自定义调度程序行为
unsigned int flags; /* per process flags, defined below */
/*Per process flags 进程标志有以下标志*/
#define PF_EXITING 0x00000004 /* getting shut down */
#define PF_EXITPIDONE 0x00000008 /* pi exit done on shut down */
#define PF_VCPU 0x00000010 /* I'm a virtual CPU */
#define PF_WQ_WORKER 0x00000020 /* I'm a workqueue worker */
#define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */
#define PF_MCE_PROCESS 0x00000080 /* process policy on mce errors */
#define PF_SUPERPRIV 0x00000100 /* used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* dumped core */
#define PF_SIGNALED 0x00000400 /* killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_NPROC_EXCEEDED 0x00001000 /* set_user noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH 0x00002000 /* if unset the fpu must be initialized before use */
#define PF_USED_ASYNC 0x00004000 /* used async_schedule*(), used by module init */
#define PF_NOFREEZE 0x00008000 /* this thread should not be frozen */
#define PF_FROZEN 0x00010000 /* frozen for system suspend */
#define PF_FSTRANS 0x00020000 /* inside a filesystem transaction */
#define PF_KSWAPD 0x00040000 /* I am kswapd */
#define PF_MEMALLOC_NOIO 0x00080000 /* Allocating memory without IO involved */
#define PF_LESS_THROTTLE 0x00100000 /* Throttle me less: I clean memory */
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
#define PF_RANDOMIZE 0x00400000 /* randomize virtual address space */
#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */
#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_allowed */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MEMPOLICY 0x10000000 /* Non-default NUMA mempolicy */
#define PF_MUTEX_TESTER 0x20000000 /* Thread belongs to the rt mutex tester */
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000 /* this thread called freeze_processes and should not be frozen */
unsigned int policy; //该进程的调度策略
调度策略有:
SCHED_OTHER 0 非实时进程,基于优先权的轮转法(round robin)。
SCHED_FIFO 1 实时进程,用先进先出算法。
SCHED_RR 2 实时进程,用基于优先权的轮转法。
进程标识符
pid_t pid; //进程标识符
pid_t tgid; //线程组标识符
tgid是用于表示线程组的标识符,它等于主线程的id,主线程的id也就是这个进程的id。
表示进程间亲属关系的成员
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct task_struct *group_leader; /* threadgroup leader */
real_parent 指向其父进程,如果父进程不存在,则指向1号 init 进程;
parent 是父进程,是接受SIGCHLD信号的,也就是回收子进程资源的进程;
children 链表中都是它的子进程;
sibling 把当前链表插入兄弟链表中
group_leader 指向线程组的首位。
进程地址空间
struct mm_struct *mm, *active_mm;
struct mm_struct *mm, *active_mm;
#ifdef CONFIG_COMPAT_BRK
unsigned brk_randomized:1;
#endif
#if defined(SPLIT_RSS_COUNTING)
struct task_rss_stat rss_stat;
mm 指向进程所拥有的内存描述符;
active_mm指向进程运行时所使用的内存描述符;
对于普通进程而言,这两个指针的值相同;
对于内核线程而言,它们不拥有任何内存描述符,所以它们的mm总为NULL,当内核线程运行时,它的active_mm初始化为前一个active_mm的值。
信号处理
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked, real_blocked;
sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
signal 指向信号描述符
sighand 指向信号处理程序描述符
blocked 表示被阻塞信号的掩码
real_blocked 表示临时掩码
pending 中存放未决信号(未被处理的信号)
sas_ss_sp 是信号处理程序备用堆栈的地址
sas_ss_size 表示堆栈的大小
设备驱动程序常用notifier指向的函数来阻塞进程的某些信号。(notifier_data是可能用到的数据,notifier_mask是信号的位掩码)
ptrace系统调用
gdb调试中底层就是使用 ptrace 来完成的。
unsigned int ptrace; //追踪
/*
* ptraced is the list of tasks this task is using ptrace on.
* This includes both natural children and PTRACE_ATTACH targets.
* p->ptrace_entry is p's link on the p->parent->ptraced list.
*/
struct list_head ptraced;
struct list_head ptrace_entry;
unsigned long ptrace_message;
siginfo_t *last_siginfo; /* For ptrace use. */
#ifdef CONFIG_HAVE_HW_BREAKPOINT
atomic_t ptrace_bp_refcnt;
#endif
其他数据成员
fpu_counter包含使用FPU的连续上下文切换的数量。
(1) unsigned char fpu_counter;
进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态。
(2) atomic_t usage;
用于表示获取大内核锁的次数,如果进程未获得过锁,则置为-1。
(3) int lock_depth; /* BKL lock depth */
用于管理资源分配以及释放的自旋锁。
(4) /* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,
mempolicy */
spinlock_t alloc_lock;
用于调度器统计进程的运行信息。
(5)
#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
struct sched_info sched_info;
#endif
命名空间
(6)/* namespaces */
struct nsproxy *nsproxy;
死锁检测
(7)#ifdef CONFIG_DEBUG_MUTEXES
/* mutex deadlock detection */
struct mutex_waiter *blocked_on;
#endif
内存回收
(8)struct reclaim_state *reclaim_state;
I/O调度器所使用的信息
(9)struct io_context *io_context;
存放块设备I/O数据流量信息
(10)struct backing_dev_info *backing_dev_info;
PS:本文是基于CentOS Linux release 7.3.1611版本。task_struct中还有很多其他信息,本文没有讲到,可以自行查看。