目录
scheduler介绍
scheduler使用heap(最小堆)数据结构存储events。event结构体只要两个成员:time和job。最小堆就是根节点的time for fire the event值最小,即根节点将被第一个执行。
scheduler队列给事件设置了时间,随后传给processor。
scheduler是通过堆来实现的。堆是一种特殊的树状数据结构满足以下属性:
如果B是A的子节点,那么key(A)>=或者<=key(B)。也就是最大的或者最小的key的元素就是堆的根节点(最大堆或最小堆)。
我们使用最小堆的key作为一个事件被执行的unix绝对时间。所以根节点总是要被执行事件的下一个,每次都是根节点被执行。scheduler的早期实现使用一个有序链表来存储事件。优点是删除下一个事件是相当快的,添加一个事件也是相当快的。问题是添加一个事件在任意节点之间会变慢,特别是事件的数量增长特别快的时候。对于每个连接都会有几个事件:IKE-rekey,NAT-keepalive,retransmissions,expire(半打开)等等。所以一个网关可能不得不处理上千个并发的连接,有不得不尽快处理大量事件的队列。
锁机制使情况变的更糟,为了保护线程安全,当有事件要入队列的时候,没有事件可以被处理,所以使插入动作变快就显得非常重要。
那就是堆树的优势了。添加一个元素到堆树的时间复杂度为O(log n),换句话说,删除根节点也需要O(log n)的时间复杂度。以1000个事件为例,插入一个新的事件到链表里需要1000次比较,在堆树的实现里,最糟糕的情况也就13.3次比较,这是显著地提高。实现的原理是用一个二叉树映射到一维数组上来存储元素。这样减少了存储开销并简化了遍历。位置n节点的子节点放在了位置2n和位置2n+1处(同样位置n节点的父节点在位置[n2]处)。因而上下遍历这棵树被简化为简单索引值的计算。
向堆树添加一个新节点的流程是这样的:
堆树总是从左向右塞,直到一排塞满了,开始往下一排塞。映射到一个数组就像新的节点放在数组第一个空位置那样简单。在一维数组里的位置数就等于在堆树里节点的个数。随后堆树开始调整直到满足,比如这个新的节点不得不在树里冒泡排序直到父亲节点的值更小或者这个节点变成了树的新的根节点。从堆树中删除下一个节点流程更简单:
事件本身是根节点或者存储在数组最前面的节点。在删除了它后,根节点不得不被替代,堆树又要做调整。这件事是通过移动最低端的节点(最后一排,最右边的节点)到根节点位置,并将它与它的叶子节点交换做向下调整直到它的子节点没有比它更小的值或者它就是子节点了。
原文描述请见:libstrongswan/processing/scheduler.h
关于堆树heap的数据结构操作可参考用数组实现堆的描述。
scheduler调度的event与job的关系
如以下代码清晰可见,event就是标上time的job。
/**
* Event containing a job and a schedule time
*/
struct event_t {
/**
* Time to fire the event.
*/
timeval_t time;
/**
* Every event has its assigned job.
*/
job_t *job;
};
/* file : src/libstrongswan/processing/scheduler.c */
scheduler的主要操作
所以scheduler只是调度关系,即把要执行的事件要入堆树,在堆树内按事件的时间来调整,让事件时间值最小的节点为根节点,因为根节点总是最先执行,就保证了事件按时间大小顺序执行。
事件就是标上时间后的job,通过以下三个函数被插入heap。
lib->scheduler->schedule_job,
lib->scheduler->schedule_job_ms,
lib->scheduler->schedule_job_tv
这三个函数是把job带上time生成event塞进heap。
job又是如何创建的呢?
比如IKEv2重传消息,先用retransmit_job_create创建job,然后调用schedule_job_ms把与job绑定的event塞入heap。
job = (job_t*)retransmit_job_create(this->initiating.mid,this->ike_sa->get_id(this->ike_sa));
lib->scheduler->schedule_job_ms(lib->scheduler, job, timeout);
/* file: libcharon/sa/ikev2/task_manager_v2.c */
src/libcharon/processing/jobs/ 目录下有很多jobs的创建实现
strongswan/src/libcharon/processing/jobs$ ls *.c *.h
acquire_job.c dpd_timeout_job.h migrate_job.c rekey_ike_sa_job.h send_keepalive_job.c
acquire_job.h inactivity_job.c migrate_job.h retransmit_job.c send_keepalive_job.h
adopt_children_job.c inactivity_job.h process_message_job.c retransmit_job.h start_action_job.c
adopt_children_job.h initiate_mediation_job.c process_message_job.h retry_initiate_job.c start_action_job.h
delete_child_sa_job.c initiate_mediation_job.h redirect_job.c retry_initiate_job.h update_sa_job.c
delete_child_sa_job.h initiate_tasks_job.c redirect_job.h roam_job.c update_sa_job.h
delete_ike_sa_job.c initiate_tasks_job.h rekey_child_sa_job.c roam_job.h
delete_ike_sa_job.h mediation_job.c rekey_child_sa_job.h send_dpd_job.c
dpd_timeout_job.c mediation_job.h rekey_ike_sa_job.c send_dpd_job.h
job的执行
libstrongswan/processing/scheduler.c 里schedule函数 就是把event从heap中取出来(peak_event,即根节点:heap[1])并调用lib->processor->queue_job(lib->processor, event->job);把event关联的job传给processer。
schedule()函数末尾return JOB_REQUEUE_DIRECT;也就是一直重复执行,即一直从heap中取event传给processor处理。
process_create()会创建线程池并用set_threads()函数处理jobs。
set_threads()函数内会创建线程来执行process_jobs回调函数。
worker->thread = thread_create(process_jobs, worker);
if (worker->thread)
{
this->threads->insert_last(this->threads, worker);
this->total_threads++;
}
/* file: libstrongswan/processing/processor.c */
scheduler与event的关系:
struct private_scheduler_t {
/**
* Public part of a scheduler_t object.
*/
scheduler_t public;
/**
* The heap in which the events are stored.
*/
event_t **heap;
/**
* The size of the heap.
*/
u_int heap_size;
/**
* The number of scheduled events.
*/
u_int event_count;
/**
* Exclusive access to list
*/
mutex_t *mutex;
/**
* Condvar to wait for next job.
*/
condvar_t *condvar;
};
/* file: src/libstrongswan/processing/scheduler.c */
watcher与job之间的关系
struct private_watcher_t {
/**
* Public watcher_t interface.
*/
watcher_t public;
/**
* List of registered FDs
*/
entry_t *fds;
/**
* Last registered FD
*/
entry_t *last;
/**
* Number of registered FDs
*/
u_int count;
/**
* Pending update of FD list?
*/
bool pending;
/**
* Running state of watcher
*/
watcher_state_t state;
/**
* Lock to access FD list
*/
mutex_t *mutex;
/**
* Condvar to signal completion of callback
*/
condvar_t *condvar;
/**
* Notification pipe to signal watcher thread
*/
int notify[2];
/**
* List of callback jobs to process by watcher thread, as job_t
*/
linked_list_t *jobs;
};
/* file: src/libstrongswan/processing/watcher.c */
processor与threads,work_threads和jobs之间的关系:
struct private_processor_t {
/**
* Public processor_t interface.
*/
processor_t public;
/**
* Number of running threads
*/
u_int total_threads;
/**
* Desired number of threads
*/
u_int desired_threads;
/**
* Number of threads currently working, for each priority
*/
u_int working_threads[JOB_PRIO_MAX];
/**
* All threads managed in the pool (including threads that have been
* canceled, this allows to join them later), as worker_thread_t
*/
linked_list_t *threads;
/**
* A list of queued jobs for each priority
*/
linked_list_t *jobs[JOB_PRIO_MAX];
......
};
/* file: src/libstrongswan/processing/processor.c */
processor、thread和jobs之间的关系
/**
* Worker thread
*/
typedef struct {
/**
* Reference to the processor
*/
private_processor_t *processor;
/**
* The actual thread
*/
thread_t *thread;
/**
* Job currently being executed by this worker thread
*/
job_t *job;
/**
* Priority of the current job
*/
job_priority_t priority;
} worker_thread_t;
/* file: src/libstrongswan/processing/processor.c */
在调用queue_job函数时,输入参数为JOB_PRIO_CRITICAL的都为轮巡线程,因为job执行结束时会return JOB_REQUEUE_DIRECT;也就是继续入队列执行。
$ grep JOB_PRIO_CRITICAL . -r --exclude=*.{o,so}
./libstrongswan/processing/scheduler.c: NULL, return_false, JOB_PRIO_CRITICAL);
./libstrongswan/processing/watcher.c: JOB_PRIO_CRITICAL));
./libstrongswan/processing/watcher.c: NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
./libstrongswan/plugins/pkcs11/pkcs11_manager.c: entry, NULL, cancel_events, JOB_PRIO_CRITICAL));
./libstrongswan/plugins/keychain/keychain_creds.c: this, NULL, (void*)cancel_monitor, JOB_PRIO_CRITICAL));
./libcharon/network/receiver.c: this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
./libcharon/network/sender.c: this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
./charon-nm/nm/nm_backend.c: NULL, (callback_job_cancel_t)cancel, JOB_PRIO_CRITICAL));
./libpttls/pt_tls_dispatcher.c: JOB_PRIO_CRITICAL));