Linux进程分析
1. 介绍
进程是系统进行资源分配的基本单位,而线程则是系统调度和执行的基本单位;通常认为,一个进程包含一个或多个线程
进程是程序的一个执行过程,程序是一个静态文件存储在计算机系统的硬盘等存储空间中,而进程则是处于动态条件下由操作系统维护的系统资源管理实体。
2. 数据结构
在Linux中,用struct task_struct来表示一个进程,通常称为进程控制块
task_struct,称为进程控制块,表示一个进程,主要数据结构如下
成员 | 作用 |
char comm[TASK_COMM_LEN] | 程序名 |
volatile long state; int exit_state; | 进程状态, 对于state, 值为TASK_RUNNING,TASK_INTERRUPTIBLE等 当进程释放资源时, exit_state有效, 其值为EXIT_ZOMBIE, EXIT_DEAD |
pid_t pid | 进程标识符, 用于标识一个进程 |
pid_t tgid | 线程组的主进程号 |
void *stack | 指向进程内核栈, struct thread_info (check thread_union) |
unsigned int flags | 进程的标记, 如PF_STARTING |
struct task_struct *real_parent; | 表示进程亲属关系的成员: real_parent指向其父进程, 若创建它的父进程不再存在, 则指向init进程。 parent指向其父进程, 当它终止时, 必须向它的父进程发送信号. 通常与real_parent相同 children表示链表的头部, 链表中的所有元素都是它的子进程。 sibling用于把当前进程插入到兄弟链表中 group_leader指向其所在进程组的领头进程 |
unsigned int ptrace; | 用于ptrace系统调用: 成员ptrace表明是否需要被被跟踪(0不被跟踪) 它的可能取值如PT_PTRACED等 |
int prio, static_prio, normal_prio; | 用于进程调度 static_prio用于保存静态优先级,可以通过nice系统调用来进行修改。 rt_priority用于保存实时优先级。 normal_prio的值取决于静态优先级和调度策略。 prio用于保存动态优先级。 policy表示进程的调度策略 sched_class结构体表示调度类 se和rt都是调用实体: 一个用于普通进程, 一个用于实时进程 cpus_allowed用于控制进程可以在哪里处理器上运行。 |
struct mm_struct *mm, *active_mm | 进程地址空间 mm指向进程所拥有的内存描述符 active_mm指向进程运行时所使用的内存描述符 - 对于普通进程而言, 这两个指针变量的值相同 - 对于内核线程, 它们的mm成员总是为NULL |
int link_count, total_link_count; | fs用来表示进程与文件系统相关的信息 |
3. 进程分类
首先,Linux实际上是并不区分进程和线程,都是基于task_struct来描述和调度,只是两者只是对资源的占用不同;用户线程和用户进程的区别在于是否拥有独立的数据段和堆栈
Linux下分为内核线程,轻量级进程及用户线程三种线程
内核线程: 内核线程通常完成内核特定的任务, 其调度由内核负责, 运行在内核态, 执行的是内核中的函数
轻量级进程: 也称LWP, 是一种由内核支持的用户线程,它是基于内核线程的高级抽象;每个LWP由一个内核线程支持
用户线程: 指不需要内核支持而完全建立在用户空间的线程库,用户线程的建立、同步、销毁及调度完全在用户空间完成
TIP: 用ps命令查看,名称中加"[]"的进程,就是内核线程
用户线程/POSIX线程在Linux上的实现有LinuxThreads和NPTL
4. 进程组织
在linux系统中,所有进程都是由init进程派生而来;init进程的进程描述符由init_task静态生成.
4.1 进程链表
进程关系是由双向链表tasks组织成的,链表的头和尾都为init_task
struct list_head tasks;
内核提供了一些宏来操作进程
/* 获取下一个进程 */ #define next_task(p) \ list_entry_rcu((p)->tasks.next, struct task_struct, tasks) /* 便利进程链表 */ #define for_each_process(p) \ for (p = &init_task; (p = next_task(p)) != &init_task; )
4.2 运行队列
Linux将处于可运行状态(即在TASK_RUNNING状态)的进程组织成运行队列
队列的标志有两个:一个是"空闲进程" idle_task、一个是队列的长度nr_running
nr_running,也就是系统中处于可运行状态(TASK_RUNNING)的进程数目
若nr_running为0,就表示队列中只有空闲进程
5. 进程调度
在Linux中,进程调度由schedule()来触发
schedule
__schedule
context_switch
switch_to
__switch_to
5.1 进程状态
通常来说有如下四种进程状态
- TASK_RUNNING: 可运行状态, 处于该状态的进程可以被调度执行而成为当前进程
- TASK_INTERRUPTIBLE: 可中断的睡眠状态, 可在资源有效、或通过信号或定时中断唤醒
- TASK_UNINTERRUPTIBLE: 不可中断的睡眠状态, 处于该状态的进程仅当所需资源有效时被唤醒
- TASK_DEAD: 死亡状态, 进程结束且已释放资源, 但其task_struct仍未释放
5.2 调度方式
Linux采用"有条件的可剥夺"调度方式
对于普通进程,当其时间片结束时,调度程序挑选出下一个处于TASK_RUNNING状态的进程作为当前进程
对于实时进程,若其优先级足够高,则会从当前的运行进程中抢占CPU成为新的当前进程
5.3 调度策略
调度策略用来区分普通进程和实时进程,以满足实时进程的要求
Linux有三种调度策略,由成员变量policy来指定:
@1 SCHED_NORMAL: 面向普通进程, 分时调度策略, 是进程的默认的调度策略
@2 SCHED_FIFO: 先到先服务调度策略, 是一种实时调度策略, 用于运行所需时间比较短的实时进程
@3 SCHED_RR: 时间片轮转方式, 实时调度策略, 用于运行所需时间比较长的实时进程
@4 SCHED_BATCH: 主要用于批处理进程
进程的调度策略从父进程那里继承,也可以通过sched_setscheduler()函数来修改
5.4 进程优先级
Linux进程优先级包括动态优先级、静态优先级 、实时优先级
参考:
<Linux进程管理剖析>