Linux内核进程调度子系统总结

阅读: 评论:0

Linux内核进程调度子系统总结

Linux内核进程调度子系统总结

在业务性能分析中,很多问题都是进程调度所引起。现特地总结进程调度子系统一些关键点,参考3.10内核源码。

提纲

  • task_struct
    • 家族关系
    • pid namespace
    • 常用统计值
  • 调度器
    • 实时调度器
    • CFS调度器
  • 进程负载均衡
    • 调度域
    • 调度算法
    • 调度时机和抢占
  • 调度器统计

task_struct

在Linux内核中,task_struct是对进程和线程的统一抽象,一个task_struct结构代表了一个进程或者线程。它也是之后调度器的基本单位,也就是说,对于调度器来说,进程和线程是同等的。

家族关系

  • real_parent
    fork进程时赋值一次。
static struct task_struct *copy_process(unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size,int __user *child_tidptr,struct pid *pid,int trace)
{.../* CLONE_PARENT re-uses the old parent */                                                                                                    if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {                                                                                             p->real_parent = current->real_parent;                                                                                               p->parent_exec_id = current->parent_exec_id;                                                                                         } else {                                                                                                                                     p->real_parent = current;                                                                                                            p->parent_exec_id = current->self_exec_id;                                                                                           }                                                                                                                                            ...
}

销毁进程时,把他的子进程的real_parent赋值一次。

static void forget_original_parent(struct task_struct *father)
{	...list_for_each_entry_safe(p, n, &father->children, sibling) {                                                                                 struct task_struct *t = p;                                                                                                           do {                                                                                                                                 t->real_parent = reaper;                                                                                                     if (t->parent == father) {                                                                                                   BUG_ON(t->ptrace);                                                                                                   t->parent = t->real_parent;                                                                                          }                                                                                                                            if (t->pdeath_signal)                                                                                                        group_send_sig_info(t->pdeath_signal,                                                                                SEND_SIG_NOINFO, t);                                                                             } while_each_thread(p, t);                                                                                                           reparent_leader(father, p, &dead_children);                                                                                          }                                                                                                                                            ...
}
  • parent

用于接收SIGCHLD信号和wait4()。

当调试器trace进程时,将进程的parent修改为调试器进程。停止调试时,parent修改为real_parent。

/** ptrace a task: make the debugger its new parent and* move it to the ptrace list.** Must be called with the tasklist lock write-held.*/
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent)
{BUG_ON(!list_empty(&child->ptrace_entry));list_add(&child->ptrace_entry, &new_parent->ptraced);child->parent = new_parent;
}void __ptrace_unlink(struct task_struct *child)
{BUG_ON(!child->ptrace);child->ptrace = 0;child->parent = child->real_parent;...
}

当进程销毁时,real_parent被交给另外一个进程,此时如果子进程没有被调试中,那么parent还是赋值为real_parent,上文的forget_original_parent()代码展示了这个逻辑。

总之,在我的理解中,除非进程被调试器接管了,否则其他时候real_parent和parent总是相等的。

  • 进程号,线程组,进程组,session组(实际就是某leader的pid)

全局id,即在init进程所在namespace中的id号。


struct task_struct {...pid_t pid;   //进程号pid_t tgid;  //线程组号...
}	
SYSCALL_DEFINE0(getpid)                                                                                                                              
{                                                                                                                                                    return task_tgid_vnr(current);                                                                                                               
}                                                                                                                                                    
SYSCALL_DEFINE0(gettid)                                                                                                                              
{                                                                                                                                                    return task_pid_vnr(current);                                                                                                                
}                                                                                                                                                    

struct pid形式,可以分别表示在所有ns层次中的id号:

static inline struct pid *task_pid(struct task_struct *task)
{return task->pids[PIDTYPE_PID].pid;
}static inline struct pid *task_tgid(struct task_struct *task)
{return task->group_leader->pids[PIDTYPE_PID].pid;
}static inline struct pid *task_pgrp(struct task_struct *task)
{return task->group_leader->pids[PIDTYPE_PGID].pid;
}static inline struct pid *task_session(struct task_struct *task)
{return task->group_leader->pids[PIDTYPE_SID].pid;
}

pid namespace

pid namespace组成了树形结构。因此,如果一个进程在level 2的pid namespace中创建,那么它在level 0 和 level 1中必定也有一个pid。因此,<ns, pid>二元组才能唯一的确定一个进程。

通用struct pid数据结构,它代表pid,pgid,spid三种id号,以及在不同ns中的id号。由于进程组,session组中的进程使用相同的
id(组长的pid),那么可以直接使用leader进程的task_struct->pidsp[type]对应pid数据结构,同时把自己的task_struct挂入
对应的pid数据结构的tasks[PIDTYPE_MAX]链表中。比如,进程组使用进程组长的pid作为组id,那么就复用进程组长的pid结构,
同理作用于session组。
struct pid
{atomic_t count;unsigned int level;/* lists of tasks that use this pid */struct hlist_head tasks[PIDTYPE_MAX];struct rcu_head rcu;struct upid numbers[1];
};task_struct中,有个pids[PIDTYPE_MAX]数组,将该task_struct挂入它使用的pid数据结构中。
struct pid_link
{struct hlist_node node;struct pid *pid;
};struct pid中可能有多个upid,对应在不同ns中的具体数值。upid会加入一个全局hash表pid_hash。
按照<ns, pid>作为key,因此一个进程如果在level 2的ns中,那么他会有3个<ns, pid>,挂入pid_hash三次。
可以参考alloc_pid()函数实现。
struct upid {/* Try to keep pid_chain in the same cacheline as nr for find_vpid */int nr;struct pid_namespace *ns;struct hlist_node pid_chain;
};

常用统计值

  1. CPU级(/proc/stat)
    static int show_stat(struct seq_file *p, void *v)函数详细解释了统计方法。既统计了所有CPU总计值,也按单个CPU输出统计值。
cpu消耗的时间(单位为jiffies)划分为如下几类:
enum cpu_usage_stat {CPUTIME_USER,CPUTIME_NICE,CPUTIME_SYSTEM,CPUTIME_SOFTIRQ,CPUTIME_IRQ,CPUTIME_IDLE,CPUTIME_IOWAIT,CPUTIME_STEAL,CPUTIME_GUEST,CPUTIME_GUEST_NICE,NR_STATS,
};

通过阅读源码可知CPU时间的分布为:

USERNICESYSTEMSOFTIRQIRQIDLEIOWAITSTEAL

注意:

  1. KVM子机vcpu运行时间会保存两次,一次保存在USER/NICE中,一次保存在GUEST/GUEST_NICE中。所以GUEST/GUEST_NICE不算在总CPU时间分布中。
  2. STEAL时间指在虚拟化环境下,hypervisor窃取的vm中的时间,严格讲就是VCPU没有运行的时间(不包括VCPU主动idle的时间)。细节可以参考博客。这就意味着我们可以在自己里观察top出来的st统计值,推断出超卖情况。
  3. ksoftirqd执行的软中断,也会计算到软中断耗时上去。

硬中断总次数(各个核心上通用中断数+各个核心上体系相关中断数+中断失败数),以及中断描述表中所有单种类中断总计数。
软中断总次数,以及单种类软中断总计数。
参考源码

单个硬中断和软中断统计可以看/proc/interrupts和/proc/softirqs。

这里顺便把/proc/interrupts不好理解的中断详细分析下

 NMI:          0          0   Non-maskable interruptsLOC:      14066      15160   Local timer interruptsSPU:          0          0   Spurious interruptsPMI:          0          0   Performance monitoring interruptsIWI:         94        154   IRQ work interruptsRTR:          0          0   APIC ICR read retriesRES:      11943      11571   Rescheduling interruptsCAL:       1779       6332   Function call interruptsTLB:        130         94   TLB shootdownsTRM:          0          0   Thermal event interruptsTHR:          0          0   Threshold APIC interruptsDFR:          0          0   Deferred Error APIC interruptsMCE:          0          0   Machine check exceptionsMCP:          2          2   Machine check pollsERR:          0MIS:          0PIN:          0          0   Posted-interrupt notification eventNPI:          0          0   Nested posted-interrupt eventPIW:          0          0   Posted-interrupt wakeup event
简写中断向量中断处理函数备注
NMIX86_TRAP_NMInmi不可屏蔽中断
LOCLOCAL_TIMER_VECTORsmp_apic_timer_interrupt
SPUSPURIOUS_APIC_VECTORsmp_spurious_interrupt
PMIX86_TRAP_NMInmi根据不可屏蔽中断原因,最后调用perf_event_nmi_handler()
IWIIRQ_WORK_VECTORsmp_irq_work_interrupt
RTRInterrupt Command Register (ICR)
RESRESCHEDULE_VECTORsmp_reschedule_interrupt强制指定cpu进行一次进程调度
CALCALL_FUNCTION_VECTORsmp_call_function_interruptCAL和TLB走同一个中断向量,这里统计会剔除TLB数量
TLBCALL_FUNCTION_VECTORsmp_call_function_interruptCAL和TLB走同一个中断向量,这里只统计TLB数量
TRMTHERMAL_APIC_VECTORsmp_thermal_interrupt
THRTHRESHOLD_APIC_VECTORsmp_threshold_interrupt
DFRDEFERRED_ERROR_VECTORsmp_deferred_error_interrupt
MCEX86_TRAP_MCmachine_check
MCP
ERR
MIS
PINPOSTED_INTR_VECTORsmp_kvm_posted_intr_ipiVT-d Posted Interrupt
NPIPOSTED_INTR_NESTED_VECTORsmp_kvm_posted_intr_nested_ipi
PIWPOSTED_INTR_WAKEUP_VECTORsmp_kvm_posted_intr_wakeup_ipi
  • task_struct级
    统计信息保存在了/proc/XX/stat中。参考源码
  1. pid: 进程pid。
  2. tcomm: 进程名。
  3. state:
进程状态内核对应宏定义备注
R(running)TASK_RUNNING进程当前正在运行,或者正在运行队列中等待调度。
S(sleeping)TASK_INTERRUPTIBLE进程处于睡眠状态,正在等待某些事件发生。进程可以被信号中断。接收到信号或被显式的唤醒呼叫唤醒之后,进程将转变为 TASK_RUNNING 状态。
D(disk sleep)TASK_UNINTERRUPTIBLE此进程状态类似于 TASK_INTERRUPTIBLE,只是它不会处理信号。中断处于这种状态的进程是不合适的,因为它可能正在完成某些重要的任务。 当它所等待的事件发生时,进程将被显式的唤醒呼叫唤醒。
T(stopped)__TASK_STOPPED进程已中止执行,它没有运行,并且不能运行。接收到 SIGSTOP 和 SIGTSTP 等信号时,进程将进入这种状态。接收到 SIGCONT 信号之后,进程将再次变得可运行。
t(tracing stop)__TASK_TRACED正被调试程序等其他进程监控时,进程将进入这种状态。
Z(zombie)EXIT_ZOMBIE进程已终止,它正等待其父进程收集关于它的一些统计信息。
X(dead)EXIT_DEAD最终状态(正如其名)。将进程从系统中删除时,它将进入此状态,因为其父进程已经通过 wait4() 或 waitpid() 调用收集了所有统计信息。
x(dead)TASK_DEAD同上
K(wakekill)TASK_WAKEKILLTASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE。TASK_KILLABLE除了可以响应终止进程信号,其它跟TASK_INTERRUPTIBLE一样
W(waking)TASK_WAKING主要用在try_to_wake_up()函数中,表示进程被唤醒但是还没有加入运行队列这个中间状态。
P(parked)TASK_PARKEDper-cpu进程,当cpu热拔出时,该进程进入park阻塞状态,即使被强制唤醒也会继续park阻塞。除非热插入cpu。可以参考博客
  1. ppid:父pid,用的是real_parent值。
  2. pgid:进程组pid。
  3. sid:进程会话组ID。
  4. tty_nr:当前进程的tty终端设备号。
  5. tty_pgrp:终端的进程组号,也就是当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID。
  6. flags:task->flags,参考源码注释。
  7. minflt:该线程组的所有线程(包括正在运行的线程,以及死去的线程总和)次要缺页中断的次数总和,即无需从磁盘加载内存页。注意,无论从该线程组中哪一个线程的/proc/pid/进入,看到的该统计值都是线程组的总和,如果要看单个线程的,需要从/proc/pid/task/tpid/stat查看。接下来的maj_flt,utime,stime,gtime都要遵从该规则。
  8. cmin_flt:曾经的所有子进程(sys_wait4后被回收的子进程才加入该统计)的次要缺页中断的次数总和,这个统计值保存在task->signal,可见是通过信号传递。
  9. maj_flt:该线程组的所有线程(包括正在运行的线程,以及死去的线程总和)主要缺页中断的次数总和,即需要从磁盘加载内存页。
  10. cmaj_flt:曾经的所有子进程(sys_wait4后被回收的子进程才加入该统计)的主要缺页中断的次数总和,这个统计值保存在task->signal,可见是通过信号传递。
  11. utime:该线程组的所有线程(包括正在运行的线程,以及死去的线程总和)在用户态jiffies(时钟节拍数)。
  12. stime:该线程组的所有线程(包括正在运行的线程,以及死去的线程总和)在内核态jiffies。
  13. cutime:曾经的所有子进程(sys_wait4后被回收的子进程才加入该统计)的在用户态jiffies总和,这个统计值保存在task->signal,可见是通过信号传递。
  14. stime:曾经的所有子进程(sys_wait4后被回收的子进程才加入该统计)的在内核态jiffies总和,这个统计值保存在task->signal,可见是通过信号传递。
  15. priority:进程的动态优先级,这里p->prio - MAX_RT_PRIO做了处理,仅仅用在proc展示用,内核依旧是使用p->prio来做调度。
  16. nice:普通进程nice值。
  17. num_threads:进程内线程数。进程内的task/xxx/stat文件里,该值都相同。
  18. it_real_value:已废弃,永远为0,无意义。
  19. start_time:自系统启动后的进程创建那个时间偏移点,单位为jiffies。
  20. vsize:进程分配的虚拟内存大小。
  21. rss:等我研究完内存子系统后再补上。内核对rss内存统计分为三种:MM_FILEPAGES,MM_ANONPAGES,MM_SWAPENTS。
  22. rsslim:该进程允许的rss的上限,单位字节数。
  23. mm->start_code:进程代码段起始地址。
  24. mm->end_code:进程代码段结束地址。
  25. mm->start_stack:进程栈的起始地址。
  26. esp:ESP寄存器(栈顶指针)当前内容。
  27. eip:EIP寄存器(指令计数器)当前内容。
  28. pending:bitmap of pending signals。代码注释表明已经废弃,以后再分析。
  29. blocked:bitmap of blocked signals。代码注释表明已经废弃,以后再分析。
  30. sigign:bitmap of ignored signals。代码注释表明已经废弃,以后再分析。
  31. sigcatch:bitmap of catched signals。代码注释表明已经废弃,以后再分析。
  32. wchan:如果进程处于睡眠状态,那么这里保存了该进程睡眠在哪个内核函数的地址。
  33. 0:无意义。
  34. 0:无意义。
  35. task->exit_signal:当进程退出时,发给父进程的信号集合。
  36. task_cpu:进程当前运行所在cpu编号。
  37. task->rt_priority:实时优先级,如果进程不是实时进程,这个值就没意义。
  38. task->policy:进程调度策略。SCHED_FIFO,SCHED_RR,SCHED_NORMAL,SCHED_BATCH,SCHED_IDLE之一。
  39. blkio_ticks:进程同步磁盘IO操作延时+进程swap in操作延时。参考源码,单位为jiffies。
  40. gtime:该线程组的所有线程(包括正在运行的线程,以及死去的线程总和)在guest虚拟机运行的jiffies(时钟节拍数)。
  41. cgtime:曾经的所有子进程(sys_wait4后被回收的子进程才加入该统计)的在guest虚拟机运行的jiffies,这个统计值保存在task->signal,可见是通过信号传递。
  42. mm->start_data:进程data+bss段起始地址。
  43. mm->end_data:进程data+bss段结束地址。
  44. mm->start_brk:进程堆起始地址。
  45. mm->arg_start:进程参数起始地址。
  46. mm->arg_end:进程参数结束地址。
  47. mm->env_start:进程环境变量起始地址。
  48. mm->env_end:进程环境变量结束地址。
  49. exit_code:the thread’s exit_code in the form reported by the waitpid system call。没核对源码,感觉没啥用,以后再看。

调度器

  • 调度单元(struct sched_entity)的组织

每个CPU都有一个struct rq结构,保存在runqueues全局变量中,含有成员struct cfs_rq cfs和struct rt_rq rt。分别记录了普通进程和实时进程的调度单元。

每个进程根据分类不同,指向不同的struct sched_class结构:

主调度器函数__schedule():

根据内核源码总结进程调度配置特性(可以在/sys/kernel/debug/sched_features配置):

  • GENTLE_FAIR_SLEEPERS

周期性调度器scheduler_tick(),这个函数由传统低分辨率时钟周期调用。

实时调度器

CFS调度器

进程负载均衡

调度域

调度算法

调度时机和抢占

根据内核源码注释总结进程调度时机为:

  • 主动进入睡眠放弃cpu:可睡眠的锁,等待队列,IO等等。
  • 中断返回内核和系统切换回用户空间时,会检查进程的TIF_NEED_RESCHED标记,看是否需要做一次进程调度。这也是常说的进程抢占检测点之一。按照时钟周期性调度的scheduler_tick()会设置该标记。
  • 被唤醒的进程,根据配置可以选择是否发起抢占。如果可以抢占当前进程,那么设置TIF_NEED_RESCHED标记,之后一次抢占检测点到来时会进行进程切换。
    1. 如果内核配置了内核抢占,那么在syscall或者exception上下文时,preempt_enable()调用时会检测抢占。
    2. 无论内核配置都会发生的抢占点:显示调用cond_resched(),schedule()。syscall或者exception返回用户态。
      interrupt-handler返回用户态。

调度器统计

查看/proc/pid/schedstat文件(设置/proc/sys/kernel/sched_schedstats为1才能看到详细统计)

本文发布于:2024-01-31 17:32:47,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170669357030211.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:子系统   内核   进程   Linux
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23