mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 09:59:32 +00:00
![Frederic Weisbecker](/assets/img/avatar_default.png)
The netlink connector uses its own workqueue to relay the datas sent from userspace to the appropriate callback. If you launch the test from Documentation/connector and change it a bit to send a high flow of data, you will see thousands of events coming to the "cqueue" workqueue by looking at the workqueue tracer. This flow of events can be sent very quickly. So, to not encumber the kevent workqueue and delay other jobs, the "cqueue" workqueue should remain. But this workqueue is pointless most of the time, it will always be created (assuming you have built it of course) although only developpers with specific needs will use it. So avoid this "most of the time useless task", this patch proposes to create this workqueue only when needed once. The first jobs to be sent to connector callbacks will be sent to kevent while the "cqueue" thread creation will be scheduled to kevent too. The following jobs will continue to be scheduled to keventd until the cqueue workqueue is created, and then the rest of the jobs will continue to perform as usual, through this dedicated workqueue. Each time I tested this patch, only the first event was sent to keventd, the rest has been sent to cqueue which have been created quickly. Also, this patch fixes some trailing whitespaces on the connector files. Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Acked-by: Evgeniy Polyakov <zbr@ioremap.net> Signed-off-by: David S. Miller <davem@davemloft.net>
236 lines
6 KiB
C
236 lines
6 KiB
C
/*
|
|
* cn_queue.c
|
|
*
|
|
* 2004-2005 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/list.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/connector.h>
|
|
#include <linux/delay.h>
|
|
|
|
|
|
/*
|
|
* This job is sent to the kevent workqueue.
|
|
* While no event is once sent to any callback, the connector workqueue
|
|
* is not created to avoid a useless waiting kernel task.
|
|
* Once the first event is received, we create this dedicated workqueue which
|
|
* is necessary because the flow of data can be high and we don't want
|
|
* to encumber keventd with that.
|
|
*/
|
|
static void cn_queue_create(struct work_struct *work)
|
|
{
|
|
struct cn_queue_dev *dev;
|
|
|
|
dev = container_of(work, struct cn_queue_dev, wq_creation);
|
|
|
|
dev->cn_queue = create_singlethread_workqueue(dev->name);
|
|
/* If we fail, we will use keventd for all following connector jobs */
|
|
WARN_ON(!dev->cn_queue);
|
|
}
|
|
|
|
/*
|
|
* Queue a data sent to a callback.
|
|
* If the connector workqueue is already created, we queue the job on it.
|
|
* Otherwise, we queue the job to kevent and queue the connector workqueue
|
|
* creation too.
|
|
*/
|
|
int queue_cn_work(struct cn_callback_entry *cbq, struct work_struct *work)
|
|
{
|
|
struct cn_queue_dev *pdev = cbq->pdev;
|
|
|
|
if (likely(pdev->cn_queue))
|
|
return queue_work(pdev->cn_queue, work);
|
|
|
|
/* Don't create the connector workqueue twice */
|
|
if (atomic_inc_return(&pdev->wq_requested) == 1)
|
|
schedule_work(&pdev->wq_creation);
|
|
else
|
|
atomic_dec(&pdev->wq_requested);
|
|
|
|
return schedule_work(work);
|
|
}
|
|
|
|
void cn_queue_wrapper(struct work_struct *work)
|
|
{
|
|
struct cn_callback_entry *cbq =
|
|
container_of(work, struct cn_callback_entry, work);
|
|
struct cn_callback_data *d = &cbq->data;
|
|
|
|
d->callback(d->callback_priv);
|
|
|
|
d->destruct_data(d->ddata);
|
|
d->ddata = NULL;
|
|
|
|
kfree(d->free);
|
|
}
|
|
|
|
static struct cn_callback_entry *cn_queue_alloc_callback_entry(char *name, struct cb_id *id, void (*callback)(void *))
|
|
{
|
|
struct cn_callback_entry *cbq;
|
|
|
|
cbq = kzalloc(sizeof(*cbq), GFP_KERNEL);
|
|
if (!cbq) {
|
|
printk(KERN_ERR "Failed to create new callback queue.\n");
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(cbq->id.name, sizeof(cbq->id.name), "%s", name);
|
|
memcpy(&cbq->id.id, id, sizeof(struct cb_id));
|
|
cbq->data.callback = callback;
|
|
|
|
INIT_WORK(&cbq->work, &cn_queue_wrapper);
|
|
return cbq;
|
|
}
|
|
|
|
static void cn_queue_free_callback(struct cn_callback_entry *cbq)
|
|
{
|
|
/* The first jobs have been sent to kevent, flush them too */
|
|
flush_scheduled_work();
|
|
if (cbq->pdev->cn_queue)
|
|
flush_workqueue(cbq->pdev->cn_queue);
|
|
|
|
kfree(cbq);
|
|
}
|
|
|
|
int cn_cb_equal(struct cb_id *i1, struct cb_id *i2)
|
|
{
|
|
return ((i1->idx == i2->idx) && (i1->val == i2->val));
|
|
}
|
|
|
|
int cn_queue_add_callback(struct cn_queue_dev *dev, char *name, struct cb_id *id, void (*callback)(void *))
|
|
{
|
|
struct cn_callback_entry *cbq, *__cbq;
|
|
int found = 0;
|
|
|
|
cbq = cn_queue_alloc_callback_entry(name, id, callback);
|
|
if (!cbq)
|
|
return -ENOMEM;
|
|
|
|
atomic_inc(&dev->refcnt);
|
|
cbq->pdev = dev;
|
|
|
|
spin_lock_bh(&dev->queue_lock);
|
|
list_for_each_entry(__cbq, &dev->queue_list, callback_entry) {
|
|
if (cn_cb_equal(&__cbq->id.id, id)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
list_add_tail(&cbq->callback_entry, &dev->queue_list);
|
|
spin_unlock_bh(&dev->queue_lock);
|
|
|
|
if (found) {
|
|
cn_queue_free_callback(cbq);
|
|
atomic_dec(&dev->refcnt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cbq->seq = 0;
|
|
cbq->group = cbq->id.id.idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cn_queue_del_callback(struct cn_queue_dev *dev, struct cb_id *id)
|
|
{
|
|
struct cn_callback_entry *cbq, *n;
|
|
int found = 0;
|
|
|
|
spin_lock_bh(&dev->queue_lock);
|
|
list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) {
|
|
if (cn_cb_equal(&cbq->id.id, id)) {
|
|
list_del(&cbq->callback_entry);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_bh(&dev->queue_lock);
|
|
|
|
if (found) {
|
|
cn_queue_free_callback(cbq);
|
|
atomic_dec(&dev->refcnt);
|
|
}
|
|
}
|
|
|
|
struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls)
|
|
{
|
|
struct cn_queue_dev *dev;
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return NULL;
|
|
|
|
snprintf(dev->name, sizeof(dev->name), "%s", name);
|
|
atomic_set(&dev->refcnt, 0);
|
|
INIT_LIST_HEAD(&dev->queue_list);
|
|
spin_lock_init(&dev->queue_lock);
|
|
init_waitqueue_head(&dev->wq_created);
|
|
|
|
dev->nls = nls;
|
|
|
|
INIT_WORK(&dev->wq_creation, cn_queue_create);
|
|
|
|
return dev;
|
|
}
|
|
|
|
void cn_queue_free_dev(struct cn_queue_dev *dev)
|
|
{
|
|
struct cn_callback_entry *cbq, *n;
|
|
long timeout;
|
|
DEFINE_WAIT(wait);
|
|
|
|
/* Flush the first pending jobs queued on kevent */
|
|
flush_scheduled_work();
|
|
|
|
/* If the connector workqueue creation is still pending, wait for it */
|
|
prepare_to_wait(&dev->wq_created, &wait, TASK_UNINTERRUPTIBLE);
|
|
if (atomic_read(&dev->wq_requested) && !dev->cn_queue) {
|
|
timeout = schedule_timeout(HZ * 2);
|
|
if (!timeout && !dev->cn_queue)
|
|
WARN_ON(1);
|
|
}
|
|
finish_wait(&dev->wq_created, &wait);
|
|
|
|
if (dev->cn_queue) {
|
|
flush_workqueue(dev->cn_queue);
|
|
destroy_workqueue(dev->cn_queue);
|
|
}
|
|
|
|
spin_lock_bh(&dev->queue_lock);
|
|
list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry)
|
|
list_del(&cbq->callback_entry);
|
|
spin_unlock_bh(&dev->queue_lock);
|
|
|
|
while (atomic_read(&dev->refcnt)) {
|
|
printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n",
|
|
dev->name, atomic_read(&dev->refcnt));
|
|
msleep(1000);
|
|
}
|
|
|
|
kfree(dev);
|
|
dev = NULL;
|
|
}
|