1. 进程状态切换

state value desc
TASK_NEW 0x00000800 新建
TASK_RUNNING 0x00000000 就绪或占有cpu运行
TASK_INTERRUPTIBLE 0x00000001 可中断睡眠
TASK_UNINTERRUPTIBLE 0x00000002 不可中断睡眠
TASK_WAKEKILL 0x00000100 可以在stopped/traced/killable的情况下唤醒
TASK_WAKING 0x00000200 正在被唤醒
__TASK_STOPPED 0x00000004 和其他状态组合,用于唤醒包含此状态标记的进程
TASK_STOPPED 0x00000104 TASK_WAKEKILL和__TASK_STOPPED的组合
TASK_IDLE 0x00000402 TASK_UNINTERRUPTIBLE和TASK_NOLOAD的组合
TASK_DEAD 0x00000080 进程消亡,即将回收task_struct
EXIT_DEAD 0x00000010 来自EXIT_ZOMBIE,即将回收task_struct
EXIT_ZOMBIE 0x00000020 僵尸态,进程已经结束,但task_struct还没有回收

EXIT前缀的状态,是由struct task_struct.exit_state使用的。

 1/// include/linux/sched.h
 2/*
 3 * Task state bitmask. NOTE! These bits are also
 4 * encoded in fs/proc/array.c: get_task_state().
 5 *
 6 * We have two separate sets of flags: task->__state
 7 * is about runnability, while task->exit_state are
 8 * about the task exiting. Confusing, but this way
 9 * modifying one set can't modify the other one by
10 * mistake.
11 */

2. 进程状态

2.1. 创建

使用kernel_clone新创建的任务为TASK_NEW状态,在copy_process将新进程的信息填充后,使用wake_up_new_task将进程设为TASK_RUNNING。

2.2. 睡眠

在程序的运行时中,可能会因为资源不满足比如等待IO、信号量等,或者主动让出cpu而进入睡眠状态,睡眠状态包括TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE统称为TASK_NORMAL状态。常见进入可中断睡眠的接口:msleep_interruptible、schedule_timeout_interruptible、wait_event_interruptible,常见进入不可中断睡眠的接口:msleep、schedule_timeout、wait_event。

与睡眠状态类似的还有TASK_IDLE,这个状态是TASK_UNINTERRUPTIBLE和TASK_NOLOAD的组合,影响的是rq->nr_uninterruptible

2.3. 唤醒

当进程从睡眠状态被唤醒时,try_to_wake_up会将状态设为TASK_WAKING,之后通过ttwu_do_activate将进程状态设为TASK_RUNNING。然后进程会被加入到运行队列,被调度器调度运行。

2.4. 停止

在程序的运行过程中,进程被跟踪或者收到SIGSTOP(19)信号后,会被置为包含__TASK_STOPPED的状态,比如TASK_STOPPED。此时进程仍然可以接收信号,但是不能被调度器调度运行。进程收到SIGCONT(18)信号后,会唤醒__TASK_STOPPED的进程。

2.5. 退出

Linux实现了exit和exit_group系统调用,需要注意的是,为了支持POSIX,进程退出用到的exit、_exit都是调用的exit_group,而不是exit。

对于单线程的进程,程序可以选择退出时是否通知父进程。如果不需要通知父进程,do_exit会逐步将task->exit_state设为EXIT_ZOMBIE,然后在设为EXIT_DEAD,最后将task->__state设为TASK_DEAD,然后主动调度,在调度时释放task_struct。如果需要通知父进程,do_exit会将task->exit_state设为EXIT_ZOMBIE,然后将task->__state设为TASK_DEAD,在父进程调用wait时,将 task->exit_state设为TASK_DEAD,并最后回收task_struct。

对于多线程进程,只有父进程只关心主线程的突出状态。任一线程调用exit_group时会通过zap_other_threads向其他线程发送SIGKILL信号,并唤醒线程,然后进行清理工作。非主线程的子线程的退出不需要通知父进程,可以直接释放task_sturct。退出流程也与单线程进程不需要通知父进程的过程类似,task->exit_statetask->__state值的变化顺序与单线程进程一致。主线程的情况比较复杂,但task->exit_statetask->__state值的变化顺序与单线程进程一致,只是在一些细节上存在差异。