非同期による遅延処理(workqueue, delayed_workqueue, kthread_queue)

非同期による遅延処理とは

 非同期による遅延処理は、別のスレッド用いて、特定の処理を非同期に遅らせて実行する為の仕組みです。割り込み等のイベントが発生した後に、急ぎで無い処理を実行する際等によく使用されます。代表的な物としてworkqueue、delayed_workqueue、kthread_queueの3つがあり、それぞれについて説明します。

workqueueの使い方

 workqueueは最も多用される非同期による遅延処理です。queueにタスクを登録することで、タスクが順次実行されます。タスクの実行は、kernel全体で共有されているqueueにより行われる為、処理の重い内容は後述のkthread_queueを用いる必要があります。create_workqueueというあたかも専用のqueueを作るような関数がありますが、互換性の為に残っている関数であり、これを用いてもkernel全体で共有されているqueueが使われます。

照度センサデバイスドライバでは、以下に使用箇所があります。

kernel/mediatek/4.4/drivers/misc/mediatek/sensors-1.0/alsps/alsps.c

INIT_WORK(&obj->report_als, als_work_func);

INIT_WORKは以下に定義されています。

kernel/mediatek/4.4/include/linux/workqueue.h

#define INIT_WORK(_work, _func)						\
	__INIT_WORK((_work), (_func), 0)

workqueueに登録するタスクを初期化する為の関数となり、第1引数には初期化するstruct work_struct型のポインタを指定し、第2引数には実行するコールバック関数を指定します。実際にタスクをworkqueueに登録するには同ファイルに定義されているschedule_work関数を使用します。

/**
 * schedule_work - put work task in global workqueue
 * @work: job to be done
 *
 * Returns %false if @work was already on the kernel-global workqueue and
 * %true otherwise.
 *
 * This puts a job in the kernel-global workqueue if it was not already
 * queued and leaves it in the same position on the kernel-global
 * workqueue otherwise.
 */
static inline bool schedule_work(struct work_struct *work)

引数にはINIT_WORKでも指定した、struct work_struct型のポインタを指定します。

delayed_workqueueの使い方

 delayed_workqueueは、workqueue内容に加えて、指定した時間を遅らせた上で、非同期による遅延処理を実行する為の仕組みです。時間経過は、一定間隔でインクリメントされていくjiffies単位で指定します。queueがkernel全体で共有される点はworkqueueと同様です。主要な関数には、以下のようなものがあります。

kernel/mediatek/4.4/include/linux/workqueue.h

#define INIT_DELAYED_WORK(_work, _func)					\
	__INIT_DELAYED_WORK(_work, _func, 0)

delayed_workqueueに登録するタスクを初期化する為の関数です。第1引数には初期化するstruct delayed_work型のポインタを指定し、第2引数には実行するコールバック関数を指定します。

/**
 * schedule_delayed_work - put work task in global workqueue after delay
 * @dwork: job to be done
 * @delay: number of jiffies to wait or 0 for immediate execution
 *
 * After waiting for a given time this puts a job in the kernel-global
 * workqueue.
 */
static inline bool schedule_delayed_work(struct delayed_work *dwork,
					 unsigned long delay)

delayed_workqueueにタスクを登録する為の関数です。第1引数にはINIT_DELAYED_WORKでも指定したstruct delayed_work型のポインタを指定し、第2引数には時間(jiffies数)を指定します。queueへ追加できるのは1種類のタスクについて1個であり、タスクの実行完了前にもう一度呼び出すと失敗するので注意が必要です。

kthread_queueの使い方

 kthread_queueは、独自のスレッドを作成し、そのスレッドで特定のタスクを実行する為の仕組みです。独自のスレッドを作成する為、他機能に与える影響を抑え、非同期による遅延処理を実現できます。主要な関数には、以下のようなものがあります。(kernel 4.9 以降での仕組みだった為、Linux標準のソースコードから転記しております。)

include/linux/kthread.h
kernel/kthread.c

#define kthread_init_worker(worker)					\
	do {								\
		static struct lock_class_key __key;			\
		__kthread_init_worker((worker), "("#worker")->lock", &__key); \
	} while (0)

タスクを処理するworkerを初期化するための関数です。第1引数には初期化するstruct kthread_worker型のポインタを指定します。

#define kthread_init_delayed_work(dwork, fn)				\
	do {								\
		kthread_init_work(&(dwork)->work, (fn));		\
		__setup_timer(&(dwork)->timer,				\
			      kthread_delayed_work_timer_fn,		\
			      (unsigned long)(dwork),			\
			      TIMER_IRQSAFE);				\
	} while (0)

kthread_queueに登録するタスクを初期化する為の関数です。第1引数には初期化するstruct kthread_delayed_work型のポインタを指定し、第2引数には実行するコールバック関数を指定します。

/**
 * kthread_run - create and wake a thread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#define kthread_run(threadfn, data, namefmt, ...)			   \
({									   \
	struct task_struct *__k						   \
		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
	if (!IS_ERR(__k))						   \
		wake_up_process(__k);					   \
	__k;								   \
})

新しいスレッドを作成して実行する為の関数です。今回の流れで使用する際は、第1引数のthreadfnには固定でkthread_worker_fnを指定し、第2引数にはkthread_init_workerでも指定した、struct kthread_worker型のポインタを指定します。第3引数には任意の文字列を指定します。

/**
 * kthread_queue_delayed_work - queue the associated kthread work
 *	after a delay.
 * @worker: target kthread_worker
 * @dwork: kthread_delayed_work to queue
 * @delay: number of jiffies to wait before queuing
 *
 * If the work has not been pending it starts a timer that will queue
 * the work after the given @delay. If @delay is zero, it queues the
 * work immediately.
 *
 * Return: %false if the @work has already been pending. It means that
 * either the timer was running or the work was queued. It returns %true
 * otherwise.
 */
bool kthread_queue_delayed_work(struct kthread_worker *worker,
				struct kthread_delayed_work *dwork,
				unsigned long delay)

kthread_queueにタスクを登録する為の関数です。第1引数は、kthread_init_workerでも指定した、struct kthread_worker型のポインタを指定し、第2引数には、kthread_init_delayed_workでも指定した、struct kthread_delayed_work型のポインタを指定します。第3引数には、schedule_delayed_workと同じく、時間(jiffies数)を指定します。

最後に

 delayed_workqueueとkthread_queueで使用する時間であるjiffiesは、1秒間にCONFIG_HZの数だけ増えていきます。その為、正確な時間に処理を動かすことは不可能です。

unsigned long timeout = jiffies + msecs_to_jiffies(10);

等の使用例において、CONFIG_HZが100を示している場合、

timeout = jiffies + 1

となります。しかし、jiffiesがインクリメントされる直前である場合、jiffiesの値はすぐにtimeoutと同値になります。この記載方法では、基本的に10msec以内にtimeoutとなるので注意が必要です。正確な時間経過後に処理を実行したい場合、次章のTimer処理に記載するhrtimerを使用することが有効です。

コメント

タイトルとURLをコピーしました