Copy_to_user() and locks.

From: Richard B. Johnson (root@chaos.analogic.com)
Date: Tue Feb 08 2000 - 15:46:34 EST


I just read a message from someone who wanted some clarification
about kernel locks and copying to/from the user. Unfortunately my
machine hung (version 2.3.41) so I lost the response during the
reboot. So here is a response recovered from /lost+found with
a bit of editing.

You must not copy_to_user() under a lock. This is because the
user's pages may not be in memory and the kernel needs to page-
fault.

Unfortunately, this often means that you have to copy twice, double
buffer. However, a typical PC nowdays can copy over 800 megabytes per
second.

Here is a typical bench-mark.

Script started on Tue Feb 8 15:14:27 2000
# platinum/test/perf
Testing shared memory copy from 000A0000...done
Testing shared memory copy from 000B0000...done
Testing shared memory copy from 000B8000...done
Testing shared memory copy from 000C0000...done
Testing shared memory copy from 000C8000...done
Testing shared memory copy from 000D0000...done
Testing shared memory copy from 000D8000...done
Testing shared memory copy from 000E0000...done
Testing shared memory copy from 000E8000...done
Testing checksum...done
Testing memory copy...done
I/O port speed...done

                 chksum speed = 678 Mb/s
                     RAM copy = 1677 Mb/s
               I/O port speed = 657 kb/s
     Shared RAM copy 000A0000 = 1 Mb/s
     Shared RAM copy 000B0000 = 1 Mb/s
     Shared RAM copy 000B8000 = 3 Mb/s
     Shared RAM copy 000C0000 = 614 Mb/s
     Shared RAM copy 000C8000 = 17 Mb/s
     Shared RAM copy 000D0000 = 1 Mb/s
     Shared RAM copy 000D8000 = 1 Mb/s
     Shared RAM copy 000E0000 = 17 Mb/s
     Shared RAM copy 000E8000 = 17 Mb/s
# exit
Script done on Tue Feb 8 15:14:58 2000

This shows that double-buffering adds little overhead to the transfer
compared to the time it already took to get data from a shared-RAM device
of from an I/O port. In this case, ram-to-ram went at 1,677 megabytes
per second.

What you have to do with buffers that could be modified either
by an interrupt or by another thread is something like this:

    /*
     * This is not going to change until after we return, so you
     * can check this with no lock.
     */
    if(!access_okay(VERIFY_WRITE, user_buf, request_length);
        return -EFAULT;
    /*
     * Now get a temporary buffer. Its allocation does not have to
     * be atomic because there are no locks yet and we are in kernel
     * mode, but user context.
     */
    ptr = (WHATEVER *) kmalloc(sizeof(WHATEVER), GFP_KERNEL); /* Not locked */
    /*
     * Now lock the buffer with a lock that will work with SMP as well
     * as single-cpu machines.
     */
    spin_lock_irqsave(&device_lock, flags);
    /*
     * Now copy to the local buffer under the lock.
     */
    memcpy(ptr, irq_buffer, len);
    /*
     * Release the lock as soon as possible.
     */
    spin_unlock_irqrestore(&device_lock, flags);
    /*
     * Now copy to the user without a lock.
     */
    copy_to_user(user_buf, ptr, len);
    /*
     * Don't want a memory leak.
     */
    kfree(ptr);

    Now, with some drivers, it's better to allocate the temporary buffer
    once when the driver is installed. You free it once before you
    uninstall the device. This is more efficient. However, in many
    cases you need a linked-list of buffers. At some point it is
    more efficient to allocate and deallocate as you go.

Cheers,
Dick Johnson

Penguin : Linux version 2.3.41 on an i686 machine (800.63 BogoMips).

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Tue Feb 15 2000 - 21:00:13 EST