Linux posix_cpu_clock_gettask进程CPU时间时钟读取posix_cpu_clock_gettask是Linux内核中实现进程/线程CPU时间时钟读取的核心函数。它对应于clock_gettime系统调用中使用CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID时的底层操作。CPU时间时钟与实时时钟的本质区别在于实时时钟基于墙上时间单调前进而CPU时间时钟仅在进程获得CPU调度时累加。因此其实现依赖调度器中的时间记账机制而非clocksource直接读取。posix_cpu_clock_gettask定义在kernel/time/posix-cpu-timers.c中static int posix_cpu_clock_gettask(struct task_struct *tsk,clockid_t clkid, struct timespec64 *tp){u64 ns;if (CPUCLOCK_PERTHREAD(clkid)) {if (same_thread_group(tsk, current))ns cpu_clock_sample(CPUCLOCK_THREAD, tsk);elsereturn -EINVAL;} else {if (tsk current || thread_group_leader(tsk))ns cpu_clock_sample(CPUCLOCK_PROCESS, tsk);elsereturn -EINVAL;}*tp ns_to_timespec64(ns);return 0;}CPUCLOCK_PERTHREAD宏判断时钟类型是线程级CLOCK_THREAD_CPUTIME_ID还是进程级CLOCK_PROCESS_CPUTIME_ID。权限检查确保只能读取自身或线程组leader的CPU时间。cpu_clock_sample执行实际的时间采样static u64 cpu_clock_sample(const clockid_t clkid, struct task_struct *p){u64 ns;if (CPUCLOCK_PERTHREAD(clkid)) {ns task_sched_runtime(p);} else {struct thread_group_cputimer *cputimer p-signal-cputimer;thread_group_cputime(p, cputimer-cputime);ns cputime_to_nsecs(cputimer-cputime.sum_exec_runtime);}return ns;}对于线程级时钟直接读取当前线程的sum_exec_runtime调度器维护的总执行时间。对于进程级时钟累加线程组内所有线程的sum_exec_runtime。task_sched_runtime返回线程累积的CPU执行时间u64 task_sched_runtime(struct task_struct *p){u64 ns;unsigned long flags;struct rq *rq;rq task_rq_lock(p, flags);ns p-se.sum_exec_runtime do_task_delta_runtime(p, rq);task_rq_unlock(rq, p, flags);return ns;}do_task_delta_runtime计算当前尚未计入sum_exec_runtime的delta执行时间。若p是当前正在运行的线程则加上从上次更新时间点到当前为止的差值static u64 do_task_delta_runtime(struct task_struct *p, struct rq *rq){u64 delta 0;if (task_current(rq, p)) {delta rq_clock_task(rq) - p-se.exec_start;if (delta 0)delta min(delta, (u64)NSEC_PER_SEC / HZ);}return delta;}rq_clock_task返回运行队列的task clock基于CFS调度器的clock_task。exec_start是线程最近一次获得CPU的时间戳。二者之差就是当前tick周期中已执行但未记账的CPU时间。这里用NSEC_PER_SEC / HZ做上限裁剪防止异常值。thread_group_cputime用于累加进程内所有线程的CPU时间void thread_group_cputime(struct task_struct *tsk,struct task_cputime *cputime){struct task_struct *t;struct task_cputime totals;totals (struct task_cputime){ 0 };rcu_read_lock();for_each_thread(tsk, t) {u64 t_stime, t_utime;task_cputime(t, t_utime, t_stime);totals.utime t_utime;totals.stime t_stime;totals.sum_exec_runtime task_sched_runtime(t);}rcu_read_unlock();cputime-utime totals.utime;cputime-stime totals.stime;cputime-sum_exec_runtime totals.sum_exec_runtime;}遍历线程组中的所有线程累加每个线程的utime用户态、stime内核态和sum_exec_runtime。线程组的cputimer结构本身维护了一份缓存值由thread_group_cputimer不断更新避免每次都遍历全部线程。thread_group_cputimer的更新由调度器的帐号机制驱动在每次tick中断和上下文切换时更新void thread_group_cputimer(struct task_struct *tsk,struct task_cputime *cputime){struct thread_group_cputimer *cputimer tsk-signal-cputimer;unsigned long flags;if (!READ_ONCE(cputimer-running))return;raw_spin_lock_irqsave(cputimer-lock, flags);cputime-utime cputimer-cputime.utime;cputime-stime cputimer-cputime.stime;cputime-sum_exec_runtime cputimer-cputime.sum_exec_runtime;raw_spin_unlock_irqrestore(cputimer-lock, flags);}thread_group_cputimer的running标志位在第一个CPU时间定时器被设置时才置位避免了无进程级定时器时的不必要开销。这是posix_cpu_timers中的一个重要优化lazy accounting。clock_gettime的CLOCK_PROCESS_CPUTIME_ID最终调用路径为sys_clock_gettime - posix_cpu_clock_get - posix_cpu_clock_gettask。返回的timespec64包含秒和纳秒字段精度受到task_sched_runtime的内部精度限制通常为纳秒级。posix_cpu_clock_gettask在读取过程中必须处理任务退出和重入问题。task_cputime_adjusted和do_task_delta_runtime中的task_rq_lock通过禁用抢占和获取rq锁来保证一致性。对于已退出的任务通过exit_signals和release_task的清理机制确保时钟读取不会访问已释放的数据结构。
Linux posix_cpu_clock_gettask进程CPU时间时钟读取
发布时间:2026/6/14 6:07:01
Linux posix_cpu_clock_gettask进程CPU时间时钟读取posix_cpu_clock_gettask是Linux内核中实现进程/线程CPU时间时钟读取的核心函数。它对应于clock_gettime系统调用中使用CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID时的底层操作。CPU时间时钟与实时时钟的本质区别在于实时时钟基于墙上时间单调前进而CPU时间时钟仅在进程获得CPU调度时累加。因此其实现依赖调度器中的时间记账机制而非clocksource直接读取。posix_cpu_clock_gettask定义在kernel/time/posix-cpu-timers.c中static int posix_cpu_clock_gettask(struct task_struct *tsk,clockid_t clkid, struct timespec64 *tp){u64 ns;if (CPUCLOCK_PERTHREAD(clkid)) {if (same_thread_group(tsk, current))ns cpu_clock_sample(CPUCLOCK_THREAD, tsk);elsereturn -EINVAL;} else {if (tsk current || thread_group_leader(tsk))ns cpu_clock_sample(CPUCLOCK_PROCESS, tsk);elsereturn -EINVAL;}*tp ns_to_timespec64(ns);return 0;}CPUCLOCK_PERTHREAD宏判断时钟类型是线程级CLOCK_THREAD_CPUTIME_ID还是进程级CLOCK_PROCESS_CPUTIME_ID。权限检查确保只能读取自身或线程组leader的CPU时间。cpu_clock_sample执行实际的时间采样static u64 cpu_clock_sample(const clockid_t clkid, struct task_struct *p){u64 ns;if (CPUCLOCK_PERTHREAD(clkid)) {ns task_sched_runtime(p);} else {struct thread_group_cputimer *cputimer p-signal-cputimer;thread_group_cputime(p, cputimer-cputime);ns cputime_to_nsecs(cputimer-cputime.sum_exec_runtime);}return ns;}对于线程级时钟直接读取当前线程的sum_exec_runtime调度器维护的总执行时间。对于进程级时钟累加线程组内所有线程的sum_exec_runtime。task_sched_runtime返回线程累积的CPU执行时间u64 task_sched_runtime(struct task_struct *p){u64 ns;unsigned long flags;struct rq *rq;rq task_rq_lock(p, flags);ns p-se.sum_exec_runtime do_task_delta_runtime(p, rq);task_rq_unlock(rq, p, flags);return ns;}do_task_delta_runtime计算当前尚未计入sum_exec_runtime的delta执行时间。若p是当前正在运行的线程则加上从上次更新时间点到当前为止的差值static u64 do_task_delta_runtime(struct task_struct *p, struct rq *rq){u64 delta 0;if (task_current(rq, p)) {delta rq_clock_task(rq) - p-se.exec_start;if (delta 0)delta min(delta, (u64)NSEC_PER_SEC / HZ);}return delta;}rq_clock_task返回运行队列的task clock基于CFS调度器的clock_task。exec_start是线程最近一次获得CPU的时间戳。二者之差就是当前tick周期中已执行但未记账的CPU时间。这里用NSEC_PER_SEC / HZ做上限裁剪防止异常值。thread_group_cputime用于累加进程内所有线程的CPU时间void thread_group_cputime(struct task_struct *tsk,struct task_cputime *cputime){struct task_struct *t;struct task_cputime totals;totals (struct task_cputime){ 0 };rcu_read_lock();for_each_thread(tsk, t) {u64 t_stime, t_utime;task_cputime(t, t_utime, t_stime);totals.utime t_utime;totals.stime t_stime;totals.sum_exec_runtime task_sched_runtime(t);}rcu_read_unlock();cputime-utime totals.utime;cputime-stime totals.stime;cputime-sum_exec_runtime totals.sum_exec_runtime;}遍历线程组中的所有线程累加每个线程的utime用户态、stime内核态和sum_exec_runtime。线程组的cputimer结构本身维护了一份缓存值由thread_group_cputimer不断更新避免每次都遍历全部线程。thread_group_cputimer的更新由调度器的帐号机制驱动在每次tick中断和上下文切换时更新void thread_group_cputimer(struct task_struct *tsk,struct task_cputime *cputime){struct thread_group_cputimer *cputimer tsk-signal-cputimer;unsigned long flags;if (!READ_ONCE(cputimer-running))return;raw_spin_lock_irqsave(cputimer-lock, flags);cputime-utime cputimer-cputime.utime;cputime-stime cputimer-cputime.stime;cputime-sum_exec_runtime cputimer-cputime.sum_exec_runtime;raw_spin_unlock_irqrestore(cputimer-lock, flags);}thread_group_cputimer的running标志位在第一个CPU时间定时器被设置时才置位避免了无进程级定时器时的不必要开销。这是posix_cpu_timers中的一个重要优化lazy accounting。clock_gettime的CLOCK_PROCESS_CPUTIME_ID最终调用路径为sys_clock_gettime - posix_cpu_clock_get - posix_cpu_clock_gettask。返回的timespec64包含秒和纳秒字段精度受到task_sched_runtime的内部精度限制通常为纳秒级。posix_cpu_clock_gettask在读取过程中必须处理任务退出和重入问题。task_cputime_adjusted和do_task_delta_runtime中的task_rq_lock通过禁用抢占和获取rq锁来保证一致性。对于已退出的任务通过exit_signals和release_task的清理机制确保时钟读取不会访问已释放的数据结构。