Message ID | 20221107141638.3790965-8-john.ogness@linutronix.de |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:6687:0:0:0:0:0 with SMTP id l7csp2078737wru; Mon, 7 Nov 2022 06:21:03 -0800 (PST) X-Google-Smtp-Source: AMsMyM47Xcpegjxz4PA1WJzA2glxmxloTcv50Gmi1M6cm+tPK3YQTPqmZG4tI28946kro17eafDm X-Received: by 2002:aa7:959d:0:b0:56d:27ac:778 with SMTP id z29-20020aa7959d000000b0056d27ac0778mr47648725pfj.29.1667830863308; Mon, 07 Nov 2022 06:21:03 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1667830863; cv=none; d=google.com; s=arc-20160816; b=XapO67MiYNftKPfZFCRKfpx8tlibGzBP39EXvouxGNWK16oUNOVoK0bjyFyBjIy7k/ +qFbb6NO8HPde15BlrGg7Cw15oPbYgfLbeI6eMwv/9V8KgfmYpuD/VR6/GUCHaa4ESK1 szQTC2yQeOJJSFg6mQoPwRJKiymund5qBBtkcl8Gg99X1fJuFGKbNRLpZcLIPRUzccMa WdzNSM/Ao20cjVMbB8Ri4ymMo05/PJCZd+w9iuSkx0g84buDdWli+cgao0c3cHfvxqsr srcrXdU0MLn3Nex6dLQTePKV411EopomqKMziebCC48gZwd1rV2DttPgzsvXIpf6Zjh7 5qew== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:dkim-signature :dkim-signature:from; bh=5TCVppZa0E2AuHd0nGaCpwvvOyi1zDUsbUnYVLs1Wys=; b=sdOBUBQ4B8Vjx6sVslZ5WloM5SRZp4MdQXs6XPftHevjyfeThgUW6HgvKI/cf9RstH g7FvzAlYD3x/9WQmE/1cZDshluh6OBRlZu2IqYfczCWQ++K41mPjpbyMi0l/bln5ybDY t9Te2iwSPQpsMeOqE56sd5u/Blxb/b6Q15mXtW97jdg5qfxhfgv4+cOFo/oFz8kBVj/y VHTEwCJrOQVyQdE5U9tfwl1nAbY70R/dhWNFWA+YaHqQk7iGfYegxJ1r7N3FQqJxnH3B 0g94mQT7A4WpPU665E/+uycNHArA66YXC0HWs/UQy5bdGxnj76CMW3lqbATkMDgu/8CZ 9NeA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linutronix.de header.s=2020 header.b=q0O+msnT; dkim=neutral (no key) header.i=@linutronix.de header.s=2020e header.b=yzRUX7qM; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=linutronix.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id v14-20020a63480e000000b00470693437c0si6574080pga.420.2022.11.07.06.20.44; Mon, 07 Nov 2022 06:21:03 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@linutronix.de header.s=2020 header.b=q0O+msnT; dkim=neutral (no key) header.i=@linutronix.de header.s=2020e header.b=yzRUX7qM; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=linutronix.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232328AbiKGOSD (ORCPT <rfc822;hjfbswb@gmail.com> + 99 others); Mon, 7 Nov 2022 09:18:03 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55684 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232056AbiKGOQq (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Mon, 7 Nov 2022 09:16:46 -0500 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1A9D41CFFE for <linux-kernel@vger.kernel.org>; Mon, 7 Nov 2022 06:16:45 -0800 (PST) From: John Ogness <john.ogness@linutronix.de> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1667830603; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5TCVppZa0E2AuHd0nGaCpwvvOyi1zDUsbUnYVLs1Wys=; b=q0O+msnTIqJDVliJS2mLPmiTeN1bT4M4lS4vv1ta4Iles3swgem1QVh35gabpuNYnUkW9o kumUOwPGqpUFtE9uIVqJVyyJusic9EbpwXtVXM/DphHFhu7j/llHcsOvNhdoXOvKW8MwAQ J78ruFn8rKOWjEbg/78XMS7uTzmJ0ui6PHsH4xVdm6WM1flZAoz4X6TCt3/2V92m0+iDBx sC+yKZCWx+xbCVsF7NxRyQDNHLlA6cBfu2CHxYQN0D53Mjjz9COGM9xALCiHtPORtU1PtI 8M97XR5at4c2Z232jKNiAH5yZuym/gYqEsz5toH1Kq4UKFF4sVPpB5fzAj40Yw== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1667830603; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5TCVppZa0E2AuHd0nGaCpwvvOyi1zDUsbUnYVLs1Wys=; b=yzRUX7qMIVq9AxG/h9VUumLt/k5928UDKof4oMa1D5IebwjjtNBshv84ecqWvcCu5r9Fi1 i2W2BEAaV5h3zaDg== To: Petr Mladek <pmladek@suse.com> Cc: Sergey Senozhatsky <senozhatsky@chromium.org>, Steven Rostedt <rostedt@goodmis.org>, Thomas Gleixner <tglx@linutronix.de>, linux-kernel@vger.kernel.org, Greg Kroah-Hartman <gregkh@linuxfoundation.org> Subject: [PATCH printk v3 07/40] console: introduce console_is_enabled() wrapper Date: Mon, 7 Nov 2022 15:22:05 +0106 Message-Id: <20221107141638.3790965-8-john.ogness@linutronix.de> In-Reply-To: <20221107141638.3790965-1-john.ogness@linutronix.de> References: <20221107141638.3790965-1-john.ogness@linutronix.de> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-3.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,INVALID_DATE_TZ_ABSURD, RCVD_IN_DNSWL_MED,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1748847414963371188?= X-GMAIL-MSGID: =?utf-8?q?1748847414963371188?= |
Series |
reduce console_lock scope
|
|
Commit Message
John Ogness
Nov. 7, 2022, 2:16 p.m. UTC
After switching to SRCU for console list iteration, some readers
will begin readings console->flags as a data race. Locklessly
reading console->flags provides a consistent value because there
is at most one CPU modifying console->flags and that CPU is
using only read-modify-write operations.
The primary reason for readers to access console->flags is to
check if the console is enabled. Introduce console_is_enabled()
to mark such access as a data race.
Signed-off-by: John Ogness <john.ogness@linutronix.de>
---
include/linux/console.h | 27 +++++++++++++++++++++++++++
kernel/printk/printk.c | 10 +++++-----
2 files changed, 32 insertions(+), 5 deletions(-)
Comments
On Mon 2022-11-07 15:22:05, John Ogness wrote: > After switching to SRCU for console list iteration, some readers > will begin readings console->flags as a data race. Locklessly > reading console->flags provides a consistent value because there > is at most one CPU modifying console->flags and that CPU is > using only read-modify-write operations. > > The primary reason for readers to access console->flags is to > check if the console is enabled. Introduce console_is_enabled() > to mark such access as a data race. > > Signed-off-by: John Ogness <john.ogness@linutronix.de> > --- > include/linux/console.h | 27 +++++++++++++++++++++++++++ > kernel/printk/printk.c | 10 +++++----- > 2 files changed, 32 insertions(+), 5 deletions(-) > > diff --git a/include/linux/console.h b/include/linux/console.h > index f4f0c9523835..d9c636011364 100644 > --- a/include/linux/console.h > +++ b/include/linux/console.h > @@ -172,6 +172,33 @@ extern void console_srcu_read_unlock(int cookie); > > extern struct hlist_head console_list; > > +/** > + * console_is_enabled - Locklessly check if the console is enabled > + * @con: struct console pointer of console to check > + * > + * Unless the caller is explicitly synchronizing against the console > + * register/unregister/stop/start functions, this function should be > + * used instead of manually readings console->flags and testing for > + * the CON_ENABLED bit. > + * > + * This function provides the necessary READ_ONCE() and data_race() > + * notation for locklessly reading the console flags. The READ_ONCE() > + * in this function matches the WRITE_ONCE() when @flags are modified > + * for registered consoles. > + * > + * Context: Any context. > + */ > +static inline bool console_is_enabled(const struct console *con) > +{ > + /* > + * Locklessly reading console->flags provides a consistent > + * read value because there is at most one CPU modifying > + * console->flags and that CPU is using only read-modify-write > + * operations to do so. > + */ > + return (data_race(READ_ONCE(con->flags)) & CON_ENABLED); This API is step into the right direction, definitely. I am just not sure about all the related READ_WRITE() calls, see below. It is not easy to check and maintain. > +} > + > /** > * for_each_console_srcu() - Iterator over registered consoles > * @con: struct console pointer used as loop cursor > diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c > index 8974523f3107..79811984da34 100644 > --- a/kernel/printk/printk.c > +++ b/kernel/printk/printk.c > @@ -3021,7 +3021,7 @@ void console_stop(struct console *console) > { > __pr_flush(console, 1000, true); > console_lock(); > - console->flags &= ~CON_ENABLED; > + WRITE_ONCE(console->flags, console->flags & ~CON_ENABLED); My first reaction is that using the atomic operation only for the store side is suspicious. It is correct because the read is serialized by console_lock(). But it is far from obvious why we need and can do it this way. It would deserve a comment. But there are several other writes. Also it is not obvious why many other con->flags modifications do not need this. I think about hiding this into an API. We could also add some checks that it is used the right way. Also it might make sense to avoid using the READ_ONCE()/WRITE_ONCE by using set_bit()/test_bit(). I mean the following: /** * console_set_flag - set the given console flag * * @con: A pointer to struct console * @flag_bit: Number of the bit that will be set * * Must be called under console_lock() when the console * is registered. Serialization for non-registered * console is up to the related console driver code. * * Never access console->flags directly. */ void console_set_flag(struct console *con, int flag_nr) { WARN_ON_ONCE(console_is_registered(con) && !held_console_lock()); if (WARN_ON_ONCE(flag_nr) >= sizeof(con->flags); return; set_bit(flag_nr, &con->flags); } bool console_check_flag(const struct console *con, int flag_nr) { WARN_ON_ONCE(console_is_registered(con) && !held_console_lock()); if (WARN_ON_ONCE(flag_nr) >= sizeof(con->flags); return; return test_bit(flag_nr, &con->flags); } We could then use it directly: if (console_check_flag(con, CON_ENABLED_BIT)) ... or we could hide it into a wrapper. Safe access: static inline bool console_is_enabled(const struct console *con) { return console_check_bit(con, CON_ENABLED_BIT); } The above is for the safe access. Another wrapper would be needed for the unsafe access. /** * console_check_flag_unsafe - check console flag without * synchronization * * @con: A pointer to struct console * @flag_bit: Number of the bit that will be set * * Must be called under console_srcu_read_lock() when the console * is registered. Use console_check_flag() in all other situations. * * Never access console->flags directly. */ bool console_check_flag_unsafe(const struct console *con, int flag_nr) { WARN_ON_ONCE(console_is_registered(con) && !console_srcu_read_lock_is_held()); if (WARN_ON_ONCE(flag_nr) >= sizeof(con->flags); return; return data_race(test_bit(flag_nr, &con->flags)); } Most callers should use the safe variant. It will even check that it is really used the safe way. Only the few users of _unsafe() variant would need an extra comment why it is acceptable. I know that it is more work. There seem to be 49 locations that would need update at this point. Some of them are touched by the patchset anyway. There are "only" 39 accesses at the end of the patchset. I used the following query: $> git grep -e "\->flags.*CON_" | grep -v "\.flags" | grep -E "(c|con|cons|console)->flags" | wc -l 39 > console_unlock(); > > /* I would prefer to use the proposed API because it should make all accesses more clear and safe. And most importantly, it would help use to catch bugs. But I do not resist on it. The patch looks correct and we could do this later. I could live with it if we add some comments above the WRITE_ONCE() calls. Best Regards, Petr
On 2022-11-08, Petr Mladek <pmladek@suse.com> wrote: >> --- a/kernel/printk/printk.c >> +++ b/kernel/printk/printk.c >> @@ -3021,7 +3021,7 @@ void console_stop(struct console *console) >> { >> __pr_flush(console, 1000, true); >> console_lock(); >> - console->flags &= ~CON_ENABLED; >> + WRITE_ONCE(console->flags, console->flags & ~CON_ENABLED); > > My first reaction is that using the atomic operation only for the > store side is suspicious. It is correct because the read is serialized > by console_lock(). But it is far from obvious why we need and can do > it this way. The READ_ONCE()/WRITE_ONCE() usage is really about documenting data-race reads and the writes that they are racing with. For WRITE_ONCE() the rule is: - If console->flags is modified for a registered console, it is done under the console_list_lock and using WRITE_ONCE(). If we use a wrapper for this rule, then we can also add the lockdep assertion that console_list_lock is held. For READ_ONCE() the rule is: - If console->flags is read for a registered console, then either console_list_lock must be held _or_ it must be read via READ_ONCE(). If we use wrappers here, then we can use lockdep assertion on console_list_lock for the non-READ_ONCE wrapper, and scru_read_lock assertion for the READ_ONCE wrapper. > It would deserve a comment. But there are several other writes. > Also it is not obvious why many other con->flags modifications > do not need this. > > I think about hiding this into an API. We could also add some > checks that it is used the right way. Also it might make sense > to avoid using the READ_ONCE()/WRITE_ONCE by using > set_bit()/test_bit(). I do not see any advantage of set_bit()/test_bit(). They have the disadvantage that they only work with 1 bit at a time. And there are multiple sites where more than 1 bit is set/tested. It is important that the multi-bit tests are simultaneous. READ_ONCE()/WRITE_ONCE() are perfectly fine for what we are doing. The writes (for registered consoles) are synchronized by the console_list_lock. There is no need to use atomic operations. > I would prefer to use the proposed API because it should make all > accesses more clear and safe. And most importantly, it would help use > to catch bugs. > > But I do not resist on it. The patch looks correct and we could do > this later. I could live with it if we add some comments above the > WRITE_ONCE() calls. I do not want to do a full API replacement for all console->flags access in this series or at this time. I am concerned that it is taking us too far away from our current goal. Also, with the upcoming atomic/threaded model, all consoles need to be modified that want to use it anyway. So that would be a more appropriate time to require the use of new API's. For console_is_enabled() I will add the srcu_read_lock check. I suppose I should also name the function console_srcu_is_enabled(). For the WRITE_ONCE() calls, I will add a static inline wrapper in printk.c that includes the lockdep console_list_lock assertion. Perhaps called console_srcu_write_flags(struct console *con, short flags). In console_srcu_write_flags() and console_srcu_is_enabled() I can document their relationship and when they are to be used. Both these functions are used rarely and should be considered the exception, not the rule. For code that is reading registered console->flags under the console_list_lock, I will leave the "normal access" as is. Just as I am leaving the "normal access" for non-registered console-flags as is. We can convert those to a new generic API later if we think it is really necessary. John
On Thu 2022-11-10 16:11:43, John Ogness wrote: > On 2022-11-08, Petr Mladek <pmladek@suse.com> wrote: > >> --- a/kernel/printk/printk.c > >> +++ b/kernel/printk/printk.c > >> @@ -3021,7 +3021,7 @@ void console_stop(struct console *console) > >> { > >> __pr_flush(console, 1000, true); > >> console_lock(); > >> - console->flags &= ~CON_ENABLED; > >> + WRITE_ONCE(console->flags, console->flags & ~CON_ENABLED); > > > > My first reaction is that using the atomic operation only for the > > store side is suspicious. It is correct because the read is serialized > > by console_lock(). But it is far from obvious why we need and can do > > it this way. > > The READ_ONCE()/WRITE_ONCE() usage is really about documenting data-race > reads and the writes that they are racing with. > > For WRITE_ONCE() the rule is: > > - If console->flags is modified for a registered console, it is done > under the console_list_lock and using WRITE_ONCE(). > > If we use a wrapper for this rule, then we can also add the lockdep > assertion that console_list_lock is held. > > For READ_ONCE() the rule is: > > - If console->flags is read for a registered console, then either > console_list_lock must be held _or_ it must be read via READ_ONCE(). > > If we use wrappers here, then we can use lockdep assertion on > console_list_lock for the non-READ_ONCE wrapper, and scru_read_lock > assertion for the READ_ONCE wrapper. Exactly, all the assertions are one big advantage for hiding this into an API. > > It would deserve a comment. But there are several other writes. > > Also it is not obvious why many other con->flags modifications > > do not need this. > > > > I think about hiding this into an API. We could also add some > > checks that it is used the right way. Also it might make sense > > to avoid using the READ_ONCE()/WRITE_ONCE by using > > set_bit()/test_bit(). > > I do not see any advantage of set_bit()/test_bit(). They have the > disadvantage that they only work with 1 bit at a time. And there are > multiple sites where more than 1 bit is set/tested. It is important that > the multi-bit tests are simultaneous. Fair enough. > > I would prefer to use the proposed API because it should make all > > accesses more clear and safe. And most importantly, it would help use > > to catch bugs. > > > > But I do not resist on it. The patch looks correct and we could do > > this later. I could live with it if we add some comments above the > > WRITE_ONCE() calls. > > I do not want to do a full API replacement for all console->flags access > in this series or at this time. I am concerned that it is taking us too > far away from our current goal. Also, with the upcoming atomic/threaded > model, all consoles need to be modified that want to use it anyway. So > that would be a more appropriate time to require the use of new API's. Fair enough. > For console_is_enabled() I will add the srcu_read_lock check. I suppose > I should also name the function console_srcu_is_enabled(). > > For the WRITE_ONCE() calls, I will add a static inline wrapper in > printk.c that includes the lockdep console_list_lock assertion. Perhaps > called console_srcu_write_flags(struct console *con, short flags). > > In console_srcu_write_flags() and console_srcu_is_enabled() I can > document their relationship and when they are to be used. Both these > functions are used rarely and should be considered the exception, not > the rule. > > For code that is reading registered console->flags under the > console_list_lock, I will leave the "normal access" as is. Just as I am > leaving the "normal access" for non-registered console-flags as is. We > can convert those to a new generic API later if we think it is really > necessary. Sounds reasonable. The main motivation for introducing the wrappers is that there are currently 40+ locations where console->flags are touched. It is easy to miss something especially when we are reworking the locking around this code. Some of the callers are outside kernel/printk/* which even increases the risk of a misuse. The lockdep checks would help us to catch potential mistakes. Anyway, I am fine with adding the wrappers for the synchronized reads later. This patchset is already big enough. Best Regards, Petr
diff --git a/include/linux/console.h b/include/linux/console.h index f4f0c9523835..d9c636011364 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -172,6 +172,33 @@ extern void console_srcu_read_unlock(int cookie); extern struct hlist_head console_list; +/** + * console_is_enabled - Locklessly check if the console is enabled + * @con: struct console pointer of console to check + * + * Unless the caller is explicitly synchronizing against the console + * register/unregister/stop/start functions, this function should be + * used instead of manually readings console->flags and testing for + * the CON_ENABLED bit. + * + * This function provides the necessary READ_ONCE() and data_race() + * notation for locklessly reading the console flags. The READ_ONCE() + * in this function matches the WRITE_ONCE() when @flags are modified + * for registered consoles. + * + * Context: Any context. + */ +static inline bool console_is_enabled(const struct console *con) +{ + /* + * Locklessly reading console->flags provides a consistent + * read value because there is at most one CPU modifying + * console->flags and that CPU is using only read-modify-write + * operations to do so. + */ + return (data_race(READ_ONCE(con->flags)) & CON_ENABLED); +} + /** * for_each_console_srcu() - Iterator over registered consoles * @con: struct console pointer used as loop cursor diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 8974523f3107..79811984da34 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -3021,7 +3021,7 @@ void console_stop(struct console *console) { __pr_flush(console, 1000, true); console_lock(); - console->flags &= ~CON_ENABLED; + WRITE_ONCE(console->flags, console->flags & ~CON_ENABLED); console_unlock(); /* @@ -3037,7 +3037,7 @@ EXPORT_SYMBOL(console_stop); void console_start(struct console *console) { console_lock(); - console->flags |= CON_ENABLED; + WRITE_ONCE(console->flags, console->flags | CON_ENABLED); console_unlock(); __pr_flush(console, 1000, true); } @@ -3256,7 +3256,7 @@ void register_console(struct console *newcon) } else if (newcon->flags & CON_CONSDEV) { /* Only the new head can have CON_CONSDEV set. */ - console_first()->flags &= ~CON_CONSDEV; + WRITE_ONCE(console_first()->flags, console_first()->flags & ~CON_CONSDEV); hlist_add_head_rcu(&newcon->node, &console_list); } else { @@ -3308,7 +3308,7 @@ int unregister_console(struct console *console) console_lock(); /* Disable it unconditionally */ - console->flags &= ~CON_ENABLED; + WRITE_ONCE(console->flags, console->flags & ~CON_ENABLED); if (hlist_unhashed(&console->node)) { console_unlock(); @@ -3327,7 +3327,7 @@ int unregister_console(struct console *console) * console has any device attached. Oh well.... */ if (!hlist_empty(&console_list) && console->flags & CON_CONSDEV) - console_first()->flags |= CON_CONSDEV; + WRITE_ONCE(console_first()->flags, console_first()->flags | CON_CONSDEV); console_unlock();