在业务性能分析中,很多问题都是进程调度所引起。现特地总结进程调度子系统一些关键点,参考3.10内核源码。
在Linux内核中,task_struct是对进程和线程的统一抽象,一个task_struct结构代表了一个进程或者线程。它也是之后调度器的基本单位,也就是说,对于调度器来说,进程和线程是同等的。
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); } ...
}
用于接收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总是相等的。
全局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组成了树形结构。因此,如果一个进程在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;
};
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时间的分布为:
USER | NICE | SYSTEM | SOFTIRQ | IRQ | IDLE | IOWAIT | STEAL |
注意:
硬中断总次数(各个核心上通用中断数+各个核心上体系相关中断数+中断失败数),以及中断描述表中所有单种类中断总计数。
软中断总次数,以及单种类软中断总计数。
参考源码
单个硬中断和软中断统计可以看/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
简写 | 中断向量 | 中断处理函数 | 备注 |
---|---|---|---|
NMI | X86_TRAP_NMI | nmi | 不可屏蔽中断 |
LOC | LOCAL_TIMER_VECTOR | smp_apic_timer_interrupt | |
SPU | SPURIOUS_APIC_VECTOR | smp_spurious_interrupt | |
PMI | X86_TRAP_NMI | nmi | 根据不可屏蔽中断原因,最后调用perf_event_nmi_handler() |
IWI | IRQ_WORK_VECTOR | smp_irq_work_interrupt | |
RTR | Interrupt Command Register (ICR) | ||
RES | RESCHEDULE_VECTOR | smp_reschedule_interrupt | 强制指定cpu进行一次进程调度 |
CAL | CALL_FUNCTION_VECTOR | smp_call_function_interrupt | CAL和TLB走同一个中断向量,这里统计会剔除TLB数量 |
TLB | CALL_FUNCTION_VECTOR | smp_call_function_interrupt | CAL和TLB走同一个中断向量,这里只统计TLB数量 |
TRM | THERMAL_APIC_VECTOR | smp_thermal_interrupt | |
THR | THRESHOLD_APIC_VECTOR | smp_threshold_interrupt | |
DFR | DEFERRED_ERROR_VECTOR | smp_deferred_error_interrupt | |
MCE | X86_TRAP_MC | machine_check | |
MCP | |||
ERR | |||
MIS | |||
PIN | POSTED_INTR_VECTOR | smp_kvm_posted_intr_ipi | VT-d Posted Interrupt |
NPI | POSTED_INTR_NESTED_VECTOR | smp_kvm_posted_intr_nested_ipi | |
PIW | POSTED_INTR_WAKEUP_VECTOR | smp_kvm_posted_intr_wakeup_ipi |
进程状态 | 内核对应宏定义 | 备注 |
---|---|---|
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_WAKEKILL | TASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE。TASK_KILLABLE除了可以响应终止进程信号,其它跟TASK_INTERRUPTIBLE一样 |
W(waking) | TASK_WAKING | 主要用在try_to_wake_up()函数中,表示进程被唤醒但是还没有加入运行队列这个中间状态。 |
P(parked) | TASK_PARKED | per-cpu进程,当cpu热拔出时,该进程进入park阻塞状态,即使被强制唤醒也会继续park阻塞。除非热插入cpu。可以参考博客 |
每个CPU都有一个struct rq结构,保存在runqueues全局变量中,含有成员struct cfs_rq cfs和struct rt_rq rt。分别记录了普通进程和实时进程的调度单元。
每个进程根据分类不同,指向不同的struct sched_class结构:
主调度器函数__schedule():
根据内核源码总结进程调度配置特性(可以在/sys/kernel/debug/sched_features配置):
周期性调度器scheduler_tick(),这个函数由传统低分辨率时钟周期调用。
根据内核源码注释总结进程调度时机为:
查看/proc/pid/schedstat文件(设置/proc/sys/kernel/sched_schedstats为1才能看到详细统计)
本文发布于:2024-01-31 17:32:47,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170669357030211.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |