进程管理¶
约 721 个字 57 行代码 3 张图片 预计阅读时间 3 分钟
内核态和用户态¶
现代处理器架构一般允许 CPU 至少在两种不同状态下运行,即:用户态和核心态(有时也称之为监管态 supervisor mode)。执行硬件指令可使 CPU 在两种状态间来回切换。与之对应,可将虚拟内存区域划分(标记)为用户空间部分或内核空间部分。在用户态下运行时,CPU 只能访问被标记为用户空间的内存,试图访问属于内核空间的内存会引发硬件异常。当运行于核心态时,CPU 既能访问用户空间内存,也能访问内核空间内存。
仅当处理器在核心态运行时,才能执行某些特定操作。这样的例子包括:执行宕机(halt)指令去关闭系统,访问内存管理硬件,以及设备 I/O 操作的初始化等。实现者们利用这一硬件设计,将操作系统置于内核空间。这确保了用户进程既不能访问内核指令和数据结构,也无法执行不利于系统运行的操作。
Linux将task_struct中的thread_info线程信息和对应的内核栈紧凑的放在一起,例如占用连续的2个页共8K。从用户态刚切换到内核态后,进程的内核栈总是空的。内核栈位于内核直接映射区(虚拟地址=物理地址+PAGE_OFFSET,不需要页表管理,不需要缺页异常,直接线性映射物理内存),类似的每个CPU还有独立的中断栈用于中断处理程序使用。
struct task_struct {
/**
* 进程状态。
*/
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
/**
* 进程的基本信息。
*/
struct thread_info *thread_info;
....
}
struct thread_info {
unsigned long flags; /* low level flags */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value;
union fp_state fpstate;
union vfp_state vfpstate;
struct restart_block restart_block;
};

进程状态¶
/**
* 进程要么在CPU上执行,要么准备执行。
*/
#define TASK_RUNNING 0
/**
* 可中断的等待状态。
*/
#define TASK_INTERRUPTIBLE 1
/**
* 不可中断的等待状态。
* 这种情况很少,但是有时也有用:比如进程打开一个设备文件,其相应的驱动程序在探测硬件设备时,就是这种状态。
* 在探测完成前,设备驱动程序如果被中断,那么硬件设备的状态可能会处于不可预知状态。
*/
#define TASK_UNINTERRUPTIBLE 2
/**
* 暂停状态。当收到SIGSTOP,SIGTSTP,SIGTTIN或者SIGTTOU信号后,会进入此状态。
*/
#define TASK_STOPPED 4
/**
* 被跟踪状态。当进程被另外一个进程监控时,任何信号都可以把这个置于该
*/
#define TASK_TRACED 8
/**
* 僵死状态。进程的执行被终止,但是,父进程还没有调用完wait4和waitpid来返回有关
* 死亡进程的信息。在此时,内核不能释放相关数据结构,因为父进程可能还需要它。
*/
#define EXIT_ZOMBIE 16
/**
* 在父进程调用wait4后,删除前,为避免其他进程在同一进程上也执行wait4调用
* 将其状态由EXIT_ZOMBIE转为EXIT_DEAD,即僵死撤销状态。
*/
#define EXIT_DEAD 32
sleep (1) vs 挂起(Stopped/T 状态):核心区别
| 特性 | sleep (1) (S 状态) | 挂起(T 状态,kill -STOP) |
|---|---|---|
| 进程状态 | TASK_INTERRUPTIBLE(可中断睡眠) | TASK_STOPPED(暂停) |
| 唤醒方式 | 超时自动唤醒 / 信号提前唤醒 | 只能通过 SIGCONT 信号唤醒 |
| 核心目的 | 主动等待指定时间 | 被动暂停,无时间限制 |
| 返回值 | 0(超时)/ 剩余时间(被打断) | 无返回,直到收到 SIGCONT |
| 典型场景 | 延时执行、降低 CPU 占用 | 调试暂停、Ctrl+Z 暂停进程 |
即sleep是通过将进程切成TASK_INTERRUPTIBLE态,指定时间内进程不参与调度,超时中断后切回TASK_RUNNING态。
而挂起是切成TASK_STOPPED态,无法参与调度且只能通过SIGGONT信号重新切换TASK_RUNNING态;
gdb/lldb 调试程序时,打断点本质就是让进程进入 TASK_STOPPED 状态:
- 调试器向进程发送 SIGSTOP,进程进入 T 状态,停止调度;
- 你执行 next/step 时,调试器发送 SIGCONT 让进程短暂运行,再立即发 SIGSTOP 暂停;
- 整个调试过程,进程大部分时间处于 T 状态,完全不被调度。

