一、用户态、内核态
内核空间是共享的,存在整个内核的代码和所有的内核模块以及内核所维护的数据。
进程在运行时一般会处于两种状态:用户态,内核态。
用户态是指进程在用户代码中运行。
内核态是指进程进入内核代码,执行内核的代码。
用户态:Ring3运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。
内核态:Ring0在处理器的存储保护中,核心态,或者特权态(与之相对应的是用户态),是操作系统内核所运行的模式。运行在该模式的代码,可以无限制地对系统存储、外部设备进行访问。
现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在级特权级上时,就可以称之为运行在内核态。
虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
从用户态到内核态的转换情况一般有以下三种:
(1)发生系统调用
(2)CPU执行异常
(3)外围设备发来中断请求
进程切换,是指CPU执行一个进程A,进程A可能因为某些原因被阻塞(比如,等待进程B给它发送数据),那么CPU会被其他处于就绪状态的进程C抢占CPU,CPU执行进程C的相关指令。 CPU执行进程A的指令到执行进程C指令的过程就是进程切换。
在由进程A切换到进程C时:
(1)内核首先将进程A的状态以及在CPU寄存器存储的数据状态指令等,保存在进程A描述符指向的内存区域中。
(2)内核然后将进程C描述符指向的相应状态数据,装载到CPU的相应寄存器中。完之后,CPU开始执行进程B的相应指令。
因为进程是资源分配的基本单位, 因此进程之间切换时,需要保存、装载各种状态数据等资源, 所需的代价较高。
线程是CPU调度的基本单位,同一个进程内的线程共享OS给该进程分配的资源,因此线程切换比进程间切换所需的代价较小。
二、内核线程
我们知道,在 Linux 中,用户态进程的“祖先”,都是 PID 号为 1 的 init 进程。比如,现在主流的 Linux 发行版中,init 都是 systemd 进程;而其他的用户态进程,会通过 systemd 来进行管理。
稍微想一下 Linux 中的各种进程,除了用户态进程外,还有大量的内核态线程。按说内核态的线程,应该先于用户态进程启动,可是 systemd 只管理用户态进程。那么,内核态线程又是谁来管理的呢?
实际上,Linux 在启动过程中,有三个特殊的进程,也就是 PID 号最小的三个进程。
要查找内核线程,我们只需要从 2 号进程开始,查找它的子孙进程即可。比如,你可以使用 ps 命令,来查找 kthreadd 的子进程:
要查找内核线程,我们只需要从 2 号进程开始,查找它的子孙进程即可。
比如,你可以使用 ps 命令,来查找 kthreadd 的子进程:
# ps -f --ppid 2 -p 2
从上面的输出,你能够看到,内核线程的名称(CMD)都在中括号里,所以,更简单的方法,就是直接查找名称包含中括号的进程。
# ps -ef | grep "\[.*\]"
了解内核线程的基本功能,对我们排查问题有非常大的帮助。比如,我们曾经在软中断案例中提到过 ksoftirqd。它是一个用来处理软中断的内核线程,并且每个 CPU 上都有一个。
如果你知道了这一点,那么,以后遇到 ksoftirqd 的 CPU 使用高的情况,就会首先怀疑是软中断的问题,然后从软中断的角度来进一步分析。
三、参考
Linux中的程序和进程,PID和PPID
https://qastack.cn/unix/82724/ps-switches-to-display-pid-ppid-pgid-and-sid-collectively
'ps' arguments to display PID, PPID, PGID, and SID collectively
https://unix.stackexchange.com/questions/82724/ps-arguments-to-display-pid-ppid-pgid-and-sid-collectively