[Devel] [RFC PATCH vz9 v6 02/62] dm-ploop: Use READ_ONCE/WRITE_ONCE to access md page data
Alexander Atanasov
alexander.atanasov at virtuozzo.com
Wed Jan 22 15:10:42 MSK 2025
On 21.01.25 9:04, Pavel Tikhomirov wrote:
>
>
> On 1/20/25 19:35, Alexander Atanasov wrote:
>> On 20.01.25 13:27, Pavel Tikhomirov wrote:
>>>
>>>
>>> On 1/20/25 19:09, Alexander Atanasov wrote:
>>>> On 20.01.25 12:54, Pavel Tikhomirov wrote:
>>>>>
>>>>>
>>>>> On 1/20/25 16:03, Alexander Atanasov wrote:
>>>>>> On 20.01.25 9:23, Pavel Tikhomirov wrote:
>>>>>>>
>>>>>>>
>>>>>>> On 12/6/24 05:55, Alexander Atanasov wrote:
>>>>>>>> @@ -1090,9 +1090,9 @@ static int ploop_alloc_cluster(struct
>>>>>>>> ploop *ploop, struct ploop_index_wb *piwb,
>>>>>>>> clu -= piwb->page_id * PAGE_SIZE / sizeof(map_index_t) -
>>>>>>>> PLOOP_MAP_OFFSET;
>>>>>>>> to = piwb->kmpage;
>>>>>>>> - if (to[clu]) {
>>>>>>>> + if (READ_ONCE(to[clu])) {
>>>>>>>> /* Already mapped by one of previous bios */
>>>>>>>> - *dst_clu = to[clu];
>>>>>>>> + *dst_clu = READ_ONCE(to[clu]);
>>>>>>>> already_alloced = true;
>>>>>>>> }
>>>>>>>
>>>>>>> The above hunk does not look good, we explicitly do
>>>>>>> "READ_ONCE(to[clu])" twice, likely that's not what we want.
>>>>>>
>>>>>>
>>>>>> We can do without inner READ_ONCE but for consistency i think it
>>>>>> is better. /it would be optimised by the compiler anyway/
>>>>>>
>>>>>> The idea is that if a clu is already mapped we read !=0 in the if,
>>>>>> inside we will always read the !=0 too, since discard is
>>>>>> synchronized by the holes bitmap.
>>>>>
>>>>> That may be, if clu can't be un-mapped or somehow reset... Properly
>>>>> validating it will require checking all 24 uses of ->kmpage.
>>>>
>>>>
>>>> md->kmpage is mapped at init time and unmapped at destroy time.
>>>> ( [01/62] dm-ploop: md_pages map all pages at creation time )
>>>
>>> What about kmap_local_page(piwb->bat_page)[clu], where it is set/
>>> unset? I don't see it set in ploop_alloc_md_page where we set md-
>>> >kmpage in [01/62].
>>>
>>>> piwb->kmpage is gone in updated patches.
>>>
>>> This is from updated patch:
>>>
>>> to = kmap_local_page(piwb->bat_page);
>>> if (READ_ONCE(to[clu])) {
>>> /* Already mapped by one of previous bios */
>>> *dst_clu = READ_ONCE(to[clu]);
>>> already_alloced = true;
>>> }
>>>
>>> We still have double-once situation.
>>
>> I havent updated yet (now i did)
>>
>> to = kmap_local_page(piwb->bat_page);
>> - if (to[clu]) {
>> + tmp_clu = READ_ONCE(to[clu]);
>> + if (tmp_clu) {
>> /* Already mapped by one of previous bios */
>> - *dst_clu = to[clu];
>> + *dst_clu = tmp_clu;
>> already_alloced = true;
>> }
>>
>>
>>
>>>
>>> The rule of thumb of using READ_ONCE is "If we use READ_ONCE to read
>>> some shared variable, we get value, and we need to re-use this value
>>> later instead of re-reading the shared variable.".
>>
>> READ_ONCE enforces 32bit access to the variable, nothing less nothing
>> more. Without ONCE compiler can split access to two 16 bit operations
>> / pipeline optimisation/ so if two threads access in parallel they can
>> get partially updated value.
>>
>> READ_ONCE does not prevent the compiler to use cached value. neither
>> it forces new read.
>>
>> Use depends on what the function does and expects.
>> If the function expects and wants to see a change it can read as much
>> as it needs using ONCE.
>
> dm-ploop: Use READ_ONCE/WRITE_ONCE to access md page data
>
> Documentation/memory-barriers.txt
>
> > (*) It _must_not_ be assumed that the compiler will do what you want
> > with memory references that are not protected by READ_ONCE() and
> > WRITE_ONCE(). Without them, the compiler is within its rights to
> > do all sorts of "creative" transformations, which are covered in
> > the COMPILER BARRIER section.
>
>
> > (*) The compiler is within its rights to reload a variable,
> for example,
> > in cases where high register pressure prevents the
> compiler from
> > keeping all data of interest in registers. The compiler
> might
> > therefore optimize the variable 'tmp' out of our previous
> example:
> >
> > while (tmp = a)
> > do_something_with(tmp);
> >
> > This could result in the following code, which is
> perfectly safe in
> > single-threaded code, but can be fatal in concurrent code:
> >
> > while (a)
> > do_something_with(a);
> >
> > For example, the optimized version of this code could
> result in
> > passing a zero to do_something_with() in the case where
> the variable
> > a was modified by some other CPU between the "while"
> statement and
> > the call to do_something_with().
> >
> > Again, use READ_ONCE() to prevent the compiler from doing
> this:
> >
> > while (tmp = READ_ONCE(a))
> > do_something_with(tmp);
> >
> > Note that if the compiler runs short of registers, it
> might save
> > tmp onto the stack. The overhead of this saving and later
> restoring
> > is why compilers reload variables. Doing so is perfectly
> safe for
> > single-threaded code, so you need to tell the compiler
> about cases
> > where it is not safe.
>
> That basically says that READ_ONCE prevents tmp from changing it's
> value. In other words value for "tmp" is read from "a" "once", as the
> name of the macro suggests it would.
>
> note: resending it, as probably my mail client lost it...
No, ONCE does not mean read once, it means access at once.
When it is a 32 bit variable compiler can split the access into two 16
bit accesses to optimize instruction pipeline which can result in
reading an inconsistent value from two threads. _ONCE macros force it to
do 32 bit access which is atomic.
There is no point to do it twice in a function because once the value is
read it is assigned to a variable (which can be anonymous too) second
_ONCE does not force it to be reread again in the same function, as it
is not volatile.
In the case of the patch , first read guaranteed that inner will not
change in value.
>
>>
>>
>>>
>>>>
>>>>
>>>>> It would be much cleaner and simpler to do:
>>>>>
>>>>> tmp_var = READ_ONCE(smth)
>>>>> if (tmp_var) {
>>>>> *ptr_var = tmp_var;
>>>>> }
>>>>
>>>> Ok.
>>>>
>>>>> I doubt that you will find any other example of doing READ_ONCE of
>>>>> the same shared memory/variable twice in one function.
>>>>
>>>> There might be but this is irrelevant.
>>>>
>>>> in this case it can be done without temp variable.
>>>>
>>>> if (READ_ONCE(var)) {
>>>> *ptr_var = var;
>>>> }
>>>>
>>>> *ptr_var = var; will be changed to *ptr_var =
>>>> value_from_read_once(var);
>>>>
>>>> so it is the same
>>>>
>>>
>>> Sorry, but in my opinion there is a difference.
>>>
>>
>
--
Regards,
Alexander Atanasov
More information about the Devel
mailing list