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_state
和task->__state
值的变化顺序与单线程进程一致。主线程的情况比较复杂,但task->exit_state
和task->__state
值的变化顺序与单线程进程一致,只是在一些细节上存在差异。