一、什么是进程
进程是指计算机已运行的程序。程序本身只是指令、数据及其组织形式的描述。进程就是一个程序的执行实例,也就是正在执行的程序。在linux操作系统的中,进程就是一个担当分配系统资源CPU时间、内存的实体。进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态之间的转换等功能。进程在运行中不断地改变其运行状态。一个进程在运行期间,不断地从一种状态转换到另一种状态,它可以多次处于就绪状态和执行状态,也可以多次处于阻塞状态。
目前,只有CPU的资源管理器能主动发起调度,而其他资源像内存,网络,显卡等资源管理器都是提供申请服务调度的,所以要必须理解我们常说的进程调度都是指调度CPU。
进程发展史的主要内容:
-
单道批处理系统时期:早期的计算机只支持运行单个程序,从磁带或卡片读入程序,由操作员操作控制运行。这种系统被称为单道批处理系统,没有多个程序共同运行的概念,也没有进程的概念。
-
多道批处理系统时期:20世纪60年代早期,随着计算机技术的发展,多道批处理操作系统应运而生,允许多个程序同时进入内存并在CPU上运行。这里的每个程序称为一个作业(Job),但是没有进程的概念。
-
分时操作系统时期:20世纪60年代中期,分时操作系统应运而生,引入了多用户的概念,这为进程的发展铺平了道路。在分时系统中,操作系统可以运行多个进程并为它们提供时间片,每个进程只有在它的时间片内获得CPU的控制权。这一时期UNIX系统的开发者对进程进行了深入的研究。
-
多任务操作系统时期:20世纪80年代,多任务操作系统开始出现,这种操作系统允许多个进程同时运行,每个进程都有自己的地址空间、寄存器和堆栈等。操作系统必须管理和控制各个进程的运行状态,这为进程的实现和管理提供了更大的灵活性和可控性。
-
现代操作系统时期:21世纪以来,随着计算机技术的不断发展,操作系统和进程的概念也在不断更新和完善。现代操作系统支持多核处理器、多线程和分布式系统等技术,允许进程在不同的计算机上运行,进一步增强了进程的灵活性和可扩展性。同时,在容器技术的发展下,进程管理变得更加高效和简单,如Docker等。
二、进程的生命周期
Linux操作系统是一个多任务操作系统。 系统中的各个进程可以分时复用CPU时间片,通过有效的进程调度策略,实现多任务并行执行。进程并不总是可以立即运行。有时候它必须等待来自外部信号源、不受其控制的事件,例如在文本编辑器中等待键盘输入。在事件发生之前,进程无法运行。进程可能有以下几种状态:
1.创建状态:创建新进程。
2.等待状态:进程能够运行,但没有得到许可,因为CPU分配给另一个进程。调度器可以在下一次任务切换时选择该进程。
3.运行状态:进程正在cpu中执行。
4.睡眠状态:进程正在睡眠无法运行,因为它在等待一个外部事件。
5.终止状态:进程消亡。
系统将所有进程保存在一个进程表中,无论其状态是运行、睡眠或等待。但睡眠进程会特别标记出来,调度器会知道它们无法立即运行。睡眠进程会分类到若干队列中,因此它们可在适当的时间唤醒,例如在进程等待的外部事件已经发生时。详细的进程状态间关系图如下所示:
Linux内核在include/linux/sched.h文件中也为进程定义了多种状态:
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
其中比较常用的就是下面的5种:
- TASK_RUNNING(可运行状态):对应上图的等待状态或者运行状态。它是指进程处于可运行的状态,或许正在运行,或许在就绪队列(本书中也称为调度队列)中等待运行。
- TASK_INTERRUPTIBLE(可中断睡眠态):对应上图的睡眠状态。进程进入睡眠状态(被阻塞)来等待某些条件的达成或者某些资源的就位,一旦条件达成或者资源就位,内核就可以把进程的状态设置成TASK_RUNNING并将其加入就绪队列中。也有人将这个状态称为浅睡眠状态。
- TASK_UNINTERRUPTIBLE(不可中断态):对应上图的睡眠状态。这个状态和上面的TASK_INTERRUPTIBLE状态类似,唯一不同的是,进程在睡眠等待时不受干扰,对信号不做任何反应,所以这个状态称为不可中断态。通常使用ps命令看到的被标记为D状态的进程,就是处于不可中断态的进程,不可以发送SIGKILL信号使它们终止,因为它们不响应信号。也有人把这个状态称为深度睡眠状态。
- EXIT_ZOMBIE(僵尸态):对应上图的终止状态。进程已经消亡,但是task_struct数据结构还没有释放,这个状态叫作僵尸态。
- __TASK_STOPPED(终止态):对应上图的终止状态。进程停止运行。task_struct中的资源包括task_struct数据已经释放完了。
三、进程优先级
并非所有进程都具有相同的重要性。除了大家熟悉的进程优先级之外,进程还有不同的关键度类别,以满足不同需求。首先要知道linux的进程分为以下几种,每一种的优先级都是不一样的:
1.期限进程:优先级为-1。优先级最高。
2.实时进程:优先级1-99,优先级数值越大便是优先级越高。
3.普通进程:优先级为100-139,优先级数值越小便是优先级越高。可以通过nice值改变普通进程优先级。
4.空闲进程:优先级为140。优先级最低。
四、进程表示
Linux内核涉及进程和程序的所有算法都围绕一个名为 task_struct 的数据结构建立,该结构定义在 include/linux/sched.h 中。这是系统中主要的一个结构。task_struct 包含很多成员,将进程与各个内核子系统联系起来,我们看看代码:
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;//把thread_info从栈中抽出来,方便current宏的实现
#endif
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state; //表示进程的状态标志
/*
* This begins the randomizable portion of task_struct. Only
* scheduling-critical items should be added above here.
*/
randomized_struct_fields_start
void *stack;//指向内核栈的指针
refcount_t usage;//进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态
/* Per task flags (PF_*), defined further below: */
unsigned int flags;//标记,表示进程的类型
unsigned int ptrace;
#ifdef CONFIG_SMP
struct llist_node wake_entry;
int on_cpu;
#ifdef CONFIG_THREAD_INFO_IN_TASK
/* Current CPU: */
unsigned int cpu;//当前运行的cpuid
#endif
unsigned int wakee_flips;
unsigned long wakee_flip_decay_ts;
struct task_struct *last_wakee;
/*
* recent_used_cpu is initially set as the last CPU used by a task
* that wakes affine another task. Waker/wakee relationships can
* push tasks around a CPU where each wakeup moves to the next one.
* Tracking a recently used CPU allows a quick search for a recently
* used CPU that may be idle.
*/
int recent_used_cpu;//上一次调度时使用的cpuid
int wake_cpu;
#endif
int on_rq;
int prio;//进程的当前优先级。一般等于normal_prio,但是当进程A占有互斥锁并且进程B在等待锁的时候,会把A进程的优先级临时提高
int static_prio;//普通进程的静态优先级,就是120+nice,若是限期进程或者实时进程则为0
int normal_prio;//普通进程的正常优先级,一般等于static_prio,若是限期进程则为-1,若是实时进程,则为99-rt_priority
unsigned int rt_priority;//实时进程的优先级,数值越大优先级越高,如果是限期进程或者普通进程,则为0
const struct sched_class *sched_class;//进程的调度类
struct sched_entity se;//普通进程的调度实体
struct sched_rt_entity rt;//实时进程的调度实体
#ifdef CONFIG_CGROUP_SCHED
struct task_group *sched_task_group;//指向进程所在的cgroup
#endif
struct sched_dl_entity dl;//deadline进程的调度实体
#ifdef CONFIG_UCLAMP_TASK
/* Clamp values requested for a scheduling entity */
struct uclamp_se uclamp_req[UCLAMP_CNT];
/* Effective clamp values used for a scheduling entity */
struct uclamp_se uclamp[UCLAMP_CNT];
#endif
#ifdef CONFIG_PREEMPT_NOTIFIERS
/* List of struct preempt_notifier: */
struct hlist_head preempt_notifiers;
#endif
#ifdef CONFIG_BLK_DEV_IO_TRACE
unsigned int btrace_seq;
#endif
unsigned int policy;//进程调度策略
int nr_cpus_allowed;//该进程允许使用的cpu的数量
const cpumask_t *cpus_ptr;//表示该进程允许在哪个cpu上运行
cpumask_t cpus_mask;//表示该进程不允许在哪个cpu上运行
...
struct sched_info sched_info;//记录进程运行时的实时信息
struct list_head tasks;//进程链表
#ifdef CONFIG_SMP
struct plist_node pushable_tasks;
struct rb_node pushable_dl_tasks;
#endif
struct mm_struct *mm;//指向内存描述符,如果是内核进程,则为空
struct mm_struct *active_mm;//指向内存描述符,如果是内核进程,则指向从进程借用的内存描述符
/* Per-thread vma caching: */
struct vmacache vmacache;
#ifdef SPLIT_RSS_COUNTING
struct task_rss_stat rss_stat;
#endif
int exit_state;//进程的退出状态,
int exit_code;//进程的终止代号
int exit_signal;//进程接受到的退出信号
/* The signal sent when the parent dies: */
int pdeath_signal;//表示当父进程死亡时要发送的信号,然后托管给0进程
/* JOBCTL_*, siglock protected: */
unsigned long jobctl;
...
unsigned long atomic_flags; /* Flags requiring atomic access. */
struct restart_block restart_block;
pid_t pid;//表示该进程的进程号
pid_t tgid;//表示该进程的线程号
#ifdef CONFIG_STACKPROTECTOR
/* Canary value for the -fstack-protector GCC feature: */
unsigned long stack_canary;
#endif
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;//指向创建进程的进程描述符(生父进程),如果生父进程不存在了,指向进程1(systemd进程)
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;//指向进程的当前父进程,一般和real_parent一致,只有在调试的时候指向调试的进程
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;//指向该进程的子进程链表
struct list_head sibling;//指向进程的兄弟进程
struct task_struct *group_leader;//一般是指向自己,如果该进程是用户创建的线程,则指向创建线程的进程
...
/* Process credentials: */
/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;//指向客观和真实的主观任务凭证
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;//指向有效的主观任务凭证
#ifdef CONFIG_KEYS
/* Cached requested key. */
struct key *cached_requested_key;
#endif
/*
* executable name, excluding path.
*
* - normally initialized setup_new_exec()
* - access it with [gs]et_task_comm()
* - lock it with task_lock()
*/
char comm[TASK_COMM_LEN];//进程的名称
struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
struct sysv_sem sysvsem;//用于信号量
struct sysv_shm sysvshm;//用于共享内存
#endif
#ifdef CONFIG_DETECT_HUNG_TASK
unsigned long last_switch_count;
unsigned long last_switch_time;
#endif
/* Filesystem information: */
struct fs_struct *fs;//指向进程的当前工作目录
/* Open file information: */
struct files_struct *files;//打开的文件
/* Namespaces: */
struct nsproxy *nsproxy;//命名空间
/* Signal handlers:主要用于信号处理 */
struct signal_struct *signal;//指向进程的信号描述符
struct sighand_struct *sighand;//指向进程的信号处理程序描述符
sigset_t blocked;//表示被阻塞信号的掩码
sigset_t real_blocked;//临时掩码
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;//使用了set_restore_sigmask()则恢复的掩码
struct sigpending pending;//存放私有挂起信号的数据结构
unsigned long sas_ss_sp;//信号处理程序备用堆栈的地址
size_t sas_ss_size;//堆栈的大小
unsigned int sas_ss_flags;//堆栈的标志位
struct callback_head *task_works;
...
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
u64 curr_chain_key;
int lockdep_depth;//表示获取大内核锁的次数
unsigned int lockdep_recursion;
struct held_lock held_locks[MAX_LOCK_DEPTH];
#endif
...
};