tft每日頭條

 > 科技

 > linux下常用性能分析的指令

linux下常用性能分析的指令

科技 更新时间:2024-08-16 09:25:13

linux下常用性能分析的指令?clock是timer的基礎,任何一個timer都需要運作在一個指定的clock上來内核中維護了若幹的clock根據計時的特點,clock分成兩種:一種是真實世界的時間概念,另外一個是僅僅計算CPU執行時間 從clock的生命周期來看,可以分成靜态和動态的posix clock,靜态是一直存在于内核中的,而動态clock有創建和銷毀的概念,今天小編就來聊一聊關于linux下常用性能分析的指令?接下來我們就一起去研究一下吧!

linux下常用性能分析的指令(Linux時間子系統之POSIXClock)1

linux下常用性能分析的指令

一、前言

clock是timer的基礎,任何一個timer都需要運作在一個指定的clock上來。内核中維護了若幹的clock。根據計時的特點,clock分成兩種:一種是真實世界的時間概念,另外一個是僅僅計算CPU執行時間 。從clock的生命周期來看,可以分成靜态和動态的posix clock,靜态是一直存在于内核中的,而動态clock有創建和銷毀的概念。

二、基本概念

1、核心數據結構

所謂clock,實際上就是一種計時工具,可能是硬件,也可能是軟件,當然對于POSIX clock而言,當然是指軟件抽象了。clock能夠記錄一段時間的流逝,這段時間可能是真實的牆上時間,也可能是虛拟的時間,例如基于某個進程或者線程的CPU執行時間。在linux kernel中,用struct k_clock來抽象,具體定義如下:

struct k_clock {

int (*clock_getres) (const clockid_t which_clock, struct timespec *tp);

int (*clock_set) (const clockid_t which_clock, const struct timespec *tp);

int (*clock_get) (const clockid_t which_clock, struct timespec * tp);

int (*clock_adj) (const clockid_t which_clock, struct timex *tx);

int (*timer_create) (struct k_itimer *timer);

int (*nsleep) (const clockid_t which_clock, int flags, struct timespec *, struct timespec __user *);

long (*nsleep_restart) (struct restart_block *restart_block);

int (*timer_set) (struct k_itimer * timr, int flags, struct itimerspec * new_setting,

struct itimerspec * old_setting);

int (*timer_del) (struct k_itimer * timr);

void (*timer_get) (struct k_itimer * timr, struct itimerspec * cur_setting);

};

clock作為一個計時工具當然有計時精度,通過clock_getres函數可以獲取該clock的時間精度,需要說明的是這個精度是和timer相關的,用于将用戶設定的timer超時時間規整到clock精度允許的數值上。clock_get和clock_set函數可以分别獲取和設定當前的時間,這個時間值是一個絕對時間值(對于時間軸而言,這個絕對時間也是相對的,是相對于該timeline的epoch而言),标記了當前時間點。clock計時有可能是不準确的,例如基于系統晶振的clock。一方面本身晶振的精度有限,時間累積長了會出現較大誤差。另外,晶振也會随着使用時間的推移、溫度的變化等等因素而導緻誤差。clock_adj函數允許系統根據外部的精确時間信息對本clock進行調整。nsleep和nsleep_restart這兩個成員函數可以讓進程sleep一段時間。timer_xxx系列函數是和POSIX interval timer相關,具體會在POSIX timer文檔中描述。

2、靜态定義的clock

static struct k_clock posix_clocks[MAX_CLOCKS];

posix_clocks數組定義了系統支持的所有的clock,相關的定義如下:

#define CLOCK_REALTIME 0

#define CLOCK_MONOTONIC 1

#define CLOCK_PROCESS_CPUTIME_ID 2

#define CLOCK_THREAD_CPUTIME_ID 3

#define CLOCK_MONOTONIC_RAW 4

#define CLOCK_REALTIME_COARSE 5

#define CLOCK_MONOTONIC_COARSE 6

#define CLOCK_BOOTTIME 7

#define CLOCK_REALTIME_ALARM 8

#define CLOCK_BOOTTIME_ALARM 9

#define CLOCK_SGI_CYCLE 10 /* Hardware specific */

#define CLOCK_TAI 11

#define MAX_CLOCKS 16

POSIX标準定義了4種類型的clock,CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID,其他是linux specific。如果一個clock的timeline是基于CPU運行時間的,那麼我們稱之CPU-time clock。CPU-time clock主要是用來為某個進程或者線程的執行時間進行計時的,一旦線程(進程)被切換,那麼該clock就停掉了,直到下次調度器切換回該線程(進程)執行。

各個具體的操作系統實現可以定義自己特有的clock,對于Linux kernel,我們定義了若幹種clock。CLOCK_MONOTONIC_RAW啟動時間點被設成0,此後一直不斷累加,而且能設定,不會随NTP調整。CLOCK_REALTIME_COARSE、CLOCK_MONOTONIC_COARSE的概念和CLOCK_REALTIME、CLOCK_MONOTONIC的概念是類似的,隻不過是精度是比較粗的版本。有時候,timer沒有必要要求那麼高的精度,那麼我們可以使用這種clock,從而可以獲取更好的性能。CLOCK_BOOTTIME和CLOCK_MONOTONIC類似,也是單調上述,在系統初始化的時候設定的基準數值是0,不過CLOCK_BOOTTIME計算系統suspend的時間,也就是說,不論是running還是suspend(這些都算是啟動時間),CLOCK_BOOTTIME都會累積計時,直到系統reset或者shutdown。

CLOCK_REALTIME_ALARM和CLOCK_BOOTTIME_ALARM主要用于Alarmtimer,這種timer是基于RTC的,更詳細的内容請參考本站Alarmtimer的文檔。CLOCK_TAI是原子鐘的時間,和基于UTC的CLOCK_REALTIME類似,不過沒有leap second。

用戶空間的clock_xxx函數會傳遞clock id的參數,在内核态,根據id作為index在posix_clocks數組中可以索引到對應的clock,然後調用clock對應的callback函數就OK了。當然基本意思就是這樣,具體實現如下:

static struct k_clock *clockid_to_kclock(const clockid_t id)

{

if (id < 0)

return (id & CLOCKFD_MASK) == CLOCKFD ?

&clock_posix_dynamic : &clock_posix_cpu;

if (id >= MAX_CLOCKS || !posix_clocks[id].clock_getres)

return NULL;

return &posix_clocks[id];

}

clockid_to_kclock這個函數用來将clock id和具體的posix clock的k_clock 數據結構對應起來。在linux平台上,clockid是int類型的數據,共32個bit,高29個bit用來保存一個pid(用于CPU-time clock)或者fd(動态分配的clock),bit 2用來說明該CPU-time clock是一個進程clock還是線程clock。bit 1和bit 0用來說明該clock id的類型:PROF=0, VIRT=1, SCHED=2, or FD=3。

當clock id小于0的時候,要麼是CPU-time clock,要麼是動态分配的clock,可以根據clock id的類型來判斷。CPU-time clock和動态分配的clock後面會具體介紹。

三、各種real timeclock的定義

系統初始化的時候會調用init_posix_timers函數對各種靜态定義的real time clock進行注冊。注:monotonic clock也是real time clock的一種,全稱是monotonic real time clock。

1、real time clock的定義如下(timer相關内容不在本文描述):

struct k_clock clock_realtime = {

.clock_getres = hrtimer_get_res,

.clock_get = posix_clock_realtime_get,

.clock_set = posix_clock_realtime_set,

.clock_adj = posix_clock_realtime_adj,

.nsleep = common_nsleep,

.nsleep_restart = hrtimer_nanosleep_restart,

};

real time clock需要調用timekeeping模塊的接口來獲取和設定當前時間值。對于獲取當前時間值的函數posix_clock_realtime_get而言,是調用ktime_get_real_ts函數,該函數是timekeeping模塊的接口函數,以timespec的格式回了real time clock的當前值。posix_clock_realtime_set函數主要是調用do_settimeofday這個timekeeping模塊的接口函數。posix_clock_realtime_adj是調用do_adjtimex接口函數來實現具體的功能。

納秒級别的sleep是通過高精度timer實現的,real time clock的精度和hrtimer相關,具體可以參考hrtimer相關文檔。

2、monotonic clock的定義如下:

struct k_clock clock_monotonic = {

.clock_getres = hrtimer_get_res,

.clock_get = posix_ktime_get_ts,

.nsleep = common_nsleep,

.nsleep_restart = hrtimer_nanosleep_restart,

};

monotonic clock沒有clock_set函數,不能被設定。通過ktime_get_ts這個timekeeping模塊的接口可以獲得monotonic clock的當前值。納秒級别的sleep以及精度的獲取函數和real time clock一樣。

3、monotonic raw clock的定義如下:

struct k_clock clock_monotonic_raw = {

.clock_getres = hrtimer_get_res,

.clock_get = posix_get_monotonic_raw,

};

posix_get_monotonic_raw函數是調用timekeeping模塊getrawmonotonic接口函數實現獲取monotonic raw clock當前時間數值的。和monotonic clock一樣,不能設定。和monotonic clock不同的是該clock沒有timer相關的callback函數。

4、coarse clock

struct k_clock clock_realtime_coarse = {

.clock_getres = posix_get_coarse_res,

.clock_get = posix_get_realtime_coarse,

};

struct k_clock clock_monotonic_coarse = {

.clock_getres = posix_get_coarse_res,

.clock_get = posix_get_monotonic_coarse,

};

這兩個clock的精度都是和tick相關的,KTIME_LOW_RES定義就是tick的納秒數值。clock_get函數分别調用current_kernel_time和get_monotonic_coarse獲取當前時間點的值。

CLOCK_BOOTTIME和CLOCK_TAI的clock實現非常簡單,大家自行閱讀代碼就OK了。

四、CPU-time clock

1、概述

從用戶空間的角度看,有兩種CPU-time clock的應用場景:

(1)調用clock_xxx函數并傳遞CLOCK_PROCESS_CPUTIME_ID或者CLOCK_THREAD_CPUTIME_ID給該函數

(2)調用clock_getcpuclockid或者pthread_getcpuclockid函數來獲取指定進程或者線程的clock id,之後調用clock_xxx函數并傳遞該clock id參數

應對第一種場景,系統初始化的時候會調用init_posix_cpu_timers函數對靜态定義的CPU-time clock進行注冊。對于第二種場景,内核靜态定義了一個clock_posix_cpu的clock來應對這種需求。

2、指定進程或者線程的CPU-time clock

内核靜态定義了一個clock如下(去掉了timer的callback函數):

struct k_clock clock_posix_cpu = {

.clock_getres = posix_cpu_clock_getres,

.clock_set = posix_cpu_clock_set,

.clock_get = posix_cpu_clock_get,

.nsleep = posix_cpu_nsleep,

.nsleep_restart = posix_cpu_nsleep_restart,

};

(1)獲取精度信息

static int posix_cpu_clock_getres(const clockid_t which_clock, struct timespec *tp)

{

int error = check_clock(which_clock);--------參數校驗

if (!error) {

tp->tv_sec = 0;

tp->tv_nsec = ((NSEC_PER_SEC HZ - 1) / HZ);

if (CPUCLOCK_WHICH(which_clock) == CPUCLOCK_SCHED) {

tp->tv_nsec = 1;

}

}

return error;

}

該函數的執行邏輯分成兩個部分,一部分是參數校驗,一部分是返回精度。參數校驗需要檢查的包括:

(a)clock id中的高29個bit包含了pid,獲取pid的代碼如下:

#define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))

從代碼可知,實際上并不是将pid放到高29個bit,而是将反碼保存到了高29個bit。為何保存反碼?這樣做為了确保clock id是一個負數(MSB是1),還記得clockid_to_kclock的實現嗎?要獲取該clock id的精度,要确保該pid的task存在

(b)如果該clock id是一個進程相關的(調用clock_getcpuclockid獲得),那麼這個進程id應該是一個實實在在的進程id。在linux kernel中,pid實際上是線程ID,POSIX标準的進程ID,也就是PID在内核中被成為線程組ID。因此,所謂一個“實實在在的進程id”就是說該線程的id(pid)和tgid一樣,該pid标識的線程是線程組leader。當然,就是獲取精度而已,實際上要求并不要那麼嚴格,也許該pid标識的線程leader會退出,因此實際上要求該pid标識的task有thread group leader就OK了。(這裡有可能理解有誤,TODO)

(c)如果該clock id是一個線程相關的(調用pthread_getcpuclockid獲得),那麼調用者必須和該線程(clock id中指明的那個線程)屬于一個進程(線程組)。

返回精度部分的代碼邏輯很簡單,對于PROF和VIRT類型的CPU-time clock,其精度是tick,對于SCHED類型,精度是1ns。

(2)獲取當前時間值

同樣的,首先需要從clock id中獲取pid的值,然後根據pid的值獲取對應的task sturct,如果pid等于0,那麼不需要費勁去尋找。得到task struct之後,可以調用posix_cpu_clock_get_task函數獲取時間值:

static int posix_cpu_clock_get_task(struct task_struct *tsk, const clockid_t which_clock,

struct timespec *tp)

{

int err = -EINVAL;

unsigned long long rtn;

if (CPUCLOCK_PERTHREAD(which_clock)) {---per 線程的cpu clock

if (same_thread_group(tsk, current))---必須和調用者是同一個線程組,也就是同一個進程

err = cpu_clock_sample(which_clock, tsk, &rtn);

} else {

if (tsk == current || thread_group_leader(tsk))---進程的cpu clock

err = cpu_clock_sample_group(which_clock, tsk, &rtn);

}

if (!err)

sample_to_timespec(which_clock, rtn, tp); ---給返回值賦值

return err;

}

這裡仍然存在校驗問題,也就是說是否允許調用者獲取該task的CPU-time clock。對于進程,隻允許調用者進程獲取自己的CPU-time clock,在多線程環境下,主線程(線程組leader)可以獲取整個進程的CPU-time clock信息。對于per線程的操作,必須和調用者是同一個線程組,也就是同一個進程。

(a)獲取線程的clock信息

static int cpu_clock_sample(const clockid_t which_clock, struct task_struct *p,

unsigned long long *sample)

{

switch (CPUCLOCK_WHICH(which_clock)) {

default:

return -EINVAL;

case CPUCLOCK_PROF:

*sample = prof_ticks(p);----獲取該task在用戶空間加上在kernel space的執行時間

break;

case CPUCLOCK_VIRT:

*sample = virt_ticks(p);----獲取該task在用戶空間的執行時間

break;

case CPUCLOCK_SCHED:

*sample = task_sched_runtime(p);----和調度器相關的cpu clock

break;

}

return 0;

}

計算進程或者線程在cpu上的執行時間是一個挺煩人的事,一方面想要精度高,另外一方面又不想計算量大。因此,實際上CPU-time clock有三種,CPUCLOCK_PROF和CPUCLOCK_VIRT這兩種都是比較粗略估計CPU執行時間的clock,它的工作原理就是在周期性tick中進行進程cpu time的統計,如果該tick是用戶态(timer中斷了用戶态程序的執行),那麼整個tick的時間都是該進程的用戶态執行時間。如果該tick是内核态,并且是用戶程序進行系統調用而陷入内核,那麼整個tick的時間都是該進程的系統态執行時間。

CPUCLOCK_SCHED clock和上面的方法不一樣,它的精度是納秒級别的,是在調度器上進行計算進程時間。具體的計算方法還是留到調度器文章中再描述吧。

(b)cpu_clock_sample_group函數概念類似,不過是統計一個進程上所有線程的時間而已。

3、CLOCK_PROCESS_CPUTIME_ID 類型的clock

struct k_clock process = {

.clock_getres = process_cpu_clock_getres,

.clock_get = process_cpu_clock_get,

.nsleep = process_cpu_nsleep,

.nsleep_restart = process_cpu_nsleep_restart,

};

process_cpu_clock_getres用來獲取時間精度,該函數實際是調用posix_cpu_clock_getres(PROCESS_CLOCK, tp)來完成的。process_cpu_clock_get用來獲取當前時間值,實際上是通過調用posix_cpu_clock_get完成。posix_cpu_clock_xxx函數在上一節中已經描述。

4、CLOCK_THREAD_CPUTIME_ID類型的clock

很簡單,大家自行學習吧。

五、動态分配clock

1、源由

某些硬件提供了計時的能力,可以實現成一個posix clock,同時,這些硬件又類似USB設備那樣可以熱拔插,這也就意味着該posix clock不能靜态定義。此外,除了标準的timer和clock相關的操作,這些提供計時能力的硬件還需要一些其他的類似字符設備界面的控制接口,在這樣的需求推動下,内核提供了dynamic posix clock。

2、dynamic posix clock

系統中的每一個dynamic posix clock用struct posix_clock來抽象,如下:

struct posix_clock {

struct posix_clock_operations ops;--------------(1)

struct cdev cdev;----------------------(2)

struct kref kref;

struct rw_semaphore rwsem;

bool zombie;------------------------(3)

void (*release)(struct posix_clock *clk);-------------(4)

};

(1)ops是該dynamic posix clock的操作函數集,分成兩個group,一個是timer(例如:timer_create、timer_delete等)以及clock操作相關(例如clock_gettime、clock_settime等),另外一個是普通字符設備的操作函數(例如:open、read、write等)。

(2)該dynamic posix clock對應的cdev數據結構。在struct posix_clock_operations中有一個owner,其實在cdev中也有一個指向moudle的owner成員,看起來似乎是重複定義了。同樣的疑問也存在與kref成員,因為在cdev中有kobject成員,kobject抽象了内核最基礎的對象類别,包括名字、引用計數等,因此,我覺得隻要struct posix_clock包括了cdev成員,struct posix_clock_operations中的owner以及struct posix_clock中的kref應該沒有存在的必要了。

(3)zombie記錄了底層硬件的狀态,對于hotplug的外設,有可能硬件被拔除。rwsem用來保護該狀态信息

(4)當reference count等于0的時候會調用release函數釋放dynamic posix clock占用的資源。

3、注冊和注銷

底層的有計時能力的硬件driver可以調用posix_clock_register和posix_clock_unregister來注冊或者注銷一個posix clock,注冊代碼如下:

int posix_clock_register(struct posix_clock *clk, dev_t devid)

{

int err;

kref_init(&clk->kref);

init_rwsem(&clk->rwsem);

cdev_init(&clk->cdev, &posix_clock_file_operations);-----VFS接口的操作函數集合

clk->cdev.owner = clk->ops.owner;

err = cdev_add(&clk->cdev, devid, 1);

return err;

}

VFS接口的操作函數集合都非常簡單,基本上都是struct posix_clock_operations上的字符設備操作函數集合上。這樣,用戶空間的程序可以通過标準的文件描述符進行設備操作。

4、clock和timer接口

通過clock_xxx或者timer_xxx函數可以指定clock id,對于dynamic posix clock可以通過下面的操作來生成一個dynamic posix clock ID:

#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)

其中fd是通過設備節點打開的那個有計時能力的硬件。在内核态會通過clockid_to_kclock操作将clock id轉換成

static struct k_clock *clockid_to_kclock(const clockid_t id)

{

if (id < 0)

return (id & CLOCKFD_MASK) == CLOCKFD ?

&clock_posix_dynamic : &clock_posix_cpu;

……

}

clock_posix_dynamic可以将dynamic posix clock ID轉換成對應的posix_clock,然後調用struct posix_clock_operations上的time和clock相關的函數即可。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关科技资讯推荐

热门科技资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved