任务(job)为相关组件和操作提供了一个异步执行的框架,该框架是有状态的(记录于任务描述符job descriptor ),可以记录异步调用执行到哪里等信息,并决定进一步操作;而下层函数调用不记录任务相关的状态(某些具体实现内部可能保存自身的状态),只通过函数参数输入输出任务状态信息。
job.c文件中定义的函数很多,因为要涵盖BMI、trove、flow、dev等多个组件的使用,但是不同组件具有相近的模式,我们可以将主要的函数分成如下几类(若干函数用前后缀表示,使用通配符“*”):
- 辅以时间管理器(time manager)的异步式
job_bmi_send
job_bmi_send_list
job_bmi_recv
job_bmi_recv_list
job_flow - 基于自定义队列的异步式
job_req_sched_post
job_req_sched_change_mode
job_req_sched_post_timer
job_req_sched_release
job_trove_bstream_*
job_trove_keyval_*
job_trove_dspace_*
job_trove_fs_* - 同步式
job_dev_write
job_dev_write_list - 基于线程管理器(thread manager)的异步式
job_bmi_unexp
job_dev_unexp
下面我们分别举例,分析各类函数源代码。同类中的各个函数非常接近,通常只是调用下层接口不同。
- 辅以时间管理器的异步式
此类函数完成对下层函数的异步调用,将未立即完成的任务加入时间管理器 。我们以函数1为例,分析此类函数的主要行为,同类其他函数完成的主要步骤几乎相同,只是针对不同的下层函数。
函数1. job_bmi_send_list
先从参数说起,省略掉的是与具体下层调用相关的参数(在第1行由上层调用传入,在第33、37行转给下层调用),而剩余的参数则基本出现在所有此类函数的定义中。参数中还需说明的是,第4行out_status_p指向一个任务状态,当本函数对应的异步调用当下即完成时,直接填写这个任务状态,告知调用者相关信息;第7行的timeout_sec是超时秒数,基于时间管理器的异步式调用函数均需此参数。除此之外,第2、3、6、8行的参数用于描述任务的相关信息,存入任务描述符(见如下步骤1)。
下面说明上述函数的主要行为,可分为四步:
- 创建一个任务描述符并保存任务相关信息(第20~29行)
注释中也提到,这个任务描述符用户任务无法立即完成时加入相关等待队列,其他情况下可能不发挥作用而直接注销掉。 - 调用对应的下层函数(第30~38行),并进行错误处理(第39~47行)
错误处理方式就是撤销任务描述符,并把错误号和相关信息通过任务状态指针out_status_p传出(第42~43行)。 - 处理同步的情况
即任务当即完成,处理方式是撤销任务描述符,把相关信息通过任务状态指针out_status_p传出(第51~53行) - 处理异步的情况
即需要等待任务执行,处理方式是传出任务描述符ID,并将任务描述符加入时间管理器队列(第63行,参见job_time_mgr_add )。
还要特别说明的是job_context_id类型的参数。我们可以发现,第6行传入的context_id,只是用于在第24行记入任务描述符,并不传入下层调用。可见,该参数是用于本层往上区分任务来源(这也可以说是一种状态信息),对job层往下并不可见;而下层调用(第33、37行)只传入全局值global_bmi_context,即所有job层的BMI调用都视作来自同一个全局上下文,不加区分。
- 基于自定义队列的异步式
此类函数与上类较为相似,但使用下层组件自备的队列及其相应的对未完成任务的处理方式,函数内不涉及job提供的队列。通过下面函数2可以看到,绝大部分代码和函数1都是对应的,出去调用底层函数不同,几乎唯一的差异就是最后(第59行)没有调用任务时间管理器的job_time_mgr_add函数。
函数2. job_trove_keyval_read
- 同步式
同步式即通过job调用阻塞函数,函数操作完全结束时才继续执行。我们以job_dev_write_list为例,如函数2。
函数3. job_dev_write_list
注释中也提到,之所以放到job的框架中,也是为日后实现异步版本提供方便(不必更改调用函数)。另外,注意到最终返回值为1,遵从job中异步函数返回值的约定:0表示函数执行成功,1表示任务已立即完成,负数表示错误码。
第13~14行调用实际操作函数,这里是阻塞式,等到任务完成函数返回。不论错误处理(第18~20行),还是正常流程(第23~26行),都都是写错误码(error code)及其他相关信息,并通过任务状态指针out_status_p传出;确切说,这里没有在job层保留状态信息,因为任务已经完成。
- 基于线程管理器的异步式
此类函数与函数1也极为相似,差别也在于最后的操作。当没有意外(BMI中称作unexpected message, dev中称作unexpected receive)到达时,将等待意外任务链入全局变量dev_unexp_queue中(第58行),同时注册处理函数(第61行);与此同时,增加了两个全局计数变量的值——dev_unexp_pending_count(第59行) 和dev_unexp_count(第61行调用的函数中)。然后将后续处理工作较给线程管理器(参见src/io/job/thread-mgr )。
函数4. job_dev_unexp