Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP kernel: define and use a formal scheduling API #29668

Closed

Conversation

andrewboie
Copy link
Contributor

@andrewboie andrewboie commented Oct 30, 2020

The intention of this header is to provide a supported interface for building IPC objects. Callers can use these functions to implement IPC without worrying about safety with respect to sched_spinlock or thread objects unexpectedly changing state.

Previously, implementations of IPC objects were using various ksched.h APIs which are kernel-private and can present thread safety issues. This is a formal set of APIs which should always do the right thing.

Variants using irq locking instead of spinlocks have been intentionally omitted; anything still doing this should be considered legacy and needs to be converted ASAP.

A warning is added to ksched.h to convey how perilous it can be to use the APIs within until further cleanup and scoping can be done.

This PR addresses one possible cause of #28105 but does not completely resolve it; occasional "Attempt to resume un-suspended thread object" faults can still be observed.

Some of the more complex IPC have not been converted yet, this PR is a draft.

A problem with a recursive spinlock (sched_spinlock) when freeing memory on thread exit has been worked around; once #28611 lands I can work up a better solution.

TODO:

  • Convert k_mutex
  • Convert k_pipe
  • Convert k_timer
  • Convert k_mailbox
  • Convert k_poll
  • Analyze footprint changes
  • Fix failing test_poll_wait test in tests/kernel/poll, think the issue is with k_queue conversion somewhere
  • dedicated tests for these new APIs with full code coverage
  • file enhancements to get POSIX and CMSIS converted
  • re-work z_thread_free() after Deprecate k_mem_pool API, remove sys_mem_pool allocator #28611 lands

Andrew Boie added 9 commits October 30, 2020 13:25
To match z_thread_malloc() calls. Does not invoke the scheduler.

Signed-off-by: Andrew Boie <[email protected]>
The intention of this header is to provide a supported interface
for building IPC objects. Callers can use these functions to
implement IPC without worrying about safety with respect to
sched_spinlock or thread objects unexpectedly changing state.

Previously, implementations of IPC objects were using various
ksched.h APIs which are kernel-private and can present thread
safety issues. This is a formal set of APIs which should always
do the right thing.

Variants using irq locking instead of spinlocks have been
intentionally omitted; anything still doing this should be
considered legacy and unsupported.

A warning is added to ksched.h to convey how perilous it can be
to use the APIs within; we're not even using these in a completely
safe way within the core kernel, much less users outside of it,
although there are no known issues on uniprocessor systems.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where thread state could change in between the
z_unpend_first_thread() and z_ready_thread() calls on SMP.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where 'pending_thread' could unexpectedly
change state in between ksched.h calls, which are no longer
used.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where woken up thread could unexpectedly
change state in between ksched.h calls, which are no longer
used.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where woken up thread could unexpectedly
change state in between ksched.h calls, which are no longer
used.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where woken up thread could unexpectedly
change state in between ksched.h calls, which are no longer
used.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where woken up thread could unexpectedly
change state in between ksched.h calls, which are no longer
used.

Signed-off-by: Andrew Boie <[email protected]>
Closes races where woken up thread could unexpectedly
change state in between ksched.h calls, which are no longer
used.

Signed-off-by: Andrew Boie <[email protected]>
Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick notes on the API. This all looks pretty good to me.

k_sched_wake_cb_t cb, void *obj, void *context);

/**
* Wake up a thread pending on the provided wait queue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should document as "wake up the highest priority thread" I think. Strictly a wait queue will present the highest priority thread that has been in the queue the longest for wakeup at the head of the list. We never had to detail that expressly anywhere because it was an internal API, but it deserves a callout here I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agreed, will add this.


while (k_sched_wake(wait_q, swap_retval, swap_data)) {
woken = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (releasing the lock between each thread) is the way z_unpend_all() works currently, but this probably deserves some thought as to whether we want this to be an atomic operation (hold the lock while you release all threads simultaneously). The latter is something that can't be emulated by the caller, where they could write the loop themselves.

The question is do we want that? I dunno, but it would be more cleanly specified, less surprising, and easy to do right here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do something like that in #29618 where I had a list of waiting threads and wanted to wake a subset of them, done by iterating over the set and extracting the ones I needed to a separate list under lock before releasing each thread (because the per-thread operation was a reschedule point; see #29610). A waitq has different semantics, so maybe it isn't appropriate for this interface.

Copy link
Contributor Author

@andrewboie andrewboie Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea with these APIs is that there are two locks involved:

  • sched_spinlock, which is held for individual calls to k_sched_wake() which protects the integrity of the scheduling data structures and serializes the state of individual threads that were woken up. This keeps the scheduler consistent. This is all internal to these APIs.
  • Another, IPC-specific lock which establishes atomicity for whatever IPC operations being done. It's expected that this second lock is held by the caller across all k_sched_wake() and k_sched_wake_all() calls, until it's time to call k_sched_invoke() with that lock.

I put in the documentation for k_sched_wake():

 * It is up to the caller to implement locking such that the return value of
 * this function does not immediately become stale. Calls to wait and wake on
 * the same wait_q object must have synchronization. Calling this without
 * holding any spinlock is a sign that this API is not being used properly.

@andyross so in other words, we actually do have:

hold the lock while you release all threads simultaneously

As the second lock will cover this case with respect to users of whatever IPC object is being built.
I don't think we need to hold sched_spinlock across these calls, as the second lock should be enough.

Let me know if this approach is workable.

* expired without being woken up.
*/
int k_sched_wait(struct k_spinlock *lock, k_spinlock_key_t key,
_wait_q_t *wait_q, k_timeout_t timeout, void **data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random note, but we probably want to promote wait queues to a proper "struct k_wait_q" definition instead of the internal type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, will add a patch to this series.

* another CPU) when the callback is modifying the thread's state.
*
* It is only inside these callbacks that it is safe to inspect or modify
* the thread that was woken up.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to document the intended use of obj and context here.

Also to document what can and cannot be done within the callback. From what I can tell with #29610 if the callback invokes an operation that's a reschedule point spinlocks held by the caller might be broken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the callback holds sched_spinlock, so the true limitation is that no other API that holds sched_spinlock can be called. Simply stating this would be extremely unclear to users, I'll try to express in a better way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. So in this case the spinlock wouldn't be broken because the callback would block forever waiting to take sched_spinlock.

Could you take a look at #29610 and correct my understanding if it isn't true in general that uniprocessor spinlocks (like irq_lock) broken if the current thread is preempted as a result of making a higher priority thread read and rescheduling? Is it true in SMP?

If either is true I'll work on a documentation update, because currently we warn about that only for irq_lock.


while (k_sched_wake(wait_q, swap_retval, swap_data)) {
woken = true;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do something like that in #29618 where I had a list of waiting threads and wanted to wake a subset of them, done by iterating over the set and extracting the ones I needed to a separate list under lock before releasing each thread (because the per-thread operation was a reschedule point; see #29610). A waitq has different semantics, so maybe it isn't appropriate for this interface.

* @param lock Address of spinlock to release when we swap out
* @param key Key to the provided spinlock when it was locked
*/
void k_sched_invoke(struct k_spinlock *lock, k_spinlock_key_t key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be nice: I think it could be the thing that gets invoked at a reschedule point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this particular API is the reschedule point. it is currently a thin wrapper on z_reschedule().

pabigot added a commit to pabigot/zephyr that referenced this pull request Dec 20, 2020
This uses the cherry-picked scheduler API from zephyrproject-rtos#29668 to avoid certain
race conditions in SMP between unpending and readying threads.

Signed-off-by: Peter Bigot <[email protected]>
@github-actions
Copy link

github-actions bot commented Jan 4, 2021

This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time.

@github-actions github-actions bot added the Stale label Jan 4, 2021
@andrewboie andrewboie removed the Stale label Jan 5, 2021
pabigot added a commit to pabigot/zephyr that referenced this pull request Jan 19, 2021
These functions are a subset of proposed public APIs to clean up
several issues related to safely handling waking of threads.  They
have been made private as they interface may change, but their use
will simplify the reimplementation of the k_work functionality.

See: zephyrproject-rtos#29668

Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Peter Bigot <[email protected]>
@pabigot pabigot self-assigned this Feb 10, 2021
pabigot added a commit to pabigot/zephyr that referenced this pull request Mar 1, 2021
These functions are a subset of proposed public APIs to clean up
several issues related to safely handling waking of threads.  They
have been made private as they interface may change, but their use
will simplify the reimplementation of the k_work functionality.

See: zephyrproject-rtos#29668

Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Peter Bigot <[email protected]>
pabigot added a commit to pabigot/zephyr that referenced this pull request Mar 2, 2021
These functions are a subset of proposed public APIs to clean up
several issues related to safely handling waking of threads.  They
have been made private as they interface may change, but their use
will simplify the reimplementation of the k_work functionality.

See: zephyrproject-rtos#29668

Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Peter Bigot <[email protected]>
nashif pushed a commit that referenced this pull request Mar 4, 2021
These functions are a subset of proposed public APIs to clean up
several issues related to safely handling waking of threads.  They
have been made private as they interface may change, but their use
will simplify the reimplementation of the k_work functionality.

See: #29668

Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Peter Bigot <[email protected]>
@pabigot pabigot removed their assignment Mar 22, 2021
coreboot-org-bot pushed a commit to coreboot/zephyr-cros that referenced this pull request Jul 7, 2021
These functions are a subset of proposed public APIs to clean up
several issues related to safely handling waking of threads.  They
have been made private as they interface may change, but their use
will simplify the reimplementation of the k_work functionality.

See: zephyrproject-rtos/zephyr#29668

BUG=none
TEST=none

Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Peter Bigot <[email protected]>
(cherry picked from commit 0259c86)
Change-Id: I9e7288cecafb05dcd126ba311407670c0ac6215d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/zephyr/+/3004965
Reviewed-by: Jack Rosenthal <[email protected]>
Reviewed-by: Denis Brockus <[email protected]>
Commit-Queue: Denis Brockus <[email protected]>
Tested-by: Denis Brockus <[email protected]>
@github-actions
Copy link

This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time.

@github-actions github-actions bot added the Stale label Jul 14, 2021
@github-actions github-actions bot closed this Jul 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants