Sorry I didn't respond about this sooner.  I trimmed the CC list and added
the utrace-devel list, which is the place for picayune details of using the
utrace interface.

> While I see no single call, it can be synthesized from a sequence of
> them: utrace_attach, utrace_set_flags (... UTRACE_ACTION_QUESCE ...),
> then waiting for a callback.  Roland, is there a more compact way?

No, the interface is fundamentally asynchronous, because that's the natural
lowest-level expression of the underlying reality.  The kernel has a
plethora of fancy synchronization mechanisms you can combine with the
asynchronous utrace interface.

Below is a trivial test module illustrating how you could write the
stateless, blocking interfaces Stephane asked for.  (This works as is on
Fedora kernels with "insmod stop-thread.ko pid=<somepid>; rmmod stop-thread".)
As you can see, it is very simple (the actual utrace-using code is no longer
than the test module boilerplate).

I don't think stateless calls like these are really what anyone wants to
be using.  In real cases, you're going to be stopping and starting the
same thread many times, not just once.  It should be far more efficient
and convenient to do utrace_attach once the first time you start dealing
with a thread, and just use utrace_set_flags calls to stop and start it
several times.  You probably have your own data structures for this
thread anyway, and you can hold the engine pointer there.  If you do, you
may also have some synchronization for those data structures already.  So
your callbacks can use whatever fits your code best, not just complete().
(You just need to do utrace_detach before you abandon your data
structure, if that's before the thread gets passed to release_task.)

If you are going for efficiency, then you probably actually want a more
asynchronous model anyway.  You are ping-ponging context switches to wait
for it to stop, get notified and wake up, tweak the thread, wake it up,
and get back to running.  With utrace, you can set up your data
structures with what needs to go into the thread and poke it with
UTRACE_ACTION_QUIESCE; it runs your callback, which looks at your data
structures, updates itself, and then clears QUIESCE, so it goes back to
running in its new condition.  If your control logic needs to wait for
all threads to have done their self-updates, you can have the callback do
a count-down to wake you up before it goes back to running.


Thanks,
Roland

---

#include <linux/sched.h>
#include <linux/utrace.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/wait.h>

MODULE_DESCRIPTION("test module");
MODULE_LICENSE("GPL");

static void
stopper_reap(struct utrace_attached_engine *engine, struct task_struct *tsk)
{
        complete_all(engine->data);
}

static u32
stopper_quiesce(struct utrace_attached_engine *engine, struct task_struct *tsk)
{
        complete(engine->data);
        // UTRACE_ACTION_NEWSTATE here would make us resume immediately,
        // having woken our stopper.
        return UTRACE_ACTION_RESUME;
}

static const struct utrace_engine_ops stopper_ops =
{
        .report_quiesce = stopper_quiesce,
        .report_reap = stopper_reap,
};

int
stop_thread(struct task_struct *t, void **data)
{
        DECLARE_COMPLETION_ONSTACK(done);
        struct utrace_attached_engine *engine;
        int ret;

        engine = utrace_attach(t, UTRACE_ATTACH_CREATE, &stopper_ops, &done);
        if (IS_ERR(engine))
                return PTR_ERR(engine);

        // if we were already attached before, setting flags like this
        // with UTRACE_ACTION_QUIESCE set is all it takes to make t stop
        ret = utrace_set_flags(t, engine,
                               UTRACE_ACTION_QUIESCE |
                               UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(REAP));
        if (!ret)
                ret = wait_for_completion_interruptible(&done);

        if (ret)
                (void) utrace_detach(t, engine);
        else
                *data = engine;

        return ret;
}

void
resume_thread(struct task_struct *t, void *data)
{
        // to just resume it but stay attached:
        //utrace_set_flags(t, data, UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(REAP));
        (void) utrace_detach(t, data);
}


static int target_pid;

module_param_named(pid, target_pid, int, 0);

static int __init init_test(void)
{
        struct task_struct *t;
        int ret;
        void *cookie;

        rcu_read_lock();
        t = find_task_by_pid(target_pid);
        if (t)
                get_task_struct(t);
        rcu_read_unlock();

        if (t == NULL) {
                printk("cannot find PID %d\n", target_pid);
                return -ESRCH;
        }

        ret = stop_thread(t, &cookie);
        if (ret)
                printk("failed with %d on PID %d, state %lu\n",
                       ret, target_pid, t->state);
        else {
                printk("stopped PID %d, state %lu; letting it go\n",
                       target_pid, t->state);
                resume_thread(t, cookie);
                printk("resumed PID %d, state %lu\n",
                       target_pid, t->state);
        }

        WARN_ON(atomic_dec_and_test(&t->usage));

        return ret;
}

static void __exit exit_test(void)
{
}

module_init(init_test);
module_exit(exit_test);

Reply via email to