进程管理
什么是进程
进程就是运行中的程序。
通过ps可以看到在linux中有几类进程
- 不带括号的进程 表示这是用户态进程 如init进程
- 带括号[] 的进程 表示内核态的进程 如 kethradd 内核现场
- 带?的进程 说明不是前台启动的,一般都是后台的服务 如 tty
多进程缺点: 创建进程占用资源多; 进程间通信需拷贝内存, 不能共享
线程
一条线程指的是进程中一个单一顺序的控制流。
线程的数据细分成三类:
- 线程栈上的本地数据,每个线程都有自己的栈空间,默认大小为8M (针对于单个函数)
- 整个进程里共享的全局数据,可能冲突
- 线程私有数据
共享数据的保护机制
mutex 在共享数据访问的时候,去申请加把锁
- lock(没抢到则阻塞)
- trylock(没抢到则返回错误码);
- 条件变量(通知), 收到通知, 还是要抢锁(由 wait 函数执行); 因此条件变量与互斥锁配合使用
对应数据结构
内核中进程, 线程统一为任务, 由 task_struct 表示 也就是操作系统课程中的PCB的概念 通过链表串起 task_struct
对于一个正在运行的进程,可以通过以下命令行查看进程详细信息
ps
cat /proc/pid
-
任务ID
通过对比 pid 和 tgid 可判断是进程还是线程
-
任务状态
对应操作系统进程的状态:
- 就绪 TASK_RUNNING
- 运行
- 阻塞 TASK_INTERRUPTIBLE TASK_UNITERRUPTIBLE TASK_KILLABLE
-
权限相关
- real_cred 谁能操作此进程
- cred 此进程能操作谁 chmod
- uid gid 启动
- euid egid 生效
- fsuid fsgid 文件
capabilities 机制 将root权限拆分成不同的单元,用 getcap 和 setcap 命令 来允许普通用户来执行
-
内核栈
负责将用户态的执行和内核态的执行串起来。
将用户态运行过程中的 CPU 上下文保存起来,这样当从内核系统调用返回的时候,才能让进程在刚才的地方接着运行下去。
在内核态,32 位和 64 位的内核栈和 task_struct 的关联关系不同。32 位主要靠 thread_info,64 位主要靠 Per-CPU 变量。
调度
进程的分类:
-
实时进程 优先级的范围是 0~99
调度策略
- SCHED_FIFO 调度类 rt_sched_class 根据policy区分
- SCHED_RR 调度类 rt_sched_class 根据policy区分
- SCHED_DEADLINE dl_sched_class
-
普通进程 优先级的范围是 100~139
调度策略
- SCHED_NORMAL fair_sched_class
- SCHED_BATCH
- SCHED_IDLE idle_sched_class
对应的数据结构
一个 CPU 上有一个队列来表示所有的进程,完全公平调度算法(CFS) 的队列是一棵红黑树,树的每一个节点都是一个 sched_entity,每个 sched_entity 都属于一个 task_struct,task_struct 里面有指针指向这个进程属于哪个调度类。
设置进程和线程的调度策略
# 查看当前进程的调度策略
chrt -p <pid>
# 修改当前进度的调度优先级和策略
chrt -f -p | -i | -o <priority> <pid>
- 主动调度
- 抢占调度
如何查看进程的运行时间和上下文切换次数
cat /proc/<pid>/sched
进程创建
fork
- 将 task_struct 结构复制一份并且初始化
- 试图唤醒新创建的子进程。
线程创建
线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。
pthread_create 不是一个系统调用,是 Glibc 库的一个函数。
- 内核态创建任务
- 用户态执行线程
创建进程的话,调用的系统调用是 fork,在 copy_process 函数里面,会将五大结构 files_struct、fs_struct、sighand_struct、signal_struct、mm_struct 都复制一遍,从此父进程和子进程各用各的数据结构。
而创建线程的话,调用的是系统调用 clone,在 copy_process 函数里面, 五大结构仅仅是引用计数加一,也即线程共享进程的数据结构。