blktrace 工具集使用 及其实现原理
文章目錄
- 工具使用
- 原理分析
- 內(nèi)核I/O棧
- blktrace 代碼做的事情
- 內(nèi)核調(diào)用 ioctl 做的事情
- BLKTRACESETUP
- BLKTRACESTOP
- BLKTRACETEARDOWN
- 內(nèi)核 調(diào)用blk_register_tracepoints 之后做的事情
- 參考
最近使用blktrace 工具集來(lái)分析I/O 在磁盤(pán)上的一些瓶頸問(wèn)題,特此做一個(gè)簡(jiǎn)單的記錄。
工具用起來(lái)很簡(jiǎn)單,但越向底層看,越復(fù)雜。。。。。。越發(fā)現(xiàn)自己的無(wú)知
工具使用
blktrace 擁有如下幾個(gè)工具集合:
安裝的話也很簡(jiǎn)單:
sudo yum install blktrace iowatcher -y
-
其中
blktrace工具 主要根據(jù)用戶(hù)輸入的磁盤(pán)設(shè)備,收集這個(gè)設(shè)備上每個(gè)IO調(diào)度情況,收集的過(guò)程是交給當(dāng)前服務(wù)器的每一個(gè)core來(lái)做的,最后每一個(gè)core將各自處理的請(qǐng)求 收集到的結(jié)果保存在一個(gè)binary文件中。sudo blktrace -d /dev/nvme0n1 -o nvme-trace -w 60收集設(shè)備/dev/nvme0n1上的io 情況 60秒,將結(jié)果保存到nvme-trace文件中 -
blkparse工具 主要是將之前抓取的多個(gè)core的binary文件合并為一個(gè)文件blkparse -i nvme-trace -d nvme-trace.bin -o nvme-trace.txt,將nvme-trace開(kāi)頭的所有文件合并為一個(gè)nvme-trace.bin,這個(gè)過(guò)程中的輸出放在nvme-trace.txt中。 -
btt工具,blkparse 解析的數(shù)據(jù)文件 雖然已經(jīng)有了一些匯總信息,但還是不易讀,比如我們想知道磁盤(pán)I/O在每一個(gè)階段耗時(shí)分布,從blkparse的解析中很難看出來(lái)的。blkparse 的匯總信息如下:
通過(guò)btt工具來(lái)進(jìn)行計(jì)算:
btt -i nvme-trace.bin -o btt.txt
其中
btt.txt.avg就是我們想要的請(qǐng)求信息分布情況
也可以通過(guò)
btt -A -i nvme-trace.bin | less看到每一個(gè)I/O線程各個(gè)階段的IO延時(shí)情況
計(jì)算blktrace工具抓到的分位數(shù)指標(biāo)(p50,p99,p995,p9999 等)腳本如下,輸入的參數(shù)是通過(guò)btt -i nvme-trace.bin -l d2c_data生成的請(qǐng)求全集文件:#!/bin/bash input=$1num=`cat $input |wc -l` if [ $num -eq 0 ];thenecho "input is null "exit -1 fip50=$(echo "$num * 0.5" | bc) p50=${p50%.*} # to int p99=$(echo "$num * 0.99" | bc) p99=${p99%.*} p995=$(echo "$num * 0.995" | bc) p995=${p995%.*} p9999=$(echo "$num * 0.9999" | bc) p9999=${p9999%.*}echo "lines -- p50: $p50 p99 : $p99 p995: $p995 p9999: $p9999 total: $num "cat "$input" | awk -F. '{print $3}' | sort > buff.txtecho "p50 " sed -n " $p50 p" buff.txt echo "p99" sed -n " $p99 p" buff.txt echo "p995 " sed -n "$p995 p" buff.txt echo "p9999 " sed -n "$p9999 p" buff.txt -
我們有抓取的I/O的歷史數(shù)據(jù),那同樣可以用
iowather來(lái)將歷史的io變化情況用圖形展示出來(lái),包括磁盤(pán)帶寬、延時(shí)等
iowatcher -t nvme-trace.bin -o nvme-trace.svg解析blkparse合并的文件,輸出到nvme-trace.svg中
-
如果你僅僅想看看磁盤(pán)的I/O塊大小,都是一些什么I/O,不想這么麻煩,可以直接
btrace /dev/nvme0n1這樣,會(huì)將打印輸出到標(biāo)準(zhǔn)輸出中
-
如果你想在塊基礎(chǔ)上看看磁盤(pán)延時(shí)/塊大小的分布,那
blkiomon就比較適用了
blktrace /dev/nvme0n1 -a issue -a complete -w 3600 -o - | blkiomon -I 1 -h test,這里只抓取complete的io,請(qǐng)求的結(jié)果分析(延時(shí)/塊大小)就以直方圖的形態(tài)非常方便得被展示出來(lái)。
當(dāng)然,以上blktrace,blkparse,btrace 都可以?xún)H僅抓單獨(dú)類(lèi)似的io請(qǐng)求,包括只抓取write, read, sync, issue等(可以通過(guò)man blktrace查看masks支持的action。),這樣我們就能夠更近一步得區(qū)分每一種類(lèi)型的請(qǐng)求,方便我們從底層排查問(wèn)題。
關(guān)于傳統(tǒng)的btrace, blkparse等解析data文件之后的輸出 含義內(nèi)容,直接看網(wǎng)友們貼的這張圖就可以了:
主要的幾個(gè)Event信息含義如下:
- Q: 即將生成I/O
- G: 生成I/O 請(qǐng)求
- I: I/O 請(qǐng)求進(jìn)入scheduler 隊(duì)列
- D: I/O 請(qǐng)求進(jìn)入driver
- C: I/O 執(zhí)行完畢
原理分析
洋洋灑灑,工具如何使用,介紹了一大串,能夠節(jié)省一丟丟大家的時(shí)間,man手冊(cè)已經(jīng)很通用了,使用上就沒(méi)什么需要探索的了。但是能夠真正讓大家看到收獲的其實(shí)是工具背后的原理,為什么blktrace能夠?qū)崟r(shí)得追蹤到每一個(gè)io請(qǐng)求,它追蹤的請(qǐng)求個(gè)數(shù)/大小是否準(zhǔn)確,是否有請(qǐng)求會(huì)被遺漏?這一些請(qǐng)求在操作系統(tǒng)I/O架構(gòu)中每一個(gè)階段處于什么樣的位置,內(nèi)核在做什么事情?這一些問(wèn)題如果我們每一個(gè)都仔細(xì)去探索,背后則是整個(gè)操作系統(tǒng)內(nèi)核I/O棧的龐大調(diào)度邏輯,都會(huì)讓我們對(duì)內(nèi)核I/O有更為深刻的理解,有了底層架構(gòu)的知識(shí)才能幫助我們更好得設(shè)計(jì)上層應(yīng)用。 畢竟,底層架構(gòu)的每一行代碼,每一個(gè)算法都是無(wú)數(shù)開(kāi)發(fā)者精心雕琢的表現(xiàn)。
不多說(shuō),直接進(jìn)入正題。
內(nèi)核I/O棧
blktrace 抓取的IO 內(nèi)核棧的層級(jí)如下:
blktrace統(tǒng)計(jì)的主要是I/O進(jìn)入通用塊層 --> I/O 調(diào)度層 --> 塊設(shè)備驅(qū)動(dòng)層 完成落盤(pán)返回的整個(gè)過(guò)程,上圖并未體現(xiàn)通用塊層,其實(shí)是在I/O Scheduler之上的一層I/O封裝。
- 通用塊層 : 接受direct_io/ page_cache flush下來(lái)的請(qǐng)求,做一層請(qǐng)求封裝,一般是4k大小。
- I/O 調(diào)度層: 將請(qǐng)求加入調(diào)度隊(duì)列,通過(guò)一系列調(diào)度算法來(lái)調(diào)度封裝好的I/O請(qǐng)求 到對(duì)應(yīng)的device-driver(sata/nvme/iscsi等)
- 塊設(shè)備驅(qū)動(dòng)層:這里就是每一個(gè)物理塊設(shè)備封裝好的對(duì)接自己物理磁盤(pán)空間的內(nèi)核驅(qū)動(dòng),請(qǐng)求到這里會(huì)按照對(duì)應(yīng)設(shè)備的邏輯進(jìn)入到底層物理磁盤(pán)中
知道了大體的I/O棧,也就清楚了大概一個(gè)I/O請(qǐng)求從page-cache或者direct_io 到磁盤(pán)所經(jīng)歷的大體層,這個(gè)時(shí)候也就對(duì)blktrace輸出信息的Event的幾個(gè)字段有一定的理解了(Q,G,I,D,C),都是對(duì)應(yīng)的請(qǐng)求進(jìn)入到了I/O棧中的哪一層。
Blktrace 追蹤過(guò)程大體可以用如下這張官方的圖來(lái)描述:
blktrace 啟動(dòng)追蹤的時(shí)候會(huì)讓每一個(gè)cpu(每一個(gè)請(qǐng)求都是由對(duì)應(yīng)的cpu來(lái)調(diào)度處理的)綁定一個(gè)relay-channel,通過(guò)ioctl下發(fā)的觸發(fā)信息會(huì)讓內(nèi)核將每一個(gè)請(qǐng)求的信息通過(guò)trace函數(shù)添加到relay-channel對(duì)應(yīng)的trace文件,當(dāng)blktrace停止追蹤時(shí)會(huì)告知內(nèi)核將relay-flush 每一個(gè)relay-channel,將trace文件信息拷貝到用戶(hù)態(tài)。
那blktrace 是如何從外部獲取到這一些請(qǐng)求的信息的呢?接著往下看,后面的描述會(huì)整體從內(nèi)核代碼角度告訴你這個(gè)外部工具如何在不影響內(nèi)核I/O性能的情況下拿到這一些I/O 請(qǐng)求的詳細(xì)信息的。
blktrace 代碼做的事情
源碼GitHub: https://github.com/efarrer/blktrace
如果不使用blktrace 網(wǎng)絡(luò)模式的情況下(是的,blktrace 支持抓取遠(yuǎn)端服務(wù)器的磁盤(pán)請(qǐng)求信息,blktrace -l 啟動(dòng)server, blktrace -h ip指定抓取的ip),會(huì)走如下調(diào)用棧邏輯:
main -- blktrace.crun_tracerssetup_buts -- 初始化一些配置start_tracers -- 為每一個(gè)cpu 創(chuàng)建一個(gè)tracer線程,獲取io信息start_buts -- 開(kāi)啟記錄,將請(qǐng)求詳細(xì)信息記錄到初始化的文件中stop_tracers -- 終止追蹤
其中的主體操作都是通過(guò)ioctl來(lái)向內(nèi)核發(fā)送觸發(fā)信息:
ioctl(dpp->fd, BLKTRACESETUP, &buts) -- 發(fā)送 初始化配置
ioctl(dpp->fd, BLKTRACESTART) -- 發(fā)送 啟動(dòng)配置
ioctl(dpp->fd, BLKTRACESTOP) -- 發(fā)送終止配置
ioctl(fd, BLKTRACETEARDOWN) -- 發(fā)送down 配置,由內(nèi)核回寫(xiě)結(jié)果到trace-data文件
這個(gè)時(shí)候,每一個(gè)觸發(fā)配置 的ioctl系統(tǒng)調(diào)用會(huì)進(jìn)入內(nèi)核來(lái)做一些對(duì)應(yīng)的事情。
這一些邏輯也可以通過(guò)strace blktrace -d /dev/nvme0n1命令來(lái)追蹤:
open("/dev/nvme0n1", O_RDONLY|O_NONBLOCK) = 3
statfs("/sys/kernel/debug", {f_type=DEBUGFS_MAGIC, f_bsize=4096, f_blocks=0, f_bfree=0, f_bavail=0, f_files=0, f_ffree=0, f_fsid={0, 0}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_RELATIME}) = 0
rt_sigaction(SIGINT, {0x403410, [INT], SA_RESTORER|SA_RESTART, 0x7fa1a4fd0270}, {SIG_DFL, [], 0}, 8) = 0 # strace main函數(shù)注冊(cè)的信號(hào)
rt_sigaction(SIGHUP, {0x403410, [HUP], SA_RESTORER|SA_RESTART, 0x7fa1a4fd0270}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGTERM, {0x403410, [TERM], SA_RESTORER|SA_RESTART, 0x7fa1a4fd0270}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGALRM, {0x403410, [ALRM], SA_RESTORER|SA_RESTART, 0x7fa1a4fd0270}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7fa1a4fd0270}, {SIG_DFL, [], 0}, 8) = 0
ioctl(3, BLKTRACESETUP, {act_mask=65535, buf_size=524288, buf_nr=4, start_lba=0, end_lba=0, pid=0, name="nvme0n1"}) = 0
ioctl(3, BLKTRACESTART)
...
內(nèi)核調(diào)用 ioctl 做的事情
這里不討論ioctl整個(gè)系統(tǒng)調(diào)用的邏輯,細(xì)節(jié)還是很多的。主要看一下blktrace 調(diào)用ioctl發(fā)送相應(yīng)的state后內(nèi)核做的事情。
內(nèi)核代碼版本:3.10.1
ioctl 系統(tǒng)調(diào)用針對(duì)以上state的處理如下:
int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,unsigned long arg)
{...case BLKTRACESTART:case BLKTRACESTOP:case BLKTRACESETUP:case BLKTRACETEARDOWN:ret = blk_trace_ioctl(bdev, cmd, (char __user *) arg);break;...
}
通過(guò)blk_trace_ioctl的邏輯如下:
int blk_trace_ioctl(struct block_device *bdev, unsigned cmd, char __user *arg)
{struct request_queue *q;int ret, start = 0;char b[BDEVNAME_SIZE];q = bdev_get_queue(bdev);if (!q)return -ENXIO;mutex_lock(&bdev->bd_mutex);switch (cmd) {case BLKTRACESETUP:bdevname(bdev, b);// 初始化配置ret = blk_trace_setup(q, b, bdev->bd_dev, bdev, arg);break;
#if defined(CONFIG_COMPAT) && defined(CONFIG_X86_64)case BLKTRACESETUP32:bdevname(bdev, b);ret = compat_blk_trace_setup(q, b, bdev->bd_dev, bdev, arg);break;
#endifcase BLKTRACESTART:start = 1; // 設(shè)置啟動(dòng)追蹤的標(biāo)記case BLKTRACESTOP:// 結(jié)束追蹤ret = blk_trace_startstop(q, start);break;case BLKTRACETEARDOWN:// 將trace文件拷貝到用戶(hù)目錄ret = blk_trace_remove(q);break;default:ret = -ENOTTY;break;}mutex_unlock(&bdev->bd_mutex);return ret;
}
BLKTRACESETUP
啟動(dòng)的時(shí)候會(huì)進(jìn)入到這個(gè)函數(shù)blk_trace_setup,主要?jiǎng)?chuàng)建以下幾個(gè)文件:
- 創(chuàng)建
/sys/kernel/debug/block目錄 - 在上面的目錄下創(chuàng)建一個(gè)設(shè)備目錄
nvme0n1 - 在設(shè)備目錄下創(chuàng)建
dropped文件,如果需要relay-channel flush的話會(huì)將這個(gè)文件置為true - 為每一個(gè)cpu綁定一個(gè)trace 文件,接受relay-channel 的請(qǐng)求輸出,一般為
traceid - 注冊(cè)
/sys/kernel/debug/tracing/events/block下的events,也就是我們前面看到的請(qǐng)求輸出Event(Q,I,D,C等),其實(shí)就是這一些events
代碼如下:
int do_blk_trace_setup(struct request_queue *q, char *name, dev_t dev,struct block_device *bdev,struct blk_user_trace_setup *buts)
{struct blk_trace *old_bt, *bt = NULL;struct dentry *dir = NULL;int ret, i;...mutex_lock(&blk_tree_mutex);if (!blk_tree_root) {blk_tree_root = debugfs_create_dir("block", NULL); // 創(chuàng)建/sys/kernel/debug/block目錄if (!blk_tree_root) {mutex_unlock(&blk_tree_mutex);goto err;}}mutex_unlock(&blk_tree_mutex);dir = debugfs_create_dir(buts->name, blk_tree_root); // 創(chuàng)建/sys/kernel/debug/block/nvme0n1目錄if (!dir)goto err;bt->dir = dir;bt->dev = dev;atomic_set(&bt->dropped, 0);ret = -EIO;bt->dropped_file = debugfs_create_file("dropped", 0444, dir, bt, // 在創(chuàng)建好的目錄下創(chuàng)建dropped文件&blk_dropped_fops);if (!bt->dropped_file)goto err;bt->msg_file = debugfs_create_file("msg", 0222, dir, bt, &blk_msg_fops); // 創(chuàng)建msg文件if (!bt->msg_file)goto err;bt->rchan = relay_open("trace", dir, buts->buf_size, // 為每個(gè)cpu創(chuàng)建一個(gè)trace文件buts->buf_nr, &blk_relay_callbacks, bt);if (!bt->rchan)goto err;bt->act_mask = buts->act_mask;if (!bt->act_mask)bt->act_mask = (u16) -1;blk_trace_setup_lba(bt, bdev);...if (atomic_inc_return(&blk_probes_ref) == 1)blk_register_tracepoints(); // 注冊(cè)并追蹤/sys/kernel/debug/tracing/events/block 的events,內(nèi)核開(kāi)始追蹤請(qǐng)求return 0;
err:blk_trace_free(bt);return ret;
}
BLKTRACESTOP
blk_trace_startstop執(zhí)行blktrace的開(kāi)關(guān)操作,停止過(guò)后將per cpu的relay chanel強(qiáng)制flush出來(lái)。
int blk_trace_startstop(struct request_queue *q, int start)
{int ret;struct blk_trace *bt = q->blk_trace;
...ret = -EINVAL;if (start) { // 這個(gè)標(biāo)記是BLKTRACESTART的時(shí)候設(shè)置的,如果沒(méi)有抓取結(jié)束if (bt->trace_state == Blktrace_setup ||bt->trace_state == Blktrace_stopped) {blktrace_seq++;smp_mb();bt->trace_state = Blktrace_running;trace_note_time(bt); // 用戶(hù)可能會(huì)傳入一個(gè)抓取的時(shí)間ret = 0;}} else {if (bt->trace_state == Blktrace_running) {bt->trace_state = Blktrace_stopped;relay_flush(bt->rchan); // relay flush 刷數(shù)據(jù)到trace文件ret = 0;}}return ret;
}
BLKTRACETEARDOWN
釋放blktrace設(shè)置創(chuàng)建的buffer、刪除相關(guān)文件節(jié)點(diǎn),并去注冊(cè)trace events。
static void blk_trace_cleanup(struct blk_trace *bt)
{blk_trace_free(bt);if (atomic_dec_and_test(&blk_probes_ref))blk_unregister_tracepoints();
}int blk_trace_remove(struct request_queue *q)
{struct blk_trace *bt;bt = xchg(&q->blk_trace, NULL);if (!bt)return -EINVAL;if (bt->trace_state != Blktrace_running)blk_trace_cleanup(bt); // 注銷(xiāo)之前注冊(cè)的/sys/kernel/debug/tracing/events/block 的eventsreturn 0;
}
到此整個(gè)blktrace 通過(guò)ioctl 調(diào)度起來(lái)自己的任務(wù),并能夠取到自己想要的數(shù)據(jù)。
總結(jié)成如下這一張圖來(lái)概述整個(gè)blktrace的過(guò)程:
當(dāng)然取數(shù)據(jù)的過(guò)程是通過(guò)向內(nèi)核注冊(cè)一些block的events。
接下來(lái)我們核心看一下這一些events是如何讓內(nèi)核將數(shù)據(jù)給出來(lái)的?
內(nèi)核 調(diào)用blk_register_tracepoints 之后做的事情
在這個(gè)函數(shù)內(nèi)部會(huì)逐個(gè)注冊(cè)每一個(gè)/sys/kernel/debug/tracing/events/block下的事件,這里會(huì)通過(guò)一個(gè)宏定義 進(jìn)入
#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args) \extern struct tracepoint __tracepoint_##name; \ // 這里是聲明一些外部的trace point變量static inline void trace_##name(proto) \ // 定義一些trace point用到的公共函數(shù){ \if (static_key_false(&__tracepoint_##name.key)) \ // 如果打開(kāi)了trace point__DO_TRACE(&__tracepoint_##name, \ // 便利trace point中的樁函數(shù)(外部聲明的樁函數(shù))TP_PROTO(data_proto), \TP_ARGS(data_args), \TP_CONDITION(cond),,); \} \__DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args), \ PARAMS(cond), PARAMS(data_proto), PARAMS(data_args)) \static inline int \register_trace_##name(void (*probe)(data_proto), void *data) \ // 注冊(cè)trace point{ \return tracepoint_probe_register(#name, (void *)probe, \data); \} \static inline int \unregister_trace_##name(void (*probe)(data_proto), void *data) \{ \return tracepoint_probe_unregister(#name, (void *)probe, \ // 注銷(xiāo)trace pointdata); \} \static inline void \check_trace_callback_type_##name(void (*cb)(data_proto)) \{ \}
而在block.h中已經(jīng)預(yù)定義好了一些列trace io需要的樁函數(shù),類(lèi)似如下:
TRACE_EVENT(block_bio_complete,TP_PROTO(struct request_queue *q, struct bio *bio, int error),TP_ARGS(q, bio, error),TP_STRUCT__entry(__field( dev_t, dev )__field( sector_t, sector )__field( unsigned, nr_sector )__field( int, error )__array( char, rwbs, RWBS_LEN)),TP_fast_assign(__entry->dev = bio->bi_bdev->bd_dev;__entry->sector = bio->bi_sector;__entry->nr_sector = bio_sectors(bio);__entry->error = error;blk_fill_rwbs(__entry->rwbs, bio->bi_rw, bio->bi_size);),TP_printk("%d,%d %s %llu + %u [%d]",MAJOR(__entry->dev), MINOR(__entry->dev), __entry->rwbs,(unsigned long long)__entry->sector,__entry->nr_sector, __entry->error)
);
而在我們前面說(shuō)的blk_register_tracepoints函數(shù)中會(huì)調(diào)用:
ret = register_trace_block_bio_complete(blk_add_trace_bio_complete, NULL); 對(duì)block_bio_complete進(jìn)行注冊(cè),注冊(cè)之后相當(dāng)于上面宏定義中打開(kāi)了針對(duì)當(dāng)前name的trace point,然后block_bio_complete這個(gè)trace event函數(shù)會(huì)被放在對(duì)應(yīng)的I/O連路上(已經(jīng)在主要的I/O連路上了,只是如果我們注冊(cè)了event,那就會(huì)在主體鏈路打印它的追蹤信息),而如果不需要開(kāi)啟的話也就是不注冊(cè)事件函數(shù)則基本不消耗性能。
// 電梯調(diào)度算法的入口
void __elv_add_request(struct request_queue *q, struct request *rq, int where)
{trace_block_rq_insert(q, rq);blk_pm_add_request(q, rq);...
}
說(shuō)到打印,這也就是以上tracepoint 的核心目的,內(nèi)核模塊太多,我們想要將內(nèi)部調(diào)試信息打出來(lái)到文件肯定不現(xiàn)實(shí),為了方便調(diào)試,這里的trace point就是將內(nèi)核中各個(gè)模塊的printk信息 打印到ring_buffer中,這里面的數(shù)據(jù)只通過(guò)debugfs才能夠獲取到。
blktrace 則會(huì)通過(guò)blk追蹤器將每個(gè)cpu 的ring_buffer數(shù)據(jù)綁定一個(gè)trace-data文件,后續(xù)完成追蹤之后將這一些文件從debugfs拷出來(lái)。
到此我們大體知道了內(nèi)核如何將I/O請(qǐng)求的信息暴漏出來(lái)給用戶(hù)讀取,其實(shí)就是維護(hù)了系列trace-event,用戶(hù)注冊(cè)之后就開(kāi)啟追蹤,內(nèi)核會(huì)在trace-event函數(shù)中打印每個(gè)請(qǐng)求的情況到一個(gè)ring-buffer中,用戶(hù)通過(guò)debug-fs(這里其實(shí)是blktrace 自己去debug-fs)將打印的數(shù)據(jù)取出來(lái)。
當(dāng)然,內(nèi)核的trace_event整體的宏設(shè)計(jì)還是比較復(fù)雜的,宏的易讀性雖然不是特別好,但人家能夠在編譯時(shí)展開(kāi),避免了程序運(yùn)行時(shí)的函數(shù)入出棧,對(duì)程序執(zhí)行的效率還是有很大的好處的。
參考
https://blog.csdn.net/geshifei/article/details/94360470
總結(jié)
以上是生活随笔為你收集整理的blktrace 工具集使用 及其实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 上海欢乐谷万圣节夜场期间地铁接驳车最晚到
- 下一篇: cannot find main mod