Linux进程分析

1. 介绍

进程是系统进行资源分配的基本单位,而线程则是系统调度和执行的基本单位;通常认为,一个进程包含一个或多个线程

进程是程序的一个执行过程,程序是一个静态文件存储在计算机系统的硬盘等存储空间中,而进程则是处于动态条件下由操作系统维护的系统资源管理实体。

09194625-3402ef9dae8b4fa1a46657ac23e82505 

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;
struct task_struct *parent;
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;

表示进程亲属关系的成员:
real_parent指向其父进程, 若创建它的父进程不再存在, 则指向init进程。
parent指向其父进程, 当它终止时, 必须向它的父进程发送信号. 通常与real_parent相同
children表示链表的头部, 链表中的所有元素都是它的子进程。
sibling用于把当前进程插入到兄弟链表中
group_leader指向其所在进程组的领头进程

unsigned int ptrace;
struct list_head ptraced;
struct list_head ptrace_entry;
unsigned long ptrace_message;
siginfo_t *last_siginfo;

用于ptrace系统调用:
成员ptrace表明是否需要被被跟踪(0不被跟踪)
它的可能取值如PT_PTRACED等

int prio, static_prio, normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
unsigned int policy;
cpumask_t cpus_allowed;

用于进程调度
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;
struct fs_struct *fs;
struct files_struct *files;

fs用来表示进程与文件系统相关的信息
files表示进程当前打开的文件

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;

12214436-0690267d0c8441f6ac5a505720107dc6

内核提供了一些宏来操作进程

/* 获取下一个进程 */
#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进程管理剖析>

原文地址:https://www.cnblogs.com/hzl6255/p/2843415.html