Access to User Space in Linux

Memory access was handled differently in the 2.0 kernels. The Linux virtual memory system was less well developed at that time, and memory access was handled a little differently. The new system was the key change that opened 2.1 development, and it brought significant improvements in performance; unfortunately, it was accompanied by yet another set of compatibility headaches for driver writers.

The functions used to access memory under Linux 2.0 were as follows:

verify_area(int mode, const void *ptr, unsigned long size); This function worked similarly to access_ok, but performed more extensive checking and was slower. The function returned 0 in case of success and

-EFAULT in case of errors. Recent kernel headers still define the function, but it's now just a wrapper around access_ok. When using version 2.0 of the kernel, calling verijy_area is never optional; no access to user space can safely be performed without a prior, explicit verification.

put_user(datum, ptr)

The put_user macro looks much like its modern-day equivalent. It differed, however, in that no verification was done, and there was no return value.

get_user(ptr)

This macro fetched the value at the given address, and returned it as its return value. Once again, no verification was done by the execution of the macro.

verify_area had to be called explicitly because no user-area copy function performed the check. The great news introduced by Linux 2.1, which forced the incompatible change in the get_user and put_user functions, was that the task of verifying user addresses was left to the hardware, because the kernel was now able to trap and handle processor exceptions generated during data copies to user space.

As an example of how the older calls are used, consider scull one more time. A version of scull using the 2.0 API would call verijy_area in this way:

* extract the type and number bitfields, and don't decode

* wrong cmds: return ENOTTY before verify_area() */

if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;

if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; /*

* the direction is a bit mask, and VERIFY_WRITE catches R/W

* transfers. 'Type' is user oriented, while

* verify_area is kernel oriented, so the concept of "read" and

err = verify_area(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE)

err = verify_area(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return err;

Then get_user and put_user can be used as follows:

case SCULL_IOCXQUANTUM: /* exchange: use arg as pointer */ tmp = scull_quantum;

break;

default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY;

return 0;

Only a small portion of the ioctl switch code has been shown, since it is little different from the version for 2.2 and beyond.

Life would be relatively easy for the compatibility-conscious driver writer if it weren't for the fact that put_user and get_user are implemented as macros in all Linux versions, and their interfaces changed. As a result, a straightforward fix using macros cannot be done.

One possible solution is to define a new set of version-independent macros. The path taken by sysdep.h consists in defining upper-case macros: GET_USER,

__GET_USER, and so on. The arguments are the same as with the kernel macros of Linux 2.4, but the caller must be sure that verify_area has been called first (because that call is needed when compiling for 2.0).

Continue reading here: Using IO Ports

Was this article helpful?

0 0