分类 Linux 相关 下的文章

Linux 的 scheduler

Linux 从 2.6.23 开始使用 CFS(Completely Fair Scheduler). 在 Linux 内核看来, 它不区分进程和线程, 每个都是可调度的 Thread.

每个新创建的 Thread 都有一个 Schedule Policy 属性和一个 Static Priority 属性. CFS 主要根据这2 个属性的值, 确定当前线程是不是能分配 CPU 时间.

Linux 上的 Scheduler Policy 主要有:

  1. SCHED_OTHER # Linux kernel 里面又名 SCHED_NORMAL
  2. SCHED_IDLE
  3. SCHED_BATCH
  4. SCHED_FIFO #First In First Out 先进先出
  5. SCHED_RR # Round Robin 是 SCHED_FIFO 的改进版本

以上这些 Policy 又被分为 2 大类(Classes):

  1. Normal scheduling policies (正常) 包含 SCHED_OTHER, SCHED_IDLE, SCHED_BATCH;
  2. real-time policies (实时) 包含 SCHED_FIFO, SCHED_RR

对于 Normal scheduling policies 的线程, 它的静态优先级一定是 0, 这个值在scheduler 安排他们在 CPU 上执行决策的时候, 是不考虑的.
对于 Real-time policies 的线程, 它的静态优先级范围是 1(低) ~ 99(高).

从概念上讲(实际并不一定是这么做的, 效果是一样的), 每个优先级都有一个线程列表, 里面都是同优先级的线程. CFS 从高优先级队列到低优先级队列按照顺序去找, 高优先级队列里面执行完了, 再执行第优先级队列. 同一个队列里面从头往后执行.

不同优先级线程之间根据优先级(priority) 去安排, 相同优先级的线程根据 Scheduler Policy 去安排执行顺序和时间;

Linux 默认的线程 Policy 是SCHED_OTHER, 所以这些默认的静态优先级都是 0. 它们都比 Real-time 的线程优先级低. 对于 SCHED_OTHER 队列里面的线程, 因为静态优先级都是0, 所以它们使用动态优先级(dynamic priority) 来决定谁先运行. 动态优先级使用 nice 的值来决定谁先运行. nice 的值决定一个线程在一个时间区间内可以运行的时长, 通常情况下没有 nice 值的线程都有相同的运行时长, 改变 nice 的值, 就能改变在一个时间区间内它能运行的时长.

nice 的值只对 policy 是 SCHED_OTHER & SCHED_BATCH 的线程有效. 在 Linux 上 nice 的值是 per-thread 的. 在当前的 Linux 系统, nice 的值可以是: -20 (high priority) to +19 (low priority). 它意味着你越 nice, 你的优先级越低, 越不 nice (负值), 你的优先级就越高. 当前 Linux 系统里面, nice 的值相差一个单位, 一个时间区间内大概运行时间比原来是 1:1.25.

PolicyStatic PriorityDynamic Priority(nice)
SCHED_OTHER0-20 ~ 19
SCHED_RR0 ~ 99N/A

我们看到上面说 Linux 的 Real-time 的线程优先级是 1(低) ~ 99(高), 而 Normal 的线程优先级是 0, 根据 nice 可以调整的值, Normal 线程可以做到nice 的值 -20 ~ 19, 如果 mapping 的到优先级, 则可以是 0 ~ 39. 这里 0 ~ 39 值越低, 优先级越高. 如果把 Real-time 的优先级都加一个负号, 就变成了 -99(高) ~ 1(低), 那么就可以连起来了. 所有优先级是: -99 ~ 1, 0 ~ 39, 并且值越小, 优先级越高. 所以可以认为 Normal 的优先级默认是 20(本来是 0), 通过 nice 改成了 0(-20) ~ 39 (19). 这样一来, 是不是就跟 top 里面的 prio 和 ni 值匹配了?

普通用户只能使用 nice 的值把进程调的更 nice (0 ~ 20), 不能调的更不 nice (-10 ~ -1). 但是 root 可以.

另外, 当没有 CPU 竞争的时候, 比如系统有 8 个 CPU, 只有 3 个在忙, Priority 基本不起作用.

参考: https://man7.org/linux/man-pages/man7/sched.7.html

Linux 线程的状态

通常的操作系统概念里, 都会区分进程和线程, 可是在 Linux 内核里面, 对于系统调度来说, 它不区分进程和线程, 统一的术语都是线程(Thread), 线程做为调度的基本单位.

对于一个系统来说, 硬件的处理器(CPU)个数(插槽数), 一个 CPU 上的核(core)的数量, 一个核上的硬件线程数(Hyper-Thread)数都是有限的. 有的线程正在 CPU 上运行(on-cpu), 有的不在 CPU 上运行(off-cpu). 那么对于 Thread 来说, 它有哪些状态呢?

通常我们会在 Linux 上面看到这些状态:
• Runnable state 可运行状态,正在 CPU 运行或在队列等待运行
• Sleeping state 等待 I/O 或 调用 sleep 函数 响应 其它 interrupt
• Uninterruptable sleep state 等待 I/O 不响应其它 interrupt
• Defunct or Zombie state 等待父线程/root 线程 ack 死亡

为什么会有僵尸(Zombie)状态?
每个进程运行结束之后, 会返回一个状态码, 本身结束运行之后, 进入 Zombie 状态, 然后创建它的父进程(除了 1 号进程没有父进程)收到这个状态码, 做个响应, 它就可以正常死亡了. 如果它的父进程一直没时间响应, 它就会一直停留在 Zombie 状态. 如果某个进程的父进程早就自己死亡了, 那么 1 号进程就会作为它的父进程进行响应.

如果在 ps 命令下, 我们能看到稍多状态:

D    uninterruptible sleep (usually IO)
I    Idle kernel thread
R    running or runnable (on run queue)
S    interruptible sleep (waiting for an event to complete)
T    stopped by job control signal
t    stopped by debugger during the tracing
X    dead (should never be seen)
Z    defunct ("zombie") process, terminated but not reaped by its parent

对于 ps 命令, 这些状态从哪里获得?
从 /proc/<pid>/task/<pid>/status 得到, 比如:

supra@suprabox:/proc/3119/task/3125$ cat /proc/3119/task/3125/status
Name:    gmain
Umask:    0002
State:    S (sleeping)
Tgid:    3119

对于 sleep 的线程, 它到底从哪个函数进入的 sleep?
通过 cat /proc/<pid>/task/<pid>/wchan 可获得(wchan: waiting channel), 比如:

cat /proc/3119/task/3125/wchan
poll_schedule_timeout.constprop.13

进入 sleep (S & D) 状态的线程, Scheduler 不会再给他们安排 CPU 执行它们的代码. 当线程进入 sleep 之前, 它会进入内核态, 更新它自己在内核的里面 scheduler 数据结构的状态, 告诉内核scheduler 把自己从等待执行的队列中放到一个等待队列中. 当自己等待的信号(I/O interrupt 或者 sleep的时间到), 内核处理 interrupt 的内核进程会重新把它们放到认为等待执行队列. 于是它们重新获得执行机会.

Linux 进程状态 D 状态

有时候, 我们会看到有些进程进入 D 状态, 如下:
cpuD.png

什么是 D 状态:

State "D" (uninterruptible sleep) means that the process is in kernel space (in a system call), usually attempting to perform IO. These processes will not respond to signals (or SIGKILL) and cannot be debugged with gdb or pstack.
An uninterruptible sleep state is a sleep state that won't handle a signal right away. It will wake only as a result of a waited-upon resource becoming available or after a time-out occurs during that wait (if specified when put to sleep). It is mostly used by device drivers waiting for disk or network IO (input/output). When the process is sleeping uninterruptibly, signals accumulated during the sleep will be noticed when the process returns from the system call or trap.

为什么 D 状态影响 load

When load averages first appeared in Linux, they reflected CPU demand, as with other operating systems. But later on Linux changed them to include not only runnable tasks, but also tasks in the uninterruptible state (TASK_UNINTERRUPTIBLE or nr_uninterruptible). This state is used by code paths that want to avoid interruptions by signals, which includes tasks blocked on disk I/O and some locks. You may have seen this state before: it shows up as the "D" state in the output ps and top. The ps(1) man page calls it "uninterruptible sleep (usually IO)".
更多解释: https://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html

它们不是僵尸进程 (Zombie)

You may sometimes see entries marked Z (or H under Linux, I don't know what the distinction is) in the ps or top output. These are technically not processes, they are zombie processes, which are nothing more than an entry in the process table, kept around so that the parent process can be notified of the death of its child. They will go away when the parent process pays attention (or dies).

为什么 kill -9 <pid> 不能杀掉它?

参见: https://unix.stackexchange.com/questions/5642/what-if-kill-9-does-not-work/5648#5648

Linux source 命令

source 是 shell 内置的命令, 它加载给的函数文件, 在当前shell/命令行窗口执行.
格式是:
source filename [arguments]

文件名查找路径根据 $PATH 去查.

它区别于直接执行 sh filename [arguments]的重要区别是: source 在当前 shell 执行, 可能改变当前 shell 的某些全局状态或特性. 而一般的 sh 或 直接 ~$ <可执行文件> 的方式是新开一个 shell 去执行, 也就是它是一个新的 process.

eric@tianxiaohui.com ~$ type source
source is a shell builtin

source 等同于 . 所以:

source script == . script
./script # 新开一个 shell 执行这个 script 脚本