在磁盘测试中,fio是最常用的测试的工具,其下载网址为https://github.com/axboe/fio;
对于fio,其测试命令有许多,这个大家很容易就可以查到,此处不讲解具体的测试命令,
而是讲一下大概的源码框架。
Fio的入口函数在fio.c的main函数,其结构如下所示:
fio.c文件:
int main(int argc,char*argvO,char*envp0){if(initialize_fio(envp)) //libfio.c 文件————进行fio初始化,有64位对齐、大端小端模式、hash、文件锁等return 1;if(fio_server_create_sk_key()) // server.c文件-为线程创建私有数据TSD-goto done;if(parse_options(argc,argv)) // read-to-pipe-async 文件-解析main函数的参数goto done_key;fio_time_init(); // 初始化时钟相关if(nr_clients){set_genesis_time();if(fio_start_all_clients()) // 与一些驱动进行远程连接操作,例如SPKDgoto done_key;ret=fio_hanuie_clienis(&tio_cliei.t_ops);}elseret=fio_backend(NULL); // backend.c 文件 fio 逻辑走向,开始处理问题
}
fio_server_create_sk_key()函数是为线程创建私有数据,关于线程私有数据的概念可以参
考该链接-https://www.cnblogs.com/smarty/p/4046215.html;
接下来看一下fio_backend()函数——backend.c文件:
int fio_backend(struct sk_out *sk_out) {...... //加载文件、mmap 映射、锁初始化、获取时间、创建helper线程、run_threads(sk_out); //会建立主要的I0线程...... //用于一些变量的销毁、环境的收尾
}
该函数中最主要的是run_threads(sk_out)函数,该函数会根据需要要启动jobs和处理 jobs,
比较重要,接下来看一下这个函数:
backend.c 文件:
static void run_threads(struct sk_out *sk_out)
{...... // 在io线程之前设置其他线程、设置信号量
、缓冲、检查挂载、设置文件处理顺序、修改线程状态}else{pid_t pid;dprint(FD_PROCESS,"will fork\n");pid=fork();if(!pid){int ret;ret=(int)(uintptr_t)thread_main(fd); // 建立IO提交过程_exit(ret);}else if(i==fio_debug_jobno)*fio_debug_jobp=pid;}...... // 线程状态收尾
}
在上面中,最关键的是thread_main(fd)函数,其主要是建立了10提交过程;
backend.c 文件:
static void*thread_main(void *data)
{...... //获取任务 pid,初始化时钟、锁,设置 uid,设置优先级(会影响内存分配),参数转换/初始化,if(td->o.verify_only && td_write(td))verify_bytes=do_dry_run(td);else{do_io(td,bytes_done); // 进行IO的提交和处理过程if(!ddir_rw_sum(bytes_done)){fio_mark_td_terminate(td);verify_bytes=0;}else{verify_bytes=bytes_done[DDIR_WRITE]+bytes_done[DDIR_TRIM];}}...... //超时保护,线程竞争锁,err处理
}
在该函数中,最重要的是 do_io(td,bytes_done)这个函数,其进行10的提交和进一步的处
理,接下来看一下该该函数:
static void do_io(struct thread_data *td,uint64_t *bytes_done)
{...... //写模式字节数计算、10异常判断、验证end_io、记录IO动作else{ret=io_u_submit(td,io_u); // 调用实际存储引擎注册的io_submit 函数if(should_check_rate(td))td->rate_next_io_time[ddir] = usec_for_io(td,ddir);if (io_queue_event(td,io_u,&ret,ddir,&bytes_issued,0,&comp_time)) // 判断当前是否还有没有处理完的io eventsbreak; //判断是否进一步处理reap:full=queue_full(td)||(ret==FIO_Q_BUSY&& td->cur_depth);if(full || io_in_polling(td))ret=wait_for_completions(td,&comp_time); //会调用后端实际存储引擎注册的getevents 函数......}
}
do_io 函数主要进行io_u的处理和排队,在此过程中会检查速率和错误,其返回被处理完的
字节数;该函数中有三处关键点,分别为io_u_submit()、io_queue_event()和
wait_for_completions().
首先看io_u_submit()函数:
static enum fio_q_status io_u_submit(struct thread_data *td,struct io_u *io_u)
{// 确保有一个IO运行在队列中if(td->o.serialize_overlap && td->cur_depth>1&&in_flight_overlap(&td->io_u_all,io_u))return FIO_Q_BUSY;return td_io_queue(td,io_u);
}enum fio_q_status td_io_queue(struct thread_data *td,struct io_u xio_u)
{//检查并释放锁、保存write io、错误处理、O_DIRECT添加警告声明等
}
接着看io_queue_event()函数:
int io_queue_event(struct thread_data*td,struct io_u *io_u,int*ret.
enum fio_ddir ddir,uint64_t*bytes_issued,int from_verify.
struct timespec*comp_time)
{//根据状态来确定处理逻辑-FIO_Q_COMPLETED/FIO_Q_QUEUED/FIO_Q_BUSY
}
接下来看下 wait_for_completions()函数:
static int wait_for_completions(struct thread_data *td,struct timespec *time)
{//队列满,则处理一个事件
do{ret=io_u_queued_complete(td,min_evts); // io_u.c文件if(ret<0)break:}while(full &&(td->cur_depth>td->o.iodepth_low));
}int io_u_queued_complete(struct thread_data *td,int min_evts) //调用异步10引擎来完成
min_events 事件
{ret=id_io_getevents(td,min_evts,td->o.iodepth_batch_complete_max,tvp); //ioengines.c
文件-修复min_evts的min和max
}
上面就是fio的大概框架,更具体的需要研究每一个函数的细枝末节,本文不做过多讲解。
参考链接:
FIO线程模型【附源码】_存储之厨_51CTO博客FIO线程模型【附源码】,分享了fio测试框架的主要流程https://blog.51cto.com/xiamachao/2409539