1. 什么是进程
在 《面对多任务处理、程序员是怎样榨干计算机资源的》提到,不管是进程、线程、协程都是用来处理用户任务的。从内核角度来看,进程就是用来分配CPU、内存等资源的。后来觉得进程分配CPU时间片太重了,于是轻量级进程、线程就出现了,成了CPU调度的基本单位,但是资源分配的基本单位还是进程。通俗的讲,进程被定义为程序运行的一个实例。为了管理进程,内核引入了一个进程描述符的数据结构。
2. 进程的生命周期(状态)
进程描述符中的state字段描述了进程所处的状态。粗略的看,进程要么在运行状态,要么不再运行状态。但非运行状态也可能有多种,比如就进程在等待外部信号、等待IO就绪等,或者说它可以马上运行,但是当前CPU被其他进程所占用了,前一种状态我们称之为睡眠,后一种状态我们称之为等待,当然还有一种终止状态。它们之间的状态转换如上图所示。
// include\linux\sched.h
#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_DEAD 16
#define EXIT_ZOMBIE 32
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_PARKED 512
#define TASK_NOLOAD 1024
#define TASK_STATE_MAX 2048
在内核源码中,我们看到了很多状态,远不止刚刚讲的四种,不过它们也都属于这四大类。
TASK_RUNNING 为运行状态
TASK_INTERRUPTIBLE 可中断的等待状态,进程休眠,直到某个条件为真。 什么条件呢?比如传递一个信号过来。再比如说,产生一个硬件中断,释放了正在等待的资源。这都可以作为触发条件。
TASK_UNINTERRUPTIBLE 不可中断的等待状态。一般很少,在设备驱动中会用到这种状态。
__TASK_STOPPED 进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOUT等信号时,进入暂停状态。
__TASK_TRACED debugger时进程暂停的状态。
EXIT_ZOMBIE 僵死状态,进程被终止,资源被释放。但是父进程没有回收,没有wait waitpid等操作,内核不能丢弃这些描述符,还会残留在进程表中。
EXIT_DEAD 退出状态
后两个状态,既可以放在state中,也可以放在exit_state中。
ps命令中STAT 就是对应了进程的当前的状态。
ubuntu@VM-0-16-ubuntu:~$ ps -au
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1065 0.0 0.1 15308 2048 ttyS0 Ss+ Apr19 0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220
root 1075 0.0 0.0 15532 1624 tty1 Ss+ Apr19 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
ubuntu 13980 0.0 0.2 22004 5152 pts/0 Ss 16:52 0:00 -bash
ubuntu 14371 0.0 0.1 36720 3412 pts/0 R+ 16:55 0:00 ps -a
3. 进程结构分析
task_struct 包含的非常多的属性,下面列出些常见的。
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack; // 栈指针
atomic_t usage;// 进程计数,表示当前有几个进程正在使用此结构
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;//ptrace 调试跟踪用的
// 多处理器用到的
#ifdef CONFIG_SMP
struct llist_node wake_entry;
int on_cpu;
unsigned int wakee_flips;
unsigned long wakee_flip_decay_ts;
struct task_struct *last_wakee;
int wake_cpu;
#endif
// 运行队列和进程调试相关程序
int on_rq;
//优先级
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;
int nr_cpus_allowed;
cpumask_t cpus_allowed;
......
// 进程的地址空间
struct mm_struct *mm, *active_mm;
......
// 进程pid
pid_t pid;
pid_t tgid;
......
// 孩子/兄弟进程列表
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 */
.....
};
大概分为以下及部分
- 状态和执行信息
- 虚拟内存的信息
- 身份凭据
- 所使用文件的信息
- 线程是信息
- 进程间通信的信息
4. 进程类型
- fork产生的进程,被看作是原进程的拷贝,称为子进程。它们是两个独立的实例,有同一组打开的文件、同样的工作目录、内存中同样的数据。这里有写时拷贝的优化。
- exec可以从一个可执行程序中加载另一个应用程序,使之替代当前进程。
- clone用于实现线程,和fork类似,不同的是clone出的轻量级进程共享父进程的内存、打开文件、堆等资源。
参考:
[0] 深入理解Linux内核
[1] 深入Linux内核架构