Untitled
进程管理
进程数据结构
进程的数据结构是task struct
- cpu资源:
- 调度优先级
- 内存地址空间资源:
- mm_struct
- 打开的文件资源:
- file_struct files (一个数组, 存的就是打开的文件的地址, 索引即是文件描符 fd)
进程自己的信息与状态
- 进程状态: 存储在task->state, task->exit_state两个字段中
- 如TASK_RUNNING, TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE, __TASK_STOPED…
- 唯一ID
- pid: 线程级别的id
- gtid: 进程级别的id
- 文件系统信息
struct fs_struct *fs - namespace
- 进程树关系: 该进程在整个进程树里面的位置
进程的状态
1 | /* Used in tsk->state: */ |
常见状态语义说明:
存储在task->state的
- TASK_RUNNING: 进程正在运行队列中, 准备运行/正在运行
- TASK_NORAML/TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE:
- TASK_NORMAL =
(TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)是这两个状态的综合, 表明进程正在睡眠 - TASK_INTERRUPTIBLE: 进程处于可打断的睡眠状态, 正在等待某个条件满足, 被wake_up唤醒, 或者被信号唤醒. 不会被计入load average
- TASK_UNINTERRUPTIBLE: 进程处于不可中断的睡眠状态, 只能在条件满足时, 被wake_up唤醒, 不能被信号唤醒, 常用于(如磁盘 I/O)等不能被中途打断的SLEEP, 这也代表, 在这个状态的进程的数量能一定程度反应当前计算机的物理负载, 这个状态会被计入load average
- TASK_KILLABLE =
(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)可以被致命信号杀死(SIGKILL)的不可中断睡眠 - __TASK_STOPPED = 进程被暂停执行了, 不会被调度. 1. 收到SIGSTOP / SIGSTP / SIGTTIN / SIGTTOU 信号 2. ptrace attach 后发送 SIGSTOP. 常见的进入情景: 1. Ctrl + z 2. debug. 退出的时机: 收到 SIGCONT 信号
- __TASK_TRACED = 进程正在被调试器跟踪
- TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD):
- 语义: TASK_UNINTERRUPTIBLE类似的不可中断睡眠, 但不计入 load average
- 内核中空闲时会运行的 idle线程, 内核中某些无关紧要的等待
- 如果 idle线程用TASK_UNINTERRUPTIBLE而包含TASK_NOLOAD会导致load average空长, 不能反应真实物理资源负载
- TASK_NORMAL =
存储在task->exit_code的
- EXIT_ZOMBIE: 僵尸状态
- 语义: 代码已经执行完毕
- 进入的时机: 进程调用exit() -> do_exit() -> exit_notify() 中设置 tsk->exit_state = EXIT_ZOMBIE
- 退出的时机: 父进程调用
wait/waitpid()读取退出状态 ->wait_task_zombie()->exit_state中改为EXIT_DEAD->task_struct被释放
- EXIT_DEAD (0x10) : 彻底死亡
- 语义: 进程的task_struct正在被回收或者已经被回收
- 进入的时机: 父进程
wait()成功后, 状态从EXIT_ZOMBIE变成EXIT_DEAD - 退出的时机: 这是一个瞬态, 存在的时间极短, 之后
task_struct被回收
常见的进程类型
僵尸进程: 进程(task_struct)已经被释放, 但是还保留PID和exit_state为EXIT_ZOMBIE
- 为什么需要这个状态: 父进程可能需要在进程被关闭以后, 获取进程的PID并检查它的退出码来衡量进程是不是正常退出的
- 资源泄漏: 状态为EXIT_ZOMBIE的进程占用一个PID和少量的内核内存. 如果大量堆积, 会导致内核内存泄漏, 或PID耗尽
- 怎么产生的: 父进程没有调用wait()或者waitpid()
- 解决方式:
- 父进程调用 wait() / waitpid()
- 设置 signal(SIGCHLD, SIG_IGN) 让内核自动回收 (do_notigy_parent返回true的时候直接设置状态为EXIT_DEAD)
- 杀死父进程 -> 僵尸被init(PID=1)收养 -> init会自动
wait()清理
孤儿进程: 进程的父进程先于子进程被杀死了, 子进程就成为了孤儿进程
- 内核会将孤儿进程的父进程指向init, 或者当前 PID namespace 中的 subreaper 进程, init进程会定期调用
wait()来清理 - 本身是正常的, 不会造成问题
- 内核会将孤儿进程的父进程指向init, 或者当前 PID namespace 中的 subreaper 进程, init进程会定期调用
守护进程: 在后台运行的, 不再与任何终端关联的长期运行的进程. 如
sshd, nginx等- 不是一个内核概念, 而是一个用户空间的编程的概念
- 经典的创建过程: double-fork
fork()- 创建子进程- 父进程退出 - 子进程被托孤给init
- setsid - 创建新的session (和原终端解绑)
- 再次
fork()- 确保不会重新获得中断控制能力 - chdir(“/) - 避免占用可卸载的文件系统
- 关闭/重定向 stdin/stdout/stderr
进程的唯一ID
tgid和pid, 为什么要有两个ID
经典的用途是区分进程和线程, 本身是进程组概念, 父进程和他fork出来的子进程组成一个进程组, 共享一个tgid.
他们的id (pid, tgid)会是 (1, 1), (2, 1), (3, 1). 对于操作系统来说, 只有进程这个概念, 线程实际上是一个轻量级进程, 对于操作系统的调度来说, 进程和线程是同等地位的实体, 唯一通过PID来作为唯一标识, 而tgid是标识”进程”而非”线程”的唯一标识.
进程树
Comments
