Demandare una operazione ad un altro contesto: le workqueue
Una workqueue è - lo dice il nome - una coda di operazioni. Ad ogni workqueue è associato un kernel thread (o meglio, un thread per cpu) che esegue le operazioni indicate in contesto di processo.
Il loro utilizzo è molto semplice; riprendiamo l'esempio sui kernel thread:
Codice:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
static struct task_struct *ktask;
static struct workqueue_struct *wq;
static void kt_work(void *data)
{
struct work_struct *work = data;
printk(KERN_INFO "%s\n", __func__);
kfree(work);
}
static int kt_thread(void *arg)
{
struct work_struct *work;
struct workqueue_struct *wq = arg;
while (!kthread_should_stop()) {
work = kmalloc(sizeof(*work), GFP_KERNEL);
if (work) {
INIT_WORK(work, kt_work, work);
queue_work(wq, work);
} else
printk(KERN_ERR "%s: OOM!\n", __func__);
msleep(1000);
}
return 0;
}
static int __init kt_init(void)
{
wq = create_workqueue("kworker");
if (!wq)
return -ENOMEM;
ktask = kthread_create(kt_thread, wq, "kthread");
if (IS_ERR(ktask)) {
destroy_workqueue(wq);
return PTR_ERR(ktask);
} else
wake_up_process(ktask);
return 0;
}
static void __exit kt_exit(void)
{
kthread_stop(ktask);
flush_workqueue(wq);
destroy_workqueue(wq);
}
module_init(kt_init);
module_exit(kt_exit);
MODULE_AUTHOR("ilsensine");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("sample kernel process");
La workqueue viene creata tramite la funzione create_workqueue. I thread assegnati alla workqueue saranno visibili tramite top. Un task viene accodato alla queue tramite la funzione queue_work. Un singolo work è descritto dalla struttura work_struct, inizializzata dalla macro INIT_WORK(struttura, funzione, parametro della funzione).
Note:
- In questo esempio la workqueue viene creara e distrutta esternamente al kernel thread. Potevamo crearla e distruggerla direttamente nel thread? La risposta è no: il nostro thread viene terminato invocando la funzione kthread_stop. Anche la funzione destroy_workqueue chiama kthread_stop per terminare i propri thread. Orbene, kthread_stop blocca un semaforo finché il thread non è terminato; quindi se il thread in fase di chiusura chiama a sua volta kthread_stop (o una funzione che la richiama, come destroy_workqueue), abbiamo un bel deadlock.
- Un work può essere schedulato nel futuro tramite la funzione queue_delayed_work, che ha il prototipo (v. linux/workqueue.h):
Codice:
int queue_delayed_work(struct workqueue_struct *wq,
struct work_struct *work, unsigned long delay)
dove delay è espresso in jiffies (tick di scheduling. In un secondo occorrono HZ tick)
- Solo i moduli con licenza GPL possono creare proprie workqueue. I moduli proprietari possono utilizzare le workqueue di keventd, tramite le funzioni schedule_[delayed_]work.
- flush_workqueue attende la terminazione dei work in coda. Viene chiamata anche da destroy_workqueue, quindi nel codice illustrato risulta superflua (è stata messa solo come esempio).
- Visto che le workqueue sono eseguite in contesto di processo, è possibile chiamare funzioni che possono schedulare (come ad es. down).